聶永的博客

          記錄工作/學習的點點滴滴。

          SO_REUSEPORT學習筆記補遺

          前言

          因為能力有限,還是有很多東西(SO_REUSEADDR和SO_REUSEPORT的區別等)沒有能夠在一篇文字中表達清楚,作為補遺,也方便以后自己回過頭來復習。

          SO_REUSADDR VS SO_REUSEPORT

          兩者不是一碼事,沒有可比性。有時也會被其搞暈,自己總結的不好,推薦StackOverflow的Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ?資料,總結的很全面。

          簡單來說:

          • 設置了SO_REUSADDR的應用可以避免TCP 的 TIME_WAIT 狀態 時間過長無法復用端口,尤其表現在應用程序關閉-重啟交替的瞬間
          • SO_REUSEPORT更強大,隸屬于同一個用戶(防止端口劫持)的多個進程/線程共享一個端口,同時在內核層面替上層應用做數據包進程/線程的處理均衡

          若有困惑,推薦兩者都設置,不會有沖突。

          Netty多線程使用SO_REUSEPORT

          上一篇講到SO_REUSEPORT,多個程綁定同一個端口,可以根據需要控制進程的數量。這里講講基于Netty 4.0.25+Epoll navtie transport在單個進程內多個線程綁定同一個端口的情況,也是比較實用的。

          TCP服務器,同一個進程多線程綁定同一個端口

          這是一個PING-PONG示范應用:

               public void run() throws Exception {
                      final EventLoopGroup bossGroup = new EpollEventLoopGroup();
                      final EventLoopGroup workerGroup = new EpollEventLoopGroup();
                      ServerBootstrap b = new ServerBootstrap();
          
                     b.group(bossGroup, workerGroup)
                               .channel(EpollServerSocketChannel. class)
                               .childHandler( new ChannelInitializer<SocketChannel>() {
                                      @Override
                                      public void initChannel(SocketChannel ch) throws Exception {
                                          ch.pipeline().addLast(
                                                      new StringDecoder(CharsetUtil.UTF_8 ),
                                                      new StringEncoder(CharsetUtil.UTF_8 ),
                                                      new PingPongServerHandler());
                                     }
                               }).option(ChannelOption. SO_REUSEADDR, true)
                               .option(EpollChannelOption. SO_REUSEPORT, true)
                               .childOption(ChannelOption. SO_KEEPALIVE, true);
          
                      int workerThreads = Runtime.getRuntime().availableProcessors();
                     ChannelFuture future;
                      for ( int i = 0; i < workerThreads; ++i) {
                          future = b.bind( port).await();
                           if (!future.isSuccess())
                                throw new Exception(String. format("fail to bind on port = %d.",
                                           port), future.cause());
                     }
                     Runtime. getRuntime().addShutdownHook (new Thread(){
                           @Override
                           public void run(){
                               workerGroup.shutdownGracefully();
                               bossGroup.shutdownGracefully();
                          }
                     });
               }
          

          打成jar包,在CentOS 7下面運行,檢查同一個端口所打開的文件句柄。

          # lsof -i:8000
          COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
          java    3515 root   42u  IPv6  29040      0t0  TCP *:irdmi (LISTEN)
          java    3515 root   43u  IPv6  29087      0t0  TCP *:irdmi (LISTEN)
          java    3515 root   44u  IPv6  29088      0t0  TCP *:irdmi (LISTEN)
          java    3515 root   45u  IPv6  29089      0t0  TCP *:irdmi (LISTEN)
          

          同一進程,但打開的文件句柄是不一樣的。

          UDP服務器,多個線程綁同一個端口

          /**
           * UDP諺語服務器,單進程多線程綁定同一端口示范
           */
          public final class QuoteOfTheMomentServer {
          
                 private static final int PORT = Integer.parseInt(System. getProperty("port" ,
                             "9000" ));
          
                 public static void main(String[] args) throws Exception {
                       final EventLoopGroup group = new EpollEventLoopGroup();
          
                      Bootstrap b = new Bootstrap();
                      b.group(group).channel(EpollDatagramChannel. class)
                                  .option(EpollChannelOption. SO_REUSEPORT, true )
                                  .handler( new QuoteOfTheMomentServerHandler());
          
                       int workerThreads = Runtime.getRuntime().availableProcessors();
                       for (int i = 0; i < workerThreads; ++i) {
                            ChannelFuture future = b.bind( PORT).await();
                             if (!future.isSuccess())
                                   throw new Exception(String.format ("Fail to bind on port = %d.",
                                               PORT), future.cause());
                      }
          
                      Runtime. getRuntime().addShutdownHook(new Thread() {
                             @Override
                             public void run() {
                                  group.shutdownGracefully();
                            }
                      });
                }
          }
          }
          
          @Sharable
          class QuoteOfTheMomentServerHandler extends
                      SimpleChannelInboundHandler<DatagramPacket> {
          
                 private static final String[] quotes = {
                             "Where there is love there is life." ,
                             "First they ignore you, then they laugh at you, then they fight you, then you win.",
                             "Be the change you want to see in the world." ,
                             "The weak can never forgive. Forgiveness is the attribute of the strong.", };
          
                 private static String nextQuote() {
                       int quoteId = ThreadLocalRandom.current().nextInt( quotes .length );
                       return quotes [quoteId];
                }
          
                 @Override
                 public void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
                             throws Exception {
                       if ("QOTM?" .equals(packet.content().toString(CharsetUtil. UTF_8))) {
                            ctx.write( new DatagramPacket(Unpooled.copiedBuffer( "QOTM: "
                                        + nextQuote(), CharsetUtil. UTF_8), packet.sender()));
                      }
                }
          
                 @Override
                 public void channelReadComplete(ChannelHandlerContext ctx) {
                      ctx.flush();
                }
          
                 @Override
                 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                      cause.printStackTrace();
                }
          }
          

          同樣也要檢測一下端口文件句柄打開情況:

          # lsof -i:9000
          COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
          java    3181 root   26u  IPv6  27188      0t0  UDP *:cslistener
          java    3181 root   27u  IPv6  27217      0t0  UDP *:cslistener
          java    3181 root   28u  IPv6  27218      0t0  UDP *:cslistener
          java    3181 root   29u  IPv6  27219      0t0  UDP *:cslistener
          

          小結

          以上為Netty+SO_REUSEPORT多線程綁定同一端口的一些情況,是為記載。

          posted on 2015-02-25 22:23 nieyong 閱讀(6701) 評論(1)  編輯  收藏 所屬分類: Socket

          評論

          # re: SO_REUSEPORT學習筆記補遺 2015-02-28 10:00 額頭上長痘痘是什么原因

          樓主有句話說得不錯,很多東西不是用一句話就可以說得清楚的,只有不斷總結,完善。  回復  更多評論   

          公告

          所有文章皆為原創,若轉載請標明出處,謝謝~

          新浪微博,歡迎關注:

          導航

          <2015年2月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          1234567

          統計

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 万全县| 鄱阳县| 会昌县| 塔河县| 定西市| 青阳县| 双峰县| 沙田区| 桦川县| 湖北省| 郁南县| 阳朔县| 特克斯县| 马鞍山市| 景德镇市| 周口市| 宝应县| 平塘县| 自贡市| 惠来县| 唐河县| 镇赉县| 车致| 澄迈县| 新昌县| 白河县| 钦州市| 宿松县| 西吉县| 东阿县| 广平县| 桓台县| 大化| 黑龙江省| 稷山县| 凌源市| 济宁市| 五莲县| 高要市| 江津市| 敦煌市|