обработка ошибок в сетевом программировании Linux
обработка ошибок в сетевом программировании Linux

В сетевом программировании Linux errno — очень важная переменная. Он записывает самые последние коды ошибок системных вызовов. При написании сетевых приложений правильная обработка errno может помочь нам лучше понять проблемы в программе и отладить их.

Обычно при возникновении ошибки в сетевом программировании Linux для параметра errno устанавливается ненулевое значение. Поэтому мы всегда должны проверять значение errno после выполнения системного вызова. Мы можем использовать функцию perror для вывода сообщения об ошибке в стандартный вывод ошибок или использовать функцию strerror для преобразования кода ошибки в строку сообщения об ошибке.

В сетевом программировании обработка сетевых соединений, подключение для отправки и получения данных и т. д. часто включают обработку errno. Изучив большой объем информации, я обнаружил, что не существует систематического объяснения того, с какими ошибками можно столкнуться на разных этапах и как с этими ошибками бороться. Поэтому данная статья будет разделена на три части.

1. Примите соединение (accept)

Этот этап наступает, когда Accept получает TCP-соединение.

В процессе принятия TCP-соединения вы можете столкнуться со следующей ошибкой:

  • EAGAIN или EWOULDBLOCK: указывает, что в настоящее время нет соединения для принятия. Вы можете продолжать пытаться принимать соединения в неблокирующем режиме.
  • ECONNABORTED: указывает, что соединение по какой-то причине было прервано, и вы можете повторить попытку принятия соединения.
  • EINTR: Указывает, что системный вызов был прерван, и вы можете повторить попытку принятия соединения.
  • EINVAL: указывает, что сокет не поддерживает прием операций подключения, и вам необходимо проверить правильность сокета.

Среди них EINTR, EAGAIN и EWOULDBLOCK указывают, что, возможно, произошло системное прерывание, и эти ошибки необходимо игнорировать. Если это другие ошибки, необходимо выполнить обратный вызов ошибки или обработать ошибку напрямую.

Макрос EVUTIL_ERR_ACCEPT_RETRIABLE определен в libevent для этих ошибок, которые необходимо игнорировать. Макрос определяет три вышеуказанных сигнала, которые необходимо игнорировать. Во время обработки принятия будет принято решение игнорировать эти сигналы, если они встречаются, и просто попробуйте еще раз. в следующий раз.

Язык кода:c++
копировать
/* 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. Установить соединение (подключиться)

Эта фаза происходит во время подключения соединения.

В процессе подключения вы можете столкнуться со следующей ошибкой:

  • EINPROGRESS: указывает, что соединение находится в процессе и необходимо дождаться завершения соединения.
  • EALREADY: указывает, что запрос на соединение был отправлен в неблокирующем режиме сокета, но соединение не было завершено и необходимо дождаться завершения соединения.
  • EISCONN: указывает, что сокет подключен и нет необходимости подключаться повторно.
  • EINTR: Указывает, что системный вызов был прерван и вы можете повторить попытку соединения.
  • ENETUNREACH: указывает, что сеть недоступна и вам необходимо проверить, нормально ли сетевое соединение.

Среди них EINPROGRESS, EALREADY и EINTR указывают, что соединение выполняется и вам необходимо дождаться завершения соединения или повторить попытку соединения. Если это другая ошибка, вам необходимо выполнить обратный вызов ошибки или обработать ошибку напрямую.

В обычных обстоятельствах нам нужно дождаться завершения соединения с помощью функций мультиплексирования ввода-вывода, таких как select, poll и epoll, или использовать неблокирующий метод для подключения и дождаться завершения соединения, прежде чем переходить к следующий шаг.

В libevent макрос EVUTIL_ERR_CONNECT_RETRIABLE определен для этих ошибок, которые необходимо игнорировать. Макрос определяет три вышеуказанных сигнала, которые необходимо игнорировать. Во время обработки соединения будет принято решение игнорировать эти сигналы, если они встречаются, и просто попытаться. еще раз в следующий раз.

Язык кода:c++
копировать
/* 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 на этапах чтения и записи соединения может возникнуть следующая ошибка:

  • EINTR: Указывает, что системный вызов был прерван и вы можете повторить попытку чтения и записи.
  • EAGAIN или EWOULDBLOCK: указывает, что в данный момент нет данных для чтения или буфера для записи. Прежде чем пытаться выполнить чтение или запись, необходимо дождаться следующего события чтения и записи. Вы можете продолжить попытки чтения и записи в неблокирующем режиме. режим.
  • ECONNRESET или EPIPE: указывает, что соединение было сброшено или партнер закрыл соединение и ему необходимо повторно установить соединение.
  • ENOTCONN: Указывает, что соединение не установлено или отключено и его необходимо восстановить.
  • ETIMEDOUT: указывает, что время ожидания соединения истекло и его необходимо восстановить.
  • ECONNREFUSED: указывает, что в соединении было отказано и его необходимо восстановить.
  • EINVAL: указывает, что сокет не поддерживает операции чтения и записи, и вам необходимо проверить правильность сокета.

Среди них EINTR, EAGAIN или EWOULDBLOCK указывают, что, возможно, произошло системное прерывание или в данный момент нет данных для чтения или буфера для записи. Эти ошибки следует игнорировать. Если это другие ошибки, вам необходимо выполнить ошибку. обратный вызов или обработать ошибку напрямую.

В libevent макрос EVUTIL_ERR_RW_RETRIABLE определен для этих ошибок, которые необходимо игнорировать. Макрос определяет сигналы EINTR, EAGAIN или EWOULDBLOCK, которые необходимо игнорировать. Во время обработки чтения и записи соединения будет определено, что если. эти сигналы встречаются, они будут проигнорированы и перезапущены в следующий раз. Просто попробуйте.

Язык кода:c++
копировать
/* 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);
}

4. Резюме

В этой статье описывается, как обрабатывать errno в сетевом программировании Linux. На этапах принятия соединения, установления соединения, а также чтения и записи соединения вы можете столкнуться с различными ошибками, такими как EINTR, EAGAIN, EWOULDBLOCK, ECONNRESET, EPIPE, ENOTCONN, ETIMEDOUT, ECONNREFUSED, EINVAL и т. д. Некоторые ошибки следует игнорировать. и другие ошибки необходимо игнорировать. Вам необходимо выполнить обратный вызов ошибки или обработать ошибку напрямую. В libevent для этих ошибок определены макросы, которые необходимо игнорировать, например EVUTIL_ERR_ACCEPT_RETRIABLE, EVUTIL_ERR_CONNECT_RETRIABLE, EVUTIL_ERR_RW_RETRIABLE и т. д., чтобы облегчить разработчикам обработку этих ошибок.

boy illustration
Учебное пособие по Jetpack Compose для начинающих, базовые элементы управления и макет
boy illustration
Код js веб-страницы, фон частицы, код спецэффектов
boy illustration
【новый! Суперподробное】Полное руководство по свойствам компонентов Figma.
boy illustration
🎉Обязательно к прочтению новичкам: полное руководство по написанию мини-программ WeChat с использованием программного обеспечения Cursor.
boy illustration
[Забавный проект Docker] VoceChat — еще одно приложение для мгновенного чата (IM)! Может быть встроен в любую веб-страницу!
boy illustration
Как реализовать переход по странице в HTML (html переходит на указанную страницу)
boy illustration
Как решить проблему зависания и низкой скорости при установке зависимостей с помощью npm. Существуют ли доступные источники npm, которые могут решить эту проблему?
boy illustration
Серия From Zero to Fun: Uni-App WeChat Payment Practice WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа
boy illustration
Серия uni-app: uni.navigateЧтобы передать скачок значения
boy illustration
Апплет WeChat настраивает верхнюю панель навигации и адаптируется к различным моделям.
boy illustration
JS-время конвертации
boy illustration
Обеспечьте бесперебойную работу ChromeDriver 125: советы по решению проблемы chromedriver.exe не найдены
boy illustration
Поле комментария, щелчок мышью, специальные эффекты, js-код
boy illustration
Объект массива перемещения объекта JS
boy illustration
Как открыть разрешение на позиционирование апплета WeChat_Как использовать WeChat для определения местонахождения друзей
boy illustration
Я даю вам два набора из 18 простых в использовании фонов холста Power BI, так что вам больше не придется возиться с цветами!
boy illustration
Получить текущее время в js_Как динамически отображать дату и время в js
boy illustration
Вам необходимо изучить сочетания клавиш vsCode для форматирования и организации кода, чтобы вам больше не приходилось настраивать формат вручную.
boy illustration
У ChatGPT большое обновление. Всего за 45 минут пресс-конференция показывает, что OpenAI сделал еще один шаг вперед.
boy illustration
Copilot облачной разработки — упрощение разработки
boy illustration
Микросборка xChatGPT с низким кодом, создание апплета чат-бота с искусственным интеллектом за пять шагов
boy illustration
CUDA Out of Memory: идеальное решение проблемы нехватки памяти CUDA
boy illustration
Анализ кластеризации отдельных ячеек, который должен освоить каждый&MarkerгенетическийВизуализация
boy illustration
vLLM: мощный инструмент для ускорения вывода ИИ
boy illustration
CodeGeeX: мощный инструмент генерации кода искусственного интеллекта, который можно использовать бесплатно в дополнение к второму пилоту.
boy illustration
Машинное обучение Реальный бой LightGBM + настройка параметров случайного поиска: точность 96,67%
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция без кодирования и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
LM Studio для создания локальных больших моделей
boy illustration
Как определить количество слоев и нейронов скрытых слоев нейронной сети?
boy illustration
[Отслеживание целей] Подробное объяснение ByteTrack и детали кода