服务器端启动监听
- 创建ServerSocket对象:在BIO(Blocking I/O)模型的服务器端,首先需要创建一个
ServerSocket
对象。这个对象用于监听指定端口上的客户端连接请求。例如,在Java中可以通过ServerSocket serverSocket = new ServerSocket(portNumber);
来创建一个监听特定端口(portNumber
)的ServerSocket
。这一步相当于在服务器上打开了一个“门”,等待客户端来“敲门”。 - 进入阻塞状态等待连接:服务器启动后,调用
ServerSocket
的accept
方法来等待客户端的连接。这是一个阻塞操作,意味着服务器线程会在这里暂停执行,直到有客户端连接请求到达。例如,Socket socket = serverSocket.accept();
这行代码会使服务器线程进入阻塞状态,直到有一个新的客户端成功连接到服务器。
- 创建ServerSocket对象:在BIO(Blocking I/O)模型的服务器端,首先需要创建一个
客户端连接建立
- 客户端发起连接请求:在客户端,需要创建一个
Socket
对象并指定服务器的IP地址和端口号来发起连接请求。例如,在Java中可以使用Socket clientSocket = new Socket(serverIP, serverPort);
来建立与服务器的连接。当客户端执行这个操作时,它会向服务器发送一个连接请求。 - 服务器接受连接并返回Socket对象:当服务器的
accept
方法检测到客户端的连接请求后,它会接受这个请求,并返回一个与该客户端通信的Socket
对象。这个Socket
对象代表了服务器和客户端之间的通信通道,双方可以通过这个通道进行数据传输。
- 客户端发起连接请求:在客户端,需要创建一个
数据传输阶段
- 获取输入输出流:在服务器端和客户端都可以通过
Socket
对象获取对应的输入流(InputStream
)和输出流(OutputStream
)。例如,在服务器端可以使用InputStream inputStream = socket.getInputStream();
来获取从客户端读取数据的输入流,使用OutputStream outputStream = socket.getOutputStream();
来获取向客户端发送数据的输出流。 - 数据读写操作(阻塞过程):
- 读数据:当服务器从输入流读取客户端发送的数据时,调用
read
方法会导致线程阻塞。例如,byte[] buffer = new byte[1024]; int length = inputStream.read(buffer);
这行代码会使服务器线程阻塞,直到客户端发送了足够的数据(最多读取buffer
长度的数据)或者客户端关闭了连接。读取到的数据存储在buffer
数组中,length
表示实际读取到的字节数。 - 写数据:当服务器向客户端发送数据时,通过输出流的
write
方法。例如,String message = "Hello, client!"; outputStream.write(message.getBytes());
,这个操作会将字符串转换为字节数组并发送给客户端。如果网络或客户端接收缓冲区等原因导致数据不能立即发送出去,write
操作也可能会阻塞,直到数据成功发送或者出现异常。
- 读数据:当服务器从输入流读取客户端发送的数据时,调用
- 获取输入输出流:在服务器端和客户端都可以通过
连接关闭阶段
- 客户端或服务器主动关闭连接:当数据传输完成后,客户端或服务器可以主动关闭连接。在服务器端可以使用
socket.close();
来关闭与客户端的连接,在客户端也可以使用相同的方式关闭连接。关闭连接后,相关的资源(如套接字、输入输出流等)会被释放。 - 清理资源:除了关闭
Socket
对象外,服务器端还需要关闭ServerSocket
对象来释放监听端口等资源。例如,serverSocket.close();
,这一步通常在服务器程序结束或者不再需要监听该端口时执行,以确保系统资源的合理利用。
- 客户端或服务器主动关闭连接:当数据传输完成后,客户端或服务器可以主动关闭连接。在服务器端可以使用
BIO的整个工作流程是一种简单直接的同步阻塞式的通信方式。在这个过程中,每个客户端连接通常需要一个单独的线程来处理,因为线程在进行I/O操作(如accept
、read
、write
)时会被阻塞,无法同时处理其他客户端的请求。这种方式在连接数较少的情况下可以正常工作,但在高并发场景下可能会导致线程资源的大量消耗和性能下降。