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

发布于:2025-08-18 ⋅ 阅读:(10) ⋅ 点赞:(0)

目录

1、UDP数据报套接字编程

1.1 API介绍

1.1.1 DatagramSocket

1.1.2 DatagramPacket

1.1.3 InetSocketAddress

1.2 代码示例

1.2.1 UDP Echo Server

1.2.2 UDP Echo Client

2、TCP流套接字编程

2.1 API介绍

2.1.1 ServerSocket

2.1.2 Socket

2.2 代码示例

2.2.1 TCP Echo Server

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

/**
 * 服务端
 */
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

/**
 * 客户端
 */
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

/**
 * 服务端
 */
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

/**
 * 客户端
 */
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阶段就结束了。

本次的分享就到这里了。

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

   


网站公告

今日签到

点亮在社区的每一天
去签到