前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高性能网络通信框架Netty-Java NIO基础

高性能网络通信框架Netty-Java NIO基础

作者头像
加多
发布2018-09-06 15:33:09
5690
发布2018-09-06 15:33:09
举报
文章被收录于专栏:Java编程技术Java编程技术

三、使用 Java NIO 搭建简单的客户端与服务端实现网络通讯

本节我们使用JDK中原生 NIO API来创建一个简单的TCP客户端与服务器交互的网络程序。

3.1 客户端程序

这个客户端功能是当客户端连接到服务端后,给服务器发送一个Hello,然后从套接字里面读取服务器端返回的内容并打印,具体代码如下:

代码语言:javascript
复制
public class NioClient {

    // (1)创建发送和接受缓冲区
    private static ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
    private static ByteBuffer receivebuffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException {
        // (2) 获取一个客户端socket通道
        SocketChannel socketChannel = SocketChannel.open();
        // (3)设置socket为非阻塞方式
        socketChannel.configureBlocking(false);
        // (4)获取一个选择器
        Selector selector = Selector.open();
        // (5)注册客户端socket到选择器
        SelectionKey selectionKey = socketChannel.register(selector, 0);
        // (6)发起连接
        boolean isConnected = socketChannel.connect(new InetSocketAddress("127.0.0.1", 7001));

        // (7)如果连接没有马上建立成功,则设置对链接完成事件感兴趣
        if (!isConnected) {
            selectionKey.interestOps(SelectionKey.OP_CONNECT);

        }

        int num = 0;
        while (true) {

            // (8) 选择已经就绪的网络IO操作,阻塞方法
            int selectCount = selector.select();
            System.out.println(num + "selectCount:" + selectCount);
            // (9)返回已经就绪的通道的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            
            //(10)处理所有就绪事件
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            SocketChannel client;
            while (iterator.hasNext()) {
                //(10.1)获取一个事件,并从集合移除
                selectionKey = iterator.next();
                iterator.remove();
                //(10.2)获取事件类型
                int readyOps = selectionKey.readyOps();
                //(10.3)判断是否是OP_CONNECT事件
                if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                    //(10.3.1)等待客户端socket完成与服务器端的链接
                    client = (SocketChannel) selectionKey.channel();
                    if (!client.finishConnect()) {
                        throw new Error();

                    }

                    System.out.println("--- client already connected----");
                    
                    //(10.3.2)设置要发送给服务端的数据
                    sendbuffer.clear();
                    sendbuffer.put("hello server,im a client".getBytes());
                    sendbuffer.flip();
                    //(10.3.3)写入输入。
                    client.write(sendbuffer);
                    //(10.3.4)设置感兴趣事件,读事件
                    selectionKey.interestOps(SelectionKey.OP_READ);

                //(10.4)判断是否是OP_READ事件
                } else if ((readyOps & SelectionKey.OP_READ) != 0) {
                    client = (SocketChannel) selectionKey.channel();
                    //(10.4.1)读取数据并打印
                    receivebuffer.clear();
                    int count = client.read(receivebuffer);
                    if (count > 0) {
                        String temp = new String(receivebuffer.array(), 0, count);
                        System.out.println(num++ + "receive from server:" + temp);
                    }

                }
            }
        }
    }
  • 代码(1)分别创建了一个发送和接受buffer,用来发送数据时候byte化内容和接受数据。
  • 代码(2)获取一个客户端套接字通道。
  • 代码(3)设置socket通道为非阻塞模式,默认是阻塞模式。
  • 代码(4)(5)获取一个选择器,然后注册客户端套接字通道到该选择器,并且设置感兴趣的事情为0,就是不对任何事件感兴趣。
  • 代码(6)(7)调用套接字通道的connect方法,连接服务器(服务器套接字地址为127.0.0.1:7001),由于步骤(3)设置了为非阻塞,所以步骤(6)马上会返回。代码(7)判断连接是否已经完成,如果没有,则设置选择器去监听OP_CONNECT事件,也就是指明对该事件感兴趣。
  • 然后进入while循环进行事件处理,其中代码(8)选择已经就绪的网络IO事件,如果当前没有就绪的则阻塞当前线程。当有就绪事件后,会返回获取的事件个数,会执行代码(9)具体取出来具体事件列表。
  • 代码(10)循环处理所有就绪事件,代码(10.1)迭代出一个事件key,然后从集合中删除,代码(10.2)获取事件key感兴趣的标志,代码(10.3)则看兴趣集合里面是否有OP_CONNECT,如果有则说明有OP_CONNECT事件已经就绪了,那么执行步骤(10.3.1)等待客户端与服务端完成三次握手,然后步骤(10.3.2)(10.3.3)写入hello server,im a client到服务器端。然后代码(10.3.4)设置对OP_READ事件感兴趣。
  • 代码(10.4)则看如果当前事件key是OP_READ事件,说明服务器发来的数据已经在接受buffer就绪了,客户端可以去具体拿出来了,然后代码10.4.1从客户端套接字里面读取数据并打印。

注:设置套接字为非阻塞后,connect方法会马上返回的,所以需要根据结果判断是否为链接建立OK了,如果没有成功,则需要设置对该套接字的op_connect事件感兴趣,在这个事件到来的时候还需要调用finishConnect方法来具体完成与服务器的链接,在finishConnect返回true后说明链接已经建立完成了,则这时候可以使用套接字通道发送数据到服务器,并且设置堆该套接字的op_read事件感兴趣,从而可以监听到服务端发来的数据,并进行处理。

3.2 服务端程序

服务端程序代码如下:

代码语言:javascript
复制
public class NioServer {

    // (1) 缓冲区
    private ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
    private ByteBuffer receivebuffer = ByteBuffer.allocate(1024);
    private Selector selector;

    public NioServer(int port) throws IOException {
        // (2)获取一个服务器套接字通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // (3)socket为非阻塞
        serverSocketChannel.configureBlocking(false);
        // (4)获取与该通道关联的服务端套接字
        ServerSocket serverSocket = serverSocketChannel.socket();
        // (5)绑定服务端地址
        serverSocket.bind(new InetSocketAddress(port));
        // (6)获取一个选择器
        selector = Selector.open();
        // (7)注册通道到选择器,选择对OP_ACCEPT事件感兴趣
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("----Server Started----");

        // (8)处理事件
        int num = 0;
        while (true) {
            // (8.1)获取就绪的事件集合
            int selectKeyCount = selector.select();
            System.out.println(num++ + "selectCount:" + selectKeyCount);

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // (8.2)处理就绪事件
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                processSelectedKey(selectionKey);
            }
        }
    }

    private void processSelectedKey(SelectionKey selectionKey) throws IOException {

        SocketChannel client = null;
        // (8.2.1)客户端完成与服务器三次握手
        if (selectionKey.isAcceptable()) {
            // (8.2.1.1)获取完成三次握手的链接套接字
            ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
            client = server.accept();
            if (null == client) {
                return;
            }
            System.out.println("--- accepted client---");

            // (8.2.1.2)该套接字为非阻塞模式
            client.configureBlocking(false);
            // (8.2.1.3)注册该套接字到选择器,对OP_READ事件感兴趣
            client.register(selector, SelectionKey.OP_READ);

            // (8.2.2)为读取事件
        } else if (selectionKey.isReadable()) {
            // (8.2.2.1) 读取数据
            client = (SocketChannel) selectionKey.channel();
            receivebuffer.clear();
            int count = client.read(receivebuffer);
            if (count > 0) {
                String receiveContext = new String(receivebuffer.array(), 0, count);
                System.out.println("receive client info:" + receiveContext);
            }
            // (8.2.2.2)发送数据到client
            sendbuffer.clear();
            client = (SocketChannel) selectionKey.channel();
            String sendContent = "hello client ,im server";
            sendbuffer.put(sendContent.getBytes());
            sendbuffer.flip();
            client.write(sendbuffer);
            System.out.println("send info to client:" + sendContent);

        }

    }

    
    public static void main(String[] args) throws IOException {
        int port = 7001;
        NioServer server = new NioServer(port);
    }
}
  • 代码(1)分别创建了一个发送和接受buffer,用来发送数据时候byte化内容,和接受数据。
  • 代码(2)获取一个服务端监听套接字通道。
  • 代码(3)设置socket通道为非阻塞模式,默认是阻塞模式。
  • 代码(4)获取与该通道关联的服务端套接字
  • 代码(5)绑定服务端套接字监听端口为7001
  • 代码(6)(7) 获取一个选择器,并注册通道到选择器,选择对OP_ACCEPT事件感兴趣,到这里服务端已经开始监听客户端链接了。
  • 代码(8) 具体处理事件,8.1选择当前就绪的事件,8.2遍历所有就绪事件,顺序调用processSelectedKey进行处理。
  • 代码(8.2.1) 当前事件key对应的OP_ACCEPT事件,则执行代码8.2.1.1获取已经完成三次握手的链接套接字,并通过代码8.2.1.2设置该链接套接字为非阻塞模式,通过代码8.2.1.3注册该链接套接字到选择器,并设置对对OP_READ事件感兴趣。
  • 代码(8.2.2) 判断如果当前事件key为OP_READ则通过代码8.2.2.1链接套接字里面获取客户端发来的数据,通过代码8.2.2.2发送数据到客户端。

注:在这个例子里面监听套接字serverSocket和serverSocket接受到的所有链接套接字都注册到了同一个选择器上,其中processSelectedKey里面8.2.1是用来处理serverSocket接受的新链接的,8.2.2是用来处理链接套接字的读写的。

到这里服务端和客户端就搭建好了,首先启动服务器,然后运行客户端,会输入如下:

代码语言:javascript
复制
0selectCount:1    
--- client already connected----  
1selectCount:1
2receive from server:hello client ,im server

这时候服务器的输出结果为:

代码语言:javascript
复制
----Server Started----
0selectCount:1
--- accepted client---
1selectCount:1
receive client info:hello server,im a client
send info to client:hello client ,im server

简单分析下结果:

  • 服务器端启动后,会先输出----Server Started----
  • 客户端启动后去链接服务器端,三次握手完毕后,服务器会获取op_accept事件,会通过accept获取链接套接字,所以输出了: 0selectCount:1 --- accepted client---
  • 然后客户端接受到三次握手信息后,获取到了op_connect事件,所以输出: 0selectCount:1 --- client already connected---- 然后发送数据到服务器端
  • 服务端收到数据后,选择器会选择出op_read事件,读取客户端发来的内容,并发送回执到客户端: 1selectCount:1 receive client info:hello server,im a client send info to client:hello client ,im server
  • 客户端收到服务器端回执后,选择器会选择出op_read事件,所以客户端会读取服务器端发来的内容,所以输出: 1selectCount:1 2receive from server:hello client ,im server

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.06.06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 三、使用 Java NIO 搭建简单的客户端与服务端实现网络通讯
    • 3.1 客户端程序
      • 3.2 服务端程序
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档