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; }
预览: