网络编程套接字

网络编程套接字

Socket(套接字)是网络编程的核心工具,也是操作系统提供给应用程序的网络通信接口。简单来说,Socket 文件是操作系统对网卡的抽象封装,读写 Socket 文件,本质上就是借助网卡完成数据的收发操作。

一、UDP 用户数据报协议

UDP 是无连接、不可靠的传输协议,Java 中通过 DatagramSocket(收发)和 DatagramPacket(封装)实现 UDP 编程。

DatagramSocket

DatagramSocket 是 UDP 对应的 Socket 实现,专门用于发送和接收 UDP 数据报,它就像一个「数据收发中转站」,负责建立应用程序与网络层的连接。

(1)构造方法

方法签名方法说明
DatagramSocket()创建一个 UDP 数据报套接字,绑定到本机任意随机端口(适用于客户端,无需固定端口)
DatagramSocket(int port)创建一个 UDP 数据报套接字,绑定到本机指定端口(适用于服务端,需要固定端口供客户端访问)

(2)常用方法

方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(无数据时会阻塞等待,直到收到数据)
void send(DatagramPacket p)从此套接字发送数据报(直接发送,不阻塞等待对方确认)
void close()关闭此数据报套接字,释放相关资源

DatagramPacket

DatagramPacket 是 UDP 数据报的「载体」,负责封装 UDP 通信中的数据内容、目标地址、端口号等信息。所有通过 DatagramSocket 收发的数据,都必须封装在这个对象中。

(1)构造方法
UDP 的收发数据报构造方法有所区分,需注意区分使用场景:

方法签名方法说明
DatagramPacket(byte[] buf, int length)用于接收数据报:接收到的数据会存入字节数组 buf,仅保留前 length 长度的内容
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)用于发送数据报:从字节数组 bufoffset 下标开始,取 length 长度的数据作为发送内容;address 指定接收端的 IP 和端口

(2)常用方法

方法签名方法说明
InetAddress getAddress()接收场景:获取发送端的 IP 地址;发送场景:获取接收端的 IP 地址
int getPort()接收场景:获取发送端的端口号;发送场景:获取接收端的端口号
byte[] getData()获取数据报中封装的原始字节数据

创建 SocketAddress

构造发送用的 DatagramPacket 时,需要传入 SocketAddress 对象,通常我们使用 InetSocketAddress 来快速创建(指定 IP 和端口):

// 示例:创建指向 本地回环地址 127.0.0.1:8888 的 Socket 地址
SocketAddress address = new InetSocketAddress("127.0.0.1", 8888);

实现回声服务器

回声服务器的核心功能是:接收客户端发送的消息,不做任何处理,直接原封不动地返回给客户端。下面我们分别实现服务端和客户端代码,一步一步完成 UDP 通信。

UDP 服务端代码

服务端需要固定端口,循环接收客户端请求,并返回响应结果,核心步骤是「接收请求 → 构造响应 → 发送响应」。

import java.io.*;
import java.net.*;

public class UdpEchoServer {
    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 requestPacket = new DatagramPacket(new byte[1024],
                    1024);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(), 0,
                    requestPacket.getLength());

            // 2. 根据请求构造响应
            String response = process(request);

            // 3. 封装响应返回客户端
            DatagramPacket responsePacket = new DatagramPacket(
                    response.getBytes(),
                    response.getBytes().length,
                    requestPacket.getSocketAddress()
            );
            socket.send(responsePacket);

            // 4. 打印日志
            System.out.printf("[客户端 %s:%d] 请求:%s | 响应:%s\n",
                    requestPacket.getAddress().getHostAddress(),
                    requestPacket.getPort(),
                    request,
                    response);
        }
    }

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

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

UDP 客户端代码

客户端需要指定服务端的 IP 和端口,读取用户输入并发送给服务端,然后接收服务端的响应并打印。

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
        // 客户端无需绑定固定端口,由操作系统自动分配随机端口(避免端口冲突)
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    public void start() throws IOException {
        System.out.println("客户端启动...");
        Scanner scanner = new Scanner(System.in);
        // 持续发送用户输入的消息
        while (true) {
        
            // 1. 从控制台读取用户输入
            System.out.println("-> ");
            String request = scanner.next();

            // 2. 构造请求,发送给服务器
            DatagramPacket requestPacket = new DatagramPacket(
                    request.getBytes(),
                    request.getBytes().length,
                    InetAddress.getByName(serverIP),
                    serverPort
            );
            socket.send(requestPacket);

            // 3. 接受服务器响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024],
                    1024);
            socket.receive(responsePacket);

            // 4. 打印结果
            String response = new String(responsePacket.getData(),
                    0, responsePacket.getLength());
            System.out.println(response);
        }
    }

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

二、TCP 传输控制协议

TCP 是面向连接、可靠的传输协议,Java 中通过ServerSocket(服务端)和Socket(客户端)建立连接,基于 InputStream/OutputStream(数据读写)实现 TCP 编程。

ServerSocket

ServerSocket 是创建TCP服务端Socket的API。

(1)构造方法

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

(2)核心方法

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

Socket

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

(1)构造方法

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

(2)核心方法

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

实现回声服务器

TCP 服务端代码

import java.io.*;
import java.net.*;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动。。。");
        // 持续监听并接收新的客户端连接
        while (true) {
            Socket socket = serverSocket.accept();
            // 应对多个客户端发送请求
            // 使用线程池避免线程频繁创建销毁
            ExecutorService service = Executors.newCachedThreadPool();
            service.submit(() -> {
                processConnection(socket);
            });
        }
    }

    private void processConnection(Socket socket){
        System.out.printf("客户端上线! [%s:%d]\n",
                socket.getInetAddress().toString(), socket.getPort());

        try (OutputStream outputStream = socket.getOutputStream();
             InputStream inputStream = socket.getInputStream()) {

            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            // 持续和同一个客户端进行多次数据收发
            while (true) {
                if (!scanner.hasNext()) {
                    break;
                }

                // 1. 接受请求并解析
                String request = scanner.next();

                // 2. 根据请求构造响应
                String response = process(request);

                // 3. 封装响应返回客户端
                printWriter.println(response);
                printWriter.flush(); // 将缓冲区数据写入 IO 设备

                // 4. 打印日志
                System.out.printf("[客户端 %s:%d] 请求: %s | 响应: %s\n",
                        socket.getInetAddress().toString(), socket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.printf("客户端下线! [%s:%d]\n",
                    socket.getInetAddress().toString(), socket.getPort());
            try {
                socket.close(); // 注意关闭资源!
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

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

TCP 客户端代码

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int port) throws IOException {
        socket = new Socket(serverIp, port);
    }

    public void start() {
        System.out.println("客户端启动。。。");
        try (OutputStream outputStream = socket.getOutputStream();
             InputStream inputStream = socket.getInputStream()) {

            Scanner scannerConsole = new Scanner(System.in);
            Scanner scannerNetWork = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);

            while (true) {
                // 1. 从控制台读取用户输入
                System.out.println("-> ");
                String request = scannerConsole.next();

                // 2. 构造请求,发送给服务器
                printWriter.println(request);
                printWriter.flush();

                // 3. 接受服务器响应
                if (!scannerNetWork.hasNext()) {
                    break;
                }
                String response = scannerNetWork.next();

                // 4. 打印结果
                System.out.println(response);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}
文件IO 2026-01-30
HTML 2026-03-16

评论区