[英]why is NON-BLOCKING sockets recommended in epoll
我正在嘗試學習如何將 epoll() 用於 tcp 服務器應用程序,因為我期待很多連接。 我試着檢查示例和教程,他們總是建議使用/設置 sockets 添加到 epoll() 中的是非阻塞 sockets。為什么?
使用傳統的阻塞I / O,每個連接必須由一個或多個專用線程處理。 隨着連接數量的增加,所需線程的數量也會增加。 這個模型在連接數量達到數百或數千的情況下工作得相當好,但它不能很好地擴展。
多路復用和非阻塞I / O反轉模型,允許一個線程為許多不同的連接提供服務。 它通過同時監視所有活動連接並且僅在套接字准備就緒時執行I / O來實現(例如,數據已到達,這意味着至少可以執行一次讀取而不會阻塞)。
這是一個更具可擴展性的解決方案,因為現在你沒有成群的大多數非活動線程坐在他們的拇指周圍。 相反,你有一個或幾個非常活躍的線程在所有套接字之間穿梭。
對於級別觸發的 epoll,非阻塞套接字可以幫助最小化 epoll_wait() 調用,這是一個優化問題。
對於邊緣觸發的 epoll,您必須使用非阻塞套接字並調用 read() 或 write() 直到它們返回 EWOULDBLOCK。 如果不這樣做,您可能會錯過內核通知。
你可以在這里找到詳細的答案: https : //eklitzke.org/blocking-io-nonblocking-io-and-epoll
這是一個很好的問題,沒有重復。 最近也在select
( select
是level-triggered only)找到一個使用nonblocking socket的教程,引發思考。
問題是:
為什么在級別觸發的epoll
、 select
或其他類似接口中使用非阻塞 IO 或將fd
設置為非閃爍?
這個案子其實有非常充分的理由。
引用本書The Linux Programming Interface :
63.1.2 使用替代 I/O 模型的非阻塞 I/O
非阻塞 I/O(
O_NONBLOCK
標志)通常與本章描述的 I/O 模型結合使用。 為什么這可能有用的一些示例如下:
- 如前一節所述,非阻塞 I/O 通常與提供邊緣觸發 I/O 事件通知的 I/O 模型結合使用。
- 如果多個進程(或線程)正在對同一個打開的文件描述執行 I/O,那么,從特定進程的角度來看,描述符的就緒狀態可能會在描述符被通知准備就緒的時間和后續通知的時間之間發生變化輸入/輸出調用。 因此,阻塞 I/O 調用可能會阻塞,從而阻止進程監視其他文件描述符。 (這可能發生在我們在本章中描述的所有 I/O 模型中,無論它們使用電平觸發通知還是邊沿觸發通知。)
- 即使在級別觸發的 API(例如
select()
或poll()
通知我們 stream 套接字的文件描述符已准備好寫入之后,如果我們在單個write()
或send()
中寫入足夠大的數據塊,那么調用仍然會阻塞。- 在極少數情況下,諸如
select()
和poll()
之類的級別觸發的 API 會返回虛假的就緒通知——它們會錯誤地通知我們文件描述符已就緒。 這可能是由 kernel 錯誤引起的,也可能是罕見情況下的預期行為。
首先,讓我們檢查案例 #2: “如果多個進程(或線程)正在對相同的打開文件描述執行 I/O...” 。
從libevent介紹中閱讀這段代碼, http://www.wangafu.net/~nickm/libevent-book/01_intro.html 。
/* For sockaddr_in */
#include <netinet/in.h>
/* For socket functions */
#include <sys/socket.h>
/* For fcntl */
#include <fcntl.h>
/* for select */
#include <sys/select.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define MAX_LINE 16384
char
rot13_char(char c)
{
/* We don't want to use isalpha here; setting the locale would change
* which characters are considered alphabetical. */
if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
return c + 13;
else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
return c - 13;
else
return c;
}
struct fd_state {
char buffer[MAX_LINE];
size_t buffer_used;
int writing;
size_t n_written;
size_t write_upto;
};
struct fd_state *
alloc_fd_state(void)
{
struct fd_state *state = malloc(sizeof(struct fd_state));
if (!state)
return NULL;
state->buffer_used = state->n_written = state->writing =
state->write_upto = 0;
return state;
}
void
free_fd_state(struct fd_state *state)
{
free(state);
}
void
make_nonblocking(int fd)
{
fcntl(fd, F_SETFL, O_NONBLOCK);
}
int
do_read(int fd, struct fd_state *state)
{
char buf[1024];
int i;
ssize_t result;
while (1) {
result = recv(fd, buf, sizeof(buf), 0);
if (result <= 0)
break;
for (i=0; i < result; ++i) {
if (state->buffer_used < sizeof(state->buffer))
state->buffer[state->buffer_used++] = rot13_char(buf[i]);
if (buf[i] == '\n') {
state->writing = 1;
state->write_upto = state->buffer_used;
}
}
}
if (result == 0) {
return 1;
} else if (result < 0) {
if (errno == EAGAIN)
return 0;
return -1;
}
return 0;
}
int
do_write(int fd, struct fd_state *state)
{
while (state->n_written < state->write_upto) {
ssize_t result = send(fd, state->buffer + state->n_written,
state->write_upto - state->n_written, 0);
if (result < 0) {
if (errno == EAGAIN)
return 0;
return -1;
}
assert(result != 0);
state->n_written += result;
}
if (state->n_written == state->buffer_used)
state->n_written = state->write_upto = state->buffer_used = 0;
state->writing = 0;
return 0;
}
void
run(void)
{
int listener;
struct fd_state *state[FD_SETSIZE];
struct sockaddr_in sin;
int i, maxfd;
fd_set readset, writeset, exset;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(40713);
for (i = 0; i < FD_SETSIZE; ++i)
state[i] = NULL;
listener = socket(AF_INET, SOCK_STREAM, 0);
make_nonblocking(listener);
#ifndef WIN32
{
int one = 1;
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
}
#endif
if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind");
return;
}
if (listen(listener, 16)<0) {
perror("listen");
return;
}
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_ZERO(&exset);
while (1) {
maxfd = listener;
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_ZERO(&exset);
FD_SET(listener, &readset);
for (i=0; i < FD_SETSIZE; ++i) {
if (state[i]) {
if (i > maxfd)
maxfd = i;
FD_SET(i, &readset);
if (state[i]->writing) {
FD_SET(i, &writeset);
}
}
}
if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {
perror("select");
return;
}
if (FD_ISSET(listener, &readset)) {
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener, (struct sockaddr*)&ss, &slen);
if (fd < 0) {
perror("accept");
} else if (fd > FD_SETSIZE) {
close(fd);
} else {
make_nonblocking(fd);
state[fd] = alloc_fd_state();
assert(state[fd]);/*XXX*/
}
}
for (i=0; i < maxfd+1; ++i) {
int r = 0;
if (i == listener)
continue;
if (FD_ISSET(i, &readset)) {
r = do_read(i, state[i]);
}
if (r == 0 && FD_ISSET(i, &writeset)) {
r = do_write(i, state[i]);
}
if (r) {
free_fd_state(state[i]);
state[i] = NULL;
close(i);
}
}
}
}
int
main(int c, char **v)
{
setvbuf(stdout, NULL, _IONBF, 0);
run();
return 0;
}
這不是多個進程(或線程)對相同的打開文件描述執行 I/O 的示例,但它演示了相同的想法。
在do_read
function 中,它在 side a while(1)
中使用recv
來讀取盡可能多的字節,但每個recv
讀取1024
個字節。 我想這是一個典型的模式。
所以這里需要非阻塞,否則recv
最終會在沒有數據in.network輸入的時候阻塞。
對於#3,如果您在阻塞套接字中寫入過多數據並且沒有足夠的緩沖區。 send
將阻塞,直到發送完所有數據。 如果發送緩沖區中沒有足夠的空間,它可能會阻塞足夠長的時間。 更多詳情請查看https://stackoverflow.com/a/74172742/5983841 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.