亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Mina、Netty、Twisted一起學(二):TCP消息邊界問題及按行分割消息

2019-11-14 21:00:18
字體:
來源:轉載
供稿:網友
Mina、Netty、Twisted一起學(二):TCP消息邊界問題及按行分割消息

在TCP連接開始到結束連接,之間可能會多次傳輸數據,也就是服務器和客戶端之間可能會在連接過程中互相傳輸多條消息。理想狀況是一方每發送一條消息,另一方就立即接收到一條,也就是一次write對應一次read。但是,現實不總是按照劇本來走。

MINA官方文檔節選:

TCP guarantess delivery of all packets in the correct order. But there is no guarantee that one write Operation on the sender-side will result in one read event on the receiving side. One call of Iosession.write(Object message) by the sender can result in multiple messageReceived(IoSession session, Object message) events on the receiver; and multiple calls of IoSession.write(Object message) can lead to a single messageReceived event.

Netty官方文檔節選:

In a stream-based transport such as TCP/IP, received data is stored into a socket receive buffer. Unfortunately, the buffer of a stream-based transport is not a queue of packets but a queue of bytes. It means, even if you sent two messages as two independent packets, an operating system will not treat them as two messages but as just a bunch of bytes. Therefore, there is no guarantee that what you read is exactly what your remote peer wrote.

上面兩段話表達的意思相同:TCP是基于字節流的協議,它只能保證一方發送和另一方接收到的數據的字節順序一致,但是,并不能保證一方每發送一條消息,另一方就能完整的接收到一條信息。有可能發送了兩條對方將其合并成一條,也有可能發送了一條對方將其拆分成兩條。所以在上一篇博文中的Demo,可以說是一個錯誤的示范。不過服務器和客戶端在同一臺機器上或者在局域網等網速很好的情況下,這種問題還是很難測試出來。

舉個簡單了例子(這個例子來源于Netty官方文檔):

消息發送方發送了三個字符串:

但是接收方收到的可能是這樣的:

那么問題就很嚴重了,接收方沒法分開這三條信息了,也就沒法解析了。

對此,MINA的官方文檔提供了以下幾種解決方案:

1、use fixed length messages

使用固定長度的消息。比如每個長度4字節,那么接收的時候按每條4字節拆分就可以了。

2、use a fixed length header that indicates the length of the body

使用固定長度的Header,Header中指定Body的長度(字節數),將信息的內容放在Body中。例如Header中指定的Body長度是100字節,那么Header之后的100字節就是Body,也就是信息的內容,100字節的Body后面就是下一條信息的Header了。

3、using a delimiter; for example many text-based PRotocols append a newline (or CR LF pair) after every message

使用分隔符。例如許多文本內容的協議會在每條消息后面加上換行符(CR LF,即"/r/n"),也就是一行一條消息。當然也可以用其他特殊符號作為分隔符,例如逗號、分號等等。

當然除了上面說到的3種方案,還有其他方案。有的協議也可能會同時用到上面多種方案。例如HTTP協議,Header部分用的是CR LF換行來區分每一條Header,而Header中用Content-Length來指定Body字節數。

下面,分別用MINA、Netty、Twisted自帶的相關API實現按換行符CR LF來分割消息。

MINA:

MINA可以使用ProtocolCodecFilter來對發送和接收的二進制數據進行加工,如何加工取決于ProtocolCodecFactory或ProtocolEncoder、ProtocolDecoder,加工后在IoHandler中messageReceived事件函數獲取的message就不再是IoBuffer了,而是你想要的其他類型,可以是字符串,java對象。這里可以使用TextLineCodecFactory(ProtocolCodecFactory的一個實現類)實現CR LF分割消息。

public class TcpServer {        public static void main(String[] args) throws IOException {          IoAcceptor acceptor = new NioSocketAcceptor();                    // 添加一個Filter,用于接收、發送的內容按照"/r/n"分割          acceptor.getFilterChain().addLast("codec",                   new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"), "/r/n", "/r/n")));                    acceptor.setHandler(new TcpServerHandle());          acceptor.bind(new InetSocketAddress(8080));      }    }    class TcpServerHandle extends IoHandlerAdapter {        @Override      public void exceptionCaught(IoSession session, Throwable cause)              throws Exception {          cause.printStackTrace();      }        // 接收到新的數據      @Override      public void messageReceived(IoSession session, Object message)              throws Exception {            // 接收客戶端的數據,這里接收到的不再是IoBuffer類型,而是字符串          String line = (String) message;          System.out.println("messageReceived:" + line);                }        @Override      public void sessionCreated(IoSession session) throws Exception {          System.out.println("sessionCreated");      }        @Override      public void sessionClosed(IoSession session) throws Exception {          System.out.println("sessionClosed");      }  }  

Netty:

Netty設計上和MINA類似,需要在ChannelPipeline加上一些ChannelHandler用來對原始數據進行處理。這里用LineBasedFrameDecoder將接收到的數據按行分割,StringDecoder再將數據由字節碼轉成字符串。同樣,接收到的數據進過加工后,在channelRead事件函數中,msg參數不再是ByteBuf而是String。

public class TcpServer {        public static void main(String[] args) throws InterruptedException {          EventLoopGroup bossGroup = new NioEventLoopGroup();          EventLoopGroup workerGroup = new NioEventLoopGroup();          try {              ServerBootstrap b = new ServerBootstrap();              b.group(bossGroup, workerGroup)                      .channel(NioServerSocketChannel.class)                      .childHandler(new ChannelInitializer<SocketChannel>() {                          @Override                          public void initChannel(SocketChannel ch)                                  throws Exception {                              ChannelPipeline pipeline = ch.pipeline();                                                            // LineBasedFrameDecoder按行分割消息                              pipeline.addLast(new LineBasedFrameDecoder(80));                              // 再按UTF-8編碼轉成字符串                              pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));                                                            pipeline.addLast(new TcpServerHandler());                          }                      });              ChannelFuture f = b.bind(8080).sync();              f.channel().closeFuture().sync();          } finally {              workerGroup.shutdownGracefully();              bossGroup.shutdownGracefully();          }      }    }    class TcpServerHandler extends ChannelInboundHandlerAdapter {        // 接收到新的數據      @Override      public void channelRead(ChannelHandlerContext ctx, Object msg) {                    // msg經過StringDecoder后類型不再是ByteBuf而是String          String line = (String) msg;          System.out.println("channelRead:" + line);      }        @Override      public void channelActive(ChannelHandlerContext ctx) {          System.out.println("channelActive");      }        @Override      public void channelInactive(ChannelHandlerContext ctx) {          System.out.println("channelInactive");      }        @Override      public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {          cause.printStackTrace();          ctx.close();      }  }  

Twisted:

Twisted的設計和上面兩者的設計不太一樣,所以實現消息分割也不太一樣。處理事件的類TcpServerHandle不再繼承Protocol,而是繼承Protocol的子類LineOnlyReceiver。接收到新數據的事件方法也不再是dataReceived,而是LineOnlyReceiver提供的lineReceived。看Twisted源碼的話可以發現LineOnlyReceiver的內部實際上自己實現了dataReceived,然后將其按行分割,有新的一行數據就調用lineReceived。

# -*- coding:utf-8 –*-    from twisted.protocols.basic import LineOnlyReceiver  from twisted.internet.protocol import Factory  from twisted.internet import reactor    class TcpServerHandle(LineOnlyReceiver):        # 新的連接建立      def connectionMade(self):          print 'connectionMade'        # 連接斷開      def connectionLost(self, reason):          print 'connectionLost'        # 接收到新的一行數據      def lineReceived(self, data):          print 'lineReceived:' + data    factory = Factory()  factory.protocol = TcpServerHandle  reactor.listenTCP(8080, factory)  reactor.run()  

下面用一個Java客戶端對三個服務器進行測試:

public class TcpClient {        public static void main(String[] args) throws IOException {            Socket socket = null;          OutputStream out = null;            try {                socket = new Socket("localhost", 8080);              out = socket.getOutputStream();                // 請求服務器              String lines = "床前明月光/r/n疑是地上霜/r/n舉頭望明月/r/n低頭思故鄉/r/n";              byte[] outputBytes = lines.getBytes("UTF-8");              out.write(outputBytes);              out.flush();            } finally {              // 關閉連接              out.close();              socket.close();          }      }  }  

MINA服務器輸出結果:

sessionCreatedmessageReceived:床前明月光messageReceived:疑是地上霜messageReceived:舉頭望明月messageReceived:低頭思故鄉sessionClosed

Netty服務器輸出結果:

channelActivechannelRead:床前明月光channelRead:疑是地上霜channelRead:舉頭望明月channelRead:低頭思故鄉channelInactive

Twisted服務器輸出結果:

connectionMadelineReceived:床前明月光lineReceived:疑是地上霜lineReceived:舉頭望明月lineReceived:低頭思故鄉connectionLost

當然,測試的時候也可以將發送的數據模擬成不按規則分割的情況,下面用一個更變態的客戶端來測試:

public class TcpClient {            public static void main(String[] args) throws IOException, InterruptedException {                              Socket socket = null;          OutputStream out = null;                    try{                            socket = new Socket("localhost", 8080);                out = socket.getOutputStream();                            String lines = "床前";              byte[] outputBytes = lines.getBytes("UTF-8");              out.write(outputBytes);              out.flush();                            Thread.sleep(1000);                            lines = "明月";              outputBytes = lines.getBytes("UTF-8");              out.write(outputBytes);              out.flush();                            Thread.sleep(1000);                            lines = "光/r/n疑是地上霜/r/n舉頭";              outputBytes = lines.getBytes("UTF-8");              out.write(outputBytes);              out.flush();                            Thread.sleep(1000);                            lines = "望明月/r/n低頭思故鄉/r/n";              outputBytes = lines.getBytes("UTF-8");              out.write(outputBytes);              out.flush();                        } finally {              // 關閉連接              out.close();              socket.close();          }      }  }  

再次分別測試上面三個服務器,結果和上面的輸出結果一樣,沒有任何問題。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产99久久精品一区二区永久免费| 欧美激情在线观看视频| 国产精品久久av| 国产精品久久久久秋霞鲁丝| 久久久久久久久久久国产| 亚洲人成在线一二| 这里只有精品在线观看| 亚州av一区二区| 麻豆一区二区在线观看| 国产精品久久久久9999| 亚洲国产精久久久久久| 亚洲欧美在线磁力| 国产一区二区日韩| 日韩欧美大尺度| 91精品国产成人www| 欧美伊久线香蕉线新在线| 国自在线精品视频| 亚洲欧美中文字幕在线一区| 久久久久久久久久久久久久久久久久av| 亚洲美女视频网站| 成人乱人伦精品视频在线观看| 亚洲综合精品一区二区| 欧美在线视频一区二区| 欧美日韩中文字幕综合视频| 亚洲精品国产精品乱码不99按摩| 国产精品福利在线观看网址| 国产成人亚洲综合| 最近2019中文免费高清视频观看www99| 亚洲欧美激情另类校园| 欧美日韩在线免费| 欧美亚洲另类制服自拍| 久热99视频在线观看| 中文字幕自拍vr一区二区三区| 国产精品网站入口| 欲色天天网综合久久| 97人洗澡人人免费公开视频碰碰碰| 最近更新的2019中文字幕| 亚洲在线视频观看| 日韩毛片在线看| 国产精品91一区| 欧美性xxxxxxx| 亚洲视频电影图片偷拍一区| 日本韩国欧美精品大片卡二| 亚洲免费小视频| 一区二区三区高清国产| 在线视频欧美日韩精品| 91av在线免费观看视频| 亚洲欧美日韩精品久久亚洲区| 亚洲欧美成人精品| 欧美成人精品在线观看| 亚洲精品久久久久| 亚洲999一在线观看www| 欧美精品一区二区三区国产精品| 日韩成人在线视频网站| 欧美日韩激情小视频| 色爱av美腿丝袜综合粉嫩av| 精品国产乱码久久久久久婷婷| 国产精品电影在线观看| 亚洲国产成人91精品| 中文字幕日韩高清| 91国产视频在线播放| 久久成人亚洲精品| 欧美一级大片在线观看| 国产精品美女免费| 国产欧美精品一区二区三区-老狼| 欧美亚洲国产日本| 精品亚洲一区二区三区在线观看| 亚洲精品国精品久久99热| 一区二区三区精品99久久| 欧美日韩国产黄| 国产精品手机播放| 在线视频精品一| 日韩有码在线播放| 精品网站999www| 日韩一级裸体免费视频| 国产精品久久久91| 在线成人激情视频| 国产国语videosex另类| 亚洲区一区二区| 欧洲成人在线视频| 亚洲另类欧美自拍| 不卡av电影在线观看| 中文字幕综合一区| 欧美在线观看一区二区三区| 日韩av在线免费| 亚洲女同精品视频| 一区二区三区久久精品| 久久影院资源站| 久久99视频精品| 久久精品2019中文字幕| 欧美成人自拍视频| 国产成人av网| 在线播放国产一区中文字幕剧情欧美| 欧美黑人狂野猛交老妇| 欧美野外猛男的大粗鳮| 精品国产欧美一区二区五十路| 国产精品一区二区三区久久久| 亚洲精品98久久久久久中文字幕| 自拍偷拍免费精品| 色999日韩欧美国产| 亚洲免费av片| 日韩欧美中文第一页| 欧美中文在线字幕| 日韩亚洲成人av在线| 亚洲护士老师的毛茸茸最新章节| 国产97免费视| 欧美成人性色生活仑片| 亚洲一区二区自拍| 欧美激情一区二区三区在线视频观看| 欧美激情网站在线观看| 性欧美长视频免费观看不卡| 亚洲最新av在线网站| 国产精品99久久久久久白浆小说| 欧美在线视频观看| 国产999在线观看| 欧美中文字幕视频在线观看| 色偷偷88888欧美精品久久久| 久久精品久久久久久| 日韩69视频在线观看| 精品久久久久久久久久久久久| 久久频这里精品99香蕉| 91视频8mav| 91免费电影网站| 欧美激情一级欧美精品| 日韩在线国产精品| 国产成人涩涩涩视频在线观看| 欧美大片第1页| 亚洲欧美一区二区三区情侣bbw| 日本三级韩国三级久久| 青青草国产精品一区二区| 91美女福利视频高清| 国产精品精品久久久| 青青草99啪国产免费| 91久久国产精品| 中文字幕精品影院| 亚洲国产精品成人va在线观看| 久久久女女女女999久久| 欧美在线精品免播放器视频| 亚洲午夜女主播在线直播| 亚洲免费电影在线观看| 国产美女91呻吟求| 欧美激情中文字幕在线| 久久精品国产欧美亚洲人人爽| 欧美日韩另类在线| 欧美日韩综合视频| 岛国av一区二区在线在线观看| 成人有码在线播放| 1769国内精品视频在线播放| 欧美国产精品va在线观看| 国产亚洲aⅴaaaaaa毛片| 中文字幕亚洲欧美日韩高清| 日韩色av导航| 中文综合在线观看| 亚洲第一精品夜夜躁人人爽| 欧美俄罗斯性视频| 国产色综合天天综合网| www.日韩免费| 国产成人久久久精品一区| 国产精品一区二区三区久久| 92裸体在线视频网站| 亚洲成人激情小说| 亚洲tv在线观看| 精品国产一区二区三区久久狼5月| 欧美尺度大的性做爰视频|