
一、实验描述本实验实现基于TCP的多人聊天室。多个用户同时访问服务端各自可以不断请求服务端获取响应的数据并可以实现群聊和私聊功能。服务器则实现对数据的转发功能。本文工具IDEA2020、JDK1.8本文源代码下载服务端ReceiveWithTCP.ziphttps://pan.baidu.com/s/1Qt2xWM43aKOEiP5GlsKFew?pwdx59j客户端ClientWithTCP.ziphttps://pan.baidu.com/s/152Jbh3bo8DS7ybk5wKjTrA?pwdv6uy二、分析1.客户端1数据发送 2数据接收 技术socket、输入流和输出流、多线程 聊天群聊、私聊 注意私聊格式服务器用户ID号msg2.服务端1数据转发 2用户注册 技术ServerScoket、每个用户对应Socket对象、多线程同时在线 数据转发私聊前缀判断、群聊所有人发送三、创建客户端创建完客户端结构如下1创建基础项目打开IDEA点击File—New—Project下面这里选择Java然后右侧选择SDK版本这里选择1.8版本下一步输入Project name这里 项目名称为 ClientWithTCP右下角选择“Finish”右击src文件夹选择New—Package这里创建一个包包名为com.guo.www 或者其他包名2创建工具类主要用于结束所有的连接。右击www文件夹选择New—Java Class名字为ChatUtils参考代码如下importjava.io.Closeable;importjava.io.DataOutputStream;importjava.io.BufferedReader;importjava.io.IOException;importjava.net.Socket;publicclassChatUtils{/** * 安全关闭多个资源支持可变参数自动处理null和异常 * param resources 可变参数可以是 DataOutputStream、DataInputStream、BufferedReader、Socket 等 */publicstaticvoidclose(AutoCloseable...resources){if(resourcesnull){return;}for(AutoCloseableresource:resources){try{if(resource!null){resource.close();}}catch(Exceptione){System.err.println(关闭资源时发生异常: e.getMessage());}}}}(3)编写客户端发送线程类右击www文件夹选择New—Java Class名字为SendThreadClient参考代码如下importjava.io.BufferedReader;importjava.io.DataOutputStream;importjava.io.IOException;importjava.io.InputStreamReader;importjava.net.Socket;publicclassSendThreadClientimplementsRunnable{privateBufferedReaderreader;privateDataOutputStreamdos;privateSocketclientSocket;privatebooleanisRunning;privateStringname;publicSendThreadClient(Socketclient,Stringname){this.clientSocketclient;this.isRunningtrue;this.namename;readernewBufferedReader(newInputStreamReader(System.in));try{dosnewDataOutputStream(client.getOutputStream());//发送用户给服务器用户名用于注册需要调用send方法send(name);}catch(IOExceptionex){ex.printStackTrace();//释放资源release();}}/** * * 线程代码只要当前连接状态就一直读取字符串和发送信息 * */Overridepublicvoidrun(){while(isRunning){Stringmsgnull;try{msgreader.readLine();}catch(IOExceptione){System.out.println(数据写入失败e.toString());release();}send(msg);}}/*** * * param msg 需要发送的信息 */publicvoidsend(Stringmsg){try{System.out.println(本机发送信息msg);dos.writeUTF(msg);dos.flush();}catch(IOExceptione){System.out.println(数据发送失败e.toString());release();//释放资源}}//释放资源publicvoidrelease(){ChatUtils.close(dos,clientSocket,reader);}}4编写客户端接收线程类右击www文件夹选择New—Java Class名字为ReceiveThreadClient参考代码如下importjava.io.DataInputStream;importjava.io.IOException;importjava.net.Socket;publicclassReceiveThreadClientimplementsRunnable{privatebooleanisRunning;privateDataInputStreamdis;privateSocketclient;/** * 构造方法 * */publicReceiveThreadClient(Socketclient){this.clientclient;this.isRunningtrue;try{disnewDataInputStream(client.getInputStream());}catch(IOExceptionex){System.out.println(DataInputStream对象创建失败);release();}}Overridepublicvoidrun(){while(isRunning){Stringmsg;msgreceive();if(!msg.equals()){System.out.println(msg);}}}/*** * 从服务端接收数据 * return */publicStringreceive(){Stringmsgnull;try{msgdis.readUTF();returnmsg;}catch(IOExceptione){System.out.println(数据接收失败);release();}return;}//释放资源publicvoidrelease(){isRunningfalse;ChatUtils.close(dis,client);}}5编写客户端类右击www文件夹选择New—Java Class名字为ClientMultiUser参考代码如下importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.net.Socket;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;publicclassClientMultiUser{staticStringServerIP127.0.0.1;staticintPort8888;publicstaticvoidmain(String[]args)throwsIOException{System.out.println(TCP客户端启动);System.out.println(请输入用户名);BufferedReaderreadernewBufferedReader(newInputStreamReader(System.in));Stringnamereader.readLine();//控制台输入数据//创建Socket,绑定服务器IP地址和端口SocketclientnewSocket(ServerIP,Port);//使用线程池管理两个线程一个是发送线程一个是接收线程ExecutorServicepoolExecutors.newFixedThreadPool(2);pool.submit(newSendThreadClient(client,name));//发送线程pool.submit(newReceiveThreadClient(client));//接收线程}}四、创建服务端服务端建议重新创建一个新项目单独运行。项目名称为ReceiveWithTCP前面的都一样创建包和ChatUtils工具类整个项目结构如下右击www文件选择New—Java Class类名为ServerMultiUser参考代码如下importjava.io.DataInputStream;importjava.io.DataOutputStream;importjava.io.IOException;importjava.net.ServerSocket;importjava.net.Socket;importjava.util.concurrent.CopyOnWriteArrayList;publicclassServerMultiUser{//用于存储所有客户端的一个容器涉及多线程的并发操作//使用CopyOnWriteArrayList保证线程安全privatestaticCopyOnWriteArrayListChannelallClientnewCopyOnWriteArrayListChannel();publicstaticvoidmain(String[]args)throwsException{System.out.println(服务端启动);//建立ServerSocket对象并绑定本地端口ServerSocketservernewServerSocket(8888);//一直循环接收来自多个客户端的请求while(true){//监听SocketclientSocketserver.accept();ChannelclientnewChannel(clientSocket);allClient.add(client);System.out.println(client.name建立了连接);newThread(client).start();}}staticclassChannelimplementsRunnable{privateDataInputStreamdis;privateDataOutputStreamdos;privateSocketclient;privatebooleanisRunning;privateStringname;//构造方法publicChannel(Socketclient){this.clientclient;this.isRunningtrue;try{disnewDataInputStream(client.getInputStream());dosnewDataOutputStream(client.getOutputStream());this.namereveive();//接收客户端的名称this.send(欢迎来到聊天室。。。);this.sendOther(this.name来到了聊天室。。。,true);}catch(IOExceptione){release();//释放资源}}Overridepublicvoidrun(){while(isRunning){Stringmsgreveive();if(!msg.equals()){sendOther(msg,false);}}}//发送数据publicvoidsend(Stringmsg){try{dos.writeUTF(msg);dos.flush();}catch(IOExceptione){System.out.println(数据发送失败);release();}}/** * 获取自己的消息然后发送给其他人 * isSys 表示是否为系统消息 * 私聊可以向某一特定的用户发送数据 * 约定格式服务器用户ID:消息 */publicvoidsendOther(Stringmsg,booleanisSys){booleanisPrivatemsg.startsWith();if(isPrivate){//寻找冒号intindexmsg.indexOf(:);//截取IDStringtargetNamemsg.substring(1,index);//截取消息内容Stringdatasmsg.substring(index1);for(Channelother:allClient){if(other.name.equals(targetName)){other.send(this.name悄悄对你说datas);}}}else{for(Channelother:allClient){if(otherthis){//自己不能给自己发continue;}if(!isSys){other.send(this.name对大家说msg);}else{other.send(msg);}}}}//接收数据publicStringreveive(){try{Stringmsg;msgdis.readUTF();returnmsg;}catch(IOExceptione){isRunningfalse;System.out.println(接收数据失败);release();}return;}//释放资源publicvoidrelease(){this.isRunningfalse;ChatUtils.close();allClient.remove(this);this.sendOther(this.name离开了聊天室...,true);}}}五、测试效果先启动服务端然后复制两个客户端单独启动分别在客户端发送数据观察其他客户端接收数据的情况最左侧是服务端中间是第一个客户端最右边是第二个客户端效果如下