基于连接通信Socket、多线程的Java聊天室
1、开发环境:
IDEA2018.1+JDK1.8
2、实现功能:
实现了模拟登录注册、群聊、私聊、显示当前在线人数列表;
在发送信息时,会向对方发送者及显示发送时间;
显示在线人数列表时,也会显示查询时间;
实现了多线程发送消息、接收消息过程。
3、代码解析(源码可见本篇博客最后):
1)客户端源码解析
客户端使用两个线程操作
一个读线程:创建客户端输入流,while循环将读到的信息输出道控制台,模拟一直等待监听输入的情况;
一个写线程:创建客户端输出流,while循环获取用户从控制台输入的内容,若用户输入的信息中包含"bye",则关闭流并且关闭该用户的Socket,此时退出while循环,写线程结束;此时读线程的if条件判断就会执行break;while循环停止,读线程结束。
2)服务器源码解析
利用Executors类创建固定大小为20的线程池(实现多线程);
使用Map集合来存储用户信息,<String, Socket>:用户名,客户端的socket;Map集合使用子类ConcurrentHashMap来实例化,保证线程安全(主要可见Java集合总结);
使用内部类来处理客户端的连接与发送信息;
根据控制台约束信息进行用户注册、私发、群聊等等功能的实现
3)两台计算机之间使用套接字建立TCP连接过程:
服务器实例化一个ServerSocket对象,new ServerSocket(6666):表示是通过服务器上的6666端口进行通信;
服务器调用ServerSocket类的accept(),该方法阻塞式等待客户端的连接;
客户端实例化一个Socket对象,new Socket("127.0.0.1", 6666):指定服务器名称和端口号来请求连接,该构造方法试图将客户端连接到指定的服务器和端口号,如果通信建立,则会在客户端创建Socket对象能够与服务器进行通信;
在服务端,accept()方法返回客户端的socket对象。
4、结果展示:
开启了三个客户端
测试: 用户上线功能、显示当前用户列表功能、私聊功能、用户下线功能
测试: 用户上线功能、显示当前用户列表功能、群聊功能
测试: 用户上线功能、显示当前用户列表功能、群发功能、私聊功能
服务端:体现出了用户上线及用户下线的情况
客户端源代码:
//读线程
class ReadThread implements Runnable{private Socket client;public ReadThread(Socket client) {this.client = client;}@Overridepublic void run() {try{Scanner in = new Scanner(client.getInputStream());while(true){if(in.hasNextLine()){System.out.println(in.nextLine());}if(client.isClosed()){break;}}} catch (IOException e) {e.printStackTrace();}}
}//写线程
class WriteThread implements Runnable{private Socket client;public WriteThread(Socket client) {this.client = client;}@Overridepublic void run() {try {PrintStream out = new PrintStream(client.getOutputStream(),true, "UTF-8");Scanner scanner = new Scanner(System.in);String str = "";while(true){System.out.println("在此输入:");if(scanner.hasNextLine()){str = scanner.nextLine();out.println(str);}if(str.contains("bye")){scanner.close();out.close();client.close();break;}}} catch (IOException e) {e.printStackTrace();}}
}public class MutilThreadClient {public static void main(String[] args) {try {Socket client = new Socket("127.0.0.1", 6666);Thread readThread = new Thread(new ReadThread(client));Thread writeThread = new Thread(new WriteThread(client));readThread.start();writeThread.start();} catch (IOException e) {e.printStackTrace();}}
}
服务器源代码:
public class MutilThreadServer {//使用Map的K,V存储用户信息(用户名,socket),模拟登陆现象private static Map<String, Socket> clientMap = new ConcurrentHashMap<String, Socket>();//线程安全//内部类,处理客户端private static class ExecuteClient implements Runnable{private Socket client;private ExecuteClient(Socket client) {this.client = client;}//构造注入客户端socket//返回给客户端信息时间SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublic void run() {try {//获取输入流Scanner in = new Scanner(client.getInputStream());String str = "";while(true){if(in.hasNextLine()){str = in.nextLine();//windows下将默认的换行\r\n中的\r替换为空字符串Pattern pattern = Pattern.compile("\r");Matcher matcher = pattern.matcher(str);str = matcher.replaceAll("");//聊天室实现的功能//用户上线:// user:123if(str.startsWith("user")){String user = str.split("\\:")[1];register(user, client);continue;}//群聊:// G:发送内容if(str.startsWith("G")){String text = str.split("\\:")[1];String user = getUser(client);if(user == ""){continue;}groupChat(text, user);continue;}//私聊//P-sendUser:textif(str.startsWith("P")){String sendUser = str.split("\\-")[1].split("\\:")[0];String text = str.split("\\-")[1].split("\\:")[1];String user = getUser(client);if(user == ""){continue;}privateChat(sendUser, text, user);continue;}//查看当前用户在线列表//用户输入ls:显示当前在线人数if(str.equals("ls")){PrintStream out = new PrintStream(client.getOutputStream(),true, "UTF-8");out.println("当前在线用户列表如下 " + sdf.format(System.currentTimeMillis()));for(String key : clientMap.keySet()){out.println(" · 用户" + key);}out.println(" · 当前在线人数:" + clientMap.size());continue;}//用户下线if(str.contains("bye")){String user = getUser(client);System.out.println("用户" + user + "下线了.....");clientMap.remove(user);continue;}}}} catch (IOException e) {e.printStackTrace();}}//通过客户端socket获取用户名private String getUser(Socket client) {String user = "";for(String key : clientMap.keySet()) {if (clientMap.get(key).equals(client)) {user = key;}}if(user == ""){try {PrintStream out = new PrintStream(client.getOutputStream(),true, "UTF-8");out.println("当前您还未注册,请先注册!");} catch (IOException e) {e.printStackTrace();}}return user;}//注册实现private void register(String user, Socket client) {System.out.println("用户"+user+"上线了.......");clientMap.put(user, client);System.out.println("当前群聊人数为" + clientMap.size() + "人");//告知用户注册成功try {PrintStream out = new PrintStream(client.getOutputStream(),true, "UTF-8");out.println("注册成功!");} catch (IOException e) {System.err.println("注册异常:" + e);}}//群聊实现private void groupChat(String text, String user) {Set<Entry<String, Socket>> clientSet = clientMap.entrySet();for (Entry<String, Socket> entry : clientSet) {try {PrintStream out = new PrintStream(entry.getValue().getOutputStream(),true, "UTF-8");out.println("用户" + user + " " + sdf.format(System.currentTimeMillis()));out.println(" 群发:" + text);} catch (IOException e) {System.err.println("群聊异常:" + e);}}}//私聊实现private void privateChat(String sendUser, String text, String user) {try {PrintStream out = new PrintStream(clientMap.get(sendUser).getOutputStream(),true, "UTF-8");out.println("用户" + user + " " + sdf.format(System.currentTimeMillis()));out.println(" 私发:"+ text);} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) throws Exception{//利用Exectors工具类创建固定大小线程池ExecutorService executorService = Executors.newFixedThreadPool(20);//创建服务端SocketServerSocket serverSocket = new ServerSocket(6666);for(int i = 0; i < 20; ++i){System.out.println("等待客户端链接.........");Socket client = serverSocket.accept();System.out.println("有新的客户端连接,端口号为" + client.getPort());//向线程池提交线程executorService.submit(new ExecuteClient(client));}executorService.shutdown();serverSocket.close();}
}
整体概括