• Java 网络编程之TCP(二):基于BIO的聊天室


    在上一篇【Java 网络编程之TCP(一):基于BIO】中,介绍Java中I/O和TCP的基本概念,本文在上文的基础上,实现一个基本的聊天室的功能。

    聊天室需求描述:

    聊天客户端:发送消息给所有其他客户端,接收其他客户端的消息

    实现说明:

    要想实现上面的聊天室的功能,我们需要一个服务端,和客户端

    服务端:接收客户端的消息,并转发给其他客户端

    客户端:发送消息给服务端,接收服务端的消息

    由于基于BIO,那么服务端都需要用两个线程,来分别进行收发消息

    代码如下:

    服务端:

    1. import java.io.IOException;
    2. import java.io.InputStream;
    3. import java.net.ServerSocket;
    4. import java.net.Socket;
    5. import java.util.LinkedList;
    6. import java.util.List;
    7. /**
    8. * 基于BIO的TCP网络通信的服务端,可以接收多个客户端连接,通过字节流接收客户端发送的消息;
    9. * 并发每个客户端的数据,转发给其余所有的客户端
    10. * 一个客户端需要使用一个线程
    11. * todo:线程资源复用; 字符流readLine 没有换行符阻塞的问题
    12. *
    13. * @author freddy
    14. */
    15. class ChatServer {
    16. // 存放所有和服务端建立连接的客户端,客户端断开,需要去除
    17. public static List clients = new LinkedList<>();
    18. public static void main(String[] args) throws IOException {
    19. // 开启server 监听端口
    20. ServerSocket serverSocket = new ServerSocket(9999);
    21. while (true) {
    22. Socket client = serverSocket.accept(); // 阻塞操作,需要新的线程处理客户端
    23. // 客户端保存,方便后面转发数据
    24. clients.add(client);
    25. // 接收Client数据,并转发
    26. new Thread(new ServerThread(client, clients)).start();
    27. }
    28. }
    29. }
    30. /**
    31. * 服务端的线程,一个客户端对应一个
    32. */
    33. class ServerThread implements Runnable {
    34. Socket socket;
    35. List clients;
    36. public ServerThread(Socket socket, List clients) {
    37. this.socket = socket;
    38. this.clients = clients;
    39. }
    40. @Override
    41. public void run() {
    42. // 获取输入流程,读取用户输入
    43. // 持续接收Client数据,并打印
    44. System.out.println("server had a client" + socket);
    45. try (InputStream inputStream = socket.getInputStream()) {
    46. byte[] buffer = new byte[1024];
    47. int len;
    48. // read操作阻塞,直到有数据可读
    49. // -1 表示流关闭,或者读到文件末尾
    50. while ((len = inputStream.read(buffer)) != -1) {
    51. System.out.println("serer receive data from " + socket + " : " + new String(buffer, 0, len));
    52. // 转发数据到其他Client
    53. for (Socket client : clients) {
    54. if (client != socket) {
    55. client.getOutputStream().write(new String(buffer, 0, len).getBytes());
    56. }
    57. }
    58. }
    59. } catch (IOException e) {
    60. System.out.println(socket + " disconnect ");
    61. // 去除当前client
    62. clients.remove(socket);
    63. }
    64. }
    65. }

    客户端代码:

    1. import java.io.IOException;
    2. import java.io.InputStream;
    3. import java.io.OutputStream;
    4. import java.net.Socket;
    5. /**
    6. * 基于BIO的TCP网络通信的客户端,接收控制台输入的数据,然后通过字节流发送给服务端
    7. *
    8. * @author freddy
    9. */
    10. class ChatClient {
    11. public static void main(String[] args) throws IOException {
    12. // 连接server
    13. Socket serverSocket = new Socket("localhost", 9999);
    14. System.out.println("client connected to server");
    15. // 读取用户在控制台上的输入,并发送给服务器
    16. new Thread(new ClientThread(serverSocket)).start();
    17. // 接收服务端发送过来的数据
    18. try (InputStream serverSocketInputStream = serverSocket.getInputStream();) {
    19. byte[] buffer = new byte[1024];
    20. int len;
    21. while ((len = serverSocketInputStream.read(buffer)) != -1) {
    22. System.out.println(
    23. "client receive data from server" + serverSocketInputStream + " : " + new String(buffer, 0, len));
    24. }
    25. }
    26. }
    27. }
    28. class ClientThread implements Runnable {
    29. private Socket serverSocket;
    30. public ClientThread(Socket serverSocket) {
    31. this.serverSocket = serverSocket;
    32. }
    33. @Override
    34. public void run() {
    35. // 读取用户在控制台上的输入,并发送给服务器
    36. InputStream in = System.in;
    37. byte[] buffer = new byte[1024];
    38. int len;
    39. try (OutputStream outputStream = serverSocket.getOutputStream();) {
    40. // read操作阻塞,直到有数据可读,由于后面还要接收服务端转发过来的数据,这两个操作都是阻塞的,所以需要两个线程
    41. while ((len = in.read(buffer)) != -1) {
    42. System.out.println("client receive data from console" + in + " : " + new String(buffer, 0, len));
    43. // 发送数据给服务器端
    44. outputStream.write(new String(buffer, 0, len).getBytes()); // 此时buffer中是有换行符
    45. }
    46. } catch (IOException e) {
    47. throw new RuntimeException(e);
    48. }
    49. }
    50. }

    测试:

    先开启服务端,再开启两个客户端,在客户端1的控制台发送i'm client1,在客户端1的控制台发送this is client2,通过日志可以看到,聊天室的功能符合要求:

  • 相关阅读:
    测试/开发程序员有8大好处,自我实现和自我超越......
    前端:弹幕标签用法详细介绍(跑马灯)
    如何做好测试管理?
    CorelDRAWX4的C++插件开发(四十)纯C++插件开发(4)继承插件结构体IVGAppPlugin和自动化接口IDispatch
    2023.11.18 Hadoop之 YARN
    用于视频压缩伪影消除的深度卡尔曼滤波网络
    JavaSE--基础语法--类和对象(第二期)
    编译原理复习笔记
    Android 组件提供的状态保存(saveInstanceState)与恢复(restoreInstanceState)
    亚马逊注册账号时老是显示内部错误
  • 原文地址:https://blog.csdn.net/u013771019/article/details/138027140