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