网络编程及多线程-TCP实现聊天(二)

    技术2022-09-01  72

    TCP案例-聊天室(实现群聊和私聊功能)

    上一节我们实现了多个客户端发送给服务端消息,服务端能够接收每个客户端发送的消息,并且将消息重新发送回给客户端的任务,https://blog.csdn.net/napoluen/article/details/107073509,这一节就在之前的基础上实现群聊功能和私聊功能:

    群聊的实现:

    客户端代码:

    /* * 客户端代码 * 两个线程分别负责发送和接收服务端响应的信息。注意:发送的时候需要带上客户端的姓名。 */ public class MutiClient { public static void main(String[] args) throws IOException { //获取用户名 BufferedReader reader=new BufferedReader(new InputStreamReader(System.in)); System.out.println("请输入您的姓名:"); String name = reader.readLine(); //Tcp连接客户端需要指定ip和端口 Socket socket = new Socket("localhost",8888); //启动一个线程来发送消息 new Thread(new Send(socket,name)).start(); //一个线程接受服务端响应 new Thread(new Receive(socket)).start(); } }

    客户端发送线程的实现:

    /** * 使用多线程封装:发送端 * 1、发送消息,发送自己的名字 * 2、从控制台获取消息,发送消息 * 3、释放资源 * 4、重写run * */ public class Send implements Runnable { //客户端socket private Socket client; //输出流 private DataOutputStream dos; //通过BufferedReader得到键盘录入对象 private BufferedReader reader; //是否循环的标识符 private boolean running; public Send(Socket socket, String name) { this.running=true; this.client=socket; try { reader=new BufferedReader(new InputStreamReader(System.in)); dos=new DataOutputStream(socket.getOutputStream()); //发送给服务端自己的名字 send(name); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { while (running) { try { //读取键盘输入 String msg = reader.readLine(); send(msg); } catch (IOException e) { realase(); } } } //发送消息 private void send(String msg) { try { dos.writeUTF(msg); dos.flush(); } catch (IOException e) { System.out.println("客户端发送消息出错"); realase(); } } //修改循环标识,释放流对象及socket对象 private void realase() { this.running=false; SxtUtils.close(dos,reader,client); } }

    客户端接收数据线程实现:

    /** * 使用多线程封装:接收端 * 1、接收消息 * 2、释放资源 * 3、重写run * */ public class Receive implements Runnable { private Socket client; //读取服务端响应信息的流对象 private DataInputStream dis; private boolean running; public Receive(Socket socket) { this.running=true; this.client=socket; try { dis=new DataInputStream(client.getInputStream()); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { while (running) { String msg=receive(); if (!msg.equals("")) { System.out.println(msg); } } } private String receive() { try { String msg = dis.readUTF(); return msg; } catch (IOException e) { realase(); } return ""; } private void realase() { this.running=false; SxtUtils.close(dis,client); } }

    服务端实现:

    /** * 在线聊天室: 服务器 * 目标: 加入容器实现群聊 */ public class MutiServer { //CopyOnWriteArrayList是java并发包下的线程安全的集合,存储每个Channel通道 public static CopyOnWriteArrayList<MyChannel> all=new CopyOnWriteArrayList<>(); public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); while (true) { Socket socket = serverSocket.accept(); System.out.println("一个客户端建立了连接"); //我们为每个连接通过线程建立单独的通道。 MyChannel channel=new MyChannel(socket); all.add(channel); new Thread(channel).start(); } } }

    这里的MyChannel可以通过内部类实现,这样就可以直接访问到集合容器,也可以单独创建一个线程类,静态引入集合容器即可,这里就采用这种方式

    MyChannel代码实现:

    /* * 每个socket连接都代表一个MyChannel. */ public class MyChannel implements Runnable { private DataInputStream dis; private DataOutputStream dos; private Socket server; private boolean running; private String name; public MyChannel(Socket socket) { this.server=socket; try { this.dis=new DataInputStream(socket.getInputStream()); this.dos=new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.running=true; //连接建立就接收客户端的姓名 this.name=receive(); //建立连接以后发送给客户端欢迎信息 send("欢迎你"+this.name); //告诉其他人有用户进入了群聊室 sendother(this.name+"进入了聊天室",true); } //群聊实现 private void sendother(String string,boolean flag) { for (MyChannel myChannel : all) { if (myChannel==this) { continue; } if (flag) { myChannel.send(string); }else { myChannel.send(this.name+":"+string); } } } private void send(String msg) { try { dos.writeUTF(msg); dos.flush(); } catch (IOException e) { System.out.println("服务端响应给客户端报错!"); realase(); } } private String receive() { try { String readUTF = dis.readUTF(); return readUTF; } catch (IOException e) { System.out.println("接受客户端数据出错!"); realase(); } return ""; } private void realase() { this.running=false; SxtUtils.close(dis,dos,server); } @Override public void run() { while (running) { String msg = receive(); if (!msg.equals("")) { sendother(msg, false); } } } }

    测试群聊:


    私聊实现

    私聊实现:私聊我们可以指定消息格式来实现,只有当消息格式为:“@zhangsan:msg”,含有“@”即私聊消息,那么对整个消息进行截取,zhangsan就为私聊对象,msg为私聊的消息。

    private void sendother(String string,boolean flag) { /* * 私聊实现,通过判断消息是否有标识符 */ boolean has = string.startsWith("@"); if(has) { int idx =string.indexOf(":"); //获取目标和数据 String targetName = string.substring(1,idx); string = string.substring(idx+1); for (MyChannel myChannel : all) { if (myChannel.name.equals(targetName)) { myChannel.send(this.name+"(私信):"+string); } } }else { for (MyChannel myChannel : all) { if (myChannel==this) { continue; } if (flag) { myChannel.send(string); }else { myChannel.send(this.name+":"+string); } } } }

    测试私聊:

    测试私发消息:由王五给李四发送私聊消息:

    张三的客户端未接收到消息:

    李四的客户端接收到私发消息:

    至此,简单聊天室开发完成。

    Processed: 0.012, SQL: 12