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

公共类方法

new([hostname,] port) → tcpserver 点击切换源代码

创建一个新的服务器套接字,绑定到 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_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);
}

公共实例方法

accept → tcpsocket 点击切换源代码

接受一个传入的连接。它返回一个新的 TCPSocket 对象。

TCPServer.open("127.0.0.1", 14641) {|serv|
  s = serv.accept
  s.puts Time.now
  s.close
}
static VALUE
tcp_accept(VALUE server)
{
    union_sockaddr buffer;
    socklen_t length = sizeof(buffer);

    return rsock_s_accept(rb_cTCPSocket, server, &buffer.addr, &length);
}
accept_nonblock([options]) → tcpsocket 点击切换源代码

在为底层文件描述符设置 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。

通过指定关键字参数 exceptionfalse,您可以指示 accept_nonblock 不应引发 IO::WaitReadable 异常,而是返回符号 :wait_readable

参见

# File socket/lib/socket.rb, line 1730
def accept_nonblock(exception: true)
  __accept_nonblock(exception)
end
listen( int ) → 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 协议不支持监听

  • 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 函数

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);
}
sysaccept → file_descriptor 点击切换源代码

返回接受的连接的文件描述符。

TCPServer.open("127.0.0.1", 28561) {|serv|
  fd = serv.sysaccept
  s = IO.for_fd(fd)
  s.puts Time.now
  s.close
}
static VALUE
tcp_sysaccept(VALUE server)
{
    union_sockaddr buffer;
    socklen_t length = sizeof(buffer);

    return rsock_s_accept(0, server, &buffer.addr, &length);
}