这是本人梳理的网络编程背景知识笔记,其中很多内容也不是原创,拿来之后根据自己的理解做的整合。分享出来,希望对大家有所帮助。不当之处,欢迎拍砖。
一、基础知识
1. 基础概念
1.1 寻址
IP协议使用IP寻址,它只能标识到主机这个层面。
TCP/UDP等传输协议,在IP寻址的基础上,使用端口标识应用进程。
FAQ 1:同一个端口,例如10000,是否同时被TCP和UDP套接字绑定?
答:可以。端口是传输协议自己的标识,TCP的端口和UDP的端口没有任何关系的。
FAQ 2:什么是通信五元组?
答:五元组是:{源IP,目的IP,传输协议,源端口,目的端口},可以用来标识一组通信的应用进程。
FAQ 3: 应用进程如何指定端口?
答:在套接字上调用bind(),可以绑定到指定端口。
对TCP服务端,在accept()返回新连接时,套接字本端口被置成监听端口。
对客户端来说,本地端口对应用没有实际意义,因此通常是直接调用connect(),由传输协议分配一个临时端口。
1.2 IP包分段
要传输的IP分组大于物理网络的MTU时,就要进行IP包分段。
通过标志(分段标志),标识(发送方每发送一个分组编号加一)和段偏移,实现IP包的分段和重组。
IP包分段对TCP/UDP等传输协议是透明的,IP包重组完成后才会交给TCP/UDP等传输协议。
最小重组缓冲区是IPv4和IPv6的任何实现都必须支持的最小数据包大小,其值对于IPv4为576字节,对于IPv6为1500字节。
对于TCP,重组缓冲区的实际值由MSS间接指定,如果MSS为700字节,则重组缓冲区的实际值就是MSS+TCP头部+IP头部,即740字节,也就是一个最大的IP数据报的大小。但是如果对端未通告MSS,则认为对端只支持最小重组缓冲区,因此默认MSS 为536字节(IPv4最小重组缓冲区大小576字节减去TCP和IPv4 头部)。一般重组缓冲区的大小和MTU是相等的。
对于UDP,我们不能判定某个给定目的地能否接受577字节的数据包,为此有许多使用UDP的IPv4网络应用避免产生大于576的数据包。
1.3 TCP报文分段
TCP是流式传输协议,它提供的是带有数据校验、重传、拥塞控制的可靠传输服务。
为了避免TCP报文被分段(保持传输效率),每个TCP报文长度小于等于MSS。
MSS是传输TCP协议范畴内的概念,顾名思义,其标识TCP能够承载的最大的应用数据段长度,因此,MSS=MTU-20字节TCP报头-20字节IP报头,那么在以太网环境下,MSS值一般就是1500-20-20=1460字节。
TCP在三次握手建立连接过程中,会在SYN报文中使用MSS选项功能,协商交互双方能够接收的最大段长MSS值。客户端与服务器端分别根据自己发包接口的MTU值计算出相应MSS值,并通过SYN报文告知对方。
但是,两端的主机并不清楚IP包要经过哪些传输网络,TCP还需要结合路径MTU发现机制,动态调整MSS。 又由于PMTUD可能存在ICMP差错报文被过滤的情况,很多中间设备的接口支持adjust tcp mss设置功能。中间设备修改经过其转发的TCP SYN报文中的MSS值,让中间设备参与进TCP三次握手时SYN报文的MSS协商来避免分片。
在TCP的MSS选项中,MSS的值是一个16位的字段,限定其最大值为65535,。这个值对于IPv4是合适的,因为IPv4数据报中的最大TCP数据量为65495(65535减去IPv4首部的20字节和TCP首部的20字节)。对于具有特大净荷选项的IPv6,需要使用另外一种技巧RFC2675。
2. TCP协议
搞懂TCP状态转换,对于理解应用程序与TCP协议交互的原理,发现和定位问题都非常重要。
2.1 TCP状态转换图
TCP状态转换图毫无疑问,最难理解的是TIME_WAIT这个状态,TIME_WATE状态存在的理由:
- 可靠地实现TCP全双工连接的终止
参考时序图,假如最后的那个ACK丢失,服务器将重新发送它的最终FIN,因此客户端必须维护状态,以允许服务器重新发送最终那个ACK。假如客户端不维护状态信息,它将以RST响应,该分节将被服务器解释为错误。
- 允许老的重复分节在网络中消逝
如果连接1关闭后端口立即被释放,新建的连接2恰好与连接1五元组相同,而连接1 的一个迷途分组珊珊来迟,它就会被误认为是连接2的数据。因此,TCP必须防止来自某个连接的老的重复分组在该连接已终止后再现,从而出现“串线”。为此,TCP将不重用处于TIME_WAIT状态的端口,持续时间是MSL的2倍,这就足以让某个方向上的分组最多存活MSL秒即被丢失,另一个方向上的应答最多存活MSL秒也被丢弃。
FAQ 4: TCP为什么需要三次握手和四次挥手?
答:TCP是全双工连接,对每个方向都要协商建立连接,释放连接。每个方向的建立/关闭都需要一个请求,一个ACK,所以一共需要四次交互。
在建立连接时,服务端的SYN请求可以携带发给客户端的ACK,所以就变成了三次握手。
2.2 TCP时序图
TCP时序图2.3 拥塞控制
CWND:Congestion Window,拥塞窗口,负责控制单位时间内,数据发送端的报文发送量。TCP 协议规定,一个 RTT(Round-Trip Time,往返时延,大家常说的 ping 值)时间内,数据发送端只能发送 CWND 个数据包(注意不是字节数)。TCP 协议利用 CWND/RTT 来控制速度。这个值是根据丢包动态计算出来的
SS:Slow Start,慢启动阶段。TCP 刚开始传输的时候,速度是慢慢涨起来的,除非遇到丢包,否则速度会一直指数性增长(标准 TCP 协议的拥塞控制算法,例如 cubic
就是如此。很多其它拥塞控制算法或其它厂商可能修改过慢启动增长特性,未必符合指数特性)。
CA:Congestion Avoid,拥塞避免阶段。当 TCP 数据发送方感知到有丢包后,会降低 CWND,此时速度会下降,CWND 再次增长时,不再像 SS 那样指数增,而是线性增(同理,标准 TCP 协议的拥塞控制算法,例如 cubic 是这样,很多其它拥塞控制算法或其它厂商可能修改过慢启动增长特性,未必符合这个特性)。
ssthresh:Slow Start Threshold,慢启动阈值。当数据发送方感知到丢包时,会记录此时的 CWND,并计算合理的 ssthresh 值(ssthresh <= 丢包时的 CWND),当 CWND 重新由小至大增长,直到 sshtresh 时,不再 SS 而是 CA。但因为数据确认超时(数据发送端始终收不到对端的接收确认报文),发送端会骤降 CWND 到最初始的状态。
SO_SNDBUF、SO_RCVBUF 发送、接收buffer
,
TCP拥塞控制
上图一旦发生丢包,cwnd降到1 ssthresh降到cwnd/2,一夜回到解放前,太保守了,实际大多情况下都是公网带宽还有空余但是链路过长,非带宽不够丢包概率增大,对此没必要这么保守(tcp诞生的背景主要针对局域网、双绞线来设计,偏保守)。RTT越大的网络环境(长肥管道)这个问题越是严重,表现就是传输速度抖动非常厉害。
改进的快速恢复算法所以改进的拥塞算法一旦发现丢包,cwnd和ssthresh降到原来的cwnd的一半。
3. UDP协议
UDP是无连接协议,UDP套接字可以给任何(IP,UDP端口)发送数据,也可以接收来自任何(IP,UDP端口)发送的数据。
UDP报文头只有8个字节,格式为:源端口|目的端口|长度|校验和
。由此也可以看出,UDP是一个非常简陋的传输协议,应用程序要自己处理丢包、乱序、流量控制等各种传输问题。
- 异步错误
由于UDP的无连接特性,它的目的地址是任意的,内核无法把目的地址连同错误信息返回给应用程序。因此,一个UDP套接字,由它引发的异步错误却并不返回给它,除非它已连接。因为,UDP套接字一旦connect,它的目的地址就被固定了,只需要返回错误信息即可。
- 组播
组播是无连接的,可以直接基于UDP传输组播数据,自己实现传输可靠性。也可以使用可靠组播协议PGM,libpgm库。
4. 常用套接字选项
4.1 SO_KEEPALIVE
给一个TCP套接字设置SO_KEEPALIVE后,如果在指定时间内该套接字的任一方向都没有数据交换,TCP就自动给对端发送一个保持存活探测分节,期望对端TCP正常以ACK响应。如果多次收不到ACK响应,或者收到ICMP的错误(主机不可达),则认为连接失效。
4.2 SO_RECVBUF 和 SO_SENDBUF
每个套接字都有一个发送缓冲区和一个接收缓冲区。
接收缓冲区被TCP、UDP用来保存收到的数据,直到由应用进程来读取。对TCP来说,接收缓冲区可用空间的大小限定了TCP通告对端的窗口大小,这就是TCP的流量控制。然而对UDP来说,当接收到的数据报装不进套接字缓冲区时,该数据报就被丢弃。
发送缓冲区相当于发送仓库的大小,仓库的货物都发走后,不能立马腾出来发新的货物,而是要等发走的获取对方确认收到了(Ack)才能腾出来发新的货物, 仓库足够大了之后接下来的瓶颈就是高速公路了(带宽、拥塞窗口)
如果是UDP,就没有send buffer的概念,有数据统统发出去,根本不关心对方是否收到。
FAQ 5:TCP窗口扩大选项Window Scale?
答:TCP窗口字段是一个16个比特的值,窗口最大只能是64K。对于长胖管道,需要使用TCP窗口扩大选项Window Scale。
一般通讯双方都是支持tcp windows scale的。
FAQ 6:发送缓冲区多大合适?
答:以链路的BDP带宽时延积做参照。
例如,对于100M,时延为20ms的链路,BDP=0.02秒*(100MB/8)=250Kb,所以SO_SNDBUF为256Kb的时候基本能跑满带宽了,再大实际意义也不大了。
因为BDP是250K,也就是拥塞窗口即将成为新的瓶颈,所以调大buffer没意义了。
FAQ 7:要不要在程序中主动设置SO_SENDBUF?
答:默认情况下Linux系统会自动调整这个buf(net.ipv4.tcp_wmem),不推荐程序中主动去设置SO_SNDBUF,除非明确知道设置的值是最优的。
主动设置SO_SNDBUF选项后,就关闭了操作系统的自动调节机制。
4.3 SO_RCVTIMEO 和 SO_SNDTIMEO
这两个选项允许我们给套接字的接收和发送设置一个超时值。
4.4 SO_REUSEADDR 和 SO_REUSEPORT
SO_REUSEADDR 允许多个套接字绑定到同一个端口,但绑定的IP地址不能相同。
SO_REUSEPORT 允许我们将任意数目的socket绑定到完全相同的源地址端口对上,只要所有之前绑定的socket都设置了SO_REUSEPORT选项。
- Linux上的行为
在Linux3.9之前,只有SO_REUSEADDR选项存在。这个选项的作用基本上同BSD系统下相同。但其仍有两个重要的区别。
第一个区别是如果一个处于监听(服务器)状态下的TCP socket已经被绑定到了一个通配符IP地址和一个特定端口下,那么不论这两个socket有没有设置SO_REUSEADDR选项,任何其他TCP socket都无法再被绑定到相同的端口下。即使另一个socket使用了一个具体IP地址(像在BSD系统中允许的那样)也不行。而非监听(客户)TCP socket则无此限制。
第二个区别是对于UDP socket来说,SO_REUSEADDR的作用和BSD中SO_REUSEPORT完全相同。所以两个UDP socket如果都设置了SO_REUSEADDR的话,它们就可以被绑定在一组完全相同的地址端口对上。
Linux3.9加入了SO_REUSEPORT选项。只要所有socket(包括第一个)在绑定地址前设置了这个选项,两个或多个,TCP或UDP,监听(服务器)或非监听(客户)socket就可以被绑定在完全相同的地址端口组合下。同时,为了防止端口劫持(port hijacking),还有一个特别的限制:所有试图绑定在相同的地址端口组合的socket必须属于拥有相同用户ID的进程。所以一个用户无法从另一个用户那里“偷窃”端口。
除此之外,对于设置了SO_REUSEPORT选项的socket,Linux kernel还会执行一些别的系统所没有的特别的操作:对于绑定于同一地址端口组合上的UDP socket,kernel尝试在它们之间平均分配收到的数据包;对于绑定于同一地址端口组合上的TCP监听socket,kernel尝试在它们之间平均分配收到的连接请求(调用accept()方法所得到的请求)。这意味着相比于其他允许地址复用但随机将收到的数据包或者连接请求分配给连接在同一地址端口组合上的socket的系统而言,Linux尝试了进行流量分配上的优化。比如一个简单的服务器进程的几个不同实例可以方便地使用SO_REUSEPORT来实现一个简单的负载均衡,而且这个负载均衡有kernel负责, 对程序来说完全免费!
- Windows上的行为
Windows仅有SO_REUSEADDR选项。在Windows中对一个socket设置SO_REUSEADDR的效果与在BSD下同时对一个socket设置SO_REUSEPORT和SO_REUSEADDR相同。但其区别在于:即使另一个已绑定地址的socket并没有设置SO_REUSEADDR,一个设置了SO_REUSEADDR的socket总是可以绑定到与另一个已绑定的socket完全相同的地址端口组合上。这个行为可以说是有些危险的。因为它允许了一个应用从另一个引用已连接的端口上偷取数据。微软意识到了这个问题,因此添加了另一个socket选项:SO_EXCLUSIVEADDRUSE。对一个socket设置SO_EXCLUSIVEADDRUSE可以确保一旦该socket绑定了一个地址端口组合,任何其他socket,不论设置SO_REUSEADDR与否,都无法再绑定当前的地址端口组合。
4.5 TCP_NODELAY
Nagle算法的目的是防止有多个小分组待确认。具体来说,当连接上有数据包待确认(未收到ACK)时, 此后的send如果发送的是小包(小于MSS),数据将被推迟发送,直到收到上一个数据包的ACK。
ACK延滞算法是在接收到数据后不立即发送ACK,而是等待一小段时间(典型值为50~200ms),期待在这一小段时间内有数据发送回对端,被延滞的ACK就可以由这些数据捎带,从而节省一个TCP分节。
Nagle和ACK延滞算法一起,在某些情形下会带来明显的延时。例如,服务器不发送响应数据(无法携带ACK),或者客户端把一个请求分成多个小数据包发送(例如先发送一个4字节的长度字段,再发送剩余的396字节的数据),因为服务器只有收到完整请求后才能产生响应数据。
有几种解决办法来修正这种客户程序:
- 使用writev,其结果是只产生一个TCP分节。
- 把前4字节的数据和后396字节的数据复制到单个缓冲区中,再调用一次write。
- 设置TCP_NODELAY,继续调用两次write,这是最不可取的办法,有损于网络效率,通常不应考虑。
二、编程实践
有了基础知识的铺垫,我们就可以讨论 编写健壮的TCP网络程序 的实践经验。
条款1. 使用心跳消息监控连接/服务状态
应用时刻跟踪对端服务的健康状况,就需要面对各种各样的问题。
- 网络中断
连接TCP的网络中断(可以通过拔网线来模拟这个场景),如果没有应用层心跳,也没有设置SO_KEEPALIVE选项,那么是不可能发现这个问题的。
- 主机崩溃
当对端主机崩溃时,TCP连接不会被正确释放,本端TCP协议栈收不到任何信息,仍然认为TCP连接是“健康”的,这和网络中断的情况很像。
- 应用服务死锁
SO_KEEPALIVE 只能监控TCP连接的活动情况,应用程序死锁等异常情况只能通过心跳消息来监控。
条款2. TCP是全双工双向通道
TCP是全双工双向
通道,每个方向有独立的MSS、窗口和缓冲区等。TCP连接一端的的发送通道和另一端的接收通道一起构成了一个单向通道,反过来也是一样。也就是说,可以把TCP想象成两个单向通道组合在一起的结果。
因此,连接关闭也是针对单方向的。例如,A调用shutdown(SHUT_WR)关闭A->B的写通道,但B->A的写通道仍然是打开的,B仍然可以给A继续发消息。
shutdown(SHUT_RDWR),或者close()可以关闭双向通道,当然close()还要考虑socket描述符的引用计数。
条款3. send()成功,仅仅只是开始
send()发送返回成功,只代表数据被成功放到TCP发送缓冲区,不代表数据已经发送到对端TCP接收缓冲区。
可能发生的异常:
- 数据还没被发送前,TCP连接中断。
- 数据已经发送到对方TCP,但对方应用异常宕机,没有来得及读数据。
- 对方主机网络不可达,TCP持续重传TCP分节,直到超时(可能会持续10分钟,是发送超时设置而定)。
因此send()调用成功仅仅是开始,还面临着九九八十一难,收到对端应用的响应消息,才算功德圆满。
条款4. 异步事件
应用程序要随时准备各种异步事件,避免长时间阻塞在某个调用上。
举个例子,应用阻塞在send调用上,此时,对方关闭了写通道,之后没有继续读数据,那么send调用就会长时间被阻塞。
解决这个问题,可以通过设置发送超时,或者使用IO多路复用(例如select/epoll) 和非阻塞套接字。
条款5. TCP的校验和没有那么可靠
TCP使用16位的crc校验和,这个校验强度是很低的(当然TCP协议出现时计算速度支持不了crc32、md5、sha1等高强度校验算法),在重要的应用中,还是要增加应用层的校验。
另外,即便TCP的校验强度足够高,也没办法防范数据包在传输路径上被非法篡改的问题,应用层校验还有安全加固的作用。
不过,重要的应用,最好是使用TLS,进行数据的全程加密。
条款6. read/write对连接异常的反馈能力是有限的
对应用程序来说,与另一进程的TCP通信其实是完全异步的过程:
- 我不知道对方什么时候、能否收到我的数据。
- 我不知道什么时候能够收到对方的数据。
- 我不知道什么时候通信结束(对方主动退出或是异常退出、机器故障、网络故障等等)。
无论对于1和2正常的数据收发,还是对于3连接事件的“通知”,都是通过read/write的结果返回给应用层的。
为什么说socket(或者说TCP/IP栈本身)对错误的反馈能力是有限的呢,我们来考虑下面的情况:
当服务器主机崩溃/网络不可达时,客户端进程根本不会收到FIN包作为连接终止的提示。
如果客户端进程阻塞在read上,那么结果只能是永远的等待(除非设置了读超时)。
如果客户端进程先write然后阻塞在read,由于收不到服务器机器TCP的ack,TCP会持续重传12次(时间跨度大约为9分钟),然后在阻塞的read调用上返回错误:ETIMEDOUT/EHOSTUNREACH/ENETUNREACH。
假如服务器崩溃后重启或者网络恢复,服务器收到客户端TCP某个重传的包,因为不能识别所以会返回一个RST,此时客户端进程上阻塞的read调用会返回错误ECONNREST。
恩,socket对这些错误还是有一定的反馈能力的,前提是在对面不可达时你依然做了一次write调用,而不是轮询或是阻塞在read上,那么总是会在重传的周期内检测出错误。如果没有那次write调用,应用层永远不会收到连接错误的通知。这也是应用层心跳的意义所在。
==write的错误最终通过read来通知应用层,有点阴差阳错?==
条款7. 常见连接异常
场景 | 异常描述 |
---|---|
信号 | 信号会中断正在进行的系统调用,有些系统调用会重启,有些不会重启。错误码 EINTR 表示系统调用被中断。 |
accept 返回前连接提前中止 | 服务器进程调用accept()时客户端连接已经断开,会返回ECONNABORTED/WSAECONNRESET或正常的连接描述符。注2 。 |
SIGPIPE信号 | 在对端关闭接收连接后,第一次发送数据会收到RST,再次发送会触发SIGPIPE。 |
服务器主机崩溃 | 发送数据会触发错误: ETIMEOUT/EHOSTUNREACH/ENETUNREACH。 |
服务器主机崩溃后重启 | 发送数据会收到对端的RST,触发错误:ECONNRESET。 |
非阻塞 connect | 当连接不能立即建立时,返回错误码:EINPROGRESS。 |
发送超时 | 错误码 ETIMEOUT |
非阻塞read/write | 当可能发生阻塞时,错误码:EAGAIN/EWOULDBLOCK。 |
注1: linux 和 windows 错误码对应关系:
linux windows 说明
EADDRINUSE WSAEADDRINUSE
EADDRNOTAVAIL WSAEADDRNOTAVAIL
EBADF WSAENOTSOCK
ECONNABORTED 无 在winsock中,WSAECONNABORTED表示本地TCP重置了连接
ECONNREST WSAECONNRESET
EHOSTUNREACH WSAEHOSTUNREACH
EINPROGRESS WSAEWOULDBLOCK 在winsock中,WSAEINPROGRESS表示在socket上有一个阻塞操作正在执行
ENETUNREACH WSAENETUNREACH
ETIMEOUT WSAETIMEDOUT
EWOULDBLOCK/EAGAIN WSAEWOULDBLOCK
函数返回值对应关系:
-1 INVALID_SOCKET(socket/accept返回)/SOCKET_ERROR(其他调用返回)
错误获取方式对应关系:
errno WSAGetLastError()
stderror() FormatMessage()
注2:在linux和windows上验证,均返回正常的连接描述符。
注2: 等到连接可写或可读时,可以通过getsockopt(SO_ERROR),判断连接是否建立成功。
注3:linux定义为同一个值,建议都检查,编译器会优化重复项
注4:WinSock1.1和WinSock2.0
Windows提供了Windows Socket API(简称WSA),WinSock,目前有两个版本:WinSock1.1 and WinSock2.0。
WinSock1.1 and WinSock2.0 两个版本向后兼容:源码和二进制代码。
WinSock2.0是对WinSock1.1的扩展(增加了很多异步函数),这些扩展是针对Windows编程的。
比如WSASelect可以用事件通知进行异步IO,而WinSock1.1由UNIX BSD socket移植过来的。
程序员要做的只是包含新的头文件winsock2.h和简单得ws2_32.lib地链接。具体如下:
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
条款8. 如何选择编程库和编程模型
在Java语言中使用Netty,这个库已经被N多成熟开源项目证明,好用,可靠。
在C/C++语言中,有几个选项:libevent、libev、libuv、boost等,网上也有很多对比文章。
这些库的共同点是封装了 select/epoll/iocp 等事件机制做了封装,屏蔽了API和操作系统的差异,提供了简单易用的
EventLoop
。差别在于抽象层次,编程模型,以及还提供了哪些额外的编程支持。
其中,libevent、libuv、boost均支持linux平台的epoll和windows平台的iocp,而libev不支持windows平台。
libevent 是最早出现的c网络编程库,历史悠久,使用广泛。
libev 是一个非常小的库,仅提供了对事件机制的抽象。它是libevent的一个替代实现,代码质量要好很多,非常好用。libuv最初就是基于libev实现的。
基本上来说,libevent/libev 解决了select/epoll等事件模型之间API不同的问题。但网络编程的细节,还得你自己动手。比如 accept(3) 连接以后需要手动 setNonBlocking 。从socket读写时需要检测 EWOULDBLOCK 和 EINTER。
libuv 最初是作者为 node.js 写的,它抽象层次要更高一些,它默默帮你搞定了了网络和文件读写的很多细节。它内里还是消息循环那套东西,但在此之上模拟了异步IO的接口,把数据读写等细节屏蔽掉了。它的网络IO操作是在消息循环线程内部处理,数据操作完成后触发用户回调。但是文件操作,则是在专门的IO线程里处理,处理完成后,把完成事件反馈到事件循环内部,再触发用户回调。整体上来说,这是一个非常不错的库。它的问题是内部没有事件优先队列,在循环非常忙的时候,像定时器这样的事件不能保证优先处理。
asio 也是非常优秀的异步IO编程库,甚至还提供了协程的支持。asio 是一个现代的C++库,里面使用了大量的函数对象、模板、协程等技术,代码量比较大,理解起来有些困难。
在高性能服务器并发模型设计中,Reactor和Proactor是两个经常用到的设计模式,前者用于同步IO,后者用于异步IO,前者在IO操作就绪的情况下通知用户,用户再采取实际的IO操作,后者是在IO操作完成后通知用户。IOCP的设计就是Proactor模式的完美体现,而epoll则很容易实现Reactor模式。linux对异步IO的支持没有windows那么完善(看看IOCP和epoll模型的区别就知道),利用epoll机制实现proactor模式的的方式就是加一次封装,内部有个循环调用epoll_wait,当有IO事件就绪时帮用户做一些操作(比如把数据拷贝到用户提交的缓冲区),然后在操作完成的时候调用用户的handler。libuv,asio 在这方面的实现原理差不多。
还有一个比较另类的库 zeromq,在数据量小的场合用用可以,它封装了自己的消息格式,很难应对需要深度控制的开发。
说了来一大堆,建议优先选择 asio 或 libuv,尤其是要支持Windows平台的时候。如果只支持类unix平台,libev也是很好的选择,代码很容易完全掌控,但需要扎实的网络编程基本功,才能写出健壮高效的网络程序。
条款9 如何优化性能
系统的参数优化运维同学都会搞定,在应用上要做哪些优化呢?个人认为重要的是这几点,
-
尽量使用长连接。
-
对数据压缩后再传输,在局域网内影响可能不大,但对于在互联网上传输数据效果非常好。
-
在发送高速数据时,控制发送速率,尽量平滑一些,避免触发TCP拥塞控制和慢启动。
网友评论