之前发现自己一遇到涉及到 TCP/IP 基础知识的问题时就抓瞎,所以决定好好学习一下 TCP/IP 的基础知识,把 UNP 的基础编程这部分粗略过了一遍,然后想回头过一遍 Ruby 的 socket 库的文档,并且希望能把 Ruby socket 库的用法和 UNP 中讲述的内容对应起来。当看到 BasicSocket#recv(maxlen, flags) 这个方法时,遇到了个问题。recv 方法的 flags 参数是一些标识选项,用于控制 socket 的行为,UNP 对 recv(2) 系统服务的 flags 的描述如下:
UNP 中提到可能不是所有系统都支持这个 MSG_DONTWAIT 选项,我顺便查下了手册,OS X / Linux 中关于 recv(2) 的手册都没有提到 MSG_DONTWAIT 选项,Linux 的 recv(2) 手册中还提到 POSIX 只规定了 MSG_OOB, MSG_PEEK, and MSG_WAITALL 中三个选项,但是实际上 OS X 和 Linux 都定义了 MSG_DONTWAIT(grep MSG_DONTWAIT /usr/include/linux/socket.h /usr/include/sys/socket.h),并且以下C语言和 Python 实现的服务器程序在设置 DONTWAIT 选项调用 recv 时,确实是表现出了 non-block 的行为(立即返回并抛出一个 EAGAIN):
C 版:
#include <sys/socket.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
int server_fd, client_fd;
socklen_t len;
struct sockaddr_in server_addr, client_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6789);
inet_aton("127.0.0.1", &server_addr.sin_addr);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
bind(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr));
listen(server_fd, 5);
while (1) {
len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &len);
char buf[10];
recv(client_fd, buf, 10, MSG_DONTWAIT);
buf[9] = '\0';
printf("data: %s\n", buf);
}
}
Python 版:
#!/usr/bin/env python
from socket import *
server_sock = socket(AF_INET, SOCK_STREAM)
server_sock.bind(("127.0.0.1", 6789))
server_sock.listen(5)
while True:
client_sock, client_addr = server_sock.accept()
data = client_sock.recv(10, MSG_DONTWAIT)
print "data: %s" % data
client_sock.close()
server_sock.close()
而 Ruby 版的程序在调用 recv 时,却没有表现出来 non-block 的特性,而是一直阻塞在 recv 调用中知道接受到客户端发送过来数据为止:
#!/usr/bin/env ruby
require "socket"
server = TCPServer.new '127.0.0.1', 6789
server.listen 5
while client = server.accept
data = client.recv(10, Socket::MSG_DONTWAIT)
# begin
# data = client.recv_nonblock 10
# rescue Errno::EAGAIN
# retry
# end
puts "data: #{data.inspect}"
client.close
end
so, why ?
顺便附上 Client 的代码:
#!/usr/bin/env ruby
require "socket"
sock = TCPSocket.new "127.0.0.1", 6789
while line = gets
sock.puts line
end