Socket编写一个简易的聊天室

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40301026/article/details/88728215

首先实现的功能:

1.群聊。一个服务器端可以承载多个客户端(用户)来请求访问。服务器端对其请求做出处理,并转发给其他的客户端(用户)。

2.私聊。因为是控制台输入输出,所以私聊格式为:@xxx:   。而且自己给私发服务器端不响应。

思路:1.用ServerSocket模拟服务器端,并且开启多线程的调用accept()等待客户端连接。

           2.Socket模拟客户端。使用多线程达到一边发一边收。 

           3.数据的传输用到的IO流DataInputStream(client.getInputStream())和DataOutputStream(client.getOutputStream())

           4.TCP底层原理不再阐述。不懂:https://blog.csdn.net/qq_40301026/article/details/88623353

           5.群聊功能:每来一个客户端,将其姓名作为key,客户端的对象地址作为value,,存在ConcurrentHashMap(线程安全)容器里面。当一个客户端发送消息时,服务器端作为中转站,从容器中拿到其他客户端,转发此消息。

           6.私聊:对接收到的客户端消息进行检查,如果符合私聊格式就从容器中拿到私聊对象,转发给对方。

先来看看实现的效果????

*启动一个服务器端(IP地址本机,端口号:8848)和三个客户端。

 *三个客户端加入聊天室

* 群聊

*私聊:

 *离开:

代码:

服务器端:

package cn.liu.chat03;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 服务器端:利用多线程和多个客户端能收发多条消息
 * 再进行封装
 * 实现群聊功能
 * @author Administrator
 *
 */
public class Server {
	private ConcurrentHashMap<String,Work> holder;//用来存储客户端
	private ServerSocket server;//服务器端
	private int serverPort;//服务器的端口号
	
	
	//启动一个服务端
	public Server(int serverPort) {
		super();
		this.serverPort = serverPort;
		this.holder = new ConcurrentHashMap<>();//创建容器
		try {
			this.server = new ServerSocket(serverPort);//创建一个服务器端
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	//服务端响应,工作。
	public void run() {
		System.out.println("----------ChatServer----------");
		//响应多个客户端
		while(true) {	
			new Thread(new Work(server,holder)).start();
		}
	}
}
package cn.liu.chat03;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;

/**
 * //将服务器响应一个客户端的功能进行封装
 * 
 * @author Administrator
 *
 */
public class Work implements Runnable{
	private Socket client;//拿到客户端套接字
	private ServerSocket server;//服务器端
	private DataInputStream is;//IO流通道
	private DataOutputStream os;//IO流通道
	private boolean flg;//控制多次收发
	private ConcurrentHashMap<String,Work> holder;//用来存储客户端
	private boolean sign ;//true是客户端消息,false是服务器消息
	
	//初始化,1.等待连接。2.并且将输入输出流通道搭建好
	public Work(ServerSocket server,ConcurrentHashMap<String,Work> holder) {
		super();
			try {
				this.client = server.accept();
				//将输入输出流通道搭建好
				this.is = new DataInputStream(client.getInputStream());
				this.os = new DataOutputStream(client.getOutputStream());
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		
		this.holder = holder;
		this.flg = true;
		this.sign = true;
	}

	//执行的线程体
	@Override
	public void run() {
		System.out.println("一个客户端已建立连接。");
		String name = judge();//得到此客户端名字
		judgeFirst(name);
		//当关闭此客户端,程序跳出judgeFirst()中while循环,服务器给其他客户端发送离开消息
		responseOther(name+":离开聊天室!");
		release();//释放相关资源
	}
	
	 //判断客户端和其他客户端是不是重名,不重名则返回,并加入容器, 重名则重新接收。
	private String judge() {
		response("欢迎到来!");
		response("起一个好听的昵称吧!");
		while(true){
			String name = receive();
			if(!holder.containsKey(name)) {
				holder.put(name,this);//加入此客户端存起来
				return name;
			}else {
				String unname = "姓名重名了!请重新起一个!";
				response(unname);
			}
		}
	}
	
	//判断客户端是不是第一次发送消息,是则发送欢迎,不是则进行聊天功能(私聊OR群聊)。
	private void judgeFirst(String name) {
		while(flg) {
			if(sign) {//第一次给其他客户端转发此消息
				responseOther("欢迎"+name+"加入群聊!");
				sign = false;
			}else {
				String str = this.receive();
				if(str.equals(""))//关闭客户端,receive()收到"",则退出聊天室
				{
					break;
				}else {
					survey(name,str);
				}
			}
		}
	}
	
	//群聊
	private void groupChat(String name,String message) {
			String datas = name+":"+message;
			responseOther(datas);
	}
	
	//私聊,系统默认识别:“@xxx:”为私聊,之后的话为私发内容
	private void privateChat(String name,String str) {
		//对数据进行分离
		int index = 0;
		while(str.charAt(index)!=':') {
			index++;
		}
		String nameOther = str.substring(1,index);//得到名字
		str = name+str.substring(index);//处理信息
		//向此人转发信息
		for(ConcurrentHashMap.Entry<String,Work> entry: holder.entrySet()) {
			if(!entry.getValue().equals(this)) {//排除和自己私聊
				if(entry.getKey().equals(nameOther)) {//找到此客户端
					entry.getValue().response(str);//服务器转发此消息
				}
			}
		}
	}
	
	//检测是群聊还是私聊,检测后并调用相应功能
	private void survey(String name,String messager) {//有@即视为想私聊
		if(messager.charAt(0)=='@') {
			privateChat(name,messager);
		}else {
			groupChat(name,messager);
		}
	}

	//拿到客户数据
	private String receive() {
		//拿到客户端数据
		String str = "";
		try {
			str = is.readUTF();
		} catch (IOException e) {
			release();
		}
		return str;
	}
	
	//给客户端做出响应
	private void response(String str) {//m表示是不是系统消息
			try {
				os.writeUTF(str);
				os.flush();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	
	//给本线程外的其他客户端做出响应
	private void responseOther(String str) {
		for(Work entry: holder.values()) {
			if(!entry.equals(this)) {//给其他客户端转发此消息
			entry.response(str);
			}
		}
	}

	//释放相关资源
	private void release() {
		this.flg = false;
		ChatUtils.close(is,os,client);
		//一个客户端退出
		removeClient();
	}
	
	//一个客户端退出
	private void removeClient() {
		String name ="";
		for(ConcurrentHashMap.Entry<String,Work> entry: holder.entrySet()) {
			if(entry.getValue().equals(this)) {
				name = entry.getKey();
				break;
			}
		}
		holder.remove(name);
	}
	
}

 客户端:

package cn.liu.chat03;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 对客户端再进行二次封装
 * Send类实现发数据
 * Receive类拿到数据
 * @author Administrator
 *
 */
public class Client {
	private Socket client;//客户端
	private String ip;//服务器ip地址
	private int serverPort;//服务器端口
	
	public Client(String ip, int serverPort) {
		this.ip = ip;
		this.serverPort = serverPort;
		try {
			this.client = new Socket(ip,serverPort);//创建一个客户端
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	//和服务器通信
	public void run() {
		System.out.println("--------------ChatClient-------------");

		new Thread(new Send(client)).start();
		new Thread(new Receive(client)).start();
	}
}
package cn.liu.chat03;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 1.收到服务器的响应
 * 2.释放资源
 * @author Administrator
 *
 */
public class Receive implements Runnable{
	private DataInputStream is;
	private Socket client;
	private boolean flg;
	
	public Receive(Socket client) {
		super();
		this.client = client;
		try {
			this.is = new DataInputStream(client.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
		} 
		this.flg = true;
	}
	
	@Override
	public void run() {
		while(flg) {
			receiveData();
		}
		release();	
	}
	
	private void receiveData(){
		try {
			String str = is.readUTF();
			System.out.println(str);
		} catch (IOException e) {
			e.printStackTrace();
		}	
	}
	
	private void release() {
		this.flg = false;
		ChatUtils.close(is,client);
	}

}
package cn.liu.chat03;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

/**
 * 1.从控制台接收消息
 * 2.把消息发给服务器端
 * 3.释放资源
 * @author Administrator
 *
 */
public class Send implements Runnable{
	private DataOutputStream os;
	private Socket client;
	private boolean flg;
	
	public Send(Socket client) {
		super();
		this.client = client;
		try {
			this.os = new DataOutputStream(client.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.flg = true;
	}

	@Override
	public void run() {
		while(flg) {
			//从控制台获取消息,发送数据到服务器端
			sendData(input());
		}
		release();
	}
	
	//从控制台获取消息
	private String input() {
		Scanner ss = new Scanner(System.in);
		String str = ss.nextLine();
		return str;
	}
	
	//发送数据到服务器端
	private void sendData(String str) {
		try {
			os.writeUTF(str);
			os.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	//释放资源
	private void release() {
		this.flg = false;
		ChatUtils.close(os,client);
	}
		
}

工具类:

package cn.liu.chat03;

import java.io.Closeable;
import java.io.IOException;

/**
 * 工具类
 * @author Administrator
 *
 */
public class ChatUtils {
	/**
	 * 释放资源
	 */
	public static void close(Closeable... resource) {
		for(Closeable a:resource) {
			try {
				if(null!=a)
				a.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

测试类:

package cn.liu.chat03;

public class ClientTest {
	public static void main(String[] args) {
		Client ss= new Client("Dick",8848);
		ss.run();
	}
}
package cn.liu.chat03;

public class TestServer {
	public static void main(String[] args) {
		Server ss = new Server(8848);
		ss.run();
	}
}

猜你喜欢

转载自blog.csdn.net/qq_40301026/article/details/88728215
今日推荐