Java Socket模拟实现聊天室
使用Java Socket模拟实现了一个聊天室,实现了基本的私聊以及群聊。分为服务器端和客户端,下面我来介绍一下实现的步骤。
服务器端服务器端是聊天室的核心所在,主要用来处理客户端的请求,先来看一下服务器端的主方法:
public static void main(String[] args) {try { ExecutorService executorService = Executors.newFixedThreadPool(100);//最多容纳100个客户端聊天 ServerSocket serverSocket = new ServerSocket(6655);//监听6655号端口 for (int i = 0; i < 100; i++) {Socket client = serverSocket.accept();System.out.println('有新的用户连接 ' + client.getInetAddress() +client.getPort());executorService.execute(new ExecuteClientThread(client)); } executorService.shutdown(); serverSocket.close();} catch (Exception e) { e.printStackTrace();} }
首先我创建了一个固定大小为100的线程池,这个聊天室的实现是一个服务器线程对应一个客户端线程的,就是说线程池的大小就是最大的同时聊天的人数。服务器的执行顺序是这样的:
1.监听端口,等待客户端连接
2.如果有客户端连接到监听的端口,那么通过accept()方法返回该客户端的Socket,并且在线程池中启动一个新的服务器线程用来与刚刚连接的客户端'沟通'。
3.把接收到的客户端的Socket构造注入新启动的服务器线程中,这样服务器线程就可以获取到客户端对应的流。
到这里,服务器已经和客户端连接成功了,我们现在来看一下服务器线程是如何处理客户端的请求的,先上一段服务器代码
private static Map<String, Socket> clientMap = new ConcurrentHashMap<>();//存储所有的用户信息 static class ExecuteClientThread implements Runnable {private Socket client;//每一个服务器线程对应一个客户端线程ExecuteClientThread(Socket client) { this.client = client;}......
代码的第一行,创建了一个ConcurrentHashmap,这个map不是某个线程中的,而是服务器的static属性,用来存储所有客户端的信息。因为客户端是有姓名,有Socket的,所以采用K-value的模式来存储,用户名作为Key。考虑到线程安全的原因,采用了ConcurrentHashmap,保证了线程安全。
接下来就是刚刚构造注入的、连接的客户端的Socket了,我们可以通过这个Socket获取到输入和输出流。
然后就是服务器的线程执行的run方法了,具体的就直接看代码把。都有注释,就不一一解释了,以下是所有服务器端的代码
import java.io.IOException;import java.io.PrintStream;import java.net.ServerSocket;import java.net.Socket;import java.util.*;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.regex.Matcher;import java.util.regex.Pattern; public class Main { private static Map<String, Socket> clientMap = new ConcurrentHashMap<>();//存储所有的用户信息 static class ExecuteClientThread implements Runnable {private Socket client;//每一个服务器线程对应一个客户端线程ExecuteClientThread(Socket client) { this.client = client;} @Overridepublic void run() { boolean Flag = true;//防止一个客户端多次注册所做的标记位置 try {PrintStream PrintToCilent = new PrintStream(client.getOutputStream());//服务器向用户输出一些提示信息 Scanner scanner = new Scanner(client.getInputStream());String str = null;//用户外部的输入信息while (true) { if (scanner.hasNext()) {str = scanner.next();//外部的用户输出 Pattern pattern = Pattern.compile('r');//排除特殊符号Matcher matcher = pattern.matcher(str);str = matcher.replaceAll(''); if (str.startsWith('userName')) { String userName = str.split(':')[1]; userRegist(userName, client, Flag); Flag = false;}// 群聊流程else if (str.startsWith('G:')) { PrintToCilent.println('已进入群聊模式!'); groupChat(scanner,client);}// 私聊流程else if (str.startsWith('P')) {//模式 String userName = str.split('-')[1]; PrintToCilent.println('已经进入与'+userName+'的私聊'); privateChat(scanner,userName);}// 用户退出else if (str.contains('byebye')) { String userName = null; for (String getKey:clientMap.keySet()) {if (clientMap.get(getKey).equals(client)) { userName = getKey;} } System.out.println('用户'+userName+'下线了..'); clientMap.remove(userName);//将此实例从map中移除} }} } catch (IOException e) {e.printStackTrace(); }} private void userRegist(String userName, Socket client, boolean Flag) throws IOException { PrintStream PrintToCilent = new PrintStream(client.getOutputStream());//服务器向用户输出一些提示信息 if(Flag) {System.out.println('用户' + userName + '上线了!'); clientMap.put(userName, client);//把用户加入储存mapSystem.out.println('当前群聊人数为' + (clientMap.size()) + '人');PrintToCilent.println('注册成功!'); }else {PrintToCilent.println('警告:一个客户端只能注册一个用户!'); }} private void groupChat(Scanner scanner,Socket client) throws IOException { // 取出clientMap中所有客户端Socket,然后遍历一遍 // 分别取得每个Socket的输出流向每个客户端输出 PrintStream PrintToClient = new PrintStream(client.getOutputStream());//在群聊的时候服务器向客户端发送数据 boolean ExitFlag = false; Set<Map.Entry<String, Socket>> entrySet = clientMap.entrySet(); String userName = null; for (Map.Entry<String, Socket> socketEntry : entrySet) {//获得:是哪个用户说的话if (socketEntry.getValue() == client) { userName = socketEntry.getKey();//发出信息的用户} } String msg = null; while (true) {if (scanner.hasNext()) { msg = scanner.next(); if('exit'.equals(msg)){//如果用户退出了for(Map.Entry<String,Socket> stringSocketEntry : entrySet){ new PrintStream(stringSocketEntry.getValue().getOutputStream(),true).println('用户'+userName+'刚刚退出了群聊!!');//给所有人发退出群聊的消息}return; } for (Map.Entry<String, Socket> stringSocketEntry : entrySet) {//遍历用户的map,获取所有用户的Sockettry { Socket socket = stringSocketEntry.getValue(); PrintStream ps = new PrintStream(socket.getOutputStream(), true); ps.println('群聊:用户' + userName + '说: ' + msg);//给每个用户发消息} catch (IOException e) { e.printStackTrace();} } } } }private void privateChat(Scanner scanner, String privatepeopleName) throws IOException { Socket privateUser = clientMap.get(privatepeopleName); PrintStream ps = new PrintStream(privateUser.getOutputStream());//拿到私聊对象的输出流 PrintStream PrintToClient = new PrintStream(client.getOutputStream());//拿到当前客户端的输出流 String Message = null; String MyName = null; Set<Map.Entry<String,Socket>> set = clientMap.entrySet(); for(Map.Entry<String,Socket> value : set){if(value.getValue() == client){ MyName = value.getKey(); break;} } while (true) {if(scanner.hasNext()) { Message = scanner.next(); if ('exit'.equals(Message)){//如果用户输入了退出PrintToClient.println('已退出和'+privatepeopleName+'的私聊');ps.println('对方已经退出了私聊');break; } ps.println(MyName+'说'+Message);//如果用户没有退出,向私聊对象发送消息} } } } public static void main(String[] args) {try { ExecutorService executorService = Executors.newFixedThreadPool(100);//最多容纳100个客户端聊天 ServerSocket serverSocket = new ServerSocket(6655); for (int i = 0; i < 100; i++) {Socket client = serverSocket.accept();System.out.println('有新的用户连接 ' + client.getInetAddress() +client.getPort());executorService.execute(new ExecuteClientThread(client)); } executorService.shutdown(); serverSocket.close();} catch (Exception e) { e.printStackTrace();} }}
然后是客户端的代码,客户端的代码比较简单:分为两个线程,一个线程用于接收服务器的数据,一个线程用于向服务器发送数据。我就直接上代码了,里面有注释的。
import java.io.IOException;import java.io.PrintStream;import java.net.Socket;import java.util.Scanner; class ExcuteServerInPut implements Runnable{//接收服务器的数据 private Socket ToServer; ExcuteServerInPut(Socket ToServer){this.ToServer = ToServer; } @Override public void run() {try { Scanner scanner = new Scanner(ToServer.getInputStream()); while (scanner.hasNext()){System.out.println(scanner.nextLine()); } scanner.close(); ToServer.close();} catch (IOException e) { e.printStackTrace();} }} class ExcuteServerOutPut implements Runnable{//向服务器发送数据 private Socket Socket; ExcuteServerOutPut(Socket Socket){this.Socket = Socket; } @Override public void run() {try { PrintStream printStream = new PrintStream(Socket.getOutputStream()); Scanner scanner = new Scanner(System.in); scanner.useDelimiter('n'); System.out.println('*****************************************'); System.out.println('***用户注册:useerName:同户名(仅限一次)***'); System.out.println('***进入群聊:G: 退出群聊:exit***'); System.out.println('***私聊:P-用户名 退出私聊:exit***'); System.out.println('***********退出聊天室:byebye*************'); while (true){if(scanner.hasNext()) { String string = scanner.next(); printStream.println(string); if ('byebye'.equals(string)) {System.out.println('退出!');printStream.close();scanner.close();break; }} } Socket.close();} catch (IOException e) { e.printStackTrace();} }} public class Main { public static void main(String[] args) throws IOException {Socket socket = new Socket('127.0.0.1', 6655);ExcuteServerInPut excuteServerInPut = new ExcuteServerInPut(socket);ExcuteServerOutPut excuteServerOutPut = new ExcuteServerOutPut(socket);new Thread(excuteServerInPut).start();new Thread(excuteServerOutPut).start();}}
后续我会做一些改进,希望可以对大家有所帮助
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持好吧啦网。
相关文章: