recvfrom 是 linux 下的一个系统调用,用于从 socket 接收网络传输的数据,它支持 BIO 和 NIO 两种模式。需要注意的是,本文中说的 NIO 是 Nonblocking IO,即非阻塞 IO,它是相对于 BIO 阻塞 IO 而言。java 中的 NIO(New IO)是指的新的 IO 模型 api,它是基于 select、poll 等系统调用实现的 IO 多路复用 IO,请不要将它们搞混。
重温BIO和NIO
我们知道,网络 IO 的速度远远低于 cpu 的运行速度,在发起 IO 请求之后,如果让 cpu 停下来等待 IO 数据准备好,那将会严重浪费 cpu 的计算资源。
一种解决方案是:如果 IO 数据还没准备好,那么就直接返回一个错误码。当前进程发现数据未准备好,就先去执行其他的计算任务,等过段时间再来查询,直到数据准备好并返回,这就是 NIO 的做法。这样做的好处是提高了 cpu 的利用率,不至于占用了 cpu 却又让 cpu 无事可做。它的缺点是需要多次轮询,以查看数据是否准备好。每次轮询都需要从用户态切换到内核态,然后再从内核态切换回用户态,频繁的上下文切换也是对资源的一种浪费。如果降低轮询的频次,那么就会增大 IO 延迟,即数据已经到达,但是还未到下次轮询时间,这期间的时间越长则延迟越大,甚至还可能导致缓冲区写满。
MSG_DONTWAIT (since Linux 2.2) Enables nonblocking operation; if the operation would block, the call fails with the error EAGAIN or EWOULDBLOCK. This provides similar behavior to setting the O_NONBLOCK flag (via the fcntl(2) F_SETFL operation), but differs in that MSG_DONT- WAIT is a per-call option, whereas O_NONBLOCK is a setting on the open file de- scription (see open(2)), which will affect all threads in the calling process and as well as other processes that hold file descriptors referring to the same open file description.
# 带调试信息的编译 gcc -g server.c -o server gcc -g client.c -o client # 运行 ./server localhost 8110 ./client 8110 test # debug server cgdb ./server b 70 r localhost 8110 # debug client cgb ./client b 77 r 8110 test
s = getaddrinfo(NULL, argv[1], &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); }
/* getaddrinfo() returns a list of address structures. Try each address until we successfully bind(2). If socket(2) (or bind(2)) fails, we (close the socket and) try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue;
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; /* Success */
close(sfd); }
freeaddrinfo(result); /* No longer needed */
if (rp == NULL) { /* No address succeeded */ fprintf(stderr, "Could not bind\n"); exit(EXIT_FAILURE); }
/* Read datagrams and echo them back to sender. */
s = getaddrinfo(argv[1], argv[2], &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); }
/* getaddrinfo() returns a list of address structures. Try each address until we successfully connect(2). If socket(2) (or connect(2)) fails, we (close the socket and) try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue;
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) break; /* Success */
close(sfd); }
freeaddrinfo(result); /* No longer needed */
if (rp == NULL) { /* No address succeeded */ fprintf(stderr, "Could not connect\n"); exit(EXIT_FAILURE); }
/* Send remaining command-line arguments as separate datagrams, and read responses from server. */
for (int j = 3; j < argc; j++) { len = strlen(argv[j]) + 1; /* +1 for terminating null byte */
if (len > BUF_SIZE) { fprintf(stderr, "Ignoring long message in argument %d\n", j); continue; }