通过netty和tomcat理解4层通信及7层协议
概述
一个网络请求包括两部分。1.建立tcp连接 2.处理报文
需要解决的问题:1.建立稳定网络连接。 2.支持高并发解决。如何利用少的应用线程解决高网络连接。 3.理解协议,按照协议实现各种功能。长连接、数据格式等。
netty也好,tomcat也罢,都需要解决这些问题。tomcat做的更多,也更重,但也简单。netty做的少,也更灵活,但也复杂。下文会阐述各自如何解决这些问题。
关键词:connection、tiimeout、线程池、keepalive、nodelay
解决问题域
netty解决问题域
netty解决的问题域:解决的是四层协议。网络连接问题。如何建立稳定的网络连接:客户端面临断连重连、网 络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题。利用职责链处理传入二进制流。
并且设计了编解码、handler的流程,通过职责链把处理类注册即可。
tomcat解决问题域
解决的四层协议(传输层)+七层(应用层)协议。利用nio技术能够处理大量连接。在七层协议方面,对http协议进行解析,把header和body解析成java对象,header,request,response等,可以直接使用。简化http网络开发。
传输层通信:传输报文处理
tcp三次握手:
tcp三次握手
概念补充-TCP三次握手:
TCP(Transmission Control Protocol)传输控制协议
TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:
位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
完成三次握手,主机A与主机B开始传送数据。
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。完成三次握手,客户端与服务器开始传送数据.
由于TCP提供可靠交付和有序性的保证,它是最适合需要高可靠并且对传输时间要求不高的应用。UDP是更适合的应用程序需要快速,高效的传输的应用,如游戏。UDP是无状态的性质,在服务器端需要对大量客户端产生的少量请求进行应答的应用中是非常有用的
应用层协议解析
http协议分析
postman构造协议
http请求格式只能是一种。application/json,multipart/form-data,x-www-form-urlencoded,等等
raw要选json
multipart/form-data:
1、既可以提交普通键值对,也可以提交(多个)文件键值对。
2、HTTP规范中的Content-Type不包含此类型,只能用在POST提交方式下,属于http客户端(浏览器、java httpclient)的扩展
3、通常在浏览器表单中,或者http客户端(java httpclient)中使用。
页面中,form的enctype是multipart/form-data,提交时,content-type也是multipart/form-data。
multipart/form-data格式
POST http://www.xx.com/myproject/service1
Host: 192.168.0.201:8694
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: c3d85a6c-9849-7e3e-5c89-5b994b335b1d
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name1"
value1
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name2"
value2
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file1"; filename="94b5b232gw1ewlx3p595wg20ak0574qq.gif"
Content-Type: image/gif
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file2"; filename="1443175219259.jpg"
Content-Type: image/jpeg
----WebKitFormBoundary7MA4YWxkTrZu0gW
application/octet-stream
1、只能提交二进制,而且只能提交一个二进制,如果提交文件的话,只能提交一个文件,后台接收参数只能有一个,而且只能是流(或者字节数组)
2、属于HTTP规范中Content-Type的一种
3、很少使用
application/x-www-form-urlencoded
1、不属于http content-type规范,通常用于浏览器表单提交,数据组织格式:name1=value1&name2=value2,post时会放入http body,get时,显示在在地址栏。
2、所有键与值,都会被urlencoded,请查看urlencoder
数据组织格式
POST http://www.xx.com/myproject/service HTTP/1.1
Host: 192.168.0.201:8694
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: f5f6240c-08d3-8695-9473-607038f71eaa
name11=file1&name2=value2
springboot解析协议
@RequestBody方式
public ResponseData queryCreateStateCar(@RequestBody VehicleParamVo vehicleParamVo) {
@RequestParam
public ResponseData vechicleNoListById(@RequestParam(required = false) String vehicleId)
springboot可以直接接MultiparFile
@RequestMapping(value = "/drivingLicenseOcr", method = {RequestMethod.POST})
@ApiOperation(value = "行驶证识别", notes = "", httpMethod = "POST", produces = MediaType.APPLICATION_JSON_VALUE)
public Object drivingLicenseOcr(@ApiParam(value = "文件(图片)上传", required = true) MultipartFile file){
Map result = new HashMap();
808协议
需要人工进行解析
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in)
throws Exception {
ByteBuf frame = (ByteBuf)super.decode(ctx, in);
if(frame != null){
byte[] b = ByteUtil.unescapeForYichexing(frame);
if(ByteUtil.validateCS(b)){
if(b == null || b.length <= 0){
return null;
}
if(b[0] == 0x01 && b[1] == 0x00){
//终端注册
RegisterRequest registerRequest = new RegisterRequest();
registerRequest.setHeader(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11]);
registerRequest.setMsgflowNo(ByteUtil.bigBytesToInt(b[10], b[11]));
registerRequest.setProvinceCode(ByteUtil.bigBytesToBCD(b[12], b[13]));
registerRequest.setCityCode(ByteUtil.bigBytesToBCD(b[14], b[15]));
// 制造商
registerRequest.setCreatorCode(ByteUtil.bigBytesToBCD(b[16], b[17], b[18], b[19], b[20]));
// 赵立彬 20191120 21-28 byte
registerRequest.setTerminalType(ByteUtil.bigBytesToBCD(b[21], b[22], b[23], b[24], b[25], b[26], b[27], b[28]));
// 赵立彬 20191120 29-32 byte
registerRequest.setTerminalCode(ByteUtil.bigBytesToBCD(b[29], b[30], b[31], b[32], b[33], b[34], b[35]));
// 赵立彬 20191120 33 byte
registerRequest.setColorCode(ByteUtil.bigBytesToBCD(b[36]));
// 34、35、36空缺
String messageId = ByteUtil.bigBytesToBCD(b[0],b[1]);
String messageBody = ByteUtil.bigBytesToBCD(b[2],b[3]);
String carId = ByteUtil.bigBytesToBCD(b[4],b[5],b[6],b[7],b[8],b[9]);
String messageNo = ByteUtil.bigBytesToBCD(b[10],b[11]);
netty使用分析
1.netty研究
--------------------------------------------------------------------------------
netty提升高并发能力方法
建立连接。
EventLoopGroup boss=new NioEventLoopGroup();
EventLoopGroup worker=new NioEventLoopGroup();
try {
ServerBootstrap bootstrap=new ServerBootstrap();
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.option(ChannelOption.SO_BACKLOG, 128);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
bootstrap.childHandler(xiaoerChannelInitializer);
提升1:增加线程处理数
EventLoopGroup worker = new NioEventLoopGroup(4, new DefaultThreadFactory("server2", true));
默认是cpu核数
boss是acceptor线程数。
SO_BACKLOG数据可以设置更大,提升等待数目。
--------------------------------------------------------------------------------
netty原理
利用socket建立tcp连接。建立稳定的网络连接。
通过职责链把编解码、handler设置进去。
ChannelPipeline p = socketChannel.pipeline();
// p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new XiaoerIKaiEVProtocolDecoder(1000, new ByteBuf[] {
Unpooled.wrappedBuffer(new byte[] { 0x7e, 0x7e }),//这个分隔符是考虑到两个包之间的粘连
Unpooled.wrappedBuffer(new byte[] { 0x7e })
}));
p.addLast(new XiaoerMessageToByteEncoder());
p.addLast(new ReadTimeoutHandler(1800));
// XiaoerBizServerHandler xiaoerBizServerHandler = (XiaoerBizServerHandler)Main.getBeanFactory().getBean("xiaoerBizServerHandler");
XiaoerBizServerHandler xiaoerBizServerHandler = SpringUtil.getBeanByClass(XiaoerBizServerHandler.class);
p.addLast(xiaoerBizServerHandler);
高并发课题
这里简单说一下,在高并发具体课题详述
高并发需要理解连接数和线程数。连接数指的是tcp连接数,线程数指的是在jvm中处理请求(报文)的线程,需要占用较大资源。在nio模式下,连接数10000,线程数200。参考值。
还有一个acceptQueue数,这个数也能增加并发能力。
操作系统参数
高并发操作系统参数:maxconn,maxproc,maxfile这些参数需要调整。端口数也需要调整。
tomcat高并发
线程数和连接数需要调整
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="500" minSpareThreads="400"/>
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
acceptCount="3000"
maxConnections="30000"
connectionTimeout="20000"
redirectPort="8443" />
Connector对应的tcp连接数。可以很大。
maxThreads对应的是jvm中的线程数,nio不用很大。
netty高并发
线程数设置
如果你的应用服务的QPS只是几百万,那么parentGroup只需要设置为2,childGroup设置为4
EventLoopGroup parentGroup = new NioEventLoopGroup(2, new DefaultThreadFactory("server1", true));
EventLoopGroup childGroup = new NioEventLoopGroup(4, new DefaultThreadFactory("server2", true));
目前自己参与的应用,QPS是一百多万,上面的设置是完全可以应付的。
至于netty客户端程序,设置4个线程。
EventLoopGroup workGroup = new NioEventLoopGroup(4, new DefaultThreadFactory("client", true));
常见问题
操作发出请求的端口数不够
Cannot assign requested address.
mac解决办法:
zlbdeMacBook-Pro:~ zlb$ sysctl -a|grep portrange
net.inet.ip.portrange.hilast: 65535
net.inet.ip.portrange.hifirst: 49152
net.inet.ip.portrange.last: 65535
net.inet.ip.portrange.first: 49152
net.inet.ip.portrange.lowlast: 1023
net.inet.ip.portrange.lowfirst: 1023
由于49152-65535,当超过这些时,会Cannot assign requested address.
sudo sysctl -w net.inet.ip.portrange.last=65535
sudo sysctl -w net.inet.ip.portrange.first=10000
调大端口范围
可以用netstat看端口情况
操作系统参数too many open files等
1.更改kernel参数:这个参数改完后open_files也要改,否则Too many open files in system
命令
# sysctl -a
会显示所有的kernel参数及值。
linux修改参数值的语法
# sysctl -w net.core.somaxconn=32768
以上命令将kernel参数net.core.somaxconn的值改成了32768。这样的改动虽然可以立即生效,但是重启机器后会恢复默认值。为了永久保留改动,需要用vi在/etc/sysctl.conf中增加一行
net.core.somaxconn= 4000
然后执行命令
# sysctl -p
更改文件的方式
/etc/sysctl.conf中添加:net.core.somaxconn = 2048
mac系统:
sudo sysctl -w kern.ipc.somaxconn=32768
只更改somaxconn,高并发时,会出现下面的异常
java.io.IOException: Too many open files in system
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method) ~[na:1.8.0_121]
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422) ~[na:1.8.0_121]
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250) ~[na:1.8.0_121]
at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:455) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
2.更改open_files、max user processes
launchctl limit,查看系统的file和maxconn
cpu unlimited unlimited
filesize unlimited unlimited
data unlimited unlimited
stack 8388608 67104768
core 0 unlimited
rss unlimited unlimited
memlock unlimited unlimited
maxproc 709 1064
maxfiles 256 unlimited
maxproc表示的是开启的最大进程数,第一个1064表示的是soft限制,第二个1064表示的是hard限制,即硬件最大的限制数,自己设置的不能高于hard限制。所以 我soft也改成了1064最大的。
查看mac操作系统相关参数
ulimit -a,查看当前用户的file和maxconn
macbookpro-1287:etc zlb$ ulimit -a
open files (-n) 256
max user processes (-u) 709
--------------------------------------------------------------------------------
在内存中更改
更改open files,max user processes
maxproc表示的是开启的最大进程数,第一个1064表示的是soft限制,第二个1064表示的是hard限制,即硬件最大的限制数,自己设置的不能高于hard限制。所以 我soft也改成了1064最大的。
maxfiles表示的是开启的最大文件数(句柄),意义同上……
打开或者新建一个文件:/Library/LaunchDaemons/limit.maxfiles.plist
输入:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>limit.maxfiles</string>
<key>ProgramArguments</key>
<array>
<string>launchctl</string>
<string>limit</string>
<string>maxfiles</string>
<string>65536</string>
<string>65536</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ServiceIPC</key>
<false/>
</dict>
</plist>
保存并重启mac即可设置最大文件数。
打开或者新建一个文件:/Library/LaunchDaemons/limit.maxproc.plist
输入:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>limit.maxproc</string>
<key>ProgramArguments</key>
<array>
<string>launchctl</string>
<string>limit</string>
<string>maxproc</string>
<string>2048</string>
<string>2048</string>
</array>
<key>RunAtLoad</key>
<true />
<key>ServiceIPC</key>
<false />
</dict>
</plist>
改完之后,生效
ulimit -n 1000
ulimit -u
在中内存更改:
sudo launchctl limit
设置打开文件数
$sudo launchctl limit maxfiles 100000 500000
$sudo ulimit -n 100000
$sudo launchctl limit maxproc 100000 100000
sysctl -a | grep "files"
kern.maxfiles: 500000
kern.maxfilesperproc: 100000
kern.num_files: 4849
sh-3.2# sysctl -w kern.maxfiles=1048600
kern.maxfiles: 12288 -> 1048600
sh-3.2# sysctl -w kern.maxfilesperproc=1048576
kern.maxfilesperproc: 10240 -> 1048576
--------------------------------------------------------------------------------
linux在文件中更改,永久生效
然后,一般来说,修改ulimit的数值,只需要修改/etc/security/limits.conf即可,但是这个参数需要修改/etc/security/limits.d/90-nproc.conf。
至于为什么需要修改这里,请看褚霸的这篇blog:
这里只介绍一种永久性的修改方式
使用root用户修改配置文件:/etc/security/limits.conf
增加如下内容
* soft nproc 10240
* hard nproc 10240
* soft nofile 10240
* hard nofile 10240
其中nofile对应open_files
nproc对应max_user_processes
但是在Linux 6.4之后,如果只修改了该文件中的nproc,那么其他非root用户对应的max_user_processes并不会改变,仍然是1024,这个是因为受到了下面这个文件的影响
/etc/security/limits.d/90-nproc.conf
查看一下:
[root@rhf ~]# cat /etc/security/limits.d/90-nproc.conf
# Default limit for number of user's processes to prevent
# accidental fork bombs.
# See rhbz #432903 for reasoning.
* soft nproc 1024
root soft nproc unlimited
此时有两种方法解决该问题:
1、修改/etc/security/limits.d/90-nproc.conf将
* soft nproc 1024
修改为:
* soft nproc 10240
2、修改/etc/security/limits.conf,将
* soft nofile 10240
修改为
oracle soft nofile 10240