修改套接字选项
有几种方法来修改和获取套接字的选项:
- getsockopt & setsockopt
- fcntl
- ioctl
getsockopt & setsockopt
这两个函数仅仅用于套接字。函数原型如下:
1 | #include <sys/socket.h> |
sockfd必须指向一个已经打开的套接字,level指向系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码(如TCP、IPv4、IPv6或SCTP)。
optval指向某个变量,变量的大小由最后一个参数optlen指定,setsockopt从optval中取得待设置的新值,而getsockopt把获取到的值存入对应的地址中。
套接字选项粗分为两大基本类型:
- 启用或禁止某个特性的标志选项(flag = 0禁止特性,flag = 1开启特性)
- 取得并返回我们可以设置或检查的特定值选项,即值选项。
套接字选项可以参见UNP的P151
通用套接字选项(部分)
这里仅仅列出部分的通用套接字选项,其实也就是挑了一些自己看得懂的选项记录下。
SO_BROADCAST
开启或禁止进程发送广播消息的能力。只有数据报支持广播,并且还是在支持广播消息的网络上(以太网、令牌网络等)。
SO_DEBUG
仅由TCP支持,当给一个TCP套接字开启本选项时,内核将为TCP在该套接字发送和接收的所有分组保留详细的跟踪信息,这些信息保存在内核的某个环形缓冲区中,并可使用trpt程序进行检查。
SO_ERROR
当一个套接字上发生错误时,内核将套接字的名为so_error的变量设置为标准的Unix Exxx值中的一个,它被成为该套接字的待处理错误(pending error),内核以以下方式通知进程这个错误:
- 如果进程阻塞在套接字的select调用上,那么无论是检查可读条件还是可读可写条件,select均返回并设置其中一个或所有的条件。
- 如果进程使用信号驱动I/O模型,那么进程或进程组就会接收到内核产生的SIGIO信息。
进程在被内核通知之后,可以通过访问SO_ERROR套接字选项获得so_error的值,由getsockopt返回的整数即待处理错误,随后so_error由内核复位成0。
这个套接字选项可以获取,但是不能设置。
SO_KEEPALIVE
给一个套接字设置保持存活(keep-alive)选项后,如果2小时内在该套接字的任意一个方向上没有数据交换,那么TCP就会自动给对端发送一个保持存活的探测分节(keep-alive probe),这是一个对端必须响应的TCP分节,它
会导致以下三种情况之一:
- 对端以期望的ACK响应,一切正常
- 对端以RST响应,对端已经崩溃并重启,套接字的待处理错误被置为ECONNRESET,套接字本身被关闭。
- 对端对保持存活的探测分节没有响应,TCP间隔一段时间再次发送探测分节,多次之后若还是没有响应,则放弃套接字的待处理错误被置为ETIMEOUT,如果收到一个ICMP错误作为某个探测分节的响应,就返回相应的错误,套接字本身被关闭。
SO_LINGER
指定close函数对面向连接的协议(TCP和SCTP)如何操作。默认的行为是立即返回,如果这时候发送缓冲区有数据残留,系统会尝试把数据发送给对方。选项要求用户进程和内核间传递如下结构:
1 | struct linger { |
- 如果l_onoff = 0,l_linger被忽略,选项关闭
- l_onoff非0,
- l_linger = 0,那么TCP丢弃发送缓冲区内的任何数据,并发送一个RST给对端,不会再有四次挥手。
- l_linger不为0,进程被阻塞,知道所有发送缓冲区的数据都发送完毕并被对方确认,或者是超过了l_linger指定的时间,如果超时,close返回EWOULDBLOCK错误。
假设客户在发送玩数据后调用了close函数,close可能在服务器读套接字接收缓冲区中的剩余数据之前就返回,在应用程序读数据之前服务器可能就会崩溃,而客户进程永远不会知道。
如果设置了SO_LINGER套接字,那么在调用close函数后,应用进程阻塞一段时间,等待数据全部发送并被对方确认(如图所示)。但是这里还有一个问题,延滞的时间可能不够,close仍然会返回EWOULDBLOCK错误,而且close的成功返回只是告诉我们先前发送的数据已经由对方确认,并不能告诉我们应用进程是否读取了数据。
让客户进程知道服务器已经读取数据的一个方法是改用shutdown(设置SHUT_WR),改用之后的流程如下图:
另外一个方法是应用程序自己做确认。客户在向服务器发送完数据后,调用read来读取一个字节
1 | char ack; |
服务器读取来自客户端的数据后发回一个字节的ACK。
1 | nbytes = Read(sockfd, buff, sizeof(buff)); |
SO_RCVBUF & SO_SNDBUF
每一个套接字有一个发送和接收缓冲区,可以利用SO_RCVBUF和SO_SNDBUF选项来修改默认的缓冲区大小。需要注意的是,由于TCP的窗口规模是在建立连接时用SYN分节与对端呼唤a的都的。对于客户端,意味着
SO_RCVBUF必须在connect调用之前设置;对于服务端,该选项必须在调用Listen之前设置。
SO_RCVLOWAT & SO_SNDLOWAT
每一个套接字还有一个接收和发送低水位标志,由select使用,用SO_RCVLOWAT和SO_SNDLOWAT可以修改低水位标志的值。
接收低水位标志是让select返回可读时套接字接收缓冲区所需要的最少的数据量。
发送低水位标志是让select返回可写是套接字发送缓冲区可存入的最少数据量。
两个低水位的默认值都为1
SO_RCVTIMEO & SO_SNDTIMEO
这两个选项允许我们给套接字的接收和发送设置一个超时值。