您的位置:首页 > 理论基础 > 计算机网络

Unix网络编程学习笔记之第7章 套接字选项

2015-06-17 13:00 567 查看

一、获取/设置套接字选项的方法

一个套接字描述符相关联的套接字选项很多。获取/设置套接字选项的方法:

1.  getsockopt和setsockopt函数

2. fcntl函数

3. ioctl函数

 

二、 getsockopt和setsockopt函数

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);
//两个函数成功返回0,失败返回-1
1. sockfd显然套接字描述符,其必须是已打开的套接字描述符。

2. level指示了后面选项名称(optname)的所属类型。

3. optname指示需要获取/设置的套接字选项名称

4. optval,get时,返回获取的选项值,set时,设置选项的值。

5. optlen,为该选项值的长度。显然get时,是指针,使得内核可以修改它。Set时,是值传递,告诉内核大小。

6. 套接字选项大致的分为两种类型:

启动或禁止某选项。这时,该选项只有0、非0两个值

获取、设置某选项。这时我们可以指定、获取这个选项的具体值(可以是int,可以是结构体)。

下面说一下几种不同level选项。

 

三、通用套接字选项

即level的参数为SOL_SOCKET

1. optname为SO_BROADCAST。

用于开启或禁止该套接字发送广播消息的能力。

注意只有数据报(UDP)才支持广播。

 

2. optname为SO_DEBUG

本选项仅支持TCP,如果开启,则发送和接收消息时,保留跟踪信息,用于调试。

 

3. optname为SO_ERROR

只可获取,不可设置。

如果该套接字发生错误,可以通过这个选项获取错误值(so_error)。

Unix是这样处理的,当某套接字发送错误时,首先设置so_error为错误类型,然后调用read/write会返回-1,然后errno被设为so_error的值,然后so_error被重置为0.

所以这个选项并没有什么特别大的作用,因为我们可以使用errno来获取错误值。

 

4. optname为SO_KEEPLIVE

这个套接字很有意思,如果开启这个选项,则如果2个小时内,该套接字的任一一个方向上都没有数据交换,则TCP就会自动向对端发送一个探测分组,这是一个对端必须响应的TCP分组。

(1) 如果对端响应,则一切正常,其又会平静的等待2小时

(2) 如果对端响应RST分组,则它告知本端对端已崩溃且已经重启。

(3) 如果对象无任何响应,则另外发送8个探测分组,相隔75s,如果还是没有响应,则关闭此连接,并且返回错误,so_error被置为具体的错误类型。

 

如果没有不启动这个选项,则如果客户端崩溃,则服务器端有可能永远不知道。我们可以设置2小时为更短的时间,但是一般不设置,因为可能影响本机开启的所有套接字。

 

本选项的功能就是检测对端主机是否崩溃或变得不可达。

 

5. optname为SO_LINGER

本选项仅支持面连连接的协议(TCP,SCTP)。不支持UDP。

本选项可以改变close函数的行为。

1. 默认的close函数的行为:立即返回,如果套接字发送缓冲区内有数据,则试着把这些数据发送给对端。

2. 该套接字对于的optval结构体为:

struct linger{
int l_onoff;
int l_linger;
};
(1) l_onoff为0,即关闭此选项,则close为默认行为。

(2) l_onoff不为0,l_linger为0,则close终止该TCP连接,即丢弃发送缓冲区数据,并发送一个RST给对方。没有了正常的四次终止,也没有了TIME_WAIT状态。

(3) l_onoff不为0,l_linger不为0,如果套接字发送缓冲区有数据,则进程会阻塞,等待一段时间(l_linger秒),直到数据发送完且收到确认、或等待时间到为止。这时进程一定要检查close的返回值,因为如果是由于时间到而不是数据发送确认完导致唤醒阻塞的话,close返回的是EWOULDBLOCK错误。且丢弃发送缓冲区数据。

close函数成功返回0,错误返回-1,错误类型记录在errno。

下面使用图来描述:

1>  close的默认行为:.



假设客户在发送了数据之后,立即调用close,则close函数立即返回,并不知道服务器是否接收到并正确读取了这些数据。如果服务器在读取这些数据之前崩溃,则客户端永远不知道。

2> 设置了linger(linger都非0)之后的行为:



设置了linger选项后,客户端等待服务器的确认。注意还是有上述的问题,服务器在读取剩余的数据之前崩溃,客户端可能永远不知道。而且使用这个选项还可能造成另一个重要的问题:如果linger的等待时间设置的过小,则:

 


所以总结一下:设置SO_LINGER选项之后,close成功返回只能告诉我们服务器端确认了这些数据,并不能说明服务器端正确读取了这些数据。

3> 前面我们已经知道,使用shutdown函数来避免服务器端进程崩溃,这里为了让客户端知道服务器端正确读取了这些数据,我们使用shutdown,然后当read返回0,可以知道服务器端正确读取了这些数据。



4> 为了获知对端已经读取我们的数据的另一个方法就是使用应用级确认,即我们发送完我们的数据之后,不调用close,而是调用read阻塞。当服务器端正确读取数据之后,发送一个一个字节数据(可以自定义)的分组来说明它已经读取完我们的数据。然后客户端收到这个分组之后,就知道对端已读取,然后调用close关闭。



总结:上面我们陈述了四种关闭连接的方法,综合比较理解。

 

6. optname为SO_RCVBUF和SO_SNDBUF

每个套接字都有一个发送缓冲区和接收缓冲区。

(1) 接收缓冲区中的可用大小限定了TCP通告对端的窗口大小。TCP永远不可能接收缓冲区溢出,因为不允许发送超过通告窗口的大小的数据。如果对端硬是要发送超出窗口大小的数据,则TCP直接丢弃。

(2) 这两个套接字允许我们修改接收和发送缓冲区的大小。

但记住当设置接收缓冲区的大小时,设置的时机很重要,因为TCP是在握手时(SYN分组)告知缓冲区大小的。对于客户端,一定要在connect之前设置,对于服务器端一定在listen之前设置。给已连接套接字设置该选项是无效的。

(3) 一般TCP套接字缓冲区至少为MSS的4倍。典型的MSS大小为536或1460。为了潜在的缓冲区空间浪费,缓冲区大小还必须是MSS值的偶数倍。

注意这里的MSS值为连接的MSS值。即两个方向协商的结果,即两个方向中的较小者。

 

7. optname为SO_RCVLOWAT和SO_SNDLOWAT

每个套接字都有一个接收低水位标记和发送低水位标记。它们由select函数使用。

这里我们可以修改这两个选项。

接收低水位标记:让select返回“可读”时,套接字接收缓冲区内所需的数据量。对于TCP、UDP、SCTP默认值为1字节。

发送低水位标记:让select返回“可写”时,套接字发送缓冲区内所需的可用空间。对于TCP默认值为2048.

 

8. optname为SO_RCVTIMEO和SO_SNDTIMEO

这两个选项运行我们为套接字设置接收和发送的一个超时值。该选项对应的optval为timeval结构的指针。

默认情况下为禁止这两个选项。

 

9. optname为SO_REUSEADDR和SO_REUSEPORT

如果套接字启动SO_REUSERADDR选项则:

(1) 允许启动一个监听服务器并绑定其众所周知的端口,即使以前建立的该端口的连接仍然存在。即复用端口的意思。

(2) 允许在同一端口上启动同一服务器的多个实例,只要每个实例绑定不同的IP地址即可。即复用端口的意思。

这里注意:对于TCP而言,在一台机器上,绝不允许两个套接字绑定的是相同的IP和端口号。

(3) 允许单个进程绑定同一端口到不同的套接字上。即复用端口的意思。

以上三条其实都是说启动该选项,可以多个套接字捆绑相同的端口,只要它们的IP地址不同即可。如果不启动该选项,则bind函数会发生错误。

(4) 对于UDP而言,运行两个套接字捆绑到同样的IP地址和端口号。一般用于多播。

而选项SO_REUSEPORT是由于多播才引入的。

1>允许相同IP和端口号的捆绑。

2>如果捆绑的IP地址为多播地址,等效于SO_REUSEADDR。

但不是所有系统都支持。

 

建议:

对于TCP服务器,在bind之前都要启动SO_REUSEADDR选项。

 

10.  optname为SO_TYPE

返回该套接字的类型。

对于TCP返回SOCK_STREAM.对于UDP返回SOCK_DGRAM。

 

四、 IPv4套接字选项。

即level的参数为IPPROTO_IP

1. optname为IP_HDRINCL

如果本选项是给一个原始IP套接字设置的,那么我们要给该套接字发送的数据报构造自己的IP首部。

 

2. optname为IP_TOS

本选项允许我们为TCP,UDP,SCTP套接字设置IP首部中的服务类型字段。

 

3. optname为IP_TTL

本选项允许我们设置或获取系统 在从某个给定套接字发送的单播分组上的默认的TTL值。

 

五、 TCP套接字选项

即level的参数为IPPROTO_TCP

1. optname为TCP_MAXMSG

本运行允许我们获取或设置TCP连接的最大分组大小(MSS)。

如果我们对未连接的套接字使用,则返回的是未从对端收到MSS选项的情况下所用的默认值。

一般我们都是获取,并不是所有系统都支持设置。

 

2. optname为TCP_NODELAY

开启本选项:禁止TCP的Nagle算法。默认该算法是开启的,即禁止该选项。

Nagle算法目的在于减少分组数目。防止连接在任何时刻有多个分组待确认。

Nagle算法:当某个连接上,有待确认分组时,就不会继续发送数据,直到确认数据被确认为止。

下图展示了每隔250时间发送六个字符,开启算法和禁止算法的情况



可以看到,禁止算法虽然所用总时间减少了一些,减少了某些分组的延时,但是增加了网络负担,其分组的数量(12)明显比开启算法的分组数量(8)要多。

这里注意:开启算法会减少分组数量,开启算法时,可能一个分组会发送两个字符,而禁止算法,一个分组只会发送一个字符。

通常不建议禁止算法,不建议设置该选项。

 

六、 fcntl函数

用于对各种描述符控制操作。

显然fcntl、ioctl、路由套接字都可以对套接字描述符进行控制操作:



从上表可以看出,设置套接字非阻塞,设置套接字信号驱动,设置/获取套接字属主,这四个操作POSIX都推荐使用fcntl函数来完成。

#include<fcntl.h>
int fcntl(int fd, int cmd, .../*int arg*/);//返回:成功取决于cmd,失败返回-1

1. 设置套接字为非阻塞式I/O

cmd为F_SETFL, arg为O_NONBLOCK

 

2. 设置套接字为信号驱动式I/O

cmd为F_SETFL,,arg为O_ASYNC

我们把一个套接字设置成一旦状态变化,内核就产生一个SIGIO信号。

 

3. 设置套接字属主

cmd为F_SETOWN,arg为进程ID或进程组ID

F_SETOWN命令用于指定接收信号SIGIO和SIGURG递交的进程ID或进程组ID。相当于设置该套接字的父进程,父进程组。

 

4. 上面我们使用F_SETFL设置套接字标志,我们也可以使用F_GETFL来获得套接字标志。

 

5. 举例说明设置套接字为非阻塞式I/O

//正确做法,先取得当前标志,然后与新标志逻辑与后再设置标志。
int flag;
if((flag=fcntl(fd,F_GETFL,0))<0)
err_sys("F_GETFL error");
flag |= O_NONBLOCK;//开启非阻塞 flag &=~O_NONBLOCK; 关闭非阻塞
if(fcntl(fd, F_SETFL,flag)<0)
err_sys("F_SETFL error");

//一般不使用下面这种方法,因为这样做也清除了其他标志。
if(fcntl(fd, F_SETFL,NONBLOCK)<0)
err_sys("F_SETFL error");

6. 信号SIGIO和SIGURG与其他信号的不同之处:

这两个信号在已使用F_SETOWN命令设置某套接字的属主后才会产生。

也就是说,我们再设置了某套接字为信号驱动后,应当立即设置它的属主。

 

7. 使用socket函数新创建的套接字没有属主

然后如果一个新套接字是从一个监听套接字创建而来,且这个监听套接字已有属主,则已连接套接字将继承监听套接字的属主。许多套接字选项都是这样继承而来。

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息