浅谈服务端编程

it2023-11-14  67

我们假定读者掌握了:

Linux环境下C/C++的系统编程和基本的socket编程方法操作系统基本概念以及Linux的基本概念和原理Linux进程和线程的内存地址空间布局和资源关系

我们谈什么,不谈什么:

Linux下的网络程序设计所遵循的规范Linux网络程序的工作模型和原理一般性质上的网络协议设计方法和原则基本上只针对Linux,基本不涉及Windows只涉及TCP协议的通信,不谈UDP

可以先行阅读的参考资料:进程眼中的线性地址空间线程眼中的线性地址空间Linux线程的前世今生聊聊内存管理Linux系统调用goroutine背后的系统知识


从基本socket函数开始

留意一些socket函数与众不同的参数细节

1 2 3 4 5 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

TCP三次握手在socket接口的位置

强调TIME_WAIT状态 MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现都必须选择一个确定的MSL值。RFC1122建议是2分钟。 TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟。

1 int listen(int sockfd, int backlog);

Linux内核协议栈

TCP 的发送

TCP的接收

协议栈完整的收发流程

关于socket接口与内核协议栈的挂接

请参考:socket接口与内核协议栈的挂接


TCP相关参数的设置方法

 

套接字设置

1 2 3 4 5 6 7 #include <sys/types.h> #include <sys/socket.h> int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

SO_REUSEADDR 当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。这个选项允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。(多块网卡的应用场合)

SO_RECVBUF / SO_SNDBUF 发送和接收缓冲区大小,不详述。

TCP_NODELAY / TCP_CHORK 是否采用Nagle算法把较小的包组装为更大的帧。HTTP服务器经常使用TCP_NODELAY关闭该算法。相关的还有TCP_CORK。

TCP_DEFER_ACCEPT 推迟accept,实际上是当接收到第一个数据之后,才会创建连接。(对于像HTTP等非交互式的服务器,这个很有意义,可以用来防御空连接攻击。)

TCP_KEEPCNT / TCP_KEEPIDLE / TCP_KEEPINTVL 如果一方已经关闭或异常终止连接,而另一方却不知道,我们将这样的TCP连接称为半打开的。TCP通过保活定时器(KeepAlive)来检测半打开连接。设置SO_KEEPALIVE选项来开启KEEPALIVE,然后通过TCP_KEEPIDLE、TCP_KEEPINTVL和TCP_KEEPCNT设置keepalive的开始时间、间隔、次数等参数。

保活时间:keepalive_time = TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT

从TCP_KEEPIDLE 时间开始,向对端发送一个探测信息,然后每过TCP_KEEPINTVL 发送一次探测信息。如果在保活时间内,就算检测不到对端了,仍然保持连接。超过这个保活时间,如果检测不到对端,服务器就会断开连接,如果能够检测到对方,那么连接一直持续。

 

内核全局设置

内核的TCP/IP调优参数都位于/proc/sys/net/目录,可以直接写入数值或者采用sysctl命令或者系统调用。

1 2 3 4 #include <unistd.h> #include <linux/sysctl.h> int _sysctl(struct __sysctl_args *args);

详细请参考:提高 Linux 上 socket 性能


常见的协议格式设计

记住,TCP是一种流协议

语出《Effective TCP/IP Programming》。意思是,TCP的数据是以字节流的方式由发送者传递给接收者,没有固有的“报文”或者“报文边界”的概念。简单说,TCP不理解应用层通信的协议,不知道应用层协议格式和边界。所以,所谓的“粘包和断包”是个伪概念。TCP压根就没有包边界的概念,何谈粘与断。

OSI模型定义的7层结构网络中,TCP协议所在的传输层和应用层之间还有会话层和表示层,原本协议包分界和加密等等操作是在这两层完成的。TCP/IP协议在设计的时候,并没有会话层和表示层。那如果用户需要这两层提供的服务怎么办?比如包的分界?答案是,用户自行在应用层代码中实现吧。

示例:

发送者发送三次

接收者可能收到这样:

避免分片的效率损失

数据链路层Maximum Transmission Unit(MTU, 最大传输单元)。

以太网通常在1500字节上下。所以单次发送的协议包数据最好小于这个值,从而避免IP层分片带来的效率损失。

扩展阅读:Linux TCP/IP协议栈关于IP分片重组的实现

最新回复(0)