socket网络编程的超时设置

前段时间我们学到socket网络编程,学到三个可以设置超时的函数:setsocketopt();select();alarm();

就是如果我们的网络有问题啊啥的,可以设置非阻塞nonblock模式,立即返回,而不是一直在那等啊等;

三个函数的原型分别是:

(一)、

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:选项所在的协议层。

level指定控制套接字的层次.可以取三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项.

optname:需要访问的选项名,即指定控制的方式。比如我们有用到

SO_REUSEADDR这个选项来取消tcp三次握手的TIME_WAIT;


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

optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),

指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。

作为出口参数时,选项值的实际长度。

对于setsockopt(),现选项的长度。


返回值:

成功执行时,返回0。失败返回-1,errno被设为以下的某个值  
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字


选项名称        说明                  数据类型
========================================================================
            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
========================================================================


SO_RCVBUF和SO_SNDBUF每个套接口都有一个发送缓冲区和一个接收缓冲区,

使用这两个套接口选项可以改变缺省缓冲区大小。


// 接收缓冲区
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));


注意:

        当设置TCP套接口接收缓冲区的大小时,函数调用顺序是很重要的,

因为TCP的窗口规模选项是在建立连接时用SYN与对方互换得到的。

对于客户,O_RCVBUF选项必须在connect之前设置;

对于服务器,SO_RCVBUF选项必须在listen前设置。


用法:


1.closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));



2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));


3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

这样做在Linux环境下是不会产生效果的,须如下定义:struct timeval timeout = {3,0};

//设置发送超时
int ret = setsockopt(socket,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(struct timeval));

如果超时ret = -1;返回错误码
//设置接收超时
setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval));
有两点注意就是:
1)recv ()的第四个参数需为MSG_WAITALL,在阻塞模式下不等到指定数目的数据不会返回,除非超时时间到。

还要注意的是只要设置了接收超时,在没有MSG_WAITALL时也是有效的。

说到底超时就是不让你的程序老在那儿等,到一定时间进行一次返回而已。
2)即使等待超时时间值未到,但对方已经关闭了socket, 则此时recv()会立即返回,并收到多少数据返回多少数据。

补充:
同样可以设置连接(connect)超时:即通过SO_SNDTIMO套节字参数让超时操作跳过select。
原因是:Linux内核源码中connect的超时参数和SO_SNDTIMO操作的参数一致。
因此,在linux平台下,可以通过connect之前设置SO_SNDTIMO来达到控制连接超时的目的。

struct timeval timeo;
timeo.tv_sec = 5;
timeo.tv_usec = 0;
Setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));


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_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));


6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));


7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));



8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置connect()延时,直到
accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));


9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),

以前我们一般采取的措施是"从容关闭"

shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体应用的要求

(即让没发完的数据发送出去后在关闭socket)?
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));

在客户端关闭异常断开的socket之后,想再新建socket来接连server,老是提示10038错误,需要设置此选项。

(二)

select

Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,

他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,

就是进程或是线程执行到这些函数时必须等待某个事件的发生,

如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞

(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,

以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,

若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,

它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。


Select的函数格式:

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout); 


先说明两个结构体: 
第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,

这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,

所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。

fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),

将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int,fd_set*),

检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。

第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。


具体解释select的参数: 
int maxfdp
是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错! 

fd_set * readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,

即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,

表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,

若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 

fd_set * writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,

即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,

表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,

若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 

fd_set * errorfds同上面两个参数的意图,用来监视文件错误异常。 

struct timeval * timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,

第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,

一定等到监视文件描述符集合中某个文件描述符发生变化为止;

第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,

都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;

第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超

时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。 

返回值:返回状态发生变化的描述符总数。 
负值:select错误

正值:某些文件可读写或出错

0:等待超时,没有可读写或错误的文件


对于 fd_set类型的变量我们所能做的就是声明一个变量,为变量赋一个同种类型变量的值,

或者使用以下几个宏来控制它:

#include
int FD_ZERO(int fd, fd_set *fdset);
int FD_CLR(int fd, fd_set *fdset);
int FD_SET(int fd, fd_set *fd_set);
int FD_ISSET(int fd, fd_set *fdset);

FD_ZERO宏将一个 fd_set类型变量的所有位都设为 0,使用FD_SET将变量的某个位置位。

清除某个位时可以使用 FD_CLR,我们可以使用 FD_SET来测试某个位是否被置位。
当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。

之后将我们所感兴趣的描述符所对应的位置位,操作如下:

fd_set rset;
int fd;
FD_ZERO(&rset);
FD_SET(fd, &rset);
FD_SET(stdin, &rset);


select返回后,用FD_ISSET测试给定位是否置位:
if(FD_ISSET(fd, &rset)
{ ... }


(三)

alarm();系统中的每个进程都有一个私有的闹钟。这个闹钟很像一个计时器,可以设置在一定秒数后闹钟。

时间一到,时钟就发送一个信号SIGALRM到进程。这就需要注册信号处理函数。


while(1)
{
bzero(recv, MAXSIZE);
alarm(5);
Read(connfd, recv, MAXSIZE);
printf("%s", recv);
}













你可能感兴趣的:(socket网络编程的超时设置)