经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Linux/Shell » 查看文章
linux网络编程中的errno处理
来源:cnblogs  作者:聆湖听风  时间:2023/3/14 8:47:32  对本文有异议

在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 处理时会判断如果遇到这些信号则进行忽略,下次重试就好。

  1. /* True iff e is an error that means a accept can be retried. */
  2. #define EVUTIL_ERR_ACCEPT_RETRIABLE(e) ((e) == EINTR || EVUTIL_ERR_IS_EAGAIN(e) || (e) == ECONNABORTED)
  3. // libevent accept 处理代码
  4. static void listener_read_cb(evutil_socket_t fd, short what, void *p)
  5. {
  6. struct evconnlistener *lev = p;
  7. int err;
  8. evconnlistener_cb cb;
  9. evconnlistener_errorcb errorcb;
  10. void *user_data;
  11. LOCK(lev);
  12. while (1) {
  13. struct sockaddr_storage ss;
  14. ev_socklen_t socklen = sizeof(ss);
  15. evutil_socket_t new_fd = evutil_accept4_(fd, (struct sockaddr*)&ss, &socklen, lev->accept4_flags);
  16. if (new_fd < 0)
  17. break;
  18. if (socklen == 0) {
  19. /* This can happen with some older linux kernels in
  20. * response to nmap. */
  21. evutil_closesocket(new_fd);
  22. continue;
  23. }
  24. ..........
  25. }
  26. err = evutil_socket_geterror(fd);
  27. if (EVUTIL_ERR_ACCEPT_RETRIABLE(err)) {
  28. UNLOCK(lev);
  29. return;
  30. }
  31. if (lev->errorcb != NULL) {
  32. ++lev->refcnt;
  33. errorcb = lev->errorcb;
  34. user_data = lev->user_data;
  35. errorcb(lev, user_data);
  36. listener_decref_and_unlock(lev);
  37. } else {
  38. event_sock_warn(fd, "Error from accept() call");
  39. UNLOCK(lev);
  40. }
  41. }

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 处理时会判断如果遇到这些信号则进行忽略,下次重试就好。

  1. /* True iff e is an error that means a connect can be retried. */
  2. #define EVUTIL_ERR_CONNECT_RETRIABLE(e) \ ((e) == EINTR || (e) == EINPROGRESS || (e) == EALREADY)
  3. // libevent connect 处理代码
  4. /* XXX we should use an enum here. */
  5. /* 2 for connection refused, 1 for connected, 0 for not yet, -1 for error. */
  6. int evutil_socket_connect_(evutil_socket_t *fd_ptr, const struct sockaddr *sa, int socklen)
  7. {
  8. int made_fd = 0;
  9. if (*fd_ptr < 0) {
  10. if ((*fd_ptr = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
  11. goto err;
  12. made_fd = 1;
  13. if (evutil_make_socket_nonblocking(*fd_ptr) < 0) {
  14. goto err;
  15. }
  16. }
  17. if (connect(*fd_ptr, sa, socklen) < 0) {
  18. int e = evutil_socket_geterror(*fd_ptr);
  19. // 处理忽略的 errno
  20. if (EVUTIL_ERR_CONNECT_RETRIABLE(e))
  21. return 0;
  22. if (EVUTIL_ERR_CONNECT_REFUSED(e))
  23. return 2;
  24. goto err;
  25. } else {
  26. return 1;
  27. }
  28. err:
  29. if (made_fd) {
  30. evutil_closesocket(*fd_ptr);
  31. *fd_ptr = -1;
  32. }
  33. return -1;
  34. }

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 需要忽略的信号,在连接的读写处理时会判断如果遇到这些信号则进行忽略,下次重试就好。

  1. /* True iff e is an error that means a read or write can be retried. */
  2. #define EVUTIL_ERR_RW_RETRIABLE(e) \ ((e) == EINTR || EVUTIL_ERR_IS_EAGAIN(e))
  3. // 连接读写处理代码例子
  4. static void bufferevent_readcb(evutil_socket_t fd, short event, void *arg)
  5. {
  6. struct bufferevent *bufev = arg;
  7. struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
  8. struct evbuffer *input;
  9. int res = 0;
  10. short what = BEV_EVENT_READING;
  11. ev_ssize_t howmuch = -1, readmax=-1;
  12. bufferevent_incref_and_lock_(bufev);
  13. if (event == EV_TIMEOUT) {
  14. /* Note that we only check for event==EV_TIMEOUT. If
  15. * event==EV_TIMEOUT|EV_READ, we can safely ignore the
  16. * timeout, since a read has occurred */
  17. what |= BEV_EVENT_TIMEOUT;
  18. goto error;
  19. }
  20. input = bufev->input;
  21. /*
  22. * If we have a high watermark configured then we don't want to
  23. * read more data than would make us reach the watermark.
  24. */
  25. if (bufev->wm_read.high != 0) {
  26. howmuch = bufev->wm_read.high - evbuffer_get_length(input);
  27. /* we somehow lowered the watermark, stop reading */
  28. if (howmuch <= 0) {
  29. bufferevent_wm_suspend_read(bufev);
  30. goto done;
  31. }
  32. }
  33. readmax = bufferevent_get_read_max_(bufev_p);
  34. if (howmuch < 0 || howmuch > readmax) /* The use of -1 for "unlimited"
  35. * uglifies this code. XXXX */
  36. howmuch = readmax;
  37. if (bufev_p->read_suspended)
  38. goto done;
  39. evbuffer_unfreeze(input, 0);
  40. res = evbuffer_read(input, fd, (int)howmuch); /* XXXX evbuffer_read would do better to take and return ev_ssize_t */
  41. evbuffer_freeze(input, 0);
  42. if (res == -1) {
  43. int err = evutil_socket_geterror(fd);
  44. // 处理需要忽略的errno
  45. if (EVUTIL_ERR_RW_RETRIABLE(err))
  46. goto reschedule;
  47. if (EVUTIL_ERR_CONNECT_REFUSED(err)) {
  48. bufev_p->connection_refused = 1;
  49. goto done;
  50. }
  51. /* error case */
  52. what |= BEV_EVENT_ERROR;
  53. } else if (res == 0) {
  54. /* eof case */
  55. what |= BEV_EVENT_EOF;
  56. }
  57. if (res <= 0)
  58. goto error;
  59. bufferevent_decrement_read_buckets_(bufev_p, res);
  60. /* Invoke the user callback - must always be called last */
  61. bufferevent_trigger_nolock_(bufev, EV_READ, 0);
  62. goto done;
  63. reschedule:
  64. goto done;
  65. error:
  66. bufferevent_disable(bufev, EV_READ);
  67. bufferevent_run_eventcb_(bufev, what, 0);
  68. done:
  69. bufferevent_decref_and_unlock_(bufev);
  70. }
  71. static void bufferevent_writecb(evutil_socket_t fd, short event, void *arg)
  72. {
  73. struct bufferevent *bufev = arg;
  74. struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
  75. int res = 0;
  76. short what = BEV_EVENT_WRITING;
  77. int connected = 0;
  78. ev_ssize_t atmost = -1;
  79. bufferevent_incref_and_lock_(bufev);
  80. if (evbuffer_get_length(bufev->output)) {
  81. evbuffer_unfreeze(bufev->output, 1);
  82. res = evbuffer_write_atmost(bufev->output, fd, atmost);
  83. evbuffer_freeze(bufev->output, 1);
  84. if (res == -1) {
  85. int err = evutil_socket_geterror(fd);
  86. // 处理需要忽略的 errno
  87. if (EVUTIL_ERR_RW_RETRIABLE(err))
  88. goto reschedule;
  89. what |= BEV_EVENT_ERROR;
  90. } else if (res == 0) {
  91. /* eof case
  92. XXXX Actually, a 0 on write doesn't indicate
  93. an EOF. An ECONNRESET might be more typical.
  94. */
  95. what |= BEV_EVENT_EOF;
  96. }
  97. if (res <= 0)
  98. goto error;
  99. bufferevent_decrement_write_buckets_(bufev_p, res);
  100. }
  101. if (evbuffer_get_length(bufev->output) == 0) {
  102. event_del(&bufev->ev_write);
  103. }
  104. /*
  105. * Invoke the user callback if our buffer is drained or below the
  106. * low watermark.
  107. */
  108. if (res || !connected) {
  109. bufferevent_trigger_nolock_(bufev, EV_WRITE, 0);
  110. }
  111. goto done;
  112. reschedule:
  113. if (evbuffer_get_length(bufev->output) == 0) {
  114. event_del(&bufev->ev_write);
  115. }
  116. goto done;
  117. error:
  118. bufferevent_disable(bufev, EV_WRITE);
  119. bufferevent_run_eventcb_(bufev, what, 0);
  120. done:
  121. bufferevent_decref_and_unlock_(bufev);
  122. }

4. 总结

本文介绍了在 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。

原文链接:https://www.cnblogs.com/listenwind666/p/17212066.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号