5

linux网络编程中的errno处理 - 聆湖听风

 1 year ago
source link: https://www.cnblogs.com/listenwind666/p/17212066.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在Linux网络编程中,errno是一个非常重要的变量。它记录了最近发生的系统调用错误代码。在编写网络应用程序时,合理处理errno可以帮助我们更好地了解程序出现的问题并进行调试。

通常,在Linux网络编程中发生错误时,errno会被设置为一个非零值。因此,在进行系统调用之后,我们应该始终检查errno的值。我们可以使用perror函数将错误信息打印到标准错误输出中,或者使用strerror函数将错误代码转换为错误信息字符串。

在网络编程中,处理网络连接、连接收发数据等经常会涉及到errno的处理。经过查阅了很多资料,发现没有一个系统的讲解,在不同阶段会遇到哪些errno,以及对这些errno需要如何处理。因此,本文将分为三个部分来讲解。

1. 接受连接(accept)

这一阶段发生在 accept 接收 tcp 连接中。

在accept接收tcp连接的过程中,可能会遇到以下errno:

  • EAGAIN或EWOULDBLOCK:表示当前没有连接可以接受,非阻塞模式下可以继续尝试接受连接
  • ECONNABORTED:表示连接因为某种原因被终止,可以重新尝试接受连接
  • EINTR:表示系统调用被中断,可以重新尝试接受连接
  • EINVAL:表示套接字不支持接受连接操作,需要检查套接字是否正确

其中 EINTR、EAGAIN与EWOULDBLOCK,表示可能遇到了系统中断,需要对这些errno忽略,如果是其他错误,则需要执行错误回调或者直接处理错误。

在 libevent 为这些需要忽略的errno定义了宏 EVUTIL_ERR_ACCEPT_RETRIABLE,宏里定义了上面三个需要忽略的信号,在 accept 处理时会判断如果遇到这些信号则进行忽略,下次重试就好。

/* True iff e is an error that means a accept can be retried. */#define EVUTIL_ERR_ACCEPT_RETRIABLE(e) \ ((e) == EINTR || EVUTIL_ERR_IS_EAGAIN(e) || (e) == ECONNABORTED) // libevent accept 处理代码static void listener_read_cb(evutil_socket_t fd, short what, void *p){ struct evconnlistener *lev = p; int err; evconnlistener_cb cb; evconnlistener_errorcb errorcb; void *user_data; LOCK(lev); while (1) { struct sockaddr_storage ss; ev_socklen_t socklen = sizeof(ss); evutil_socket_t new_fd = evutil_accept4_(fd, (struct sockaddr*)&ss, &socklen, lev->accept4_flags); if (new_fd < 0) break; if (socklen == 0) { /* This can happen with some older linux kernels in * response to nmap. */ evutil_closesocket(new_fd); continue; } .......... } err = evutil_socket_geterror(fd); if (EVUTIL_ERR_ACCEPT_RETRIABLE(err)) { UNLOCK(lev); return; } if (lev->errorcb != NULL) { ++lev->refcnt; errorcb = lev->errorcb; user_data = lev->user_data; errorcb(lev, user_data); listener_decref_and_unlock(lev); } else { event_sock_warn(fd, "Error from accept() call"); UNLOCK(lev); }}

2. 建立连接(connect )

这一阶段发生在 connect 连接中。

在connect连接的过程中,可能会遇到以下errno:

  • EINPROGRESS:表示连接正在进行中,需要等待连接完成
  • EALREADY:表示套接字非阻塞模式下连接请求已经发送,但连接还未完成,需要等待连接完成
  • EISCONN:表示套接字已经连接,无需再次连接
  • EINTR:表示系统调用被中断,可以重新尝试连接
  • ENETUNREACH:表示网络不可达,需要检查网络连接是否正常

其中 EINPROGRESS、EALREADY、EINTR 表示连接正在进行中,需要等待连接完成或重新尝试连接。如果是其他错误,则需要执行错误回调或者直接处理错误。

一般情况下,我们需要通过 select、poll、epoll 等 I/O 多路复用函数来等待连接完成,或者使用非阻塞的方式进行连接,等待连接完成后再进行下一步操作。

在 libevent 中,为这些需要忽略的 errno 定义了宏 EVUTIL_ERR_CONNECT_RETRIABLE,宏里定义了上面三个需要忽略的信号,在 connect 处理时会判断如果遇到这些信号则进行忽略,下次重试就好。

/* True iff e is an error that means a connect can be retried. */#define EVUTIL_ERR_CONNECT_RETRIABLE(e) \\ ((e) == EINTR || (e) == EINPROGRESS || (e) == EALREADY) // libevent connect 处理代码/* XXX we should use an enum here. *//* 2 for connection refused, 1 for connected, 0 for not yet, -1 for error. */int evutil_socket_connect_(evutil_socket_t *fd_ptr, const struct sockaddr *sa, int socklen){ int made_fd = 0; if (*fd_ptr < 0) { if ((*fd_ptr = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) goto err; made_fd = 1; if (evutil_make_socket_nonblocking(*fd_ptr) < 0) { goto err; } } if (connect(*fd_ptr, sa, socklen) < 0) { int e = evutil_socket_geterror(*fd_ptr); // 处理忽略的 errno if (EVUTIL_ERR_CONNECT_RETRIABLE(e)) return 0; if (EVUTIL_ERR_CONNECT_REFUSED(e)) return 2; goto err; } else { return 1; } err: if (made_fd) { evutil_closesocket(*fd_ptr); *fd_ptr = -1; } return -1;}

3. 连接的读写

在 Linux 网络编程中,连接读写阶段可能会遇到以下 errno:

  • EINTR:表示系统调用被中断,可以重新尝试读写
  • EAGAIN 或 EWOULDBLOCK:表示当前没有数据可读或没有缓冲区可写,需要等待下一次读写事件再尝试读写,非阻塞模式下可以继续尝试读写
  • ECONNRESET 或 EPIPE:表示连接被重置或对端关闭了连接,需要重新建立连接
  • ENOTCONN:表示连接未建立或已断开,需要重新建立连接
  • ETIMEDOUT:表示连接超时,需要重新建立连接
  • ECONNREFUSED:表示连接被拒绝,需要重新建立连接
  • EINVAL:表示套接字不支持读写操作,需要检查套接字是否正确

其中 EINTR、EAGAIN 或 EWOULDBLOCK 表示可能遇到了系统中断或当前没有数据可读或没有缓冲区可写,需要对这些 errno 忽略,如果是其他错误,则需要执行错误回调或者直接处理错误。

在 libevent 中,为这些需要忽略的 errno 定义了宏 EVUTIL_ERR_RW_RETRIABLE,宏里定义了 EINTR、EAGAIN 或 EWOULDBLOCK 需要忽略的信号,在连接的读写处理时会判断如果遇到这些信号则进行忽略,下次重试就好。

/* True iff e is an error that means a read or write can be retried. */#define EVUTIL_ERR_RW_RETRIABLE(e) \\ ((e) == EINTR || EVUTIL_ERR_IS_EAGAIN(e)) // 连接读写处理代码例子static void bufferevent_readcb(evutil_socket_t fd, short event, void *arg){ struct bufferevent *bufev = arg; struct bufferevent_private *bufev_p = BEV_UPCAST(bufev); struct evbuffer *input; int res = 0; short what = BEV_EVENT_READING; ev_ssize_t howmuch = -1, readmax=-1; bufferevent_incref_and_lock_(bufev); if (event == EV_TIMEOUT) { /* Note that we only check for event==EV_TIMEOUT. If * event==EV_TIMEOUT|EV_READ, we can safely ignore the * timeout, since a read has occurred */ what |= BEV_EVENT_TIMEOUT; goto error; } input = bufev->input; /* * If we have a high watermark configured then we don't want to * read more data than would make us reach the watermark. */ if (bufev->wm_read.high != 0) { howmuch = bufev->wm_read.high - evbuffer_get_length(input); /* we somehow lowered the watermark, stop reading */ if (howmuch <= 0) { bufferevent_wm_suspend_read(bufev); goto done; } } readmax = bufferevent_get_read_max_(bufev_p); if (howmuch < 0 || howmuch > readmax) /* The use of -1 for "unlimited" * uglifies this code. XXXX */ howmuch = readmax; if (bufev_p->read_suspended) goto done; evbuffer_unfreeze(input, 0); res = evbuffer_read(input, fd, (int)howmuch); /* XXXX evbuffer_read would do better to take and return ev_ssize_t */ evbuffer_freeze(input, 0); if (res == -1) { int err = evutil_socket_geterror(fd); // 处理需要忽略的errno if (EVUTIL_ERR_RW_RETRIABLE(err)) goto reschedule; if (EVUTIL_ERR_CONNECT_REFUSED(err)) { bufev_p->connection_refused = 1; goto done; } /* error case */ what |= BEV_EVENT_ERROR; } else if (res == 0) { /* eof case */ what |= BEV_EVENT_EOF; } if (res <= 0) goto error; bufferevent_decrement_read_buckets_(bufev_p, res); /* Invoke the user callback - must always be called last */ bufferevent_trigger_nolock_(bufev, EV_READ, 0); goto done; reschedule: goto done; error: bufferevent_disable(bufev, EV_READ); bufferevent_run_eventcb_(bufev, what, 0); done: bufferevent_decref_and_unlock_(bufev);} static void bufferevent_writecb(evutil_socket_t fd, short event, void *arg){ struct bufferevent *bufev = arg; struct bufferevent_private *bufev_p = BEV_UPCAST(bufev); int res = 0; short what = BEV_EVENT_WRITING; int connected = 0; ev_ssize_t atmost = -1; bufferevent_incref_and_lock_(bufev); if (evbuffer_get_length(bufev->output)) { evbuffer_unfreeze(bufev->output, 1); res = evbuffer_write_atmost(bufev->output, fd, atmost); evbuffer_freeze(bufev->output, 1); if (res == -1) { int err = evutil_socket_geterror(fd); // 处理需要忽略的 errno if (EVUTIL_ERR_RW_RETRIABLE(err)) goto reschedule; what |= BEV_EVENT_ERROR; } else if (res == 0) { /* eof case XXXX Actually, a 0 on write doesn't indicate an EOF. An ECONNRESET might be more typical. */ what |= BEV_EVENT_EOF; } if (res <= 0) goto error; bufferevent_decrement_write_buckets_(bufev_p, res); } if (evbuffer_get_length(bufev->output) == 0) { event_del(&bufev->ev_write); } /* * Invoke the user callback if our buffer is drained or below the * low watermark. */ if (res || !connected) { bufferevent_trigger_nolock_(bufev, EV_WRITE, 0); } goto done; reschedule: if (evbuffer_get_length(bufev->output) == 0) { event_del(&bufev->ev_write); } goto done; error: bufferevent_disable(bufev, EV_WRITE); bufferevent_run_eventcb_(bufev, what, 0); done: bufferevent_decref_and_unlock_(bufev);}

本文介绍了在 Linux 网络编程中处理 errno 的方法。在接受连接、建立连接和连接读写阶段可能会遇到多种 errno,如 EINTR、EAGAIN、EWOULDBLOCK、ECONNRESET、EPIPE、ENOTCONN、ETIMEDOUT、ECONNREFUSED、EINVAL 等,需要对一些 errno 进行忽略,对于其他错误则需要执行错误回调或者直接处理错误。在 libevent 中,为这些需要忽略的 errno 定义了宏,如 EVUTIL_ERR_ACCEPT_RETRIABLE、EVUTIL_ERR_CONNECT_RETRIABLE、EVUTIL_ERR_RW_RETRIABLE 等,方便开发者处理这些 errno。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK