NIO中的Socket编程实践:客户端与服务器通信

红尘紫陌 2020-11-19 ⋅ 39 阅读

在传统的Socket编程中,每个客户端与服务器之间的通信都需要创建一个新的线程来处理。然而,在高并发的环境下,创建大量线程会消耗过多的系统资源,导致性能下降。Java NIO(New Input/Output)是Java 1.4版本引入的一种非阻塞IO模型,使用NIO编程可以实现为多个并发连接服务的高效网络应用。

架构

NIO采用了IO多路复用的机制,通过一个或者几个Selector轮询一组非阻塞的通道,实现多个通道的同时读写。当某个通道有数据可读/可写时,就会通知选择器,从而避免了创建大量线程来处理连接。

NIO Socket编程实践

以下示例展示了NIO Socket编程的一个简单实践,包括客户端和服务器端的实现:

服务器端

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 Server {
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException {
        new Server().runServer();
    }

    public void runServer() throws IOException {
        // 创建选择器
        selector = Selector.open();

        // 创建ServerSocketChannel并绑定端口
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.bind(new InetSocketAddress(8888));

        // 将ServerSocketChannel注册到选择器上,并监听连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("Server started...");

        // 循环监听事件
        while (true) {
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isAcceptable()) {
                    // 处理连接请求
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("Accepted connection from " + socketChannel.getRemoteAddress());
                } else if (key.isReadable()) {
                    // 处理读事件
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    buffer.clear();
                    int bytesRead = socketChannel.read(buffer);
                    if (bytesRead == -1) {
                        socketChannel.close();
                    } else if (bytesRead > 0) {
                        buffer.flip();
                        byte[] data = new byte[buffer.limit()];
                        buffer.get(data);
                        System.out.println("Received message: " + new String(data));
                    }
                }
            }
        }
    }
}

客户端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class Client {
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException {
        new Client().runClient();
    }

    public void runClient() throws IOException {
        // 连接服务器
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
        socketChannel.configureBlocking(false);

        System.out.println("Client started...");

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("Enter message: ");
            String message = scanner.nextLine();

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

            buffer.clear();
            buffer.put(message.getBytes());
            buffer.flip();
            socketChannel.write(buffer);
        }
    }
}

在上述示例中,服务器端监听到Accept事件时接受客户端连接,并注册到选择器上。服务器端监听到Read事件时读取客户端传递的数据,并打印出来。客户端连接服务器后,可以通过控制台输入消息,将其发送给服务器。

总结

通过NIO编程,我们可以实现高并发的网络应用。使用选择器来管理多个通道的读写事件,避免了线程的创建和切换,提高了系统的性能。在实际应用中,可以根据需求对NIO进行更深入的学习和应用。


全部评论: 0

    我有话说: