netty 多个 本地udp端口_Netty学习笔记

112bee4b65a717a9892416f9ef32d05a.png

各位骚年好,我又来了.
上次写文章是啥时候? 忘了,也懒得翻记录,总之,很久没写知乎文章了
其实吧,不是很愿意把这个所谓的碎碎念记下的一些笔记(很多还是从其他地方抄的,做了一些修改和补充)叫做所谓的“文章”.感觉是对这两个字的侮辱. 或许叫做学习笔记,可能更合适,不过先这么称呼吧,总比“震惊,26岁大叔居然对Netty做出这样的事情...”这类丧心病狂的标题好一些.

回想自己年轻的时候,在那本科的青葱岁月里,我还是一个非常英俊,头发茂密的少年.追求者排成了一条长龙,从杭州市,一直排到了银河系外(此处是幻想).

然而,作为一个要在计算机领域有所建树的有志青年(此处是痴心妄想!),“两耳不闻窗外事,一心只敲心中码”.在每一个炎热的中午,专注地和大学室友构思着一件惊天动地,百思不解的大事:“午饭去哪个食堂吃饭,吃些什么!”然后回来继续做着我们的“网络编程”大作业:“利用windows C socket ,自定通讯协议.完成一个即时通讯工具,按照能支撑的并发连接数量,评定最终成绩”.
好家伙,我终于可以和“腾讯”一决高下了!(不知天高地厚)

中间过程略过不提,最终是采用了windows的重叠异步IO端口模型(windows平台的IOCP),在Acer电脑(4G内存,i3双核处理器上支持2万个并发连接),整个组全部拿到了优秀评级.

这样就说明自己真的优秀了吗? 其实自己做的事情真没多少,只是定义了一些通讯协议,根据编程模型,调用了windows提供的底层API,并做了一些比较繁琐的调用和事件处理操作.平时工作如果叫做搬砖的话,那么这个最多就是“花式搬砖”.

不过,就这个“调用windows C socket API”这个操作,说起来简单,调用过程还是相当繁琐的.大量的样板代码.(当初的代码不知道哪里去了,不然可以贴上来一段).在C/C++领域,不知道是否有成型的通讯框架(很久没接触这块了),但是好在Java对这些繁琐的操作做了封装,实现了Netty框架.对外屏蔽了很多繁琐的操作,让一般开发者不用关心底层的接口调用.

我相信很多骚年应该都会听说过Netty(就算没有真正使用过).特别是去网上搜索“高并发网络编程 Java”,然后你就会搜索到Netty,随着Dive into 的过程,哈哈哈,你就会像我一样,去某个地方记一下笔记

进入正题:

先介绍一下Socket先生,它是无人不知,无人不晓,统领TCP和UDP两位小弟,在网络界里驰骋风云.典型的高富帅人物.在计算机的世界里,两个的非同一个主机进程之间(IP地址+端口标记一个进程,我们可以称做为“端点Point”)想要进行友好沟通,必须劳烦Socket进行消息传达工作.对于我们要传达的消息,我们需要告诉socket,对方的端口号,以及IP地址(也就是传说中的“点对点通信”)

在服务端的样板代码会是这么个样子

ServerSocket serverSocket = new ServerSocket(1111);//这里的1111是服务端的socket监听端口号
Socket clientSocket = serverSocket.accept();//等待客户端向服务端发起请求
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(),true);
String request,response;
while (((request= in.readLine())!=null)){//不断循环等待客户端发送的消息
    if("Done".equals(request)){
        break;
    }
    response = request;
    out.println(response);//将客户端发来的消息原封不动地发送回去
}

启动上面的代码,我们可以用命令工具 telnet做一个小测试:

a57eda02130b57813764b3d0cc63e539.png

其中红色的helloWorld是客户端(也就是这个命令行界面)发送给上述的服务端的消息

其中绿色的helloWorld是服务端返回给该客户端的消息

这个时候我们再起一个客户端,进行同样的操作,这个时候新的客户端不管发什么消息,都将得不到响应,问题在于上述代码中等待监听的 serverSocket.accept()在收到一个客户端的连接后,将进入到后续代码的while (((request= in.readLine())!=null))中,循环等待这个客户端发送的信息,不在理会新的连接请求(也无法理会,应为监听代码accpet已经执行过去了)

这个时候,我们来改造一下,改成下面的代码:

ServerSocket serverSocket = new ServerSocket(1111);
while (true) {
    Socket clientSocket = serverSocket.accept();
    BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
    PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
    String request, response;
    while (((request = in.readLine()) != null)) {
        if ("Done".equals(request)) {
            break;
        }
        response = request;
        out.println(response);
    }
}

这么做还是存在问题,当一个客户端连接进来,并且不终端连接的情况下,代码将始终处在第二个wihle循环中,不断等待这个客户端发送数据,而没有机会在此执行到accept方法,除非这个客户端终止以后,下一个客户端才能被处理.

综上,上述的情况,一次只能接受一个客户端请求,那么,我有办法同时接受多个客户端请求吗?

有! 为每一个连接进来的客户端创建一个单独的线程来响应处理. 大概的代码是这样子的

ServerSocket serverSocket = new ServerSocket(1111);
while(true) {
    Socket clientSocket = serverSocket.accept();
    //为每一个客户端连接创建一个线程处理,主线程只负责接受客户端的连接请求
    Runnable connectionHandler = new Runnable() {
        @Override
        public void run() {
            try {
                myProcess(clientSocket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
    new Thread(connectionHandler).start();
}

好了,上述代码可以同时处理接受多个客户端的连接了. 世界大和谐!!!

b50a5e727b7ba5353f4f528267aeaf35.png

哈哈,开什么玩笑,如果就这么简单,我特么写这个流水账干什么.

在Java的世界里,新创建一个线程默认会分配64KB以上的内存(大概值),假设一个服务器有16GB内存,抛开其他一切因素,最多可以创建16*1024*1024/64= 262144个线程

等等,你真的打算就通过开线程来粗暴地完成多个客户端的连接工作吗? 我觉得你计算机的老师会把你打断狗腿.先不说完全开不出这么多线程,大量的线程在cpu上执行调度,需要进行上下文切换,但线程数量很大时,CPU的主要任务将集中在线程调度的工作上,而不再有精力去执行线程应该完成的任务.(强烈建议没有学过操作系统的骚年们,去系统学一下操作系统)

“让我们考虑一下这种方案的影响。第一,在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费。第二,需要为每个线程的调用栈都分配内存,其默认值大小区间为64 KB到1 MB,具体取决于操作系统。第三,即使Java虚拟机(JVM)在物理上可以支持非常大数量的线程,但是远在到达该极限之前,上下文切换的开销就会带来麻烦,例如,在达到10 000个连接的时候”(摘录来自: [美] Norman Maurer Marvin Allen Wolfthal. “Netty实战。” iBooks. )

323f38e1fe1bea289691c951b11a48d8.png
阻塞I/O(摘抄自Netty实战)

如果思考上面的代码,实际上是因为阻塞的IO操作(阻塞等待客户端连接进来,和发送消息),所以需要用每一个线程来包装每一个阻塞操作,使得“阻塞等待”在线程之间编程了非阻塞的异步操作,然而每一个线程内部,还是阻塞等待的状态.那么如果将阻塞的I/O改成为非阻塞的I/O也就能避免创建过多线程的情况. 这个时候,我们有请NIO上台表演一下.

来,直接把宝贝掏出来给你们看

public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();//创建一个NIO的Selector
        ServerSocketChannel serverSocket = ServerSocketChannel.open();//创建serverChannel,就是创建了一个NIO版的Socket
        serverSocket.bind(new InetSocketAddress("localhost", 1111));//将该socket绑定到本地1111端口进行监听
        serverSocket.configureBlocking(false);//置通道为非阻塞模式
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);//注册对 Accept操作的订阅
        ByteBuffer buffer = ByteBuffer.allocate(256);

        while (true) {
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();//得到已经I/O就绪的key
            Iterator<SelectionKey> iter = selectionKeys.iterator();
            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                if (key.isAcceptable()) {//如果是客户端新的连接
                    register(selector, serverSocket);
                }
                if (key.isReadable()) {//如果是已连接客户端的新数据
                    answerWithEcho(buffer, key);
                }
                iter.remove();
            }
        }
    }

    public static void register(Selector selector, ServerSocketChannel serverSocket) throws IOException {
        SocketChannel client = serverSocket.accept();//为客户端新的连接请求创建socket
        client.configureBlocking(false);
        client.register(selector, SelectionKey.OP_READ);//为这个客户端socket订阅"数据就绪"(有新的数据发过来,就是一个"数据就绪")
    }

    private static void answerWithEcho(ByteBuffer buffer, SelectionKey key) throws IOException {
        SocketChannel client = (SocketChannel) key.channel();
        client.read(buffer);
        if ("DONE".equals(new String(buffer.array()).trim())) {
            client.close();
            System.out.println("Closed");
        }
        buffer.flip();
        client.write(buffer);//将客户端的数据原样返回客户端
        buffer.clear();
    }

上述代码用一幅图来描述的话,就是下面这个样子

cfc323478e71bbb4569fad6bd0aa6b95.png
使用非阻塞NIO

上述模型是使用Java的NIO的改造,对于大量的并发可以使用更少的线程,从而达到更好的性能.对于NIO不了解的同学,可以移步我的另一篇学习笔记 玩转Java NIO

预计下次更新时间为本周末

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值