在linux我们在进行socket操作时为了更高的效率我们都使用无阻塞(non-blocking)的方式进行socket操作,而在进行处理时通常我们会遇到如下的几个错误(errno),那么他们意义如何,以及该如何处理呢?首先我们先看一下man 手册的对于EINPROGRESS、EAGAIN、EINTR、EWOULDBLOCK的解释如下:
EINPROGRESS
The socket is non-blocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by selecting the socket for writing. After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).
EAGAIN
The socket is marked non-blocking and the receive operation would block, or a receive timeout had been set and the timeout expired before data was received.
EINTR
The system call was interrupted by a signal that was caught.
EWOULDBLOCK
你可以从字面上进行理解为 it would lead to a blocking(when it is in blocking mode),在linux下其实他等于EAGAIN。
我们首先看看connect,以及为何connect也使用非阻塞的方式以及如何处理。由于socket连接的建立是需要进行三次握手的,如果在局域网内部这个倒是很快的,如果在网络没有那么通畅的环境中那么可能三次握手就会消耗较长的时间,而如果使用阻塞的方式进行就会是程序长时间处于等待的状态而影响整体的处理效率;在局域网下面虽然会快很多,但是如果在一些极端的情况下程序需要建立大量的连接,那么这导致的等待消耗也是比较客观的;所以通常我们在考虑到高效的设计时connect也都会使用无阻塞的方式去进行,通常其处理过程如下:
-
fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK);
-
int ret = connect(sock_fd, (struct sockaddr*)&s_addr, addr_len); if(ret < 0) { if( errno == EINPROGRESS ) { break;
-
} else {
-
perror("connect fail'\n"); return 0;
-
}
-
}
我们在设置O_NONBLOCK后取进行connect,即使连接操作没有完成connect也会立即返回,在没有完成时还会返回-1,这时候我们就要通过errno去进行判断,errno == EINPROGRESS就表示连接操作还在进行中。这样我们就需要后继使用select/poll/epoll之类去注册响应的事件并设置超时来判断连接是否建立成功。
使用非阻塞的 connect 是有如下几个问题需要注意:
1. 可能调用 connect 时会立即建立连接(比如:连接本地端口)。
2. 源自Berkeley的实现(和Posix.1g)有两条与select和非阻塞IO相关的规则:
A:当连接建立成功时,套接口描述符变成可写;
B:当连接出错时,套接口描述符变成既可读又可写;
然而我们不能单凭这个条件来判断连接是否建立成功,更为安全可靠的方法是使用getsockopt来获取socket的当前状态以进行判断,如果成功则返回的err值为0,否则为ECONNREFUSED,ETIMEDOUT等;
当然这里需要注意的是getsockopt源自Berkeley的实现是返回0,等待处理的错误在变量errno中返回;但是Solaris会让getsockopt返回-1,errno置为待处理的错误;我们对这两种情况都要处理;这里我们可以使用以下的技巧
-
if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &err, &len) == -1){
-
err = errno;
-
}
那么这样只要判断err就可以了。
对于recv,返回值为-1是则需要判断errno,如果其为EAGAIN(EWOULDBLOCK),EINTR通常的处理方法都是继续正常的处理即可,比如继续epoll_wait。而返回0则表示对端已经关闭连接,我们本地也必须关闭该socket防止在半开的socket上面进行读写数据。
示例代码如下:
-
#include <iostream>
-
#include <sys/socket.h>
-
#include <sys/epoll.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
#include <fcntl.h>
-
#include <unistd.h>
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <errno.h>
-
#include <assert.h>
-
-
using namespace std;
-
-
-
void setnonblocking(int sock)
-
{
-
int opts;
-
opts=fcntl(sock,F_GETFL);
-
if(opts<0)
-
{
-
perror("fcntl(sock,GETFL)");
-
exit(1);
-
}
-
opts = opts|O_NONBLOCK;
-
if(fcntl(sock,F_SETFL,opts)<0)
-
{
-
perror("fcntl(sock,SETFL,opts)");
-
exit(1);
-
}
-
}
-
-
int main(int argc, char* argv[])
-
{
-
int port, sockfd, ep, ret, events, err;
-
socklen_t len;
-
char line[1024];
-
struct sockaddr_in sock_addr;
-
struct epoll_event set_event;
-
struct epoll_event out_event[1024];
-
-
if ( 3 == argc ) {
-
if( (port = atoi(argv[2])) < 0 )
-
{
-
fprintf(stderr,"Usage:%s Ip portnumber/a/n",argv[0]);
-
return -1;
-
}
-
} else {
-
fprintf(stderr,"Usage:%s ip portnumber/a/n",argv[0]);
-
return -1;
-
}
-
-
sockfd = socket(AF_INET, SOCK_STREAM, 0);
-
setnonblocking(sockfd);
-
-
ep = epoll_create(1024);
-
assert(ep > 0);
-
set_event.events = (uint32_t) (EPOLLIN | EPOLLOUT | EPOLLET);
-
set_event.data.ptr = (void*) NULL;
-
assert( epoll_ctl(ep, EPOLL_CTL_ADD, sockfd, &set_event) == 0);
-
-
bzero(&sock_addr, sizeof(sock_addr));
-
sock_addr.sin_family = AF_INET;
-
char* local_addr = argv[1];
-
inet_aton(local_addr,&(sock_addr.sin_addr));//htons(portnumber);
-
sock_addr.sin_port = htons(port);
-
-
err = 0;
-
len = sizeof(err);
-
if (connect(sockfd, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) < 0) {
-
if (errno != EINPROGRESS)
-
{
-
printf("Connect fail%d, %s\n", errno, strerror(errno));
-
return 0;
-
}
-
}
-
-
ret = epoll_wait(ep, out_event, sizeof(out_event), 500);
-
for (int i = 0; i < ret; i++) {
-
struct epoll_event ev = out_event[i];
-
events = ev.events;
-
-
if (events & EPOLLOUT || events & EPOLLIN || events || EPOLLOUT) {
-
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len) == -1)
-
{
-
err = errno;
-
}
-
-
printf("epoll evoked ");
-
if (events & EPOLLIN) {
-
printf("EPOLLIN, ");
-
}
-
if (events & EPOLLERR) {
-
printf("EPOLLERR, ");
-
}
-
if (events & EPOLLOUT) {
-
printf("EPOLLOUT\n");
-
}
-
}
-
if (err != 0)
-
{
-
printf("Connect failed [%s]\n", strerror(err));
-
}
-
}
-
-
ret = recv(sockfd, line, sizeof(line), 0);
-
if (ret < 0) {
-
if (errno == EAGAIN || errno == EINTR /* | errno == EWOULDBLOCK */) {
-
printf("recv failed, errno:%d, try later\n", errno);
-
ret = 0;
-
}
-
else {
-
printf("recv failed, errno:%d\n", errno);
-
close(sockfd);
-
ret = -1;
-
}
-
}
-
else if (ret == 0) {
-
printf("socket have been closed, errno:%d\n", errno);
-
close(sockfd);
-
ret = -1;
-
} else {
-
printf("Message recieved: %s\n", line);
-
}
-
-
if (ret >= 0) {
-
printf("pretending to do some stuff.\n");
-
}
-
-
close(sockfd);
-
-
return 0;
-
}
阅读(1237) | 评论(0) | 转发(0) |