Skip to content

网络编程

ServerSocketChannelServerSocket都可以在Java中创建服务器端的套接字监听并接受客户端的连接,但它们属于Java两种不同的I/O模型。

ServerSocket (传统的阻塞I/O)

ServerSocket是Java原始I/O(也称为阻塞I/O)的一部分。当使用ServerSocket时:

  • 阻塞操作accept()方法会阻塞,直到一个连接建立,然后返回一个Socket对象用于与客户端通信。
  • 线程模型:通常每个连接都需要一个线程来处理,这可能在高负载时导致资源使用效率低下。
  • 简单性ServerSocket的API简单,适用于连接数较少且对性能要求不高的应用。

下面是使用ServerSocket的一个简单示例:

java
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
    Socket clientSocket = serverSocket.accept(); // 阻塞直到新连接到来
    // 使用clientSocket与客户端通信...
}

ServerSocketChannel (非阻塞I/O,Java NIO)

ServerSocketChannel是Java新I/O(NIO)的一部分。与ServerSocket相比,ServerSocketChannel提供了更多的灵活性:

  • 非阻塞操作:可以配置为非阻塞模式,在此模式下accept()方法会立即返回,不论是否有连接,都可以让线程执行其他任务。
  • 选择器(Selectors):可以与Selector一起使用,一个线程可以管理多个Channel的I/O操作。
  • 性能:在处理大量连接时,ServerSocketChannel配合选择器使用可以提供更高的性能。

下面是使用ServerSocketChannel的一个简单示例:

java
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 非阻塞模式
serverChannel.socket().bind(new InetSocketAddress(8080));
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select();
    // 处理I/O事件...
}

在实践中,你会选择使用ServerSocket还是ServerSocketChannel,这取决于你的具体需求。如果你的应用需要处理大量并发连接,并希望通过单个或少数几个线程来管理这些连接,那么ServerSocketChannel可能是更好的选择。如果你的应用连接数不多,逻辑简单,使用ServerSocket的传统阻塞模型可能会更容易实现。

java
package com.jasper.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
//        配置非阻塞
        serverSocket.configureBlocking(false);
        serverSocket.bind(new InetSocketAddress("localhost", 8080));
        //通道注册到选择器上
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        while (true){
            //选择一组键 其相应的通道已经准备好io操作
            selector.select();
//          返回已选择键集
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                if (key.isAcceptable()){
                    //接受连接
                    ServerSocketChannel server =  (ServerSocketChannel)key.channel();
                    SocketChannel client = server.accept();

                    client.configureBlocking(false);
//                注册读操作
                    client.register(selector,SelectionKey.OP_READ);
                }else if(key.isReadable()){
                    //读数据
                    SocketChannel client = (SocketChannel)key.channel();

                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int read = client.read(buffer);
                    if (read > 0) {
                        buffer.flip();
                        //返回当前位置和限制之间的元素数
                        byte[] bytes = new byte[buffer.remaining()];
                        // 从缓冲区读取字节到字节数组
                        buffer.get(bytes);
                        String message = new String(bytes);
                        System.out.println("Message received: " + message);

                        // 响应客户端
                        ByteBuffer writeBuffer = ByteBuffer.wrap(("Echo: " + message).getBytes());
                        client.write(writeBuffer);
                    } else if (read < 0) {
                        // 结束通道
                        client.close();
                    }
                    }
                // 移除处理过的键
                iterator.remove();
                }
            }
        }
    }
java
package com.jasper.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOClientWithSelector {

    public static void main(String[] args) throws Exception {
        // 打开选择器
        Selector selector = Selector.open();

        // 打开一个SocketChannel并配置为非阻塞
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        // 连接到服务器地址和端口
        socketChannel.connect(new InetSocketAddress("localhost", 8080));

        // 将通道注册到选择器上,对连接完成事件感兴趣
        socketChannel.register(selector, SelectionKey.OP_CONNECT);

        while (true) {
            // 等待事件
            selector.select();

            // 获取事件集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();

            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isConnectable()) {
                    handleConnect(key);
                } else if (key.isReadable()) {
                    handleRead(key);
                }
            }
        }
    }

    private static void handleConnect(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();

        // 完成连接
        if (channel.isConnectionPending()) {
            channel.finishConnect();
        }

        // 注册读事件
        channel.register(key.selector(), SelectionKey.OP_READ);

        // 发送消息到服务器
        String message = "Hello, Server!";
        ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }
    }

    private static void handleRead(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();

        // 读取服务器响应
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
        int readBytes = channel.read(readBuffer);

        if (readBytes > 0) {
            readBuffer.flip();
            byte[] bytes = new byte[readBuffer.remaining()];
            readBuffer.get(bytes);
            String response = new String(bytes);
            System.out.println("Message from server: " + response);

            // 关闭连接
            channel.close();
            key.selector().close();
            System.exit(0); // 正常退出程序
        }
    }
}