1.聊天室4大功能

  1. 用户上线注册、下线注销功能
  2. 私聊功能
  3. 群聊功能
  4. 统计聊天室在线人数功能

2.基于多线程的聊天室的整体思路

  1. 采用C/S模式(客户端/服务器)
  2. 客户端与服务器的连接,使用套接字建立TCP连接
  3. 处理多线程。每当有一个客户端与该服务器端建立连接后,需要创建新的线程去单独处理该客户端发送的信息
  4. 客户端方面:客户端在与服务器建立连接后,通过Socket对象获取输入输出流从而与服务器端进行通信
  5. 服务器方面:服务器端的套接字ServerSocket对象在调用accept()方法监听客户端的连接,当与客户端成功建立连接后,返回Socket对象,从而利用该Socket对象获取输入输出流进而与客户端通信

3.聊天室的具体实现

3.1 客户端与服务器端建立连接

1:服务器端通过java.net.ServerSocket类的构造方法实例化ServerSocket对象,选择的构造方法是:public ServerSocket(int port) throws IOException  //创建绑定到指定端口的服务器套接字

2:客户端通过java.net.Socket类的构造方法创建一个流套接字并将其连接到指定主机上的指定端口号,选择的方法是:public Socket(String host,int port) throws UnknowHostException,IOException

3:服务器端的ServerSocket对象调用accept()方法监听请求连接指定端口号的该服务器端的客户端,并在接收到客户端请求后返回服务器端的流套接字,即Socket对象,从而服务器端与客户端成功建立连接

4:客户端与服务器端之间的通信操作,java.net.Socket类就是提供客户端与服务器端相互通信的套接字

  1. 获取Socket套接字的输入流的方法为:public InputStream getInputStream() throws IOException
  2. 获取Socket套接字的输出流的方法为:public OutputStream getOutputStream() throws IOException

5:服务器端与客户端之间通信结束后,需要关闭套接字,调用close()方法即可

public void close() throws IOException

创建服务器端与客户端之间的连接以及通信操作

  • 服务器端代码
package chat.room.server;
import java.net.Socket;
import java.net.ServerSocket;
import java.util.Scanner;
import java.io.PrintStream;
import java.io.IOException;
public class SingleServer {
	public static void main(String[] args) throws IOException{
		//1.创建服务器端的ServerSocket对象,等待客户端连接其接口
		ServerSocket serverSocket = new ServerSocket(6666);
		System.out.println("服务器的端口号为:6666,正在等待客户端连接……");
		//2.监听并接受服务器端的连接,返回套接字Socket对象
		Socket socket = serverSocket.accept();
		//3.获取客户端的输入流,读取客户端输入的内容
		Scanner scanner = new Scanner(socket.getInputStream());
		scanner.useDelimiter("\n");
		if(scanner.hasNext()){
			System.out.println("客户端发来信息"+scanner.next());
		}
		//4.获取客户端的输出流,向客户端输出内容
		PrintStream printStream = new PrintStream(socket.getOutputStream());
		printStream.println("客户端你好,我是服务器端:"+serverSocket.getLocalPort());
		//5.关闭流
		serverSocket.close();
	}
}
  • 客户端代码
package chat.room.server;
import java.net.Socket;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Scanner;
public class SingleClient {
	public static void main(String[] args) throws IOException{
		//1.客户端连接服务器端,返回套接字Socket对象
		Socket socket = new Socket("127.0.0.1",6666);
		//2.获取服务器端的输出流,向服务器端输出内容
		PrintStream printStream = new PrintStream(socket.getOutputStream());
		printStream.println("我是客户端"+socket.getLocalPort());
		//3.获取服务器端的输入流,读取服务器端的内容
		Scanner scanner = new Scanner(socket.getInputStream());
		scanner.useDelimiter("\n");
		if(scanner.hasNext()){
			System.out.println(scanner.next());
		}
		//4.关闭流
		socket.close();
	}
}

3.2 多线程客户端的实现

客户端只需要做两件事,所以创建两个线程:

  1. 向服务器发送信息
  2. 接受服务器信息

3.2.1 客户端读取服务器端信息的线程

客户端需要读取服务器端信息就需要客户端获取服务器端的输入流,从而将信息显示到客户端

//客户端读取服务器端信息的线程
class ClientReadServer implements Runnable{
	private Socket socket;
	public ClientReadServer(Socket socket){
		this.socket = socket;
	}
	public void run(){
		//获取服务器端输入流
		Scanner scanner;
		try {
			scanner = new Scanner(socket.getInputStream());
			while(scanner.hasNext()){
				System.out.println(scanner.next());
			}
			scanner.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

3.2.2 客户端向服务器端发送信息的线程

客户端需要向服务器端发送信息就需要客户端获取服务器端的输出流,从而将信息发送到服务器端

class ClientSendServer implements Runnable{
	private Socket socket;
	public ClientSendServer(Socket socket){
		this.socket = socket;
	}
	public void run(){
		try {
			//获取服务器端的输出流
			PrintStream printStream = new PrintStream(socket.getOutputStream());
			//从键盘输入信息
			Scanner scanner = new Scanner(System.in);
			while(true){
				String msg = null;
				if(scanner.hasNext()){
					msg = scanner.next();
					printStream.println(msg);
				}
				if(msg.equals("exit")){
					scanner.close();
					printStream.close();
					break;
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

3.2.3 启动两个子线程

在客户端的主方法中,首先创建连接服务器端的Socket对象,然后通过Thread类的start()方法启动两个子线程,从而完成客户端的操作

public class MultiClient{
	public static void main(String[] args) throws IOException{
		//客户端连接服务器端,返回套接字Socket对象
		Socket socket = new Socket("127.0.0.1",6666);
		//创建读取服务器端信息的线程和发送服务器端信息的线程
		Thread read = new Thread(new ClientReadServer(socket));
		Thread send = new Thread(new ClientSendServer(socket));
		//启动线程
		read.start();
		send.start();
	}
}

3.3 多线程服务器端的实现

3.3.1 用户上线注册的功能

private void userRegist(String userName,Socket socket){
	map.put(userName,socket);
	System.out.println("[用户名为"+userName+"][客户端为"+socket+"]上线了!");
	System.out.println("当前在线人数为:"+map.size()+"人");
}

3.3.2 用户下线注销的功能

private void userExit(Socket socket){
	//利用socket取得对应的key值
	String userName = null;
	for(String key:map.keySet()){
		if(map.get(key).equals(socket)){
			userName = key;
			break;
		}		
	}
	//将userName,Socket元素从map集合中删除
	map.remove(userName,socket);
	//提醒服务器客户端已下线
	System.out.println("用户:"+userName+"已下线");
}

3.3.3 私聊功能

private void privateChat(Socket socket,String userName,String msg) throws IOException{
	//取得当前客户端的用户名
	String curUser = null;
	Set<Map.Entry<String,Socket>> set=map.entrySet();
	for(Map.Entry<String, Socket> entry:set){
		if(entry.getValue().equals(socket)){
			curUser = entry.getKey();
			break;
		}
	}
	//取得私聊用户名对应的客户端
	Socket client = map.get(userName);
	//获取私聊客户端的输出流,将私聊信息发送到指定客户端
	PrintStream printStream = new PrintStream(client.getOutputStream());
	printStream.println(curUser+"私聊说:"+msg);
}

3.3.4 群聊功能

private void groupChat(Socket socket,String mag) throws IOException{
	//将Map集合转换为Set集合
	Set<Map.Entry<String,Socket>> set = map.entrySet();
	//遍历Set集合找到发起群聊信息的用户
	String userName = null;
	for(Map.Entry<String, Socket> entry:set){
		if(entry.getValue().equals(socket)){
			userName = entry.getKey();
			break;
		}
	}
	//遍历Set集合将群聊信息发给每一个客户端
	for(Map.Entry<String,Socket> entry:set){
		//取得客户端的Socket对象
		Socket client = entry.getValue();
		//取得client客户端的输出流
		PrintStream printStream = new PrintStream(client.getOutputStream());
		printStream.println(userName+"群聊说:"+msg);
	}
}

3.3.5 统计聊天室在线人数功能

只需调用ConcurrentHashMap对象的size()方法即可

3.3.6 主方法中的操作(创建套接字、创建线程池)

在完成了所有的功能模块之后,首先需要创建服务器端的套接字ServerSocket对象,从而客户端能够进行连接。其次由于服务器端处理多个客户端时需要创建多个线程,故可创建固定大小的线程池处理多个客户端

public class MultiServer{
	public static void main(String[] args){
		try{
		//1.创建服务器端的ServerSocket对象,等待客户端连接
		ServerSocket serverSocket = new ServerSocket(6666);
		//2.创建线程池,从而可以处理多个客户端
		ExecutorService executorService = Executors.newFixedThreadPool(20);
		for(int i=0;i<20;i++){
			System.out.println("欢迎来到我的聊天室……");
			//3.监听客户端
			Socket socket = serverSocket.accept();
			System.out.println("有新的朋友加入……");
			//启动线程
			executorServie.execute(new Server(socket));
		}
		//关闭线程池
		executorService.shutdown();
		//关闭服务器
		serverSocket.close();
	} catch(IOException e){
		e.printStackTrace();
	}
}
}

 

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐