UDP/TCP套接字编程简单实战指南

目录

1、UDP数据报套接字编程

[1.1 API介绍](#1.1 API介绍)

[1.1.1 DatagramSocket](#1.1.1 DatagramSocket)

[1.1.2 DatagramPacket](#1.1.2 DatagramPacket)

[1.1.3 InetSocketAddress](#1.1.3 InetSocketAddress)

[1.2 代码示例](#1.2 代码示例)

[1.2.1 UDP Echo Server](#1.2.1 UDP Echo Server)

[1.2.2 UDP Echo Client](#1.2.2 UDP Echo Client)

2、TCP流套接字编程

[2.1 API介绍](#2.1 API介绍)

[2.1.1 ServerSocket](#2.1.1 ServerSocket)

[2.1.2 Socket](#2.1.2 Socket)

[2.2 代码示例](#2.2 代码示例)

[2.2.1 TCP Echo Server](#2.2.1 TCP Echo Server)

[2.2.2 TCP Echo Client](#2.2.2 TCP Echo Client)


1、UDP数据报套接字编程

1.1 API介绍

1.1.1 DatagramSocket

DatagramSocket 是UDP Socket,用于 发送 和 接收 UDP数据报。

DatagramSocket 构造方法:

|--------------------------|--------------------------------------------------|
| 方法 | 方法说明 |
| DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端) |
| DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口,就是port (一般用于服务端) |

DatagramSocket 方法:

|--------------------------------|---------------------------------------|
| 方法 | 方法说明 |
| void receive(DatagramPacket p) | 从此套接字也就是p,接收数据报 (如果没有接收到数据报,该方法会阻塞等待) |
| void send(DatagramPacket p) | 从此套接字也就是p,发送数据报 (不会阻塞等待,直接发送) |
| void close() | 关闭此数据报套接字 |

这里的方法出现了一个陌生的类 ------ DatagramPacket,下面进行介绍这个类。

1.1.2 DatagramPacket

DatagramPacket 是UDP Socket,发送 和 接收 的数据报,表示一个完整的 UDP 数据报。

DatagramPacket 构造方法:

|--------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| 方法 | 方法说明 |
| DatagramPacket(byte[] buf,itn length) | 构造一个 DatagramPacket以用来接收数据报,接收的数据保存在字节数组 (第一个参数buf) 中,接收指定长度 (第二个参数 length) |
| DatagramPacket(byte[] buf,int offest,itn length,SocketAddress address) | 构造一个 DatagramPacket以用来接收数据报,发送的数据为字节数组 (第一个参数buf) 中,从0到指定长度 (第二个参数 length),address 指定目的主机的 IP 和 端口号 |

DatagramPacket 方法:

|--------------------------|-------------------------------------------------|
| 方法 | 方法说明 |
| InetAddress getAddress() | 从接收的数据报中,获取发送端主机 IP 地址;或从发送的数据报中,获取接收端主机 IP 地址。 |
| int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接受端主机端口号。 |
| byte[] getData() | 获取数据报中的数据 |

构造UDP发送的数据报时候,需要传入 SocketAddress,该对象可以使用 InetSocketAddress 创建

1.1.3 InetSocketAddress

InetSocketAddress (SocketAddress 的子类) 构造方法:

|----------------------------------------------|------------------------------|
| 方法 | 方法说明 |
| InetSocketAddress(InetAddress addr,int port) | 创建一个 Socket 地址,包含 IP 地址和端口号。 |


1.2 代码示例

1.2.1 UDP Echo Server

java 复制代码
/**
 * 服务端
 */
public class UdpEchoServer {
    // UDP数据报套接字的Socket
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        // 指定了一个固定端口号, 让服务器来使用.
        socket = new DatagramSocket(port);
    }

    // 启动服务器
    public void start() throws IOException {
        // 启动
        System.out.println("服务器启动");

        while(true) {
            // 循环一次, 就相当于处理一次请求.
            // 处理请求的过程, 典型的服务器都是分成三个步骤的.
            // 1. 读取请求并解析.
            //    DatagramPacket 表示一个 UDP 数据报. 此处传入的字节数组, 就保存 UDP 的载荷部分.
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            // 这个 receive 是输出型参数,就是把参数作为 "输出结果",将 请求的数据放到requestPacket中
            socket.receive(requestPacket);
            //    把读取到的二进制数据, 转成字符串. 只是构造有效的部分. 长度是从 0 到 requestPacket.getLength()的长度
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());

            // 2. 根据请求, 计算响应. (服务器最关键的逻辑)
            //    但是此处写的是回显服务器. 这个环节相当于省略了.
            String response = process(request);

            // 3. 把响应返回给客户端
            // 这里不能是 response.length 因为这个是 字符的个数
            // 而这里需要的是 字节的个数
            // 此处还需要 UDP 协议自身没有保存对方的信息,也就是 目的IP 和 目的端口号
            // 使用requestPacket.getSocketAddress() 进行获取
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    response.getBytes().length,requestPacket.getSocketAddress());
            socket.send(responsePacket);

            // 打印一个日志
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(),request, response);
        }
    }

    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
        udpEchoServer.start();
    }
}

这里需要理解 receive方法里面的 理解输出型参数。

1.2.2 UDP Echo Client

java 复制代码
/**
 * 客户端
 */
public class UdpEchoClient {
    // UDP数据报套接字的Socket
    private DatagramSocket socket = null;

    // UDP 本身不保存对端的信息, 就自己的代码中保存一下
    private String serverIp; // 目的IP
    private int serverPort; // 目的端口号

    // 和服务器不同, 此处的构造方法是要指定访问的服务器的地址.
    public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket(); // 这里是源IP 和 源端口
    }

    // 开始客户端
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while(true) {
            // 1. 从控制台读取用户输入的内容.
            System.out.println("请输入要发送的内容:");
            if (!scanner.hasNext()) {
                break;
            }
            String request = scanner.next();
            // 2. 把请求发送给服务器, 需要构造 DatagramPacket 对象.
            //    构造过程中, 不光要构造载荷, 还要设置服务器的 IP 和端口号
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
                    request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
            // 3. 发送数据报
            socket.send(requestPacket);
            // 4. 接收服务器的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            // 5. 从服务器读取的数据进行解析, 打印出来.
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);
        udpEchoClient.start();
    }
}

这里客户端的的 目的IP 就是本机的 IP 地址。


2、TCP流套接字编程

和刚才的UDP是比较相似的。TCP的核心特点,面向字节流,读写数据的基本单位就是字节byte

2.1 API介绍

2.1.1 ServerSocket

ServerSocket 是构造TCP服务端 Socket的API。这个是专门给服务器用的。

ServerSocket 构造方法:

|------------------------|-----------------------------|
| 方法 | 方法说明 |
| ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口。 |

ServerSocket 方法:

|-----------------|-----------------------------------------------------------------------------|
| 方法 | 方法说明 |
| Socket accept() | 开始监听制定的端口(这个端口就是创建时绑定的),游客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的链接,否则阻塞等待。 |
| void close | 关闭此套接字 |

因为TCP 是有连接的,这个在上一篇博客中介绍到了。所以这里是使用 accept 这个方法来联通链接的关键操作。

2.1.2 Socket

Socket 是客户端 Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

不管是客户端还是服务端 Socket,都是装房建立连接后,保存对端的信息,及用来与对方收发数据的。

服务端和客户端都会用。

Socket 构造方法:

|------------------------------|-------------------------------------------|
| 方法 | 方法说明 |
| Socket(String host,int port) | 创建一个客户端流套接字Socket,并与对应IP 的主机上对应端口的进程建立连接。 |

这里的端口号 和 IP 也就可以理解为 服务器的IP 和 服务器的端口

Socket 方法:

|--------------------------------|-------------|
| 方法 | 方法说明 |
| InetAddress getInetAddress() | 返回套接字所链接的地址 |
| InputStream getInputStream() | 返回此套接字的输入流 |
| OutputStream getOutputStream() | 返回次套接字的输出流 |

TCP这里没有send和receive操作,这里是通过IO那里介绍的字节流的输入和输出来完成的。


2.2 代码示例

2.2.1 TCP Echo Server

java 复制代码
/**
 * 服务端
 */
public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    // 这里和 UDP 服务器类似, 也是在构造对象的时候, 绑定端口号.
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");

        // 这种情况一般不会使用 fixedThreadPool, 意味着同时处理的客户端连接数目就固定了.
        ExecutorService executorService = Executors.newCachedThreadPool();

        while (true) {
            // tcp 来说, 需要先处理客户端发来的连接.
            // 通过读写 clientSocket, 和客户端进行通信.
            // 如果没有客户端发起连接, 此时 accept 就会阻塞.

            // 主线程负责进行 accept, 每次 accept 到一个客户端, 就创建一个线程, 由新线程负责处理客户端的请求.
            Socket clientSocket = serverSocket.accept();
            // 这个是单个线程进行处理
//            processConnection(clientSocket);

            // 使用多线程的方式来调整
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();

            // 使用线程池来调整
            executorService.submit(() -> {
                processConnection(clientSocket);
            });
        }
    }

    // 处理一个客户端的连接.
    // 可能要涉及到多个客户端的请求和响应.
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 针对 InputStream 套了一层
            Scanner scanner = new Scanner(inputStream);
            // 针对 OutputStream 套了一层
            PrintWriter writer = new PrintWriter(outputStream);
            // 分成三个步骤
            while (true) {
                // 1. 读取请求并解析. 可以直接 read, 也可以借助 Scanner 来辅助完成.
                if (!scanner.hasNext()) {
                    // 连接断开了
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 返回响应到客户端
                // outputStream.write(response.getBytes());
                writer.println(response);
                writer.flush();

                // 打印日志
                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

2.2.2 TCP Echo Client

java 复制代码
/**
 * 客户端
 */
public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 直接把字符串的 IP 地址, 设置进来.
        // 127.0.0.1 这种字符串
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            // 为了使用方便, 套壳操作
            Scanner scannerNet = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);

            // 从控制台读取请求, 发送给服务器.
            while (true) {
                // 1. 从控制台读取用户输入
                String request = scanner.next();
                // 2. 发送给服务器
                writer.println(request);
                //    加上刷新缓冲区操作, 才是真正发送数据
                writer.flush();
                // 3. 读取服务器返回的响应.
                String response = scannerNet.next();
                // 4. 打印到控制台
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

到这里呢对于这个 UDP数据报套接字编程 和 TCP流套接字编程的简单的代码就演示完成了,并且介绍了对应的一些方法来进行编程代码,到这里Socket阶段就结束了。

本次的分享就到这里了。

感觉文章不错的话,期待你的一键三连哦,你的鼓励就是我的动力,让我们一起加油,顶峰相见。拜拜喽~~我们下次再见💓💓💓💓💓💓💓💓💓💓💓💓

相关推荐
Miraitowa_cheems22 分钟前
LeetCode算法日记 - Day 11: 寻找峰值、山脉数组的峰顶索引
java·算法·leetcode
海梨花27 分钟前
【从零开始学习Redis】项目实战-黑马点评D2
java·数据库·redis·后端·缓存
共享家952727 分钟前
linux-高级IO(上)
java·linux·服务器
Sammyyyyy34 分钟前
2025年,Javascript后端应该用 Bun、Node.js 还是 Deno?
开发语言·javascript·node.js
橘子郡12335 分钟前
观察者模式和发布订阅模式对比,Java示例
java
指针满天飞35 分钟前
Collections.synchronizedList是如何将List变为线程安全的
java·数据结构·list
Java技术小馆36 分钟前
重构 Controller 的 7 个黄金法则
java·后端·面试
金銀銅鐵1 小时前
[Java] 以 IntStream 为例,浅析 Stream 的实现
java·后端
William一直在路上1 小时前
Python数据类型转换详解:从基础到实践
开发语言·python
小浣浣2 小时前
为何她总在关键时“失联”?—— 解密 TCP 连接异常中断
网络·网络协议·tcp/ip