在上一篇【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
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
clients; -
- public ServerThread(Socket socket, List
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,通过日志可以看到,聊天室的功能符合要求: