在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);
}

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。

linux网络编程中的errno处理的更多相关文章

  1. linux网络编程中INADDR_ANY的含义

    INADDR_ANY选项 网络编程中常用到bind函数,需要绑定IP地址,这时可以设置INADDR_ANY INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或&q ...

  2. linux网络编程中的基本概念

    int close(int fd)(假设是服务器端) close 关闭了自身数据传输的两个方向.close一个TCP套接字的默认行为是把该套接字标记成已关闭,然后立即返回到调用进程.该套接字描述符不能 ...

  3. linux网络编程中的超时设置

    1 下面是在网上找到的资料,先非常的感谢. 用setsockopt()来控制recv()与send()的超时 在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,而设置收发超时 ...

  4. linux网络编程中阻塞和非阻塞socket的区别

    读操作 对于阻塞的socket,当socket的接收缓冲区中没有数据时,read调用会一直阻塞住,直到有数据到来才返 回.当socket缓冲区中的数据量小于期望读取的数据量时,返回实际读取的字节数.当 ...

  5. Linux 网络编程中的read和write函数正确的使用方式

    字节流套接字上的read和write函数所表现的行为不同于通常的文件IO,字节流套接字上调用read和write输入或输出的可能比请求的数量少,然而这不是出错的状态,例如某个中端使read和write ...

  6. linux网络编程中的shutdown()与close()函数

    1.close()函数 int close(int sockfd); //返回成功为0,出错为-1 close 一个套接字的默认行为是把套接字标记为已关闭,然后立即返回到调用进程,该套接字不能再由cl ...

  7. linux网络编程中需要注意的信号SIGPIPE

    在调试cs时,s端循环收,c端循环发,s端意外崩溃后,c端自动退出,终端提示SIGPIPE导致c端退出.man 7 signal: SIGPIPE Term Broken pipe: write to ...

  8. Linux网络编程中tcp_server和tcp_client函数的封装

    本文的主要目的是将server套接字和client套接字的获取,做一个简易的封装,使用C语言完成.   tcp_server   服务器端fd的获取主要分为以下几步: 1.创建socket,这一步仅仅 ...

  9. linux 网络编程

    linux网络编程中主要分为服务器和客户端两部分,而网络编程中又分为TCP和UDP两种.TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议. ...

  10. Proxy源代码分析——谈谈如何学习Linux网络编程

    Linux是一个可靠性非常高的操作系统,但是所有用过Linux的朋友都会感觉到, Linux和Windows这样的"傻瓜"操作系统(这里丝毫没有贬低Windows的意思,相反这应该 ...

随机推荐

  1. mongodb更改账户密码

    docker部署方式更改 sudo docker exec -it mongodb mongo admin (登录数据库容器) use admin db.auth('admin','shijiehep ...

  2. TypeScript 合并以及删除数组数据

    1.添加 concat() 2.删除(替换) splice() array.splice(index,int,any) index 是 array数组起始位置的index(从0开始) int是操作从i ...

  3. How to use lspci, lsscsi, lsusb, and lsblk to get Linux system devices information

    There are many utilities available to check Linux system hardware information. Some commands report ...

  4. ansible-file

    path参数 :必须参数,用于指定要操作的文件或目录,在之前版本的ansible中,使用dest参数或者name参数指定要操作的文件或目录,为了兼容之前的版本,使用dest或name也可以. stat ...

  5. 可视化 之D3 与echarts 对比以及应用场景

    可视化:我们想要看出一组数据的大小关系,单看数据显然不够直观.那么我们可以将它转换为一种简单易懂的图表的形式,就可以更加直观的获取数据所传递给我们的信息.这个过程就叫做数据可视化.可视化常用2个前端库 ...

  6. npm config get prefer-offline

    npm config get prefer-offline

  7. 使用C++进行冒泡排序

    #include "pch.h" #include <iostream> using namespace std;手动输入10个数,进行冒泡排序 int main() ...

  8. 在Unity3D中开发的Dissolve Shader

    Swordmaster Dissolve Shaders 特点 本插件共包含两种Dissolve Shader: (1).一种类型是Bumped Specular工作流的Dissolve Shader ...

  9. sequelize的创建接口以及模糊查询

    第一步: 在routes里面复制index.js更改为xxx.js(例如arctile.js) res.json()返回的就是json文件 第二步: 在views里app.js引用路由 第三步:查找数 ...

  10. go的相关包time、os、rand、fmt

    time 1.time包 2.time.Time类型, 用来表示时间 3.取当前时间, now := time.Now() 4.time.Now().Day(),time.Now().Minute() ...