TCP 基础
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议
连接:连接就是双方遵循约定,client、server相互了解,知道对方的信息并且保留地址、buffer、状态等信息的过程叫做连接
字节流:流是一方发,另一方只需要读,收到的是一串01数字;而报文是发送的一个字符串或其它一组数据,组合在一个包中进行发送
滑动窗口:每次发送包的数量,当这一批(窗口内)的包发送完成,移动到下一个窗口,删除已发送的包
三次握手:client向server发送请求,server响应ack,然后client收到后响应ack给server。只有经过三次,双方才能确定对方是没问题的,我发的包对方能收到且能返回ack
TCP如何保证接收到字节流间的顺序?
每一个包都有序号,对方将收到的一个包放在buffer;比如收到了第4个包则返回ack 4;在buffer满后再按照包顺序写入
Socket 基础
socket
就是创建一个socket对象
bind
表示绑定到一个端口,基于这个端口传输字节流
listen
表示将socket置于侦听状态
accept
表示接受一个连接(会去底层去看哪些连接三次握手成功了的连接),如果没有连接就会一直阻塞在这里
React模式
在使用Socket编码中,所有的代码都冗余在一起,更像是面向过程的,而React就是将这些逻辑进行抽象、解耦的模式
将所有职责分开,有人专门做accept、有人read、write数据,它们都是由一个handler类来处理,不同事件触发不同的方法,比如
handler.onAccept();
handler.onRead((SocketChannel) key.channel());
Netty
Netty是一个NIO客户端服务器框架,支持快速和简单网络应用程序的开发,如协议服务器和客户端。
核心组件
EventLoop:事件轮询,对所有的事件进行轮询检查
ChannelPipeline:流水线,中间所有的handler(都被ctx包裹着)都有机会处理数据,一个handler做完会给(fire)下一个handler
ch.pipeline().addLast(new LineBasedFrameDecoder(80,false,false));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new EchoHandler());
ChannelHandler:一些channel逻辑或数据处理的handler
ChannelPipeline&ChannelHandler关系?
如何对字节流拆包?
拆包:对一连串的流进行拆解
拆包方案:
- 使用分隔符:数据中可能会有此分隔符
- 在头部增加长度:对于一些不是自己的包,无法识别
- 业界正确做法:magic(魔数也就是标识符)+数据长度+数据,如下图所示
数据流就是由三部分组成,基本过程如下:
-
在接收方会根据header来识别是否是需要解析的流
-
根据length长度获取指定长度的数据
-
让后将收到的ByteBuf进行处理转换,比如转换成string
在Netty中提供了LineBasedFrameDecoder
类来进行处理基于长度的帧的解码器,可动灵活配置解码方式// 基于长度的帧的解码器,可动灵活配置解码方式 // maxFrameLength 最大的帧长度1024 // lengthFieldOffset length字段的偏移量(length在帧的位置)0 // lengthFieldLength length字段的长度2 // lengthAdjustment 帧的length是否包含length字段,不包含 // initialBytesToStrip 从解码帧中裁剪出的字节数(返回的Bytes是否包含length字段或其它其它字段)2 new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2);
思考
-
阻塞、非阻塞、异步、同步区别?
阻塞/非阻塞是api级别的,比如lock()是阻塞的,tryLock()是非阻塞的
同步/异步其实就是主动调用和被动调用的区别,同步就是从头到尾,异步就是就是之后会被调用,比如ajax -
Netty有什么缺点吗?
比如ChannelPipeline+ChannelHandler的组合,使得所有逻辑割裂,逻辑散落在各个handler类中,且handler之间有先后顺序、是否传递到下一个handler等,如果不进行调试难以阅读处理过程 -
Netty和Tomcat的区别?
netty是基于TCP,可以自定义一些特殊协议,可以成为一个tomcat也可以做HTTP协议
tomcat是应用层协议,基于servlet模式,是一个http的模型,比如getHeader()、getBody()都是http的概念
扩展
零拷贝:kernal内部有一套写入或发送的api,在java中也有byte数组存在内存中,写入到Socket的时候,需要将内存数据拷贝到kernal态侧,然后再写入到Socket中
而零拷贝是指将kerna一部分数据l映射出来,而不需要从用户态和kernal态处进行拷贝,在kernal态中直接发送数据到socket
epoll(来源百度百科):epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了