tcp socket 选项


一、SO_BROADCAST

本选项开启或禁止进程发送广播消息的能力。只有数据报套接字支持广播,并且还必须是在支持广播消息的网络上(例如以太网、令牌环网等)。如果目的地址是一个广播地址且本套接字选项没有设置,那么返回EACCES错误

二、SO_ERROR

这是个可以获取但不能设置的套接字选项。当一个套接字上发生错误时,内核将该套接字的名为so_error的变量设为标准的Unix Exxx值中的一个(pending error,待处理错误),内核可以通过”SIGIO信号“或者”select调用“立即通知进程这个错误,然后进程可以通过SO_ERROR套接字选项获取so_error的值

三、SO_KEEPALIVE

TCP长连接中可能出现的问题:

  • 很多防火墙路由器等,对于空闲的TCP连接会自动关闭。通过心跳机制可以用于保活长连接
  • 对于非正常断开,服务器并不能检测到,不能及时回收资源。通过心跳机制可以检测对端异常,通过发送空的 echo 心跳包,同时设置一个定时器,如果超时前收不到对端发送过来的反馈包,那就认为对端掉线了,此时就可以回收资源了

目前心跳机制有两种实现:

  • SO_KEEPALIVE
    优点:

    • 系统内核完全替上层应用自动给做好了,内核层面计时器相比上层应用,更为高效
    • 上层应用只需要处理数据收发、连接异常通知即可

    缺点:

    • keepAlive只能检测连接存活而不能检测连接可用。比如某台服务器因为某些原因导致负载超高,CPU满了,无法响应任何业务请求,但是使用 TCP 探针则仍旧能够确定连接状态,这就是典型的连接活着但业务提供方已死的状态,对客户端而言,这时的最好选择就是断线后重新连接其他服务器,而不是一直认为当前服务器是可用状态
    • 如果tcp连接的另一端突然掉线,这个时候我们并不知道网络已经关闭。而此时,如果有发送数据失败,tcp会自动进行重传,重传包的优先级高于keepalive的包,那就意味着,我们的keepalive总是不能发送出去。 而此时,我们也并不知道该连接已经出错而中断,只有在较长时间的重传失败之后,我们才会知道
  • 应用层协HeartBeat
    优点:

    • 有着更大的灵活性,可以控制检测时机,间隔和处理流程,甚至可以在心跳包上附带额外信息,最重要的是可以做到没有上面所说的缺点,不光可以检测连接存在,还可以检测连接可用
    • 通用, 应用层的心跳不依赖协议. 如果有一天不用TCP要改为UDP了, 协议层不提供心跳机制了, 但是你应用层的心跳依旧是通用的, 可能只需要做少许改动就可以继续使用

    缺点:

    • 需要自己实现,增加开发工作量,应用层心跳的流量消耗还是更大的,毕竟这本质上还是个普通的数据包

四、SO_RCVBUF/SO_SNDBUF

每个套接字都有一个发送缓冲区和一个接收缓冲区,这两个套接字选项允许我们改变这两个缓冲区的默认大小。对于TCP来说,套接字接收缓冲区不可能溢出,因为不允许对端发出超过本端所通告窗口大小的数据,这就是TCP的流量控制。对于UDP来说,由于没有流量控制,较快的发送端可以很容易地淹没较慢的接收端,导致接收端的UDP丢弃数据报

当设置TCP套接字接收缓冲区的大小时,函数调用顺序很重要,这是因为TCP的窗口规模选项是在建立连接时用SYN分节与对端互换得到的
对于客户,这意味着SO_RCVBUF选项必须在调用connect之前设置
对于服务器,这意味着SO_RCVBUF选项必须在调用listen之前给监听套接字设置

五、SO_RCVLOWAT/SO_SNDLOWAT

每个套接字还有一个接收低水位标记和一个发送低水位标记

  • 接收低水位标记是让read返回“可读”时套接字接收缓冲区中所需的数据量。对于TCP和UDP,其默认值为1
  • 发送低水位标记是让write返回“可写”时套接字发送缓冲区中所需的可用空间。对于TCP,其默认值为2048,对于UDP,只要一个UDP套接字的发送缓冲区大小大于该套接字的低水位标记,该UDP套接字就总是可写

六、SO_RCVTIMEO/SO_SNDTIMEO

这两个选项允许我们给套接字的接收和发送设置一个超时值。可通过设置timeval结构的值为0s和0us来禁止超时

七、SO_REUSEADDR

在socket和bind之间设置SO_REUSEADDR选项,使得bind捆绑一个现有连接时仍会成功

八、TCP_MAXSEG

本选项允许获取或设置TCP连接的最大分节大小(MSS),返回值是TCP可以发送给对端的最大数据量。如果TCP支持路径MTU发现功能,该值还可能在连接存活期内改变,本选项原为只读选项

九、TCP_NODELAY

开启本选项将禁止TCP发送时的Nagle算法,默认该算法是启动的,Nagle算法常与ACK延滞算法(delayed ACK algorithm)联合使用

  • TCP总是尽可能地发送最大大小的分组,Nagle算法的目的在于让连接在任何时刻最多只有一个小分组(小于MSS的任何分组)待确认
  • ACK延滞算法使得TCP在接收到数据后不立即发送ACK,而是等待一小段时间(40ms)才发送ACK,TCP期待ACK延滞时间内有数据发送回对端,被延滞的ACK就可以由这些数据捎带,从而省掉一个TCP包

假设客户向它的服务器发送一个400字节的请求,该请求由4字节的请求类型后跟396字节的请求数据构成。如果客户先write一个4字节,再write一个396字节后,然后read等待应答,那么第二个write操作将一直等到服务器确认了第一个write后才由客户端的TCP发送出去。由于ACK延滞算法导致服务器的应答会延迟固定时间,导致通信效率低下,对于此问题解决方式如下:

  • 使用writev而不是调用write两次
  • 将4字节的请求类型和396字节的数据复制到单个缓冲区中,然后调用write
  • 设置TCP_NODELAY套接字选项,继续调用两次write(有损网络,通常不采用)

十、SO_LINGER

SO_LINGER 选项用于控制close系统调用在关闭TCP连接时的行为

struct linger
{
    int l_onoff;    // 是否开启该选项, 非0开启0关闭
    int l_linger;   // 滞留时间
};

根据linger结构体中两个成员变量的不同值,close系统调用可能产生如下3种行为之一:

  • l_onoff等于0(关闭):此时SO_LINGER选项不起作用,close用默认行为来关闭socket
  • l_onoff不为0(开启),l_linger等于0:此时close系统调用立即返回,TCP模块将丢弃被关闭的socket对应的TCP发送缓冲区中残留的数据,同时给对方发送一个复位报文段(RST)。因此,这种情况给服务器提供了异常终止一个连接的方法
  • l_onoff不为0(开启),l_linger大于0:此时close的行为取决于两个条件:一是被关闭的socket对应的TCP发送缓冲区是否还有残留的数据;二是该socket是阻塞的,还是非阻塞的。对于阻塞的socket,close将等待一段长为l_linger的时间,直到TCP模块发送完所有残留数据并得到对方的确认。如果这段时间内TCP模块没有发送完残留数据并得到对方的确认,那么close系统调用将返回-1并设置errno为EWOULDBLOCK。如果socket是非阻塞的,close将立即返回,此时我们需要根据其返回值和errno来判断残留数据是否已经发送完毕
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TCP socket 是一种在计算机网络中实现可靠传输的协议,而 C 语言提供了一组 API 来实现 TCP socket 编程。下面是一个简单的 C 语言 TCP socket 服务器的代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 8080 int main(int argc, char const *argv[]) { int server_fd, new_socket, valread; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); char buffer[1024] = {0}; char *hello = "Hello from server"; // 创建 socket 文件描述符 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置 socket 选项 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("setsockopt"); exit(EXIT_FAILURE); } // 指定 socket 地址 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // 绑定 socket 到指定地址 if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } // 监听 socket if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } // 接受客户端连接 if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("accept"); exit(EXIT_FAILURE); } // 从客户端读取数据 valread = read(new_socket, buffer, 1024); printf("%s\n", buffer); // 向客户端发送数据 send(new_socket, hello, strlen(hello), 0); printf("Hello message sent\n"); return 0; } ``` 这个服务器程序首先创建一个 socket 文件描述符,然后设置一些 socket 选项,指定 socket 地址并将其绑定到 socket 上。然后它开始监听 socket,并等待客户端连接。一旦有客户端连接,它就会从客户端读取数据,并将其发送回客户端。这只是一个简单的示例,你可以根据自己的需求来修改和扩展它。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值