全部博文(842)
分类: LINUX
2012-05-17 16:57:43
【getsockopt/setsockopt系统调用】
功能描述:
获取或者设置与某个套接字关联的选项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。例如,为了表示一个选项由TCP协议解析,层应该设定为协议号TCP。
套接字选项最常用于在套接字层调整参数,包括调整错误处理、数据缓冲、地址处理、端口处理和收发数据的超时参数等。在这些选项当中,套接字层的SO_RCVTIMEO选项经常用来调整read()、recv()和recvfrom()函数的超时参数。
在默认情况下,read()、recv()和recvfrom()函数以阻塞方式读取数据,当函数被调用时,将会无限期地等待,直到数据被接收或出现错误。如果在某种实现情况下必须在数据未能及时到达时做出反应,则这种行为是不符合要求的。因此,可以使用SO_RCVTIMEO套接字选项,来设置读取数据的操作在返回调用函数之前的最长等待时间。
用法:
#include
#include
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
参数:
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。可以取三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项.
optname:需要访问的选项名,详细描述见表1。
optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲,是一个指向变量的指针,类型:整型,套接口结构,其他结构类型:linger{}, timeval{ }。
optlen:optval 的大小,对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。
返回说明:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值:
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字
表1:参数optname指定控制的方式(选项的名称),详细说明如下表:
|
选项名称 |
简要说明 |
数据类型 |
SOL_SOCKET |
SO_BROADCAST |
允许发送广播数据 |
int |
SO_DEBUG |
允许调试 |
int |
|
SO_DONTROUTE |
不查找路由 |
int |
|
SO_ERROR |
获得套接字错误 |
int |
|
SO_KEEPALIVE |
保持连接 |
int |
|
SO_LINGER |
延迟关闭连接 |
struct linger |
|
SO_OOBINLINE |
带外数据放入正常数据流 |
int |
|
SO_RCVBUF |
接收缓冲区大小 |
int |
|
SO_SNDBUF |
发送缓冲区大小 |
int |
|
SO_RCVLOWAT |
接收缓冲区下限 |
int |
|
SO_SNDLOWAT |
发送缓冲区下限 |
int |
|
SO_RCVTIMEO |
接收超时 |
struct timeval |
|
SO_SNDTIMEO |
发送超时 |
struct timeval |
|
SO_REUSERADDR |
允许重用本地地址和端口 |
int |
|
SO_TYPE |
获得套接字类型 |
int |
|
SO_BSDCOMPAT |
与BSD系统兼容 |
int |
|
IPPROTO_IP |
IP_HDRINCL |
在数据包中包含IP首部 |
int |
IP_OPTINOS |
IP首部选项 |
int |
|
IP_TOS |
服务类型 |
|
|
IP_TTL |
生存时间 |
int |
|
IPPRO_TCP |
TCP_MAXSEG |
TCP最大数据段的大小 |
int |
TCP_NODELAY |
不使用Nagle算法 |
int |
的例子
下面的例子代码为一个套接口改变了发送以及接收缓冲区的尺寸。在设置完这些选项以后,程序会得到并报告实际的缓冲区尺寸。
#include #include #include #include #include #include #include #include
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) { printf("socket error\n"); return -1; }
/* * set the SO_SNDBUF size : */ sndbuf = 5000; /* Send buffer size */ z = setsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf)); if(z) { printf("setsockopt error\n"); return -1; }
/* * Set the SO_RCVBUF size: */ rcvbuf = 8192; /* Receive buffer size */ z = setsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf)); if(z) { printf("setsockopt error\n"); return -1; }
/* * 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) { printf("getsockopt error\n"); return -1; }
assert(optlen == sizeof(sndbuf));
/* * Get socket option SO_RCVBUF: */ optlen = sizeof(rcvbuf); z = getsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvbuf,&optlen); if(z) { printf("getsockopt error\n"); return -1; } 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; } |
的例子
1、设置缓冲区大小的例子:
#include #include #include #include #include #include #include #include
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) { printf("socket error\n"); return -1; }
/* * set the SO_SNDBUF size : */ sndbuf = 5; /* Send buffer size */ z = setsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf)); if(z) { printf("setsockopt error\n"); return -1; } …… } |
2、设置接收超时的例子:
#include
#include
#include
#include
#include
#define MYPORT 6666
main() { int sock,new_fd; int sin_size; struct sockaddr_in my_addr; struct sockaddr_in their_addr; struct timeval recv_timeout; int len,recv_size; char buf[255]; sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { printf("socket is error"); return -1; } my_addr.sin_family=AF_INET;/*hostbyteorder*/ my_addr.sin_port=htons(MYPORT);/*short,network byte order*/ my_addr.sin_addr.s_addr=INADDR_ANY; bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/ if(bind(sock,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0) { close(sock); printf("bind error"); return -1; } if(listen(sock,5)<0) { close(sock); printf("listen error"); return -1; } printf("listen right\n"); sin_size=sizeof(struct sockaddr_in); new_fd=accept(sock,&their_addr,&sin_size); if(new_fd<0) { close(sock); printf("accept error"); return -1; } printf("their_ip is %s\n",inet_ntoa(their_addr.sin_addr)); printf("their_port is %d\n",their_addr.sin_port); //setsockopt recv_timeout.tv_sec=10; recv_timeout.tv_usec=0; int z=setsockopt(new_fd,SOL_SOCKET,SO_RCVTIMEO,&recv_timeout,sizeof(recv_timeout)); if(z) { close(sock); printf("setsockopt error\n"); return -1; } buf[0]="\0"; recv_size=recv(new_fd,buf,255,0); if(recv_size<0) { close(sock); printf("recv error\n"); return -1; } …… } |
Timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout长时间后没有文件描述符准备好即返回。struct timeval数据结构为:
struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};
在此例中,使用socket()和bind()函数创建了一个套接字,并绑定端口,然后使用setsockopt()函数设置了这个套接字的接收超时参数,超时参数存放在一个timeval结构中,具体分析如下:
1)传给函数的第1个参数为需要设置选项的套接字描述符。
2)第2个参数为所设置选项的协议层次,在本例中使用了整型常量SOL_SOCKET,它表示在套接字层设置选项。
3)第3个参数为套接字选项标志,在本例中为整型常量SO_RCVTIMEO。
4) 函数的第4个和第5个参数是根据第2个与第3个参数即套接字选项的协议层次与选项值确定的。当使用SOL_SOCKET和SO_RCVTIMEO选项时,传给第4个参数的是一个指向timeval结构的指针,传给第5个参数的是timeval结构的长度(以字节为单位)。timeval结构中的tv_sect和tv_usec成员的值表示从第1个参数处传入的套接字描述符读取数据的超时参数。
的其它用法:
1.closesocket()(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
2. 如果要已经处于连接状态的socket在调用closesocket()后强制关闭,不经历TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
struct timeval tv;
tv.sec=1000;//1秒
//发送时限
setsockopt(socket,SOL_SOCKET,SO_SNDTIMEO,(struct timeval *)&tv,sizeof(tv));
//接收时限
setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,( struct timeval *)&tv,sizeof(tv));
4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据
和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区
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));
5. 如果在发送数据时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响程序的性能:
int nZero=0;
setsockopt(socket,SOL_SOCKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_SOCKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
8.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体应用的要求(即让没发完的数据发送出去后在关闭socket)?
struct linger {
u_short l_onoff;
u_short l_linger;
};
struct 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,( struct linger *)&m_sLinger,sizeof(m_sLinger));
阻塞函数在完成其指定的任务以前不允许程序调用另一个函数。例如,程序执行一个读数据的函数调用时,在此函数完成读操作以前将不会执行下一程序语句。当服务器运行到accept语句时,而没有客户连接服务请求到来,服务器就会停止在accept语句上等待连接服务请求的到来。这种情况称为阻塞(blocking)。而非阻塞操作则可以立即完成。比如,如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接,否则就继续做其他事情,则可以通过将Socket设置为非阻塞方式来实现。非阻塞socket在没有客户在等待时就使accept调用立即返回。
#include
#include
……
sockfd = socket(AF_INET,SOCK_STREAM,0);
fcntl(sockfd,F_SETFL,O_NONBLOCK);
……
通过设置socket为非阻塞方式,可以实现"轮询"若干Socket。当企图从一个没有数据等待处理的非阻塞Socket读入数据时,函数将立即返回,返回值为-1,并置errno值为EWOULDBLOCK。但是这种"轮询"会使CPU处于忙等待方式,从而降低性能,浪费系统资源。而调用select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费CPU开销。Select函数原型为:
int select(int numfds,fd_set *readfds,fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);
其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合。如果你希望确定是否可以从标准输入和某个socket描述符读取数据,你只需要将标准输入的文件描述符0和相应的sockfd加入到readfds集合中;numfds的值是需要检查的号码最高的文件描述符加1,这个例子中numfds的值应为sockfd+1;当select返回时,readfds将被修改,指示某个文件描述符已经准备被读取,你可以通过FD_ISSSET()来测试。为了实现fd_set中对应的文件描述符的设置、复位和测试,它提供了一组宏:
FD_ZERO(fd_set *set)----清除一个文件描述符集;
FD_SET(int fd,fd_set *set)----将一个文件描述符加入文件描述符集中;
FD_CLR(int fd,fd_set *set)----将一个文件描述符从文件描述符集中清除;
FD_ISSET(int fd,fd_set *set)----试判断是否文件描述符被置位。
首先:
int sock;
sock= socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in addr; //告诉sock 应该在什么地方listen
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(11111); //端口
addr.sin_addr.s_addr=htonl(INADDR_ANY); //在本机的所有ip上开始监听
bind (sock,(struct sockaddr *)&addr,sizeof(addr));//bind....
listen(sock,5); //最大5个队列
int socka; //这个用来接受一个连接
fd_set rfd; // 描述符集 这个将用来测试有没有一个可用的连接
struct timeval timeout;
FD_ZERO(&rfd); //总是这样先清空一个描述符集
timeout.tv_sec=60; // select函数用到这个
timeout.tv_usec=0;
u_long ul=1;
ioctlsocket(sock,FIONBIO,&ul); //用非阻塞的连接
//现在开始用select
FD_SET(sock,&rfd); //把sock放入要测试的描述符集,就是说把sock放入了rfd里面,这样下一步调用select对rfd进行测试的时候就会测试sock了(因为我们将sock放入的rdf) 一个描述符集可以包含多个被测试的描述符
if(select(sock+1,&rfd,0,0, &timeout)==0) // select的第一个参数是可以忽略的(这样写是为了保持和linux下一致) 第二个参数放入需要测试的读描述符集(也就是说如果这里面有一个描述符可以读取了,select就返回) 第三个放入需要测试的写描述符集,第四个放入"可执行描述符集",第五个参数是超时时间(如果过了这个超时时间依然没有描述符准备好,select也返回.(如果为NULL,那就一直等到一个描述符集变成准备好的状态)
{ //这个大括号接上面的,返回0那么就超过了timeout预定的时间
//处理....
}
if(FD_ISSET(sock,&rfd))
{ //有一个描述符准备好了
socka=accept(sock,0,0); //好了 接受它吧
//你还要判断一下socka是不是有效的socket才行....
----------------------------------------------------------------------------------------------------------------------
一般的情况下
假设你要判断两个socket 是否可读可写,那就这样:
假设 socka 和sockb 是两个socket 他们已经被连接上,并且能够收发数据
fd_set rfd,wfd;//一个用来测试读,一个用来测试写
FD_ZERO(&rfd);
FD_ZERO(&wfd);
FD_SET(socka,&rfd);//把socka放入读描述符集
FD_SET(sockb,&rfd);//把sockb放入读描述符集
FD_SET(socka,&wfd);把socka放入写描述符集
FD_SET(sockb,&wfd);把sockb放入写描述符集
if(SOCKET_ERROR!=select(0,&rfd,&wfd,0,0)) //测试这两个描述符集,永不超时,其中rfd只用来测试读,wfd只用来测试写
{ //没有错误
if(FD_ISSET(socka,&rfd)) //socka可读
{...}
if(FD_ISSET(sockb,&rfd) //sockb可读
{...}
if(FD_ISSET(socka,&wfd) //socka 可写
{...}
if(FD_ISSET(sockb,&wfd) //sockb可写
{...}
}
当一个用户30秒内没有任何活动,就说明客户端出现了什么问题。这个时候我们就应该在服务器上主动断开连接,实现这个的机制就是超时机制,
服务器客户端间的超时机制需要满足的技术特性:
一,需要一个定时器功能。能够在一定时间后正确的发出通知。
二,定时器时间可以在程序运行中动态修改。
三,要有退出机制。当程序接受到事件后能相应作出处理
四,能够安全的在多线程环境下使用
关于超时机制的实现。考虑实现方法如下
方案一:使用select自身提供的超时机制。
优点:系统自带,省时省力。
缺点:当使用select自身提供的超时机制的时候,发现客户端无法正常连接服务器端。
结果:放弃