linux网络编程中的errno处理
在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处理的更多相关文章
- linux网络编程中INADDR_ANY的含义
INADDR_ANY选项 网络编程中常用到bind函数,需要绑定IP地址,这时可以设置INADDR_ANY INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或&q ...
- linux网络编程中的基本概念
int close(int fd)(假设是服务器端) close 关闭了自身数据传输的两个方向.close一个TCP套接字的默认行为是把该套接字标记成已关闭,然后立即返回到调用进程.该套接字描述符不能 ...
- linux网络编程中的超时设置
1 下面是在网上找到的资料,先非常的感谢. 用setsockopt()来控制recv()与send()的超时 在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,而设置收发超时 ...
- linux网络编程中阻塞和非阻塞socket的区别
读操作 对于阻塞的socket,当socket的接收缓冲区中没有数据时,read调用会一直阻塞住,直到有数据到来才返 回.当socket缓冲区中的数据量小于期望读取的数据量时,返回实际读取的字节数.当 ...
- Linux 网络编程中的read和write函数正确的使用方式
字节流套接字上的read和write函数所表现的行为不同于通常的文件IO,字节流套接字上调用read和write输入或输出的可能比请求的数量少,然而这不是出错的状态,例如某个中端使read和write ...
- linux网络编程中的shutdown()与close()函数
1.close()函数 int close(int sockfd); //返回成功为0,出错为-1 close 一个套接字的默认行为是把套接字标记为已关闭,然后立即返回到调用进程,该套接字不能再由cl ...
- linux网络编程中需要注意的信号SIGPIPE
在调试cs时,s端循环收,c端循环发,s端意外崩溃后,c端自动退出,终端提示SIGPIPE导致c端退出.man 7 signal: SIGPIPE Term Broken pipe: write to ...
- Linux网络编程中tcp_server和tcp_client函数的封装
本文的主要目的是将server套接字和client套接字的获取,做一个简易的封装,使用C语言完成. tcp_server 服务器端fd的获取主要分为以下几步: 1.创建socket,这一步仅仅 ...
- linux 网络编程
linux网络编程中主要分为服务器和客户端两部分,而网络编程中又分为TCP和UDP两种.TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议. ...
- Proxy源代码分析——谈谈如何学习Linux网络编程
Linux是一个可靠性非常高的操作系统,但是所有用过Linux的朋友都会感觉到, Linux和Windows这样的"傻瓜"操作系统(这里丝毫没有贬低Windows的意思,相反这应该 ...
随机推荐
- Software--EB--Project 身份验证
2018-01-09 16:57:51 身份验证服务 应该有两种形式得身份验证机制: 1. 在其他网站上又 Web 账号得顾客在注册或登陆到该网站时候应该能够使用这些账号. 2.没有的顾客或者希望新 ...
- Window:下载并安装FileZilla客户端
FileZilla官方网站:https://filezilla-project.org/ 环境 操作系统:Window 10 企业版LTSC;内存:8GB;操作类型:64位. 说明 本人想在腾讯云的系 ...
- cadence报错because the library part is newer than the part in the design cache.Select the part in the cache and choose Design-Update Cache,and then place the part again.
cadence报错because the library part is newer than the part in the design cache.Select the part in the ...
- mysq 报错, sql语句在数据库里运行正常, 在内网测试正常,打包放外网的时候就报下面错误
sql语句为: select t1.day as day , any_value(IFNULL(t2.avgNum,0)) as avgNum, any_value(IFNULL(t2.maxNum, ...
- PHP Redis - String (字符串)
string 是 Redis 最基本的类型,与Memcached类似,一个 key 对应一个 value string 类型是二进制 安全的.这意味着 Redis 的 string 可以包含任何数据. ...
- python 时间戳转日期 不自动补零 without zero-padding
1. 时间戳转日期 代码 import time timestamp = 1568171336 time_format = "%Y-%m-%d %H:%M:%S" time_loc ...
- js小数相加精度不准确
例: 0.1+0.1+0.1+0.7 != 1 0.1+0.2 != 0.3 解决办法: 相加 function addNum(num1, num2) { let sq1, sq2; ...
- Merge Overlapping Intervals
refer to: https://www.algoexpert.io/questions/Merge%20Overlapping%20Intervals Problem Statement Samp ...
- 三,打包electron
1,在当前项目下运行 npm install --save-dev @electron-forge/cli npx electron-forge import 此时package.json内容如下: ...
- MySQL核心知识
MySQL常用的命令 启动:net start mySql; 进入:mysql -u root -p/mysql -h localhost -u root -p databaseName; 列出数据库 ...