跳转至

14 Socket

说明

本文档正在更新中……

说明

本文档仅涉及部分内容,仅可用于复习重点知识

1 UDP

DatagramSocket:用于发送和接收数据报

1
2
3
4
5
6
7
8
9
// 创建DatagramSocket(绑定到随机端口)
DatagramSocket socket = new DatagramSocket();

// 创建DatagramSocket(绑定到指定端口)
DatagramSocket socket = new DatagramSocket(8888);

// 创建DatagramSocket(绑定到指定IP和端口)
InetAddress address = InetAddress.getByName("192.168.1.100");
DatagramSocket socket = new DatagramSocket(8888, address);

DatagramPacket:表示数据报(包含数据和目标地址)

1
2
3
4
5
6
7
8
// 用于接收的数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

// 用于发送的数据包
byte[] data = "Hello".getBytes();
InetAddress address = InetAddress.getByName("localhost");
DatagramPacket packet = new DatagramPacket(data, data.length, address, 8888);

UDP Socket 通信:

// UDP服务器端
public class UDPServer {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(8888);
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        socket.receive(packet);  // 接收数据
        String message = new String(packet.getData(), 0, packet.getLength());
        System.out.println("收到UDP消息:" + message);

        socket.close();
    }
}

// UDP客户端
public class UDPClient {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();
        String message = "Hello UDP Server";
        byte[] data = message.getBytes();

        InetAddress address = InetAddress.getByName("localhost");
        DatagramPacket packet = new DatagramPacket(
            data, data.length, address, 8888
        );

        socket.send(packet);  // 发送数据
        socket.close();
    }
}

2 TCP

Socket:客户端 socket

// 创建Socket连接到服务器
Socket socket = new Socket("localhost", 8080);

ServerSocket:服务器 socket

// 创建服务器Socket监听端口
ServerSocket serverSocket = new ServerSocket(8080);

TCP Socket 通信:

public class Server {
    public static void main(String[] args) throws IOException {
        // 1. 创建ServerSocket,监听端口8888
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器启动,等待客户端连接...");

        // 2. 等待客户端连接
        Socket socket = serverSocket.accept();
        System.out.println("客户端已连接");

        // 3. 获取输入流,读取客户端数据
        InputStream is = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String info = br.readLine();
        System.out.println("收到客户端消息:" + info);

        // 4. 获取输出流,向客户端发送数据
        OutputStream os = socket.getOutputStream();
        PrintWriter pw = new PrintWriter(os);
        pw.println("Hello Client!");
        pw.flush();

        // 5. 关闭资源
        pw.close();
        br.close();
        socket.close();
        serverSocket.close();
    }
}

public class Client {
    public static void main(String[] args) throws IOException {
        // 1. 创建Socket连接服务器
        Socket socket = new Socket("localhost", 8888);

        // 2. 获取输出流,向服务器发送数据
        OutputStream os = socket.getOutputStream();
        PrintWriter pw = new PrintWriter(os);
        pw.println("Hello Server!");
        pw.flush();

        // 3. 获取输入流,读取服务器响应
        InputStream is = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String response = br.readLine();
        System.out.println("收到服务器响应:" + response);

        // 4. 关闭资源
        br.close();
        pw.close();
        socket.close();
    }
}

3 nio

3.1 Channel

Channel 是 NIO 中用于 I/O 操作的抽象,类似于流,但有一些重要区别:

特性 Channel Stream
方向 双向(可读可写) 单向
阻塞 支持非阻塞模式 总是阻塞
操作 读写 Buffer 读写字节/字符

Channel 类型:

  1. FileChannel:文件 IO
  2. SocketChannel:TCP 客户端
  3. ServerSocketChannel:TCP 服务器端
  4. DatagramChannel:UDP

3.2 Buffer

Buffer 类型:

  1. ByteBuffer
  2. CharBuffer
  3. DoubleBuffer
  4. FloatBuffer
  5. IntBuffer
  6. LongBuffer
  7. ShortBuffer

3.3 Selector

Selector 是多路复用器,可以监控多个 Channel 的 I/O 事件

Img 1
public class NioServerExample {
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;

    public NioServerExample(int port) throws IOException {
        // 1. 创建Selector(多路复用器)
        selector = Selector.open();

        // 2. 创建ServerSocketChannel
        serverSocketChannel = ServerSocketChannel.open();

        // 3. 绑定端口并设置为非阻塞模式
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);

        // 4. 将ServerSocketChannel注册到Selector,监听ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("NIO服务器启动,监听端口: " + port);
    }

    public void start() throws IOException {
        while (true) {
            // 5. 阻塞等待就绪的Channel,超时时间1秒
            int readyChannels = selector.select(1000);

            if (readyChannels == 0) {
                continue; // 没有就绪的Channel,继续等待
            }

            // 6. 获取就绪的SelectionKey集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();

                // 7. 处理不同的事件
                if (key.isAcceptable()) {
                    handleAccept(key);
                } else if (key.isReadable()) {
                    handleRead(key);
                } else if (key.isWritable()) {
                    handleWrite(key);
                }

                // 8. 处理完成后移除当前key,防止重复处理
                keyIterator.remove();
            }
        }
    }

    /**
     * 处理客户端连接请求
     */
    private void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();

        // 9. 接受客户端连接
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);

        // 10. 注册客户端Channel到Selector,监听READ事件
        clientChannel.register(selector, SelectionKey.OP_READ);

        System.out.println("客户端连接: " + clientChannel.getRemoteAddress());

        // 发送欢迎消息
        String welcomeMsg = "欢迎连接到NIO服务器!\r\n";
        ByteBuffer buffer = ByteBuffer.wrap(welcomeMsg.getBytes());
        clientChannel.write(buffer);
    }

    /**
     * 处理读取客户端数据
     */
    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        try {
            // 11. 读取客户端数据
            int bytesRead = clientChannel.read(buffer);

            if (bytesRead == -1) {
                // 客户端关闭连接
                System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());
                clientChannel.close();
                return;
            }

            if (bytesRead > 0) {
                buffer.flip(); // 切换为读模式
                byte[] data = new byte[buffer.remaining()];
                buffer.get(data);
                String message = new String(data).trim();

                System.out.println("收到消息: " + message);

                // 12. 准备回复消息
                String response = "服务器收到: " + message + "\r\n";
                ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());

                // 13. 注册WRITE事件,准备发送回复
                clientChannel.register(selector, SelectionKey.OP_WRITE, responseBuffer);
            }
        } catch (IOException e) {
            System.out.println("读取数据异常,关闭连接");
            clientChannel.close();
        }
    }

    /**
     * 处理写入数据到客户端
     */
    private void handleWrite(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();

        if (buffer.hasRemaining()) {
            // 14. 写入数据到客户端
            clientChannel.write(buffer);
        }

        if (!buffer.hasRemaining()) {
            // 数据发送完成,重新注册READ事件
            clientChannel.register(selector, SelectionKey.OP_READ);
        }
    }

    public static void main(String[] args) {
        try {
            NioServerExample server = new NioServerExample(8888);
            server.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class NioClientExample {
    public static void main(String[] args) throws IOException {
        // 1. 创建SocketChannel
        SocketChannel socketChannel = SocketChannel.open();

        // 2. 连接到服务器
        socketChannel.connect(new InetSocketAddress("localhost", 8888));
        socketChannel.configureBlocking(false);

        System.out.println("连接到服务器...");

        // 3. 读取服务器欢迎消息
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (socketChannel.read(buffer) == 0) {
            // 非阻塞模式下可能立即返回0,稍作等待
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        System.out.println("服务器说: " + new String(data));

        // 4. 创建Scanner读取用户输入
        Scanner scanner = new Scanner(System.in);

        while (true) {
            System.out.print("请输入消息 (输入 'exit' 退出): ");
            String message = scanner.nextLine();

            if ("exit".equalsIgnoreCase(message)) {
                break;
            }

            // 5. 发送消息到服务器
            ByteBuffer writeBuffer = ByteBuffer.wrap((message + "\r\n").getBytes());
            socketChannel.write(writeBuffer);

            // 6. 读取服务器响应
            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            int bytesRead;

            // 等待服务器响应
            while ((bytesRead = socketChannel.read(readBuffer)) == 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            if (bytesRead > 0) {
                readBuffer.flip();
                byte[] responseData = new byte[readBuffer.remaining()];
                readBuffer.get(responseData);
                System.out.println("服务器回复: " + new String(responseData));
            }

            readBuffer.clear();
        }

        // 7. 关闭连接
        socketChannel.close();
        scanner.close();
        System.out.println("客户端已断开连接");
    }
}

评论区

欢迎在评论区指出文档错误,为文档提供宝贵意见,或写下你的疑问