Bootstrap

java socket 实现简易聊天室 支持一对一消息、群发消息 、获取在线用户

服务端代码

package test20230317.net.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ChatServer {

	private static final int MAX_CONNECT = 100; //最多连接数,超过这个数量连接会被断开
	
	private ServerSocket serverSocket;
	private Map<String,Socket> clientMap; //客户端:用户名-IP映射关系
	
	public ChatServer(int port) {
		try {
			serverSocket = new ServerSocket(port);
			clientMap = Collections.synchronizedMap(new HashMap<String,Socket>());
			
			ChatUtil.println("端口"+port+"绑定成功");
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("端口"+port+"绑定失败");
		}
		
	}
	
	//监听端口
	public void listen() {
		
		while(true) {
			try {
				doClient(serverSocket.accept());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 处理客户端请求
	 * @param socket
	 */
	private void doClient(Socket client) {
		new Thread(new Runnable() {

			@Override
			public void run() {
				
				String ip = client.getRemoteSocketAddress().toString();
				ChatUtil.println(ip,"连接成功");
				
				String userName = null;
				
				try {
					
					//超过最多连接数,则断开
					if(clientMap.size()>=MAX_CONNECT) {
						writeString(client,"当前聊天人数过多,请稍后再试...");
						try {
							Thread.sleep(500);
						}catch(InterruptedException e) {
						}
						
						//断开连接
						disConnect(client);
						return;
					}
					
					//获得当前连接设置的用户名
					userName = getUserName(client);
					if(userName == null) {
						disConnect(client); //断开连接
						return;
					}
					
					//打印用户列表
					printClientList();
					
					//发送欢迎词给客户端
					writeString(client,userName+"你好,欢迎进入聊天室");
					
					//发送广播给全体在线用户
					sendToAll(client,userName+"进入了聊天室");
					
					//监听客户端输入
					BufferedReader socketIn = new BufferedReader(new InputStreamReader(client.getInputStream(),"utf-8"));
					String clientInput = "";
					while(clientInput!=null) {//输入流=null,表明连接已断开
						clientInput = socketIn.readLine();
						if(clientInput!=null) {
							if(clientInput.contains("@")) {//一对一消息 hello@userName
								
								int indexAt = clientInput.indexOf("@");
								String targetName = clientInput.substring(indexAt+1);
								String targetMsg = clientInput.substring(0,indexAt);
								if(ChatUtil.isEmpty(targetMsg)) {
									writeString(client,"请勿发送空消息");
								}else {
									Socket targetSocket = clientMap.get(targetName);
									if(targetSocket != null) {
										writeString(targetSocket,userName+"对你说:"+targetMsg);
									}else {
										writeString(client,"目标用户不存在或已断开连接");
									}
								}
								
							}else if("/user".equals(clientInput)) { //获取当前在线用户
								writeString(client,"在线用户:"+clientMap.keySet().toString());
							
							}else { //群发消息
								ChatUtil.println(userName,"收到回复",clientInput);
								
								//发送给其他在线用户
								sendToAll(client,userName+"说:"+clientInput);
							}
						}
					}
					
				} catch (IOException e) {
					ChatUtil.println(e.getMessage());
				} 
				
				disConnect(client); //断开连接
				
				//从在线用户列表中删除
				if(userName !=null) {
					clientMap.remove(userName);
					sendToAll(client,userName+"退出了聊天室");
				}
				
			}
			
		}).start();
	}
	
	
	/**
	 *   返回当前连接设置的用户名,如果没有设置返还null 
	 * @param client
	 * @return String 
	 */
	private String getUserName(Socket client) {
		
		//查找当前客户端是否已经设置了用户名
		String oldUserName = null;
		for(Map.Entry<String, Socket> entry:clientMap.entrySet()) {
			if(entry.getValue() == client) {
				oldUserName = entry.getKey();
				break;
			}
		}
		
		if(oldUserName == null) {//未设置用户名
			
			try {
				
				//获取客户端输入,超时自动时间为3秒
				client.setSoTimeout(3000);
				BufferedReader socketIn = new BufferedReader(new InputStreamReader(client.getInputStream(),"utf-8"));
				String clientInput = socketIn.readLine();
				
				if(clientInput!=null) {
					if(clientInput.startsWith("userName=")){ //设置用户名
						String userName = clientInput.substring("userName=".length());
						if(!ChatUtil.isEmpty(userName)) {
							if(clientMap.containsKey(userName)) {
								writeString(client,"用户名已存在,请更换用户名");
							}else {
								ChatUtil.println(client.getRemoteSocketAddress().toString(),"设置用户名",userName);
								clientMap.put(userName, client);
								client.setSoTimeout(0);
								return userName;
							}
						}else {
							writeString(client,"用户名不能为空");
						}
						
					}else {//该条命令不是设置用户名
						writeString(client,"请先设置用户名");
					}
				}
				
			}catch(IOException e) {
				ChatUtil.println(e.getMessage());
			}
			
		}else {
			return oldUserName;
		}
		
		return null;
	}
	
	/**
	 * 发送广播给除自己之外的其他在线用户
	 * @param current 当前用户
	 * @param msg
	 */
	private void sendToAll(Socket current,String msg) {
		Collection<Socket> list = clientMap.values();
		for(Socket socket:list) {
			if(current != socket) {
				writeString(socket,msg);
			}
		}
	}
	
	/**
	 * 打印用户列表
	 */
	private void printClientList() {
		ChatUtil.println("当前在线用户",clientMap.keySet().toString());
	}
	
	/**
	 * 发送数据到客户端
	 * @param client
	 * @param line
	 */
	private void writeString(Socket client,String line) {
		try {
			
			PrintWriter socketOut = new PrintWriter(new OutputStreamWriter(client.getOutputStream(),"utf-8"));
			socketOut.println(line);
			socketOut.flush();
			
			ChatUtil.println("发送消息给",client.getRemoteSocketAddress().toString(),line);
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	//断开连接
	private void disConnect(Socket client) {
		try {
			ChatUtil.println("与"+client.getRemoteSocketAddress().toString()+"的连接已断开");
			client.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public static void main(String[] args) throws IOException {
		if(args.length==0) {
			throw new RuntimeException("请输入端口号");
		}
		
		ChatServer chatServer = new ChatServer(Integer.parseInt(args[0]));
		chatServer.listen();
	}
	
}

客户端的代码

package test20230317.net.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.TreeSet;

/**
 * 客户端聊天,支持聊天室,一对一聊天
 * @author Administrator
 *
 */
public class ChatClient {

	private Socket client;
	
	public ChatClient(String ip,int port,String userName) {
		try {
			
			if(ChatUtil.isEmpty(userName)) {
				throw new RuntimeException("用户名不能为空");
			}
			
			client = new Socket(ip,port);
			ChatUtil.println("与服务器"+ip+":"+port+"连接成功");
			
			//发送我的用户名
			writeString("userName="+userName);
			
		}catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("连接主机"+ip+":"+port+"失败");
		}
	}
	
	/**
	 * 处理服务端输入流,单独启动一个线程,因为读取输入流是阻塞的
	 */
	public void doInputStream() {
		
		Thread td = new Thread(new Runnable() {

			@Override
			public void run() {
				
				try {
					//获取输入流
					BufferedReader socketIn = new BufferedReader(new InputStreamReader(client.getInputStream(),"utf-8"));
					
					//不断读取服务端输入消息
					String clientInput = "";
					while(clientInput!=null) {//输入流=null,表明连接已断开
						clientInput = socketIn.readLine();
						if(clientInput!=null) {
							ChatUtil.println("收到消息:"+clientInput);
						}
					}
					
				} catch (IOException e) {
					e.printStackTrace();
					ChatUtil.println(e.getMessage());
				}
				
				//断开连接
				disConnect();
					
				//系统退出
				System.exit(0);
				
			}
			
		});
		
		td.setDaemon(true);
		td.start();
	}
	
	/**
	 * 输入字符串,发给服务器
	 * @throws IOException 
	 */
	public void inputString(){
		try {
			
			//从控制台输入
			BufferedReader inputIn = new BufferedReader(new InputStreamReader(System.in,"utf-8"));
			
			
			//本地输入数据,发送给客户端
			String line = null;
			while(!("exit".equals(line) || client.isClosed())) {//非 (主动退出 或者 连接关闭)
				
				//本地输入
				line = inputIn.readLine();
				
				//回复给服务器
				if(ChatUtil.isEmpty(line)) {
					ChatUtil.println("系统提示:请勿回复空消息");
				}else if(!"exit".equals(line)){
					writeString(line);
				}
				
			}
			
			//断开连接
			disConnect();
		}catch(IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 发送数据到服务端
	 * @param client
	 * @param line
	 */
	private void writeString(String line) {
		try {
			PrintWriter socketOut = new PrintWriter(new OutputStreamWriter(client.getOutputStream(),"utf-8"));
			socketOut.println(line);
			socketOut.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	//断开连接
	private void disConnect() {
		try {
			ChatUtil.println("与服务器"+client.getRemoteSocketAddress().toString()+"的连接已断开");
			client.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	
	public static void main(String[] args) {
		
		if(args.length!=3) {
			throw new RuntimeException("请输入[ip port 用户名]");
		}
		
		ChatClient chatClient = new ChatClient(args[0],Integer.parseInt(args[1]),args[2]);
		chatClient.doInputStream();
		chatClient.inputString();
	}

}

工具类

package test20230317.net.chat;

import java.text.SimpleDateFormat;
import java.util.Date;

public class ChatUtil {
	/**
	 * 判断字符串是否为空
	 * @param str
	 * @return
	 */
	public static boolean isEmpty(String str) {
		if(str == null) {
			return true;
		}
		
		str = str.trim();
		return str.length()==0;
	}
	
	
	/**
	 * 格式化时间
	 * @return
	 */
	public static String getTime() {
		SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss.SSS");
		return format.format(new Date());
	}
	
	/**
	 * 打印参数
	 * @param args
	 */
	public static void println(String ... strs) {
		StringBuilder sb = new StringBuilder(getTime()+"|");
		for(String s:strs) {
			sb.append(s).append("|");
		}
		String toStr = sb.toString();
		System.out.println(toStr.substring(0, toStr.length()-1));
	}
}

运行命令

运行服务端:java test20230317.net.chat.ChatServer 2023
运行客户端:java test20230317.net.chat.ChatClient localhost 2023 user1

客户端命令:
发送1对1消息:消息@用户名,例如:nihao@user2
群发消息:消息,例如:nihao
获取在线用户:/user
退出:exit

;