跳到主要内容

Java 网络 IO

信息

示例代码:code/learn-java/com.xzp.demo.bio

什么是网络 IO

网络 IO 是指通过网络进行输入输出的操作,是指在网络中把数据从一个地方传送到另一个地方的操作。

网络 IO 可以用来实现远程数据传输、实时数据传输、分布式系统通信等功能。

什么是 BIO

BIO(Blocking IO)是 Java 中的同步阻塞 IO,也是最常用的 IO 模型。它的工作流程是:客户端发出请求后,服务器端会进行资源的分配,等待客户端发送请求,服务器接收到客户端发送的请求后,进行相应的业务处理,服务器处理完毕后,会将处理结果返回给客户端,然后客户端接收到服务器返回的结果,完成一次请求的处理。

BIO 模型的优点是简单易理解,缺点是每一次请求都需要单独开辟一个线程去处理,如果请求量很大,就会占用大量内存,效率也就较低。

BIO 可以用于小型的服务器程序,比如简单的局域网聊天服务器,简单的 FTP 服务器。

示例:一个基于 BIO 的服务端代码

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
// 这里accept函数调用地方,当外界没有任何连接建立的时候,是不会返回Socket对象的,会让程序长时间处于阻塞状态
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
// 另外,当连接建立后,如果连接的另一端没有发送数据的话,在调用read函数的时候,也是不会返回任何内容的,这样的设计也会导致程序长时间处于堵塞状态
int len = inputStream.read(bytes);
System.out.println(new String(bytes, 0, len));
inputStream.close();
socket.close();
serverSocket.close();
}
}

示例:一个基于 BIO 的客户端代码

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
try {
// 延迟3s执行
Thread.sleep(3000);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("HelloServer".getBytes());
outputStream.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

接下来,需要先启动 Server 端的程序,然后再启动 Client 端的程序,接着就会看到 Server 端程序的控制台中输出 HelloServer 的字样了。

一个简单的聊天室示例

基于 Java BIO 模型的聊天室

信息

示例代码:code/learn-java/com.xzp.demo.chat

设计思路

  1. 服务器启动后监听指定端口
  2. 客户端连接到服务器,服务器将其加入在线用户列表
  3. 客户端发送聊天消息,服务器接收并转发给其他在线用户

服务端

import java.io.*;
import java.net.*;
import java.util.*;

public class ChatRoomServer {

// 保存在线用户列表
private static List<Socket> socketList = new ArrayList<>();

public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已开启,等待客户端连接...");

while (true) {
// 等待客户端连接
Socket socket = serverSocket.accept();
System.out.println("有客户端连接上来了:" + socket);

// 将客户端加入在线列表
socketList.add(socket);

// 启动一个新线程处理该客户端的消息
new ChatHandler(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}

// 聊天处理线程
static class ChatHandler extends Thread {
private Socket socket;

public ChatHandler(Socket socket) {
this.socket = socket;
}

public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = null;
while ((msg = br.readLine()) != null) {
// 收到客户端消息后,转发给其他客户端
System.out.println("收到客户端消息:" + msg);
for (Socket s : socketList) {
if (!s.equals(socket)) {
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.println(msg);
pw.flush();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 客户端下线,从在线列表移除该客户端连接
socketList.remove(socket);
}
}
}
}

客户端

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class ChatRoomClient {

public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 8888);
System.out.println("成功连接上服务器");

// 从服务器接收消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
new Thread(new Runnable() {
@Overridepublic void run() {
String msg = null;
try {
while ((msg = br.readLine()) != null) {
System.out.println("收到服务器消息:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();

// 向服务器发送消息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
Scanner scanner = new Scanner(System.in);
String line = null;
while ((line = scanner.nextLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

总结

服务端主要流程:

  1. 监听端口,等待客户端连接
  2. 将客户端连接加入在线列表
  3. 启动一个新线程处理该客户端的消息,该线程不断从客户端输入流读取消息,转发给其他在线客户端
  4. 客户端下线时从在线列表移除该客户端连接

客户端主要流程:

  1. 连接服务器
  2. 从服务器输入流读取消息并输出
  3. 从控制台输入消息并发送给服务器

这是一个简单的基于 Java BIO 的聊天室。需要注意的是,BIO 模型下,每个客户端对应一个独立的线程,当客户端连接数增加时,线程数量也会增加,极大地浪费系统资源,因此在实际开发中不推荐使用。可选的其他模型包括 NIO 和 Netty。

参考