В сетевом программировании Linux errno — очень важная переменная. Он записывает самые последние коды ошибок системных вызовов. При написании сетевых приложений правильная обработка errno может помочь нам лучше понять проблемы в программе и отладить их.
Обычно при возникновении ошибки в сетевом программировании Linux для параметра errno устанавливается ненулевое значение. Поэтому мы всегда должны проверять значение errno после выполнения системного вызова. Мы можем использовать функцию perror для вывода сообщения об ошибке в стандартный вывод ошибок или использовать функцию strerror для преобразования кода ошибки в строку сообщения об ошибке.
В сетевом программировании обработка сетевых соединений, подключение для отправки и получения данных и т. д. часто включают обработку errno. Изучив большой объем информации, я обнаружил, что не существует систематического объяснения того, с какими ошибками можно столкнуться на разных этапах и как с этими ошибками бороться. Поэтому данная статья будет разделена на три части.
Этот этап наступает, когда Accept получает TCP-соединение.
В процессе принятия TCP-соединения вы можете столкнуться со следующей ошибкой:
Среди них EINTR, EAGAIN и EWOULDBLOCK указывают, что, возможно, произошло системное прерывание, и эти ошибки необходимо игнорировать. Если это другие ошибки, необходимо выполнить обратный вызов ошибки или обработать ошибку напрямую.
Макрос EVUTIL_ERR_ACCEPT_RETRIABLE определен в libevent для этих ошибок, которые необходимо игнорировать. Макрос определяет три вышеуказанных сигнала, которые необходимо игнорировать. Во время обработки принятия будет принято решение игнорировать эти сигналы, если они встречаются, и просто попробуйте еще раз. в следующий раз.
/* 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);
}
}
Эта фаза происходит во время подключения соединения.
В процессе подключения вы можете столкнуться со следующей ошибкой:
Среди них EINPROGRESS, EALREADY и EINTR указывают, что соединение выполняется и вам необходимо дождаться завершения соединения или повторить попытку соединения. Если это другая ошибка, вам необходимо выполнить обратный вызов ошибки или обработать ошибку напрямую.
В обычных обстоятельствах нам нужно дождаться завершения соединения с помощью функций мультиплексирования ввода-вывода, таких как select, poll и epoll, или использовать неблокирующий метод для подключения и дождаться завершения соединения, прежде чем переходить к следующий шаг.
В libevent макрос EVUTIL_ERR_CONNECT_RETRIABLE определен для этих ошибок, которые необходимо игнорировать. Макрос определяет три вышеуказанных сигнала, которые необходимо игнорировать. Во время обработки соединения будет принято решение игнорировать эти сигналы, если они встречаются, и просто попытаться. еще раз в следующий раз.
/* 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;
}
При сетевом программировании Linux на этапах чтения и записи соединения может возникнуть следующая ошибка:
Среди них EINTR, EAGAIN или EWOULDBLOCK указывают, что, возможно, произошло системное прерывание или в данный момент нет данных для чтения или буфера для записи. Эти ошибки следует игнорировать. Если это другие ошибки, вам необходимо выполнить ошибку. обратный вызов или обработать ошибку напрямую.
В libevent макрос 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);
// Обработать ошибку, которую нужно игнорировать
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);
}
В этой статье описывается, как обрабатывать errno в сетевом программировании Linux. На этапах принятия соединения, установления соединения, а также чтения и записи соединения вы можете столкнуться с различными ошибками, такими как EINTR, EAGAIN, EWOULDBLOCK, ECONNRESET, EPIPE, ENOTCONN, ETIMEDOUT, ECONNREFUSED, EINVAL и т. д. Некоторые ошибки следует игнорировать. и другие ошибки необходимо игнорировать. Вам необходимо выполнить обратный вызов ошибки или обработать ошибку напрямую. В libevent для этих ошибок определены макросы, которые необходимо игнорировать, например EVUTIL_ERR_ACCEPT_RETRIABLE, EVUTIL_ERR_CONNECT_RETRIABLE, EVUTIL_ERR_RW_RETRIABLE и т. д., чтобы облегчить разработчикам обработку этих ошибок.