程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Java网络编程

    • 网络基础知识
    • Java中的网络编程
    • TCP通信编程
      • 1. 创建简单的客户端-服务端连接
        • 1.1 服务端代码详解
        • 1.2 客户端代码详解
        • 1.3 执行过程与控制台输出
        • 1.4 重要的 API 和参数说明
      • 2. 创建更复杂的客户端-服务端连接
        • 2.1 改进后的服务端代码详解
        • 2.2 改进后的客户端代码详解
        • 2.3 执行过程与控制台输出
        • 2.4 重要的 API 和参数说明
      • 3. TCP 通信的细节
        • 1. 构造方法
        • 2. 相关方法
        • 3. 注意事项
      • 4. TCP 三次握手详解
      • 5. TCP 四次挥手详解
    • UDP通信编程
    • 网络编程综合案例
  • Java网络编程
  • Java网络编程
scholar
2024-08-24
目录

TCP通信编程

# TCP 通信编程详解

# 1. 创建简单的客户端-服务端连接

注意:在网络编程中,我们需要养成一个良好的习惯:首先编写服务端,并先启动服务端,确保服务端可以正常监听端口,然后再编写和启动客户端。

# 1.1 服务端代码详解

package org.javatop.socket;

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

/**
 * @description: TCP 服务端示例
 * @version: 1.0
 * @author: scholar
 * @date: 2023-11-02
 */
public class SocketTCPServer01 {

    public static void main(String[] args) throws IOException {

        /*
            1. 创建一个 ServerSocket 对象,绑定到指定端口(如 8888),用于监听客户端连接请求。
            细节:
            - 该端口必须未被其他服务占用。
            - ServerSocket 可以通过 accept() 方法接受多个客户端的连接请求,支持并发。
        */
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务端:正在 8888 端口监听,等待客户端连接...");

        // 2. accept() 方法会阻塞当前线程,直到有客户端连接到指定端口。
        // 当客户端连接成功时,返回一个 Socket 对象,代表客户端的连接。
        Socket socket = serverSocket.accept();

        System.out.println("服务端 socket = " + socket.getClass());

        // 3. 获取输入流,读取客户端发送的数据。
        InputStream is = socket.getInputStream();

        // 4. 使用字节数组接收数据,通过循环读取直到结束。
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = is.read(buf)) != -1) {
            // 将接收到的字节数据转换为字符串,并输出到控制台
            System.out.println(new String(buf, 0, readLen));
        }

        // 5. 关闭流和 Socket 连接,释放资源
        is.close();
        socket.close();
        serverSocket.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

代码解读:

  • ServerSocket(int port): 创建一个绑定到指定端口的服务器套接字,端口号必须未被其他服务占用。
  • serverSocket.accept(): 该方法会阻塞当前线程,直到有客户端连接成功后返回一个与客户端通信的 Socket 对象。
  • socket.getInputStream(): 获取与客户端连接的输入流,用于读取客户端发送的数据。

# 1.2 客户端代码详解

package org.javatop.socket;

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

/**
 * @description: TCP 客户端示例
 * @version: 1.0
 * @author: scholar
 * @date: 2023-11-02
 */
public class SocketTCPClient01 {

    public static void main(String[] args) throws IOException {
        /*
            1. 创建一个 Socket 对象,连接到服务端的 IP 地址和端口号。
            连接成功后,返回一个 Socket 对象。
            - 参数1: InetAddress 表示服务端的 IP 地址,可以通过 getLocalHost() 获取本机地址。
            - 参数2: int 表示服务端的端口号(如 8888)。
        */
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        System.out.println("客户端 socket = " + socket.getClass());

        // 2. 获取输出流,向服务端发送数据
        OutputStream outputStream = socket.getOutputStream();
        // 3. 通过输出流发送数据
        outputStream.write("Hello, SocketTCPServer01! 这是客户端发送的一条消息。".getBytes());

        // 4. 关闭流和 Socket 连接,释放资源
        outputStream.close();
        socket.close();
        System.out.println("客户端已退出...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

代码解读:

  • Socket(InetAddress address, int port): 创建一个 Socket 连接,指定服务端的 IP 地址和端口号。连接成功后,返回一个用于与服务端通信的 Socket 对象。
  • socket.getOutputStream(): 获取输出流,用于向服务端发送数据。
  • outputStream.write(): 将数据写入输出流,发送到服务端。

# 1.3 执行过程与控制台输出

  1. 启动服务端,服务端开始监听指定端口(如 8888),等待客户端连接:
    服务端:正在 8888 端口监听,等待客户端连接...
    
    1
  2. 启动客户端,客户端连接成功后发送消息,连接结束后关闭:
    客户端 socket = class java.net.Socket
    客户端已退出...
    
    1
    2
  3. 服务端接收到客户端发送的消息并输出:
    服务端 socket = class java.net.Socket
    Hello, SocketTCPServer01! 这是客户端发送的一条消息。
    
    1
    2

# 1.4 重要的 API 和参数说明

  • ServerSocket(int port): 创建并绑定到指定端口的服务器套接字。端口号用于标识网络服务,需确保唯一性。
  • Socket(InetAddress address, int port): 客户端通过该构造方法连接指定的 IP 地址和端口号。
  • accept(): 阻塞方法,等待客户端连接成功返回 Socket 实例,用于与客户端通信。
  • getInputStream()/getOutputStream(): 获取输入输出流,分别用于读取和发送数据。
  • write(byte[] b): 将字节数组数据写入输出流并发送。
  • close(): 关闭套接字和相关流,释放系统资源。

# 2. 创建更复杂的客户端-服务端连接

在前面的示例中,我们实现了一个简单的 TCP 通信程序,客户端发送一条消息后即断开连接。现在,我们来改进这个程序,使得客户端在发送完消息后,还可以接收到服务端的回复,再进行断开连接。

需求: 客户端连接到服务端后,发送一条消息;服务端接收到消息后,回发一条消息给客户端,客户端接收后再断开连接。

实现思路:

  1. 客户端在发送完消息后,通过设置标识来告知服务端消息发送完毕。
  2. 服务端在接收到消息并确认后,回发一条消息给客户端,并设置标识表示消息发送完成。
  3. 客户端收到服务端的消息后,断开连接。

# 2.1 改进后的服务端代码详解

package org.javatop.socket;

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

/**
 * @description: TCP 服务端示例 - 改进版
 * @version: 1.0
 * @author: scholar
 * @date: 2023-11-02
 */
public class SocketTCPServer02 {

    public static void main(String[] args) throws IOException {

        /*
            1. 创建一个 ServerSocket 对象,绑定到指定端口(如 8888),用于监听客户端连接请求。
            - ServerSocket 可以通过 accept() 方法接受多个客户端的连接请求,支持并发。
        */
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务端:正在 8888 端口监听,等待客户端连接...");

        // 2. accept() 方法会阻塞当前线程,直到有客户端连接到指定端口。
        // 当客户端连接成功时,返回一个 Socket 对象,代表客户端的连接。
        Socket socket = serverSocket.accept();

        System.out.println("服务端 socket = " + socket.getClass());

        // 3. 获取输入流,读取客户端发送的数据。
        InputStream is = socket.getInputStream();

        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = is.read(buf)) != -1) {
            // 将接收到的字节数据转换为字符串,并输出到控制台
            System.out.println(new String(buf, 0, readLen));
        }

        // 4. 获取输出流,向客户端发送消息
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello, SocketTCPClient02! 这是服务端发送的一条消息...".getBytes());

        // 5. 设置结束标记,告知客户端消息发送完毕
        socket.shutdownOutput();

        // 6. 关闭流和 Socket 连接,释放资源
        is.close();
        socket.close();
        serverSocket.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

代码解读:

  • socket.shutdownOutput(): 该方法用于告诉接收方当前输出流的写操作已完成,通常用于标记消息发送完毕。接收方在读取到流的结束标记后会停止接收数据。

# 2.2 改进后的客户端代码详解

package org.javatop.socket;

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

/**
 * @description: TCP 客户端示例 - 改进版
 * @version: 1.0
 * @author: scholar
 * @date: 2023-11-02
 */
public class SocketTCPClient02 {

    public static void main(String[] args) throws IOException {
        /*
            1. 创建一个 Socket 对象,连接到服务端的 IP 地址和端口号。
            - 参数1: InetAddress 表示服务端的 IP 地址,可以通过 getLocalHost() 获取本机地址。
            - 参数2: int 表示服务端的端口号(如 8888)。
        */
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        System.out.println("客户端 socket = " + socket.getClass());

        // 2. 获取输出流,向服务端发送数据
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello, SocketTCPServer02! 这是客户端发送的一条消息...".getBytes());

        // 3. 设置结束标记,告知服务端消息发送完毕
        socket.shutdownOutput();

        // 4. 获取输入流,读取服务端回发的数据
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println("客户端收到服务端的回复信息 = " + new String(buf, 0, readLen));
        }

        // 5. 关闭流和 Socket 连接,释放资源
        outputStream.close();
        inputStream.close();
        socket.close();
        System.out.println("客户端已退出...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

代码解读:

  • socket.shutdownOutput(): 该方法在客户端发送完消息后调用,标记输出流已完成,通知服务端可以开始读取数据。
  • 客户端在接收完服务端的回复后,关闭所有相关流和 Socket 连接。

# 2.3 执行过程与控制台输出

  1. 服务端启动并监听端口,等待客户端连接:
    服务端:正在 8888 端口监听,等待客户端连接...
    
    1
  2. 客户端连接成功后发送消息,并等待服务端的回复:
    客户端 socket = class java.net.Socket
    客户端收到服务端的回复信息 = hello, SocketTCPClient02! 这是服务端发送的一条消息...
    客户端已退出...
    
    1
    2
    3
  3. 服务端接收到客户端消息后回发,并标记消息发送完毕:
    服务端 socket = class java.net.Socket
    hello, SocketTCPServer02! 这是客户端发送的一条消息...
    
    1
    2

# 2.4 重要的 API 和参数说明

  • socket.shutdownOutput():用于标记输出流的结束,通知接收方不再有更多数据。当接收方读取到流的结束标记时,会停止读取,避免数据传输不完整。
  • accept():服务端阻塞方法,用于等待客户端连接。一旦连接成功,返回一个与客户端通信的 Socket 对象。
  • getInputStream()/getOutputStream():用于获取输入/输出流,分别用于读取和发送数据。
  • close():关闭流和 Socket,释放系统资源。

# 3. TCP 通信的细节

# 1. 构造方法

TCP 通信中的构造方法决定了如何创建服务端和客户端的 Socket 实例。以下是常用的构造方法:

方法名 说明
ServerSocket(int port) 创建一个绑定到指定端口的服务器套接字,用于监听客户端连接

说明:

  • ServerSocket(int port):此构造方法用于在指定的端口上创建一个 ServerSocket 实例,服务端通过这个套接字监听客户端的连接请求。端口号必须唯一,且未被其他服务占用。

# 2. 相关方法

在实际通信过程中,服务端和客户端需要使用以下关键方法进行连接和数据传输:

方法名 说明
Socket accept() 监听连接请求并接受与客户端的连接,返回一个 Socket 对象

说明:

  • accept():该方法用于服务端监听客户端的连接请求。调用此方法时,程序会进入阻塞状态,直到有客户端连接成功,返回一个与客户端通信的 Socket 对象。

# 3. 注意事项

在进行 TCP 通信编程时,需要注意以下几点:

  1. accept() 方法是阻塞的:服务端在调用 accept() 方法时会一直等待,直到有客户端连接成功。这种阻塞是必要的,以确保客户端连接的稳定性。

  2. 客户端连接服务器的过程使用了三次握手协议:三次握手过程确保客户端和服务器之间的连接可靠性。这个过程建立了连接,使得双方可以开始通信。

  3. 数据流的方向性:

    • 对于客户端来说,它发送数据,因此使用的是 输出流(OutputStream)。
    • 对于服务端来说,它接收数据,因此使用的是 输入流(InputStream)。
  4. read() 方法也是阻塞的:在读取数据时,read() 方法会阻塞线程,直到有数据可读或连接关闭。这种阻塞有助于确保数据接收的完整性。

  5. 客户端关闭连接时会发送结束标记:在客户端关闭输出流时,会自动发送一个结束标记(EOF),通知服务端数据已发送完毕。这一点在双向通信中尤为重要。

  6. 连接的关闭通过四次挥手协议完成:四次挥手过程确保连接被安全终止,避免数据丢失或传输中断。

# 4. TCP 三次握手详解

三次握手(Three-Way Handshake) 是建立 TCP 连接的过程,确保连接的可靠性。具体步骤如下:

  1. 第一次握手:客户端发送一个 SYN 标志位的数据包给服务端,表示请求建立连接。
  2. 第二次握手:服务端收到请求后,向客户端回传一个带有 SYN/ACK 标志位的数据包,表示同意建立连接,并请求客户端进行确认。
  3. 第三次握手:客户端收到服务端的 SYN/ACK 包后,再次回传一个带有 ACK 标志位的数据包,表示确认连接建立成功。

至此,客户端和服务端的连接成功建立,双方可以开始数据传输。

TCP 三次握手

# 5. TCP 四次挥手详解

四次挥手(Four-Way Handshake) 是终止 TCP 连接的过程,确保数据传输的完整性。具体步骤如下:

  1. 第一次挥手:客户端发送一个 FIN 标志位的数据包,表示它已经没有数据要发送,并请求关闭连接。
  2. 第二次挥手:服务端收到 FIN 后,回传一个带有 ACK 标志位的数据包,表示确认关闭请求。此时客户端进入 FIN_WAIT_2 状态,等待服务端的关闭。
  3. 第三次挥手:服务端发送一个 FIN 标志位的数据包,表示它的数据已经发送完毕,准备关闭连接。
  4. 第四次挥手:客户端收到服务端的 FIN 后,回传一个带有 ACK 标志位的数据包,表示确认关闭。此时,客户端和服务端都进入 CLOSED 状态,连接正式关闭。

TCP 四次挥手

总结

  • 三次握手 确保在通信开始前,客户端和服务端的通信链路是可靠且可用的。
  • 四次挥手 确保在通信结束时,双方的数据都已传输完成,不会出现数据丢失或中断。
编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08
Java中的网络编程
UDP通信编程

← Java中的网络编程 UDP通信编程→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式