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

UNIX网络编程——常用套接字选项(SOL_SOCKET级别)

2013-09-18 13:14 232 查看
[cpp] view
plaincopyprint?

#include <sys/socket.h>

int setsockopt( int socket, int level, int option_name,const void *option_value, size_t option_len);

第一个参数socket是套接字描述符。

第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为SOL_SOCKET。

第三个参数 option_name指定准备设置的选项,option_name可以有哪些常用取值,这取决于level,以linux 2.6内核为例(在不同的平台上,这种关系可能会有不同),在套接字级别上(SOL_SOCKET):

1. SO_BROADCAST 套接字选项

本选项开启或禁止进程发送广播消息的能力。只有数据报套接字支持广播,并且还必须是在支持广播消息的网络上(例如以太网,令牌环网等)。我们不可能在点对点链路上进行广播,也不可能在基于连接的传输协议(例如TCP和SCTP)之上进行广播。

2. SO_DEBUG 套接字选项

本选项仅由TCP支持。当给一个TCP套接字开启本选项时,内核将为TCP在该套接字发送和接受的所有分组保留详细跟踪信息。这些信息保存在内核的某个环形缓冲区中,并可使用trpt程序进行检查。

3. SO_KEEPALIVE 套接字选项

给一个TCP套接字设置保持存活选项后,如果2小时内在该套接字的任何一方向上都没有数据交换,TCP就自动给对端发送一个保持存活探测分节。这是一个对端必须相应的TCP分节,它会导致以下3种情况之一。

(1)对端以期望的ACK响应。应用进程得不到通知(因为一切正常)。在又经过仍无动静的2小时后,TCP将发出另一个探测分节。

(2)对端以RST响应,它告知本端TCP:对端已崩溃且已重新启动。该套接字的待处理错误被置为ECONNRESET,套接字本身则被关闭。

(3)对端对保持存活探测分节没有任何响应。

如果根本没有对TCP的探测分节的响应,该套接字的待处理错误就被置为ETIMEOUT,套接字本身则被关闭。然而如果该套接字收到一个ICMP错误作为某个探测分节的响应,那就返回响应的错误,套接字本身也被关闭。

本选项的功能是检测对端主机是否崩溃或变的不可达(譬如拨号调制解调器连接掉线,电源发生故障等等)。如果对端进程崩溃,它的TCP将跨连接发送一个FIN,这可以通过调用select很容易的检测到。

本选项一般由服务器使用,不过客户也可以使用。服务器使用本选项时因为他们花大部分时间阻塞在等待穿越TCP连接的输入上,也就是说在等待客户的请求。然而如果客户主机连接掉线,电源掉电或者系统崩溃,服务器进程将永远不会知道,并将继续等待永远不会到达的输入。我们称这种情况为半开连接。保持存活选项将检测出这些半开连接并终止他们。



4. SO_LINGER 套接字选项

本选项指定close函数对面向连接的协议(例如TCP和SCTP,但不是UDP)如何操作。默认操作是close立即返回,但是如果有数据残留在套接字发送缓冲区中,系统将试着把这些数据发送给对端。

SO_LINGER如果选择此选项,close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后才会返回。否则,调用将立即返回。

该选项的参数(option_value)是一个linger结构:

[cpp] view
plaincopyprint?

struct linger {

int l_onoff; /* 延时状态(打开/关闭) */

int l_linger; /* 延时多长时间 */

};



5. SO_RCVBUF和SO_SNDBUF套接字选项

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

接收缓冲区被TCP,UDP和SCTCP用来保存接收到的数据,直到由应用进程读取。对于TCP来说,套接字接收缓冲区可用空间的大小限制了TCP通告对端的窗口大小。TCP套接字接收缓冲区不可以溢出,因为不允许对端发出超过本端所通告窗口大小的数据。这就是TCP的流量控制,如果对端无视窗口大小而发出了超过窗口大小的数据,本端TCP将丢弃它们然而对于UDP来说,当接收到的数据报装不进套接字接收缓冲区时,该数据报就被丢弃。回顾一下,UDP是没有流量控制的:较快的发送端可以很容易的淹没较慢的接收端,导致接收端的UDP丢弃数据报。

这两个套接字选项允许我们改变着两个缓冲区的默认大小。对于不同的实现,默认值得大小可以有很大的差别。如果主机支持NFS,那么UDP发送缓冲区的大小经常默认为9000字节左右的一个值,而UDP接收缓冲区的大小则经常默认为40000字节左右的一个值。

当设置TCP套接字接收缓冲区的大小时,函数调用的顺序很重要。这是因为TCP的出口规模选项时在建立连接时用SYN分节与对端互换得到的。对于客户,这意味着SO_RCVBUF选项必须在调用connect之前设置对于服务器,这意味着该选项必须在调用listen之前给监听套接字设置。给已连接套接字设置该选项对于可能存在的出口规模选项没有任何影响,因为accept直到TCP的三路握手玩抽才会创建并返回已连接套接字。这就是必须给监听套接字设置本选项的原因。

6. SO_RCVLOWAT 和 SO_SNDLOWAT套接字选项

每个套接字还有一个接收低水位标记和一个发送低水位标记。他们由select函数使用,这两个套接字选项允许我们修改这两个低水位标记。

接收低水位标记是让select返回“可读”时,套接字接收缓冲区中所需的数据量。对于TCP,UDP和SCTP套接字,其默认值为1发送低水位标记是让select返回“可写”时套接字发送缓冲区中所需的可用空间对于TCP套接字,其默认值通常为2048。UDP也使用发送低水位标记,然而由于UDP套接字的发送缓冲区中可用空间的字节数从不改变(意味UDP并不为由应用进程传递给它的数据报保留副本),只要一个UDP套接字的发送缓冲区大小大于该套接字的低水位标记,该UDP套接字就总是可写。我们记得UDP并没有发送缓冲区,而只有发送缓冲区大小这个属性。

7. SO_RCVTIMEO 和 SO_SNDTIMEO套接字选项

这两个选项允许我们给套接字的接收和发送设置一个超时值。注意,访问他们的getsockopt和setsockopt函数的参数是指向timeval结构的指针,与select所用参数相同。这可让我们用秒数和微妙数来规定超时。我们通过设置其值为0s和0μs来禁止超时。默认情况下着两个超时都是禁止的。

接收超时影响5个输入函数:read,readv,recv,recvfrom和recvmsg。发送超时影响5个输出函数:write,writev,send,sendto和sendmsg。

8. SO_REUSEADDR 和 SO_REUSEPORT 套接字选项

SO_REUSEADDR套接字选项能起到以下4个不同的功用。

(1)SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知的端口,即使以前建立的将该端口用作他们的本地端口的连接仍存在。这个条件通常是这样碰到的:

(a)启动一个监听服务器;

(b)连接请求到达,派生一个子进程来处理这个客户;

(c)监听服务器终止,但子进程继续为现有连接上的客户提供服务;

(d)重启监听服务器。

默认情况下,当监听服务器在步骤d通过调用socket,bind和listen重新启动时,由于他试图捆绑一个现有连接(即正由早先派生的那个子进程处理着的连接)上的端口,从而bind调用会失败。但是如果该服务器在socket和bind两个调用之间设置了SO_REUSEADDR套接字选项,那么将成功。所有TCP服务器都应该指定本套接字选项,以允许服务器在这种情况下被重新启动。

(2)SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们绝对不可能启动捆绑相同IP地址和相同端口号的多个服务器:这是完全重复的捆绑,即使我们给第二个服务器设置了SO_REUSEADDR套接字也不管用。

(3)SO_REUSEADDR 允许单个进程捆绑同一端口到多个套接字上,只要每次捆绑指定不同的本地IP地址即可。

(4)SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口号已绑定到某个套接字上时,如果传输协议支持,同样的IP地址和端口还可以捆绑到另一个套接字上。一般来说本特性仅支持UDP套接字。

UNIX网络编程——套接字选项(setsockopt)

setsockopt的一些用法:

close socket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:

[cpp] view
plaincopyprint?

BOOL bReuseaddr=TRUE;

setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));

如果要已经处于连接状态的soket在调用close socket后强制关闭,不经历TIME_WAIT的过程:

[cpp] view
plaincopyprint?

BOOL bDontLinger = FALSE;

setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:

[cpp] view
plaincopyprint?

int nNetTimeout=1000;//1秒

//发送时限

setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));

//接收时限

setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:

[cpp] view
plaincopyprint?

// 接收缓冲区

int nRecvBuf=32*1024;//设置为32K

setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//发送缓冲区

int nSendBuf=32*1024;//设置为32K

setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响程序的性能:

[cpp] view
plaincopyprint?

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):

[cpp] view
plaincopyprint?

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:

[cpp] view
plaincopyprint?

BOOL bBroadcast=TRUE;

setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置connect()延时,直到accept()被呼叫(本函数设置只有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大)

[cpp] view
plaincopyprint?

BOOL bConditionalAccept=TRUE;

setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了close socket(),以前我们一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体应用的要求(即让没发完的数据发送出去后在关闭socket)

[cpp] view
plaincopyprint?

struct linger {

u_short l_onoff;

u_short l_linger;

};

linger m_sLinger;

m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)

// 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;

m_sLinger.l_linger=5;//(容许逗留的时间为5秒)

setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: