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

发布于:2024-04-28 ⋅ 阅读:(37) ⋅ 点赞:(0)

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

聊天室需求描述:

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

实现说明:

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

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

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

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

代码如下:

服务端:

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);
        }
    }
}

客户端代码:

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,通过日志可以看到,聊天室的功能符合要求:


网站公告

今日签到

点亮在社区的每一天
去签到