JDK 提供了对 TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)这两个数据传输协议的支持。本文开始探讨 TCP。
TCP 基础知识
在“服务器-客户端”这种架构中,服务器和客户端各自维护一个端点,两个端点需要通过网络进行数据交换。TCP 为这种需求提供了一种可靠的流式连接,流式的意思是传出和收到的数据都是连续的字节,没有对数据量进行大小限制。一个端点由 IP 地址和端口构成(专业术语为“元组 {IP 地址, 端口}”)。这样,一个连接就可以由元组 {本地地址, 本地端口, 远程地址, 远程端口} 来表示。
连接过程
在 TCP 编程接口中,端点体现为 TCP 套接字。共有两种 TCP 套接字:主动和被动,“被动”状态也常被称为“侦听”状态。服务器和客户端利用套接字进行连接的过程如下:
1、服务器创建一个被动套接字,开始循环侦听客户端的连接。
2、客户端创建一个主动套接字,连接服务器。
3、服务器接受客户端的连接,并创建一个代表该连接的主动套接字。
4、服务器和客户端通过步骤 2 和 3 中创建的两个主动套接字进行数据传输。
下面是连接过程的图解:
一个简单的 TCP 服务器
JDK 提供了 ServerSocket 类来代表 TCP 服务器的被动套接字。下面的代码演示了一个简单的 TCP 服务器(多线程阻塞模式),它不断侦听并接受客户端的连接,然后将客户端发送过来的文本按行读取,全文转换为大写后返回给客户端,直到客户端发送文本行 bye:
- public class TcpServer implements Runnable {
- private ServerSocket serverSocket;
-
- public TcpServer(int port) throws IOException {
-
- serverSocket = new ServerSocket(port);
- }
-
- @Override
- public void run() {
- while (true) {
- try {
-
- Socket socket = serverSocket.accept();
-
- new Thread(new ClientHandler(socket)).start();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
- }
-
- public class ClientHandler implements Runnable {
- private Socket socket;
-
- public ClientHandler(Socket socket) {
- this.socket = Objects.requireNonNull(socket);
- }
-
- @Override
- public void run() {
- try (Socket s = socket) {
-
- BufferedReader in = new BufferedReader(new InputStreamReader(
- s.getInputStream(), StandardCharsets.UTF_8));
-
- PrintWriter out = new PrintWriter(new OutputStreamWriter(
- s.getOutputStream(), StandardCharsets.UTF_8), true);
-
- String line = null;
- while ((line = in.readLine()) != null) {
- if (line.equals("bye")) {
- break;
- }
-
-
- out.println(line.toUpperCase(Locale.ENGLISH));
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
|