Socket套接口选项.doc
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Socket 接口 选项
- 资源描述:
-
Socket套接口选项 在前面的几章中,我们讨论了使用套接口的基础内容。现在我们要来探讨一些可用的其他的特征。在我们掌握了这一章的概念之后,我们就为后面的套接口的高级主题做好了准备。在这一章,我们将会专注于下列主题: 如何使用getsockopt(2)函数获得套接口选项值 如何使用setsockopt(2)函数设置套接口选项值 如何使用这些常用的套接口选项 得到套接口选项 有时,一个程序需要确定为当前为一个套接口进行哪些选项设置。这对于一个子程序库函数尤其如此,因为这个库函数并不知道为这个套接口进行哪些设置,而这个套接口需要作为一个参数进行传递。程序也许需要知道类似于流默认使用的缓冲区的大小。 允许我们得到套接口选项值的为getsockopt函数。这个函数的概要如下: #include <sys/types.h> #include <sys/socket.h> int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen); 函数参数描述如下: 1 要进行选项检验的套接口s 2 选项检验所在的协议层level 3 要检验的选项optname 4 指向接收选项值的缓冲区的指针optval 5 指针optlen同时指向输入缓冲区的长度和返回的选项长度值 当函数成功时返回0。当发生错误时会返回-1,而错误原因会存放在外部变量errno中。 协议层参数指明了我们希望访问一个选项所在的协议栈。通常我们需要使用下面中的一个: SOL_SOCKET来访问套接口层选项 SOL_TCP来访问TCP层选项 我们在这一章的讨论将会专注于SOL_SOCKET层选项的使用。 参数optname为一个整数值。在这里所使用的值首先是由所选用的level参数来确定的。在一个指定的协议层,optname参数将会确定我们希望访问哪一个选项。下表列出了一些层与选项的组合值: 协议层 选项名字 SOL_SOCKET SO_REUSEADDR SOL_SOCKET SO_KKEPALIVE SOL_SOCKET SO_LINGER SOL_SOCKET SO_BROADCAST SOL_SOCKET SO_OOBINLINE SOL_SOCKET SO_SNDBUF SOL_SOCKET SO_RCVBUF SOL_SOCKET SO_TYPE SOL_SOCKET SO_ERROR SOL_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 <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <assert.h> /* * 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: %d\n",s); printf("Send buf: %d bytes\n",sndbuf); printf("Recv buf: %d bytes\n",rcvbuf); close(s); return 0; } 程序的运行结果如下: $ ./getsndrcv socket s : 3 Send buf: 65535 bytes Recv buf: 65535 bytes 设置套接口选项 如果认为套接口的默认发送以及接收缓冲区的尺寸太大时,作为程序设计者的我们可以将其设计为一个小的缓冲区。当我们程序一个程序的几个实例同时运行在我们的系统上时,这显得尤其重要。 可以通过setsockopt(2)函数来设计套接口选项。这个函数的概要如下: #include <sys/types.h> #include <sys/socket.h> int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen); 这个函数与我们在上面所讨论的getsockopt函数类似,setsockopt函数的参数描述如下: 1 选项改变所要影响的套接口s 2 选项的套接口层次level 3 要设计的选项名optname 4 指向要为新选项所设置的值的指针optval 5 选项值长度optlen 这个函数参数与上面的getsockopt函数的参数的区别就在于最后一个参数仅是传递参数值。在这种情况下只是一个输入值。 应用setsockopt函数 下面的例子代码为一个套接口改变了发送以及接收缓冲区的尺寸。在设置完这些选项以后,程序会得到并报告实际的缓冲区尺寸。 /*setsndrcv.c * * Set SO_SNDBUF & SO_RCVBUF Options: */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <assert.h> /* * 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: %d\n",s); printf(" Send buf: %d bytes\n",sndbuf); printf(" Recv buf: %d bytes\n",rcvbuf); close(s); return 0; } 程序的运行结果如下: $ ./setsndrcv Socket 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 /* Don't error on this BSD doesn't and if you think 400 about it this is right. Otherwise apps have to 401 play 'guess the biggest size' games. RCVBUF/SNDBUF 402 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) < SOCK_MIN_SNDBUF) 409 sk->sk_sndbuf = SOCK_MIN_SNDBUF; 410 else 411 sk->sk_sndbuf = val * 2; 412 413 /* 414 * Wake up sending tasks if we 415 * 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 /* Don't error on this BSD doesn't and if you think 429 about it this is right. Otherwise apps have to 430 play 'guess the biggest size' games. RCVBUF/SNDBUF 431 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 for 439 * "struct sk_buff" etc. overhead. Applications 440 * assume that the SO_RCVBUF setting they make will 441 * allow that much actual data to be received on that 442 * socket. 443 * 444 * Applications are unaware that "struct sk_buff" and 445 * other overheads allocate from the receive buffer 446 * during socket buffer allocation. 447 * 448 * And after considering the possible alternatives, 449 * returning the value we actually used in getsockopt 450 * is the most desirable behavior. 451 */ 452 if ((val * 2) < SOCK_MIN_RCVBUF) 453 sk->sk_rcvbuf = SOCK_MIN_RCVBUF; 454 else 455 sk->sk_rcvbuf = val * 2; 456 break; 取得套接口类型 实际上我们只可以得到一些套接口选项。SO_TYPE就是其中的一例。这个选项会允许传递套接口的一个子函数来确定正在处理的是哪一种套接口类型。 如下面是一段得到套接口s类型的示例代码: /*gettype.c * * Get SO_TYPE Option: */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <assert.h> /* * 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: %d\n",s); printf(" SO_TYPE : %d\n",so_type); printf(" SO_STREAM = %d\n",SOCK_STREAM); close(s); return 0; } 程序的运行结果如下: $./gettype Socket s: 3 SO_TYPE : 1 SO_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为TRUE 3 调用bind函数 套接口现在被标记为可重用。如果监听服务器进程因为任何原因终止,我们可以重新启动这个服务器。当一个客户正为另一个服务器进程使用同一个IP和端口号进行服务时尤其如此。 为了有效的使用SO_REUSEADDR选项,需要考虑下面的情况: 在监听模式下并没有同样的IP地址和端口号的其他套接口 所有的同一个IP地址和端口号的套接口必须将SO_REUSEADDR选项设置为TRUE 这就意味着一个指定的IP地址和端口号对上只可以用一个监听器。如果这样的套接口已经存在,那么设置这样的选项将不会达到我们的目的。 只有所有存在的同一个地址和端口号的套接口有这个选项设置,将SO_REUSEADDR设置为TRUE才会有效。如果存在的套接口没有这个选项设置,那么bind函数就会继续并且会返回一个错误号。 下面的代码显示如何将这个选项设置为TRUE: #define TRUE 1 #define FALSE 0 int 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 0 int 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 0 int 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来解决这个问题。 下面的例子显示了如何在套展开阅读全文
咨信网温馨提示:1、咨信平台为文档C2C交易模式,即用户上传的文档直接被用户下载,收益归上传人(含作者)所有;本站仅是提供信息存储空间和展示预览,仅对用户上传内容的表现方式做保护处理,对上载内容不做任何修改或编辑。所展示的作品文档包括内容和图片全部来源于网络用户和作者上传投稿,我们不确定上传用户享有完全著作权,根据《信息网络传播权保护条例》,如果侵犯了您的版权、权益或隐私,请联系我们,核实后会尽快下架及时删除,并可随时和客服了解处理情况,尊重保护知识产权我们共同努力。
2、文档的总页数、文档格式和文档大小以系统显示为准(内容中显示的页数不一定正确),网站客服只以系统显示的页数、文件格式、文档大小作为仲裁依据,个别因单元格分列造成显示页码不一将协商解决,平台无法对文档的真实性、完整性、权威性、准确性、专业性及其观点立场做任何保证或承诺,下载前须认真查看,确认无误后再购买,务必慎重购买;若有违法违纪将进行移交司法处理,若涉侵权平台将进行基本处罚并下架。
3、本站所有内容均由用户上传,付费前请自行鉴别,如您付费,意味着您已接受本站规则且自行承担风险,本站不进行额外附加服务,虚拟产品一经售出概不退款(未进行购买下载可退充值款),文档一经付费(服务费)、不意味着购买了该文档的版权,仅供个人/单位学习、研究之用,不得用于商业用途,未经授权,严禁复制、发行、汇编、翻译或者网络传播等,侵权必究。
4、如你看到网页展示的文档有www.zixin.com.cn水印,是因预览和防盗链等技术需要对页面进行转换压缩成图而已,我们并不对上传的文档进行任何编辑或修改,文档下载后都不会有水印标识(原文档上传前个别存留的除外),下载后原文更清晰;试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓;PPT和DOC文档可被视为“模板”,允许上传人保留章节、目录结构的情况下删减部份的内容;PDF文档不管是原文档转换或图片扫描而得,本站不作要求视为允许,下载前可先查看【教您几个在下载文档中可以更好的避免被坑】。
5、本文档所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用;网站提供的党政主题相关内容(国旗、国徽、党徽--等)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
6、文档遇到问题,请及时联系平台进行协调解决,联系【微信客服】、【QQ客服】,若有其他问题请点击或扫码反馈【服务填表】;文档侵犯商业秘密、侵犯著作权、侵犯人身权等,请点击“【版权申诉】”,意见反馈和侵权处理邮箱:1219186828@qq.com;也可以拔打客服电话:0574-28810668;投诉电话:18658249818。




Socket套接口选项.doc



实名认证













自信AI助手
















微信客服
客服QQ
发送邮件
意见反馈



链接地址:https://www.zixin.com.cn/doc/5965376.html