Socket套接口选项.doc_第1页
Socket套接口选项.doc_第2页
Socket套接口选项.doc_第3页
Socket套接口选项.doc_第4页
Socket套接口选项.doc_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Socket套接口选项在前面的几章中,我们讨论了使用套接口的基础内容。现在我们要来探讨一些可用的其他的特征。在我们掌握了这一章的概念之后,我们就为后面的套接口的高级主题做好了准备。在这一章,我们将会专注于下列主题:如何使用getsockopt(2)函数获得套接口选项值如何使用setsockopt(2)函数设置套接口选项值如何使用这些常用的套接口选项得到套接口选项有时,一个程序需要确定为当前为一个套接口进行哪些选项设置。这对于一个子程序库函数尤其如此,因为这个库函数并不知道为这个套接口进行哪些设置,而这个套接口需要作为一个参数进行传递。程序也许需要知道类似于流默认使用的缓冲区的大小。允许我们得到套接口选项值的为getsockopt函数。这个函数的概要如下:#include #include int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);函数参数描述如下:1 要进行选项检验的套接口s2 选项检验所在的协议层level3 要检验的选项optname4 指向接收选项值的缓冲区的指针optval5 指针optlen同时指向输入缓冲区的长度和返回的选项长度值当函数成功时返回0。当发生错误时会返回-1,而错误原因会存放在外部变量errno中。协议层参数指明了我们希望访问一个选项所在的协议栈。通常我们需要使用下面中的一个:SOL_SOCKET来访问套接口层选项SOL_TCP来访问TCP层选项我们在这一章的讨论将会专注于SOL_SOCKET层选项的使用。参数optname为一个整数值。在这里所使用的值首先是由所选用的level参数来确定的。在一个指定的协议层,optname参数将会确定我们希望访问哪一个选项。下表列出了一些层与选项的组合值:协议层 选项名字SOL_SOCKET SO_REUSEADDRSOL_SOCKET SO_KKEPALIVESOL_SOCKET SO_LINGERSOL_SOCKET SO_BROADCASTSOL_SOCKET SO_OOBINLINESOL_SOCKET SO_SNDBUFSOL_SOCKET SO_RCVBUFSOL_SOCKET SO_TYPESOL_SOCKET SO_ERRORSOL_TCP SO_NODELAY上表所列的大多数选项为套接口选项,其中的层是由SOL_SOCKET指定的。为了比较的目的包含了一个TCP层套接口选项,其中的层是由SOL_TCP指定的。大多数套接口选项获得后存放在int数据类型中。当查看手册页时,数据类型int通常会有一些假设,除非表明了其他东西。当使用一个布尔值时,当值为非零时,int表示TRUE,而如果为零,则表示FALSE。应用getsockopt(2)在这一部分,我们将会编译并运行一个getsndrcv.c的程序,这个程序会获得并报告一个套接口的发送以及接收缓冲区的大小尺寸。/*getsndrc.v* Get SO_SNDBUF & SO_RCVBUF Options:*/#include #include #include #include #include #include #include #include /* This function report the error and* exits back to the shell:*/static void bail(const char *on_what) if(errno != 0) fputs(strerror(errno),stderr); fputs(: ,stderr); fputs(on_what,stderr); fputc(n,stderr); exit(1);int main(int argc,char *argv) int z; int s=-1; /* Socket */ int sndbuf=0; /* Send buffer size */ int rcvbuf=0; /* Receive buffer size */ socklen_t optlen; /* Option length */ /* * Create a TCP/IP socket to use: */ s = socket(PF_INET,SOCK_STREAM,0); if(s=-1) bail(socket(2); /* * Get socket option SO_SNDBUF: */ optlen = sizeof sndbuf; z = getsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,&optlen); if(z) bail(getsockopt(s,SOL_SOCKET, SO_SNDBUF); assert(optlen = sizeof sndbuf); /* * Get socket option SON_RCVBUF: */ optlen = sizeof rcvbuf; z = getsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvbuf,&optlen); if(z) bail(getsockopt(s,SOL_SOCKET, SO_RCVBUF); assert(optlen = sizeof rcvbuf); /* * Report the buffer sizes: */ printf(Socket s: %dn,s); printf(Send buf: %d bytesn,sndbuf); printf(Recv buf: %d bytesn,rcvbuf); close(s); return 0;程序的运行结果如下:$ ./getsndrcvsocket s : 3 Send buf: 65535 bytes Recv buf: 65535 bytes设置套接口选项如果认为套接口的默认发送以及接收缓冲区的尺寸太大时,作为程序设计者的我们可以将其设计为一个小的缓冲区。当我们程序一个程序的几个实例同时运行在我们的系统上时,这显得尤其重要。可以通过setsockopt(2)函数来设计套接口选项。这个函数的概要如下:#include #include int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);这个函数与我们在上面所讨论的getsockopt函数类似,setsockopt函数的参数描述如下:1 选项改变所要影响的套接口s2 选项的套接口层次level3 要设计的选项名optname4 指向要为新选项所设置的值的指针optval5 选项值长度optlen这个函数参数与上面的getsockopt函数的参数的区别就在于最后一个参数仅是传递参数值。在这种情况下只是一个输入值。应用setsockopt函数下面的例子代码为一个套接口改变了发送以及接收缓冲区的尺寸。在设置完这些选项以后,程序会得到并报告实际的缓冲区尺寸。/*setsndrcv.c* Set SO_SNDBUF & SO_RCVBUF Options:*/#include #include #include #include #include #include #include #include /* This function report the error and* exits back to the shell:*/static void bail(const char *on_what) if(errno!=0) fputs(strerror(errno),stderr); fputs(: ,stderr); fputs(on_what,stderr); fputc(n,stderr); exit(1);int main(int argc,char *argv) int z; int s=-1; /* Socket */ int sndbuf=0; /* Send buffer size */ int rcvbuf=0; /* Receive buffer size */ socklen_t optlen; /* Option length */ /* * Create a TCP/IP socket to use: */ s = socket(PF_INET,SOCK_STREAM,0); if(s=-1) bail(socket(2); /* * set the SO_SNDBUF size : */ sndbuf = 5000; /* Send buffer size */ z = setsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof sndbuf); if(z) bail(setsockopt(s,SOL_SOCKET, SO_SNDBUF); /* * Set the SO_RCVBUF size: */ rcvbuf = 8192; /* Receive buffer size */ z = setsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof rcvbuf); if(z) bail(setsockopt(s,SOL_SOCKET, SO_RCVBUF); /* * As a check on the above . * Get socket option SO_SNDBUF: */ optlen = sizeof sndbuf; z = getsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,&optlen); if(z) bail(getsockopt(s,SOL_SOCKET, SO_SNDBUF); assert(optlen = sizeof sndbuf); /* * Get socket option SO_RCVBUF: */ optlen = sizeof rcvbuf; z = getsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvbuf,&optlen); if(z) bail(getsockopt(s,SOL_SOCKET SO_RCVBUF); assert(optlen = sizeof rcvbuf); /* * Report the buffer sizes: */ printf(Socket s: %dn,s); printf( Send buf: %d bytesn,sndbuf); printf( Recv buf: %d bytesn,rcvbuf); close(s); return 0;程序的运行结果如下:$ ./setsndrcvSocket s : 3 Send buf: 10000 bytes Recv buf: 16384 bytes$在这里我们要注意程序所报告的结果。他们看上去似乎是所指定的原始尺寸的两倍。这个原因可以由Linux内核源码模块net/core/sock.c中查到。我们可以查看一下SO_SNDBUF以及SO_RCVBUF的case语句。下面一段是由内核模块sock.c中摘录的一段处理SO_SNDBUF的代码:398 case SO_SNDBUF:399 /* Dont error on this BSD doesnt and if you think400 about it this is right. Otherwise apps have to401 play guess the biggest size games. RCVBUF/SNDBUF402 are treated in BSD as hints */403 404 if (val sysctl_wmem_max)405 val = sysctl_wmem_max;406 set_sndbuf:407 sk-sk_userlocks |= SOCK_SNDBUF_LOCK;408 if (val * 2) sk_sndbuf = SOCK_MIN_SNDBUF;410 else411 sk-sk_sndbuf = val * 2;412 413 /*414 * Wake up sending tasks if we415 * upped the value.416 */417 sk-sk_write_space(sk);418 break;由这段代码我们可以看到实际发生在SO_SNDBUF上的事情:1 检测SO_SNDBUF选项值来确定他是否超过了缓冲区的最大值2 如果步骤1中的SO_SNDBUF选项值没有超过最大值,那么就使用这个最大值,而不会向调用者返回错误代码3 如果SO_SNDBUF选项值的2倍小于套接口SO_SNDBUF的最小值,那么实际的SO_SNDBUF则会设置为SO_SNDBUF的最小值,否则则会SO_SNDBUF选项值则会设置为SO_SNDBUF选项值的2倍从这里我们可以看出SO_SNDBUF的选项值只是所用的一个提示值。内核会最终确定为SO_SNDBUF所用的最佳值。查看更多的内核源码,我们可以看到类似的情况也适用于SO_RCVBUF选项。如下面的一段摘录的代码:427 case SO_RCVBUF:428 /* Dont error on this BSD doesnt and if you think429 about it this is right. Otherwise apps have to430 play guess the biggest size games. RCVBUF/SNDBUF431 are treated in BSD as hints */432 433 if (val sysctl_rmem_max)434 val = sysctl_rmem_max;435 set_rcvbuf:436 sk-sk_userlocks |= SOCK_RCVBUF_LOCK;437 /*438 * We double it on the way in to account for439 * struct sk_buff etc. overhead. Applications440 * assume that the SO_RCVBUF setting they make will441 * allow that much actual data to be received on that442 * socket.443 *444 * Applications are unaware that struct sk_buff and445 * other overheads allocate from the receive buffer446 * during socket buffer allocation.447 *448 * And after considering the possible alternatives,449 * returning the value we actually used in getsockopt450 * is the most desirable behavior.451 */452 if (val * 2) sk_rcvbuf = SOCK_MIN_RCVBUF;454 else455 sk-sk_rcvbuf = val * 2;456 break;取得套接口类型实际上我们只可以得到一些套接口选项。SO_TYPE就是其中的一例。这个选项会允许传递套接口的一个子函数来确定正在处理的是哪一种套接口类型。如下面是一段得到套接口s类型的示例代码:/*gettype.c* Get SO_TYPE Option:*/#include #include #include #include #include #include #include #include /* This function report the error and* exits back to the shell:*/static void bail(const char *on_what) if(errno!=0) fputs(strerror(errno),stderr); fputs(: ,stderr); fputs(on_what,stderr); fputc(n,stderr); exit(1);int main(int argc,char *argv) int z; int s = -1; /* Socket */ int so_type = -1; /* Socket type */ socklen_t optlen; /* Option length */ /* * Create a TCT/IP socket to use: */ s = socket(PF_INET,SOCK_STREAM,0); if(s=-1) bail(socket(2); /* * Get socket option SO_TYPE: */ optlen = sizeof so_type; z = getsockopt(s,SOL_SOCKET,SO_TYPE,&so_type,&optlen); if(z) bail(getsockopt(s,SOL_SOCKET, SO_TYPE); assert(optlen = sizeof so_type); /* * Report the result: */ printf(Socket s: %dn,s); printf( SO_TYPE : %dn,so_type); printf( SO_STREAM = %dn,SOCK_STREAM); close(s); return 0;程序的运行结果如下:$./gettypeSocket s: 3SO_TYPE : 1SO_STREAM = 1设置SO_REUSEADDR选项在第11章,并发客户端服务器的第一部分中,提供并测试了一个使用fork系统调用设计的服务器。图12.1显示了在一个telnet命令与服务器建立连接之后的三个步骤。这些步骤如下:1 启动服务器进程(PID 926)。他监听客户端连接。2 启动客户端进程(telnet命令),并且连接到服务器进程(PID 926)。3 通过fork调用创建服务器子进程,这会保留的原始的父进程(PID 926)并且创建一个新的子进程(PID 927)。4 连接的客户端套接口由服务器父进程(PID 926)关闭,仅在子进程(PID 927)中保持连接的客户端套接口处理打开状态。5 telnet命令与服务器子进程(PID 927)随意交互,而独立于父进程(PID 926)。在步骤5,有两个套接口活动:服务器(PID 926)监听192.168.0.1:9099客户端由套接口192.168.0.1:9099进行服务(PID 927),他连接到客户端地址192.168.0.2:1035客户端由进程ID 927进行服务。这意味着我们可以杀掉进程ID 926,而客户端仍可以继续被服务。然而,却不会有新的连接连接到服务器,因为并没有服务器监听新的连接(监听服务器PID 926已被杀死)现在如果我们重启服务器来监听新的连接,就会出现问题。当新的服务器进程试着绑定IP地址192.168.0.1:9099时,bind函数就会返回 EADDRINUSE的错误代码。这个错误代码表明IP已经在9099端口上使用。这是因为进程PID 927仍然在忙于服务一个客户端。地址192.168.0.1:9099仍为这个进程所使用。这个问题的解决办法就是杀掉进程927,这个关闭套接口并且释放IP地址和端口。然而,如果正在被服务的客户是我们所在公司的CEO,这样的做法似乎不是一个选择。同时,其他的部门也会抱怨我们为什么要重新启动服务器。这个问题的一个好的解决办法就是使用SO_REUSEADDR套接口选项。所有的服务器都应使用这个选项,除非有一个更好的理由不使用。为了有效的使用这个选项,我们应在监听连接的服务器中执行下面的操作:1 使用通常的socket函数创建一个监听套接口2 调用setsockopt函数设置SO_REUSEADDR为TRUE3 调用bind函数套接口现在被标记为可重用。如果监听服务器进程因为任何原因终止,我们可以重新启动这个服务器。当一个客户正为另一个服务器进程使用同一个IP和端口号进行服务时尤其如此。为了有效的使用SO_REUSEADDR选项,需要考虑下面的情况:在监听模式下并没有同样的IP地址和端口号的其他套接口所有的同一个IP地址和端口号的套接口必须将SO_REUSEADDR选项设置为TRUE这就意味着一个指定的IP地址和端口号对上只可以用一个监听器。如果这样的套接口已经存在,那么设置这样的选项将不会达到我们的目的。只有所有存在的同一个地址和端口号的套接口有这个选项设置,将SO_REUSEADDR设置为TRUE才会有效。如果存在的套接口没有这个选项设置,那么bind函数就会继续并且会返回一个错误号。下面的代码显示如何将这个选项设置为TRUE:#define TRUE 1#define FALSE 0int z; /* Status code */int s; /* Socket number */int so_reuseaddr = TRUE;z = setsockopt(s,SOL_SOCKET,SO_REUSEADDR, &so_reuseaddr, sizeof so_reuseaddr);如果需要SO_REUSEADDR选项可以由getsockopt函数进行查询。设置SO_LINGER选项另一个常用的选项就是SO_LINGER选项。与SO_REUSEADDR选项所不同的是这个选项所用的数据类型并不是一个简单的int类型。SO_LINGER选项的目的是控制当调用close函数时套接口如何关闭。这个选项只适用于面向连接的协议,例如TCP。内核的默认行为是允许close函数立即返回给调用者。如果可能任何未发送的TCP/IP数据将会进行传送,但是并不会保证这样做。因为close函数会立即向调用者返回控制权,程序并没有办法知道最后一位的数据是否进行了发送。SO_LINGER选项可以作用在套接口上,来使得程序阻塞close函数调用,直到所有最后的数据传送到远程端。而且,这会保证两端的调用知道套接口正常关闭。如果失败,指定的选项超时,并且向调用程序返回一个错误。通过使用不同的SO_LINGER选项值,可以应用一个最后场景。如果调用程序希望立即中止通信,可以在linger结构中设置合适的值。然后,一个到close的调用会初始化一个通信中止连接,而丢弃所有未发送的数据,并立即关闭套接口。SO_LINGER的这种操作模式是由linger结构来控制的:struct linger int l_onoff; int l_linger;成员l_onoff为一个布尔值,非零值表示TRUE,而零则表示FALSE。这个选项的三个值描述如下:1 设置l_onoff为FALSE使得成员l_linger被忽略,而使用默认的close行为。也就是说,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据。2 设置l_onoff为TRUE将会使得成员l_linger的值变得重要。当l_linger非零时,这代表应用在close函数调用上的以秒计的超时时限。如果超时发生之前,有未发送的数据并且成功关闭,函数将会成功返回。否则,将会返回错误,而将变量errno的值设置为EWOULDBLOCK。3 将l_onoff设置为TRUE而l_linger设置为零时使得连接中止,在调用close时任何示发送的数据都会丢弃。我们也许希望得到一些建议,在我们的程序中使用SO_LINGER选项,并且提供一个合理的超时时限。然后,可以检测由close函数的返回值来确定连接是否成功关闭。如果返回了一个错误,这就告知我们的程序也许远程程序并不能接收我们发送的全部数据。相对的,他也许仅意味着连接关闭时发生的问题。然而,我们必须保持清醒,这样的方法在一些服务器设计中会产生新的问题。当在close函数调用上将SO_LINGER选项配置为超时(linger),当我们的服务器在close函数调用中执行超时时会阻止其他的客户端进行服务。如果我们正在一个进程中服务多个客户端进程时就会存在这个问题。使用默认的行为也许更为合适,因为这允许close函数立即返回。而任何未发送的数据也会为内核继续发送。最后,如果程序或是服务器知道连接应何时中止时可以使用中止行为。这也许适用于当服务器认为没有访问权限的用户正试着进行访问的情况。这种情况下的客户并不会得到特别的关注。下面的代码是一个使用SO_LINGER选项的例子,使用30秒的超时时限:#define TRUE 1#define FALSE 0int z; /* Status code*/ int s; /* Socket s */struct linger so_linger;.so_linger.l_onoff = TRUE;so_linger.l_linger = 30;z = setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);if ( z ) perror(setsockopt(2);下面的例子显示了如何设置SO_LINGER的值来中止套接口s上的当前连接:#define TRUE 1#define FALSE 0int z; /* Status code */int s; /* Socket s */struct linger so_linger;.so_linger.l_onoff = TRUE;so_linger.l_linger = 0;z = setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);if ( z ) perror(setsockopt(2); close(s); /* Abort connection */在上面的这个例子中,当调用close函数时,套接口s会立即中止。中止的语义是通过将超时值设置为0来实现的。设置SO_KKEPALIVE选项当使用连接时,有时他们会空闲相当长的时间。例如,建立一个telnet会话通过访问股票交易服务。他也许会执行一些初始的查询,然后离开连接而保持服务打开,因为他希望回来查询更多的内容。然而,同时连接处理空闲状态,也许一次就是一个小时。任何一个服务器认为他有一个连接的客户时会为其分配相应的资源。如果服务器是一个派生类型(fork),那么整个Linux进程及其相应的内存都分配给这个客户。如果事情顺利,这个场景并不会产生任何问题。然而当出现网络崩溃时,困难出现了,我们所有的578个客户都会从我们的股票交易服务中失去连接。在网络服务恢复后,578个客户会试着连接到我们的服务器,重建连接。这对于我们来说是一个真实的问题,因为我们的服务器在之前并没有意识到他失去了空闲客户SO_KKEPALIVE来解决这个问题。下面的例子显示了如何在套接口s上使用SO_KKEPALIVE选项,从而一个断开的空闲连接可以被检测到:#define TRUE 1#define FALSE 0int z; /* Status code */ int s; /* Socket s */int so_keepalive;.so_keepalive = TRUE;z = setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive, sizeof so_keepalive);if ( z ) perror(setsockopt(2);在上面的例子中设置了SO_KEEPALIVE选项,这样当套接口连接空闲相当长的时间时,一个探测信息(probe message)就会发送到远程端。这通常是在两个小时的无活动后完成的。对于一个保持活动的探测信息会有三个可能的反应。他们分别是:1 端会合适的返回表明一切正常。并没有向程序返回任何指示信息,因为这是程序假定的开始。2 端响应表明他对连接一无所知。这表明端自上次通信以后与主机进行重新连接。这样当下次套接口操作时会向程序返回ECONNRESET错误代码。3 端没有响

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论