class TCPServer
TCPServer 代表一个 TCP/IP 服务器套接字。
一个简单的 TCP 服务器可能如下所示:
require 'socket' server = TCPServer.new 2000 # Server bind to port 2000 loop do client = server.accept # Wait for a client to connect client.puts "Hello !" client.puts "Time is #{Time.now}" client.close end
一个更实用的服务器(服务多个客户端):
require 'socket' server = TCPServer.new 2000 loop do Thread.start(server.accept) do |client| client.puts "Hello !" client.puts "Time is #{Time.now}" client.close end end
公共类方法
源码
static VALUE
tcp_svr_init(int argc, VALUE *argv, VALUE sock)
{
VALUE hostname, port;
rb_scan_args(argc, argv, "011", &hostname, &port);
return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil, Qfalse, Qnil);
}
创建一个新的服务器套接字,绑定到 port。
如果给定 hostname,则套接字会绑定到该主机名。
serv = TCPServer.new("127.0.0.1", 28561) s = serv.accept s.puts Time.now s.close
在内部,TCPServer.new 调用 getaddrinfo() 函数来获取地址。如果 getaddrinfo() 返回多个地址,TCPServer.new 尝试为每个地址创建一个服务器套接字,并返回第一个成功的套接字。
公共实例方法
源码
static VALUE
tcp_accept(VALUE server)
{
union_sockaddr buffer;
socklen_t length = sizeof(buffer);
return rsock_s_accept(rb_cTCPSocket, server, &buffer.addr, &length);
}
接受一个传入的连接。它返回一个新的 TCPSocket 对象。
TCPServer.open("127.0.0.1", 14641) {|serv| s = serv.accept s.puts Time.now s.close }
源码
# File ext/socket/lib/socket.rb, line 1730 def accept_nonblock(exception: true) __accept_nonblock(exception) end
在为底层文件描述符设置 O_NONBLOCK 后,使用 accept(2) 接受传入的连接。它返回一个接受的 TCPSocket,用于传入的连接。
示例¶ ↑
require 'socket' serv = TCPServer.new(2202) begin # emulate blocking accept sock = serv.accept_nonblock rescue IO::WaitReadable, Errno::EINTR IO.select([serv]) retry end # sock is an accepted socket.
请参阅 Socket#accept,了解如果调用 TCPServer#accept_nonblock 失败可能抛出的异常。
TCPServer#accept_nonblock 可能会引发任何与 accept(2) 失败相对应的错误,包括 Errno::EWOULDBLOCK。
如果异常是 Errno::EWOULDBLOCK、Errno::EAGAIN、Errno::ECONNABORTED、Errno::EPROTO,则它会被 IO::WaitReadable 扩展。因此,IO::WaitReadable 可以用于捕获异常,以便重试 accept_nonblock。
通过将关键字参数 exception 指定为 false,您可以指示 accept_nonblock 不应引发 IO::WaitReadable 异常,而是返回符号 :wait_readable。
参见¶ ↑
源码
VALUE
rsock_sock_listen(VALUE sock, VALUE log)
{
rb_io_t *fptr;
int backlog;
backlog = NUM2INT(log);
GetOpenFile(sock, fptr);
if (listen(fptr->fd, backlog) < 0)
rb_sys_fail("listen(2)");
return INT2FIX(0);
}
使用指定的 int 作为 backlog 来监听连接。只有当 socket 的类型为 SOCK_STREAM 或 SOCK_SEQPACKET 时,调用 listen 才适用。
参数¶ ↑
-
backlog- 等待连接队列的最大长度。
示例 1¶ ↑
require 'socket' include Socket::Constants socket = Socket.new( AF_INET, SOCK_STREAM, 0 ) sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' ) socket.bind( sockaddr ) socket.listen( 5 )
示例 2 (监听任意端口,仅限基于 Unix 的系统):¶ ↑
require 'socket' include Socket::Constants socket = Socket.new( AF_INET, SOCK_STREAM, 0 ) socket.listen( 1 )
基于 Unix 的异常¶ ↑
在基于 Unix 的系统上,上述代码将起作用,因为在地址 ADDR_ANY 上会创建一个新的 sockaddr 结构,用于内核移交的任意端口号。它在 Windows 上将不起作用,因为 Windows 要求在 listen 之前通过调用 bind 来绑定 socket。
如果 backlog 大于实现相关的最大队列长度,则将使用该实现的最大队列长度。
在基于 Unix 的系统上,如果调用 listen 失败,可能会引发以下系统异常:
-
Errno::EBADF - socket 参数不是有效的文件描述符
-
Errno::EDESTADDRREQ - socket 没有绑定到本地地址,并且该协议不支持在未绑定的套接字上进行监听
-
Errno::EINVAL - socket 已连接
-
Errno::ENOTSOCK - socket 参数不是指套接字
-
Errno::EOPNOTSUPP - socket 协议不支持 listen
-
Errno::EACCES - 调用进程没有适当的权限
-
Errno::EINVAL - socket 已关闭
-
Errno::ENOBUFS - 系统中没有足够的资源来完成调用
Windows 异常¶ ↑
在 Windows 系统上,如果调用 listen 失败,可能会引发以下系统异常:
-
Errno::ENETDOWN - 网络已关闭
-
Errno::EADDRINUSE - 套接字的本地地址已被使用。这通常发生在 bind 执行期间,但如果对部分通配符地址 (涉及 ADDR_ANY) 调用 bind,并且需要在调用 listen 时提交特定地址,则可能会延迟发生。
-
Errno::EINPROGRESS - Windows Sockets 1.1 调用正在进行中,或者服务提供商仍在处理回调函数
-
Errno::EINVAL -
socket尚未通过调用 bind 绑定。 -
Errno::EISCONN -
socket已连接 -
Errno::EMFILE - 没有更多可用的套接字描述符
-
Errno::ENOBUFS - 没有可用的缓冲区空间
-
Errno::ENOTSOC -
socket不是套接字 -
Errno::EOPNOTSUPP - 引用的
socket不是支持 listen 方法的类型
参见¶ ↑
-
基于 Unix 的系统上的 listen 手册页
-
Microsoft 的 Winsock 函数参考中的 listen 函数
源码
static VALUE
tcp_sysaccept(VALUE server)
{
union_sockaddr buffer;
socklen_t length = sizeof(buffer);
return rsock_s_accept(0, server, &buffer.addr, &length);
}
返回已接受连接的文件描述符。
TCPServer.open("127.0.0.1", 28561) {|serv| fd = serv.sysaccept s = IO.for_fd(fd) s.puts Time.now s.close }