套接字选项
获取和设置影响套接字的选项的方法
- getsockopt和setsockopt函数
- fcntl函数
- ioctl函数
getsockopt和setsockopt函数
这两个函数仅用于套接字。
#include<sys/socket.h>
int getsockopt(int sockfd,int level,int optname, void *optval,socklen_t *optlen);
int setsockopet(int sockfd,int level,int optname,const void *optval,socklen_t optlen);
均返回:若成功则为0,若出错则为-1
optval是一个指向某个变量的指针,setsockopt从optval中取得选项待设置的新值,getsockopt则把已获取的选项当前值存放到optval中。*optval的大小由最后一个参数指定,它对于setsockopt是一个值参数,对于getsockopt是一个值-结果参数。
套接字选项粗分为两大基本类型:一是启用或禁止某个特性的二元选项(称为标志选项),二是取得并返回我们可以设置或检查的特定值的选项(称为值选项)。标有“标志”的列指出一个选项是否为标志选项。当给这些标志选项调用getsockopt函数是,optval是一个整数。optval中返回的值为0表示相应选项被禁止,不为0表示相应选项被启用。类似地,setsockopt函数需要一个不为0的optval值来启用选项,一个为0的optval值来禁用选项。如果“标志”列不含有**“·”**,那么相应选项用于在用户进程与系统之间传递所指定数据类型的值。
套接字状态
下面的套接字选项是由TCP已连接套接字从监听套接字继承来的:SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWAT、SO_SNDBUF、SO_SNDLOWAT、TCP_MAXSEG和TCP_NODELAY。如果想在三路握手完成时确保这些套接字选项中的某一个是给已连接套接字设置的,那么我们必须先给监听套接字设置该选项。
SO_LINGER套接字选项
本选项指定close函数面对连接的协议如何操作。默认操作是close立即返回,但是如果有数据残留在套接字发送缓冲区中,系统将试着把这些数据发送给对端。
SO_LINGER套接字选项使得我们可以改变这个默认设置。本选项要求在用户进程和内核间传递如下结构。
#include<sys/socket.h>
struct linger{
int l_onoff; /* 0=off,nonzero=on*/
int l_linger; /* linger time, POSIX specifies units as seconds */
};
对于setsockopt的调用有以下三种情形:
(1)如果l_onoff为0,那么关闭本选项。l_linger的值被忽略,先前讨论的TCP默认设置生效,即close立即返回。
(2)如果l_onoff为非0值且l_linger为0,那么当close某个连接时TCP将中止该连接。这就是说TCP将丢弃保留在套接字发送缓冲区中的任何数据,并发送一个RST给对端,而没有通常的四分组连接终止序列。这么一来避免了TCP的TIME WAIT状态,然而存在以下可能性:在2MSL秒内创建该连接的另一个化身,导致来自刚被终止的连接上的旧的重复分节被不正确地递送到新的化身上。
(3)如果l_onoff为非0值且l_linger也为非0值,那么当套接字关闭时内核将拖延一段时间。这就是说如果在套接字发送缓冲区中仍残留有数据,那么进程将被置于休眠状态,直到(所有数据都已发送完且均被对方确认或(b)延滞时间到。如果套接字被设置为非阻塞型,那么它将不等待close完成,即使延滞时间为非0也是如此。当使用SO_LINGER选项的这个特性时,应用进程检查close的返回值是非常重要的,因为如果在数据发送完并被确认前延海时间到的话,close将返EWOULDBLOCK错误且套接字发送缓冲区中的任何残留数据都被丢弃。
现在我们需要看看,对于已讨论的各种情况,套接字上的close确切来说是什么时候返回的。我们假设客户将数据写到套接字上,然后调用close。图7-7给出了默认情况。
我们假设在客户数据到达时,服务器暂时处于忙状态。那么这些数据由TCP加入服务器的套接字接收缓冲区中。类似地,下一个分节即客户的FIN也加入该套接字接收缓冲区中(不论实现以何种方法记录该连接上已收到一个FIN这一事件)。默认情况下客户的close立即返回。 如图所示,客户的close可能在服务器读套接字接收缓区中的剩余数据之前就返回。对于服务器主机来说,在服务器应用进程读这些剩余数据之前就崩溃是完全可能的,而且客户应用进程永远不会知道。
客户可以设置SO_L工NGER套接字选项,指定一个正的延滞时间。这种情况下客户的close 要到它的数据和FIN已被服务器主机的TCP确认后才返回,如图7.8所示。
然而我们仍然有与图7-7一样的问题:在服务器应用进程读剩余数据之前,服务器主机可能崩溃,并且客户应用进程永远不会知道。更糟糕的是,图7-9展示了当给SO_LINGER选项设置偏低的延滞时间值时可能发生的现象。
这里有一个基本原则:设置SO_LINGER套接字选项后,close的成功返回只是告诉我们先前发送的数据(和FIN)已由对端TCP确认,而不能告诉我们对端应用进程是否已读取数据。如果不设置该套接字选项,那么我们连对端TCP是否确认了数据都不知道。
让客户知道服务器己读取其数据的一个方法是改为调用shutdown(并设置它的第二个参数 为SHUT_WR)而不是调用close,并等待对端close连接的当地端(服务器端),如图7-10所示。
*比较本图与图7.7及图7.8我们看到,当关闭连接的本地端(客户端)时,根据所调用的函 数(close或shutdown)以及是否设置了SO_LINGER套接字选项,可在以下3个不同的时机 返回。
(1)close立即返回,根本不等待(默认状况,图7-7)。
(2)close一直拖延到接收了对于客户端FIN的ACK才返回(图7.8)。
(3)后跟一个:read调用的shutdown-直等到接收了对端的FIN才返回(图7-10)。
获知对端应用进程己读取我们的数据的另外一个方法是使用应用级确认(application・level acknowledge,简称应用ACK (application ACK))o在下面的例子中,客户在向服务器发送数据 后调用read来读取1字节的数据:
char ack;
Write(sockfdz data, nbytes); /* data from client to server */
n = Read(sockfd, &ack, 1) ; /* wait for application-level ACK */
服务器读取来自客户的数据后发回1字节的应用级ACK:
nbytes = Read(sockfd, buff, sizeof(buff)); /* data from client */
/* server verifies it received correct
amount of data from the client */
Write (sockfd, "", 1) ; /* server' s ACK back to client */
当客户的read返回时,我们可以保证服务器进程己读完了我们所发送的所有数据。(假设 服务器知道客户要发送多少数据,或者由应用程序定义了某个记录结束标志,不过这儿没有给 出。)本例子的应用级ACK是值为0的1字节,不过该字节的内容可以用来从服务器向客户指示 其他的条件。图7-11展示了可能的分组交换过程。
图7-12汇总了对shutdown的两种可能调用和对close的三种可能调用,以及它们对TCP套接字的影响。
fcntl函数
#include<fcntl.h>
int fcntl(int fd, int cmd, ... /* int arg */);
**返回:若成功则取决于cmd,若出错则为-1**
与代表“file control"(文件控制)的名字相符,fcntl函数可执行各种描述符控制操作。 在讲解该函数及其如何影响套接字之前,我们需要看得远一点。图7.20汇总了由fcntl、ioctl和路由套接字执行的不同操作。
其中前六个操作可由任何进程应用于套接字,接着两个操作(接口操作)比较少见,不过也是通用的,后两个操作(ARP和路由表操作)由诸如ifeonfig和route之类管理程序执行。
执行前四个操作的方法不止一种,不过我们在最后一列指出,POSIX规定fcntl方法是首选的。我们还指出,POSIX提供sockatmark函数(24.3节)作为测试是否处于带外标志的首选 方法。最后一列空白的其余操作没有被POSIX标准化。
fcntl函数提供了与网络编程相关的如下特性。
- 非阻塞式I/O。通过使用F_SETFL命令设置O_NONBLOCK文件状态标志,我们可以把一个套接字设置为非阻塞型。我们将在第16章中讲述非阻塞式I/O。
- 信号驱动式I/O。通过使用F—SETFL命令设置O—ASYNC文件状态标志,我们可以把一个套接字设置成一旦其状态发生变化,内核就产生一个SHG工0信号。我们将在第25章中讨论这一点。
- F_SETOWN命令允许我们指定用于接收SIGIO和SIGURG信号的套接字属主(进程ID或进程组ID)。其中SIGIO信号是套接字被设置为信号驱动式I/O型后产生的(第25章),SIGURG信号是在新的带外数据到达套接字时产生的(第24章)。F_GETOWN命令返回套接字的当前属主。
术语“套接字属主”由POSIX定义。历史上源自Berkeley的实现称之为“套接字的进程组ID”,因为存放该ID的变量是socket结构的so_jpgid成员。
每种描述符(包括套接字描述符)都有一组由F_GETFL命令获取或由F_SETFL命令设置的文件标志。其中影响套接字描述符的两个标志是:
O_NONBLOCK 非阻塞式I/O;
O_ASYNC 信号驱动式I/O。
使用fcntl开启非阻塞式I/O的典型代码是:
int flags;
/* Set a socket as nonblocking */
if ( (flags = fcntl(fd, F_GETFL, 0)) < 0)
err_sys("F_GETFL error");
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
err_sys ("F_SETFL error");
下面是你可能会遇到的、简单地设置所期望标志的代码:
/* Wrong way to set a socket as nonblocking */
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
err_sys("F_SETFL error");
这段代码在设置非阻塞标志的同时也清除了所有其他文件状态标志。设置某个文件状态标志的唯一正确的方法是:先取得当前标志,与新标志逻辑或后再设置标志。
以下代码关闭非阻塞标志,其中假设flags是由上面所示的fcntl函数调用来设置的:
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
err_sys("F_SETFL error");
信号SIGIO和SIGURG与其他信号的不同之处在于,这两个信号仅在己使用F_SETOWN命令给 相关套接字指派了属主后才会产生。F_SETOWN命令的整数类型arg参数既可以是一个正整数, 指出接收信号的进程ID,也可以是一个负整数,其绝对值指出接收信号的进程组ID。F_GETOWN 命令把套接字属主作为fcntl函数的返回值返回,它既可以是进程ID (一个正的返回值),也可以是进程组ID (一个除-1以外的负值)。指定接收信号的套接字属主为一个进程或一个进程组的差别在于:前者仅导致单个进程接收信号,而后者则导致整个进程组中的所有进程(也许不止一个进程)接收信号。 使用socket函数新创建的套接字并没有属主。然而如果一个新的套接字是从一个监听套接 字创建来的,那么套接字属主将由己连接套接字从监听套接字继承而来。
来源:UNIX网络编程卷1第七章