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

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

聊天室需求描述:

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

实现说明:

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

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

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

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

代码如下:

服务端:

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
import java.util.List;

/**
 * 基于BIO的TCP网络通信的服务端,可以接收多个客户端连接,通过字节流接收客户端发送的消息;
 * 并发每个客户端的数据,转发给其余所有的客户端
 * 一个客户端需要使用一个线程
 * todo:线程资源复用; 字符流readLine 没有换行符阻塞的问题
 *
 * @author freddy
 */
class ChatServer {
    // 存放所有和服务端建立连接的客户端,客户端断开,需要去除
    public static List<Socket> clients = new LinkedList<>();

    public static void main(String[] args) throws IOException {
        // 开启server 监听端口
        ServerSocket serverSocket = new ServerSocket(9999);
        while (true) {
            Socket client = serverSocket.accept(); // 阻塞操作,需要新的线程处理客户端
            // 客户端保存,方便后面转发数据
            clients.add(client);
            // 接收Client数据,并转发
            new Thread(new ServerThread(client, clients)).start();
        }
    }
}

/**
 * 服务端的线程,一个客户端对应一个
 */
class ServerThread implements Runnable {
    Socket socket;

    List<Socket> clients;

    public ServerThread(Socket socket, List<Socket> clients) {
        this.socket = socket;
        this.clients = clients;
    }

    @Override
    public void run() {
        // 获取输入流程,读取用户输入
        // 持续接收Client数据,并打印
        System.out.println("server had a client" + socket);
        try (InputStream inputStream = socket.getInputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            // read操作阻塞,直到有数据可读
            // -1 表示流关闭,或者读到文件末尾
            while ((len = inputStream.read(buffer)) != -1) {
                System.out.println("serer receive data from " + socket + " : " + new String(buffer, 0, len));
                // 转发数据到其他Client
                for (Socket client : clients) {
                    if (client != socket) {
                        client.getOutputStream().write(new String(buffer, 0, len).getBytes());
                    }
                }
            }
        } catch (IOException e) {
            System.out.println(socket + " disconnect ");
            // 去除当前client
            clients.remove(socket);
        }
    }
}

客户端代码:

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 基于BIO的TCP网络通信的客户端,接收控制台输入的数据,然后通过字节流发送给服务端
 *
 * @author freddy
 */
class ChatClient {
    public static void main(String[] args) throws IOException {
        // 连接server
        Socket serverSocket = new Socket("localhost", 9999);
        System.out.println("client connected to server");

        // 读取用户在控制台上的输入,并发送给服务器
        new Thread(new ClientThread(serverSocket)).start();

        // 接收服务端发送过来的数据
        try (InputStream serverSocketInputStream = serverSocket.getInputStream();) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = serverSocketInputStream.read(buffer)) != -1) {
                System.out.println(
                    "client receive data from server" + serverSocketInputStream + " : " + new String(buffer, 0, len));
            }
        }

    }
}

class ClientThread implements Runnable {
    private Socket serverSocket;

    public ClientThread(Socket serverSocket) {
        this.serverSocket = serverSocket;
    }

    @Override
    public void run() {
        // 读取用户在控制台上的输入,并发送给服务器
        InputStream in = System.in;
        byte[] buffer = new byte[1024];
        int len;
        try (OutputStream outputStream = serverSocket.getOutputStream();) {
            // read操作阻塞,直到有数据可读,由于后面还要接收服务端转发过来的数据,这两个操作都是阻塞的,所以需要两个线程
            while ((len = in.read(buffer)) != -1) {
                System.out.println("client receive data from console" + in + " : " + new String(buffer, 0, len));
                // 发送数据给服务器端
                outputStream.write(new String(buffer, 0, len).getBytes()); // 此时buffer中是有换行符
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

测试:

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

相关推荐
阿维的博客日记2 分钟前
Nacos 为什么能让配置动态生效?(涉及 @RefreshScope 注解)
java·spring
雨辰AI2 分钟前
SpringBoot3 + 人大金仓读写分离 + 分库分表 + 集群高可用 全栈实战
java·数据库·mysql·政务
辰海Coding1 小时前
MiniSpring框架学习-完成的 IoC 容器
java·spring boot·学习·架构
小小编程路1 小时前
C++ 多线程与并发
java·jvm·c++
AI视觉网奇2 小时前
linux 检索库 判断库是否支持
java·linux·服务器
她的男孩2 小时前
从零搭一个企业后台,为什么我把能力拆成 Starter 和 Plugin
java·后端·架构
RainCity2 小时前
Java Swing 自定义组件库分享(七)
java·笔记·后端
Sam_Deep_Thinking2 小时前
连锁门店的外卖订单平台对接
java·微服务·架构·系统架构
_遥远的救世主_3 小时前
从一次结果集密集型查询 OOM 看 Java 服务的稳定性架构治理
java·后端
一楼的猫3 小时前
从工具链视角对比:番茄作家助手 vs 第三方写作辅助方案
java·服务器·开发语言·前端·学习·chatgpt·ai写作