http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520117243917934/
SOCKET
对于一个socket 是阻塞模式还是非阻塞模式的处理方法::方法:fcntl 设置;
即先用fcntl的F_GETFL获取flags,
用F_SETFL设置flags|O_NONBLOCK;
(注意,取消非阻塞的方式是F_SETFL 设置flags&~O_NONBLOCK)
并在recv,send 时,将flag参数设置为MSG_DONTWAIT。
实现
fcntl 函数可以将一个socket 句柄设置成非阻塞模式:
flags = fcntl(sockfd, F_GETFL, 0); //获取文件的flags值。
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //设置成非阻塞模式;
flags = fcntl(sockfd,F_GETFL,0);
fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK); //设置成阻塞模式;
并在recv,recvfrom和send,sendto数据时,将flag设置为MSG_DONTWAIT
即:recv, send 函数的最后有一个flag 参数可以设置成MSG_DONTWAIT
recv(sockfd, buff, buff_size,MSG_DONTWAIT); //非阻塞模式的消息发送
send(scokfd, buff, buff_size, MSG_DONTWAIT); //非阻塞模式的消息接受
设置之后每次的对于sockfd 的操作都是非阻塞的。
(
connect 当返回0时,表示立即创建了socket链接,
当返回-1时,需要判断errno是否是EINPROGRESS(表示当前进程正在处理),否则失败
(
下面会有select或epoll监听fd是否建立链接,
select的例子:
int ret = ::connect(_socket_fd, add.addr(), add.length());
if(ret == 0)
{
//建立链接成功
}
else if(ret < 0 && errno == EINPROGRESS) //errno == EINPROGRESS表示正在建立链接
{
// 等待连接完成,errno == EINPROGRESS表示正在建立链接
fd_set set;
FD_ZERO(&set);
FD_SET(_socket_fd,&set);
time_t = 10; //(超时时间设置为10秒)
struct timeval timeo;
timeo.tv_sec = timeout / 1000;
timeo.tv_usec = (timeout % 1000) * 1000;
int retval = select(_socket_fd + 1, NULL, &set, NULL, &timeo); //事件监听
if(retval < 0)
{
//建立链接错误close(_socket_fd)
}
else if(retval == 0) // 超时
{
//超时链接没有建立close(_socket_fd)
}
//将检测到_socket_fd读事件或写时间,并不能说明connect成功
if(FD_ISSET(_socket_fd,&set))
{
int error = 0;
socklen_t len = sizeof(error);
if(getsockopt(_socket_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
{
//建立简介失败close(_socket_fd)
}
if(error != 0) // 失败
{
//建立链接失败close(_socket_fd)
}
else
{
//建立链接成功
}
}
}
else
{
//出现错误 close(_sock_fd)
}
epoll的例子:
(//待续)
当epoll或select监听到sockfd上有EPOLL_IN或EPOLL_OUT时,即读写事件时,
并不能说明链接已经建立。
int error = 0;
socklen_t ilen = sizeof(error);
ret = getsockopt(fd,SOL_SOCKET,SO_ERROR,&error,&ilen);
if(ret < 0)
{
//说明链接建立失败,close(fd);
}
else if(error != 0 )
{
//说明链接建立失败,close(fd);
}
else
{
//说明链接建立成功。即可以向fd上写数据。
}
);
recv 当返回值为0时,表示对端已经关闭了这个链接,我们应该自己关闭这个链接,
即close(sockfd)。(因为异步操作会用select或epoll做事件触发,所以:)
如果使用select,应该将sockfd清除掉,不再监听。
如果使用epoll,系统会自己将sockfd清除掉,不再进行监听。
当返回值大于0 且 小于sizeof(buffer)时,表示数据肯定读完。(如果等于sizeof(buffer),可能有数据还没读,应该继续读,不可能有大于)
当返回值小于0,即等于-1时:
如果 errno 为 EAGAINE 或 EWOULDBLOCK
表示暂时无数据可读,可以继续读,或者等待epoll或select的后续通知。
如果 errno 为 EINTR
表示被中断了,可以继续读,或者等待epoll或select后续的通知。
否则,真的是读取数据失败。(此时应该close(sockfd))
(
发生EAGAINE,EWOULDBLOCK,EINTR错误时,
表明socket没有问题,即不用close(sockfd);
产生的原因:
EAGAINE 和 EWOULDBLOCK 可能是多进程读同一个sockfd,可能一个进程读
到数据,其他进程就读取不到数据,当然单个进程也可能出现这种情况。
对于这种错误,不需用close(sockfd)。
可以等待select或epoll的下一次触发,继续读。
)
参看自己的blog:: http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201094104357111/
(recvfrom的判断也是一样的)
send 返回值是实际发送的字符数。
因为我们知道要发送的总长度,所以,如果没有发送完,我们可以继续发送。
当返回值为 -1 时, 我们需要判断 errno:
如果errno为 EAGAINE 或 EWOULDBLOCK ,表示当前缓冲区写满,可以继续写,或者等待epoll或select的后续通知。
如果errno为EINTR ,表示被终端了,可以继续写,或者等待epoll或select的后续通知。
(
发生EAGAINE,EWOULDBLOCK,EINTR错误时,
表明socket没有问题,即不用close(sockfd);
产生的原因:
EAGAINE 和 EWOULDBLOCK 可能是多进程写同一个sockfd,可能一个进程写
了数据,其他进程就不能写数据,当然单个进程也可能出现这种情况。
对于这种错误,不需用close(sockfd)。
可以等待select或epoll的触发,下一次继续写。
)
参看自己的blog:: http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201094104357111/
否则真的出错了,即errno不为EAGAINE或EWOULDBLOCK或EINTR,此时应该close(sockfd)
(sendto的判断也是一样)
)
1.设置调用closesocket()后,仍可继续重用该socket。调用closesocket()一般不会立即关闭socket,而经历TIME_WAIT的过程。
BOOL bReuseaddr = FALSE;
setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );
2. 如果要已经处于连接状态的soket在调用closesocket()后强制关闭,不经历TIME_WAIT的过程:
BOOL bDontLinger = TRUE;
setsockopt( s, SOL_SOCKET, SO_DONTLINGER, ( const char* )&bDontLinger, sizeof( BOOL ) );
3.在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,可以设置收发时限:
int nNetTimeout = 1000; //1秒
//发送时限
setsockopt( socket, SOL_SOCKET, SO_SNDTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
//接收时限
setsockopt( socket, SOL_SOCKET, SO_RCVTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
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.在接收数据时,不执行将socket缓冲区的内容拷贝到系统缓冲区:
int nZero = 0;
setsockopt( s, 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.在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;则调用closesocket()后强制关闭
m_sLinger.l_linger = 5; //设置等待时间为5秒
setsockopt( s, SOL_SOCKET, SO_LINGER, ( const char* )&m_sLinger, sizeof( linger ) );