设置成非阻塞模式:异步
先用fcntl的F_GETFL获取flags,用F_SETFL设置flags|O_NONBLOCK; socket
即:blog
flags = fcntl(sockfd, F_GETFL, 0); //获取文件的flags值。进程
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //设置成非阻塞模式;事件
同时在接收和发送数据时,须要使用MSG_DONTWAIT标志get
即:博客
在recv,recvfrom和send,sendto数据时,将flag设置为MSG_DONTWAIT。it
设置成阻塞模式:多进程
先用fcntl的F_GETFL获取flags,用F_SETFL设置flags&~O_NONBLOCK; class
即:
flags = fcntl(sockfd,F_GETFL,0); //获取文件的flags值。
fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK); //设置成阻塞模式;
同时在接收和发送数据时,须要使用阻塞标志
即:
在recv,recvfrom和send,sendto数据时,将flag设置为0,默认是阻塞。
在将socket设置成非阻塞模式后,每次的对于sockfd 的操做都是非阻塞的;
非阻塞模式下:
connect
=0 当返回0时,表示当即建立了socket连接,
<0 当返回-1时,须要判断errno是不是EINPROGRESS(表示当前进程正在处理),不然失败。
例如:下面会有select或epoll监听fd是否创建连接,
select监听connect是否成功的例子,注意getsockopt验证,由于三次握手的第三个ACK有可能会丢失,可是客户端认为连接已经创建:
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); //相反的是FD_CLR(_sock_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或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 和 recvfrom
=0 当返回值为0时,表示对端已经关闭了这个连接,咱们应该本身关闭这个连接,即close(sockfd)。另外由于异步操做会用select或epoll作事件触发,因此:
一、若是使用select,应该使用FD_CLR(sockfd,fd_set)将sockfd清除掉,再也不监听。
二、若是使用epoll,系统会本身将sockfd清除掉,再也不进行监听。
>0 当返回值大于0 且 小于sizeof(buffer)时,表示数据确定读完。(若是等于sizeof(buffer),可能有数据还没读,应该继续读,不可能有大于)
<0 当返回值小于0,即等于-1时,分状况判断:
一、若是 errno 为 EAGAINE 或 EWOULDBLOCK
表示暂时无数据可读,能够继续读,或者等待epoll或select的后续通知。(EAGAINE,EWOULDBLOCK产生的
缘由:多是多进程读同一个sockfd,可能一个进程读到数据,其余进程就读取不到数据(相似惊群效应),固然
单个进程也可能出现这种状况。对于这种错误,不需用close(sockfd)。能够等待select或epoll的下一次触发,
继续读。)
二、若是 errno 为 EINTR
表示被中断了,能够继续读,或者等待epoll或select后续的通知。
不然,真的是读取数据失败。(此时应该close(sockfd))
send和sendto
返回值是实际发送的字符数,由于咱们知道要发送的总长度,因此,若是没有发送完,咱们能够继续发送。
<0 当返回值为 -1 时, 咱们须要判断 errno:
一、若是errno为 EAGAINE 或 EWOULDBLOCK ,表示当前缓冲区写满,能够继续写,
或者等待epoll或select的后续通知,一旦有缓冲区,就会触发写操做,这个也是常常利用的一个特性。
二、若是errno为EINTR ,表示被中断了,能够继续写,或者等待epoll或select的后续通知。
不然真的出错了,即errno不为EAGAINE或EWOULDBLOCK或EINTR,此时应该close(sockfd)
>=0 >=0且不等于要求发送的长度,应该继续send,若是等于要求发送的长度,发送完毕。