BasicSocket 클래스

BasicSocket 클래스

BasicSocket는 모든 Socket 클래스들의 슈퍼클래스예요. 소켓의 공통 동작(옵션 조회·설정, 데이터 송수신, 주소 조회 등)이 여기 모여 있어요.

출처: Ruby 4.0 API

본문

BasicSocket 는 모든 Socket 클래스의 슈퍼클래스예요.

Class Methods

do_not_reverse_lookup → true or false

전역 do_not_reverse_lookup 플래그를 가져와요.

BasicSocket.do_not_reverse_lookup  #=> false

do_not_reverse_lookup = bool

전역 do_not_reverse_lookup 플래그를 설정해요. 이 플래그는 각 소켓의 do_not_reverse_lookup 초기값으로 쓰여요.

s1 = TCPSocket.new("localhost", 80)
p s1.do_not_reverse_lookup                 #=> true
BasicSocket.do_not_reverse_lookup = false
s2 = TCPSocket.new("localhost", 80)
p s2.do_not_reverse_lookup                 #=> false
p s1.do_not_reverse_lookup                 #=> true

for_fd(fd) → basicsocket

파일 디스크립터 fd를 담고 있는 소켓 객체를 돌려줘요.

# If invoked by inetd, STDIN/STDOUT/STDERR is a socket.
STDIN_SOCK = Socket.for_fd(STDIN.fileno)
p STDIN_SOCK.remote_address

Public Instance Methods

close_read → nil

shutdown 시스템 콜로 더 이상의 읽기를 막아요.

s1, s2 = UNIXSocket.pair
s1.close_read
s2.puts #=> Broken pipe (Errno::EPIPE)

close_write → nil

shutdown 시스템 콜로 더 이상의 쓰기를 막아요.

UNIXSocket.pair {|s1, s2|
  s1.print "ping"
  s1.close_write
  p s2.read        #=> "ping"
  s2.print "pong"
  s2.close
  p s1.read        #=> "pong"
}

connect_address()

로컬 머신 안에서 connect에 적합한 소켓 주소를 돌려줘요.

이 메서드는 다음 조건을 제외하면 self.local_address 를 돌려줘요.

  • IPv4 지정되지 않은 주소(0.0.0.0)는 IPv4 루프백 주소(127.0.0.1)로 대체돼요.
  • IPv6 지정되지 않은 주소(::)는 IPv6 루프백 주소(::1)로 대체돼요.

로컬 주소가 connect에 적합하지 않으면 SocketError가 발생해요. 포트가 0인 IPv4·IPv6 주소는 connect에 적합하지 않아요. 경로가 없는 Unix 도메인 소켓도 connect에 적합하지 않아요.

Addrinfo.tcp("0.0.0.0", 0).listen {|serv|
  p serv.connect_address #=> #<Addrinfo: 127.0.0.1:53660 TCP>
  serv.connect_address.connect {|c|
    s, _ = serv.accept
    p [c, s] #=> [#<Socket:fd 4>, #<Socket:fd 6>]
  }
}

do_not_reverse_lookup → true or false

basicsocketdo_not_reverse_lookup 플래그를 가져와요.

require 'socket'

BasicSocket.do_not_reverse_lookup = false
TCPSocket.open("www.ruby-lang.org", 80) {|sock|
  p sock.do_not_reverse_lookup      #=> false
}
BasicSocket.do_not_reverse_lookup = true
TCPSocket.open("www.ruby-lang.org", 80) {|sock|
  p sock.do_not_reverse_lookup      #=> true
}

do_not_reverse_lookup = bool

basicsocketdo_not_reverse_lookup 플래그를 설정해요.

TCPSocket.open("www.ruby-lang.org", 80) {|sock|
  p sock.do_not_reverse_lookup       #=> true
  p sock.peeraddr                    #=> ["AF_INET", 80, "221.186.184.68", "221.186.184.68"]
  sock.do_not_reverse_lookup = false
  p sock.peeraddr                    #=> ["AF_INET", 80, "carbon.ruby-lang.org", "54.163.249.195"]
}

getpeereid → [euid, egid]

UNIX 소켓의 상대편(peer)의 사용자와 그룹을 돌려줘요. 결과는 유효 uid와 유효 gid를 담은 두 요소 배열이에요.

Socket.unix_server_loop("/tmp/sock") {|s|
  begin
    euid, egid = s.getpeereid

    # Check the connected client is myself or not.
    next if euid != Process.uid

    # do something about my resource.

  ensure
    s.close
  end
}

getpeername → sockaddr

소켓의 원격 주소를 sockaddr 문자열로 돌려줘요.

TCPServer.open("127.0.0.1", 1440) {|serv|
  c = TCPSocket.new("127.0.0.1", 1440)
  s = serv.accept
  p s.getpeername #=> "\x02\x00\x82u\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
}

바이너리 문자열보다 Addrinfo 객체가 더 낫다면 BasicSocket#remote_address를 쓰면 돼요.

getsockname → sockaddr

소켓의 로컬 주소를 sockaddr 문자열로 돌려줘요.

TCPServer.open("127.0.0.1", 15120) {|serv|
  p serv.getsockname #=> "\x02\x00;\x10\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
}

바이너리 문자열보다 Addrinfo 객체가 더 낫다면 BasicSocket#local_address를 쓰면 돼요.

getsockopt(level, optname) → socketoption

소켓 옵션을 가져와요. 이들은 프로토콜·시스템마다 다르니 자세한 내용은 로컬 시스템 문서를 보세요. 옵션은 Socket::Option 객체로 반환돼요.

파라미터

  • level은 정수로, 보통 Socket::SOL_SOCKET 같은 SOL_ 상수 중 하나 또는 프로토콜 레벨이에요. 접두사 없는 이름의 문자열이나 심볼도 받아들여져요.
  • optname은 정수로, 보통 Socket::SO_REUSEADDR 같은 SO_ 상수 중 하나예요. 접두사 없는 이름의 문자열이나 심볼도 받아들여져요.

예시

불리언 값을 가진 정수인 소켓 옵션은 getsockopt을 이렇게 호출할 수 있어요:

reuseaddr = sock.getsockopt(:SOCKET, :REUSEADDR).bool

optval = sock.getsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR)
optval = optval.unpack "i"
reuseaddr = optval[0] == 0 ? false : true

숫자 값을 가진 정수인 소켓 옵션은 이렇게:

ipttl = sock.getsockopt(:IP, :TTL).int

optval = sock.getsockopt(Socket::IPPROTO_IP, Socket::IP_TTL)
ipttl = optval.unpack1("i")

옵션 값은 구조체일 수도 있어요. 해석이 복잡할 수 있는데, 시스템 헤더를 들여다보고 올바른 정의를 알아내야 하거든요. 예를 들어 시스템 헤더에 struct linger가 이렇게 정의되어 있을 수 있어요:

struct linger {
  int l_onoff;
  int l_linger;
};

이 경우 getsockopt을 이렇게 호출할 수 있어요:

# Socket::Option knows linger structure.
onoff, linger = sock.getsockopt(:SOCKET, :LINGER).linger

optval =  sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER)
onoff, linger = optval.unpack "ii"
onoff = onoff == 0 ? false : true

local_address → addrinfo

getsockname으로 얻은 로컬 주소에 대한 Addrinfo 객체를 돌려줘요. addrinfo.protocol은 0으로 채워진다는 점에 주의하세요.

TCPSocket.open("www.ruby-lang.org", 80) {|s|
  p s.local_address #=> #<Addrinfo: 192.168.0.129:36873 TCP>
}

TCPServer.open("127.0.0.1", 1512) {|serv|
  p serv.local_address #=> #<Addrinfo: 127.0.0.1:1512 TCP>
}

recv(maxlen[, flags[, outbuf]]) → mesg

메시지를 받아요. maxlen 은 받을 최대 바이트 수예요. flagsSocket::MSG_* 상수들의 비트 OR이어야 해요. outbuf 는 메서드 호출 후 받은 데이터만 담게 되는데, 시작할 때 비어 있지 않아도 그래요.

UNIXSocket.pair {|s1, s2|
  s1.puts "Hello World"
  p s2.recv(4)                     #=> "Hell"
  p s2.recv(4, Socket::MSG_PEEK)   #=> "o Wo"
  p s2.recv(4)                     #=> "o Wo"
  p s2.recv(10)                    #=> "rld\n"
}

recv_nonblock(maxlen [, flags [, buf [, options ]]]) → mesg

기본 파일 디스크립터에 O_NONBLOCK이 설정된 뒤 recvfrom(2)로 socket에서 최대 maxlen 바이트를 받아요. flagsMSG_ 옵션 0개 이상이에요. 결과 mesg 는 받은 데이터예요.

recvfrom(2)가 0을 돌려주면 Socket#recv_nonblock은 nil을 돌려줘요. 대부분의 경우 연결이 닫혔다는 뜻이지만, UDP 연결에서는 빈 패킷을 받았다는 뜻일 수도 있어요. 기본 API로 이 두 경우를 구별할 수 없기 때문이에요.

파라미터

  • maxlen - 소켓에서 받을 바이트 수
  • flags - MSG_ 옵션 0개 이상
  • buf - 목적지 String 버퍼
  • options - 키워드 해시로, exception: false 지원

예시

serv = TCPServer.new("127.0.0.1", 0)
af, port, host, addr = serv.addr
c = TCPSocket.new(addr, port)
s = serv.accept
c.send "aaa", 0
begin # emulate blocking recv.
  p s.recv_nonblock(10) #=> "aaa"
rescue IO::WaitReadable
  IO.select([s])
  retry
end

recv_nonblock 호출이 실패할 때 던져질 수 있는 예외는 Socket#recvfrom을 참고하세요. BasicSocket#recv_nonblock은 Errno::EWOULDBLOCK을 포함해 recvfrom(2) 실패에 해당하는 어떤 오류라도 발생시킬 수 있어요.

예외가 Errno::EWOULDBLOCK 또는 Errno::EAGAIN이면 IO::WaitReadable로 확장돼요. 그래서 IO::WaitReadable로 그 예외들을 rescue해 recv_nonblock을 재시도할 수 있어요.

키워드 인자 exceptionfalse로 지정하면 recv_nonblockIO::WaitReadable 예외를 발생시키지 않고 심볼 :wait_readable을 돌려주게 할 수 있어요.

참고

  • Socket#recvfrom

recvmsg(maxmesglen=nil, flags=0, maxcontrollen=nil, opts={}) → [mesg, sender_addrinfo, rflags, *controls]

recvmsg는 recvmsg(2) 시스템 콜로 메시지를 블로킹 방식으로 받아요.

maxmesglen 은 받을 mesg의 최대 길이예요. flagsSocket::MSG_PEEK 같은 MSG_* 상수들의 비트 OR예요. maxcontrollen 은 받을 제어 데이터(ancillary data)의 최대 길이예요. opts 는 옵션 해시로, 현재는 :scm_rights=>bool이 유일한 옵션이에요.

:scm_rights 옵션은 애플리케이션이 SCM_RIGHTS 제어 메시지를 기대하는지 지정해요. 값이 nil 또는 false면 애플리케이션이 SCM_RIGHTS 제어 메시지를 기대하지 않는 거예요. 이 경우 recvmsg는 전달된 파일 디스크립터를 즉시 닫아요. 이것이 기본 동작이에요.

:scm_rights 값이 nil도 false도 아니면 애플리케이션이 SCM_RIGHTS 제어 메시지를 기대하는 거예요. 이 경우 recvmsg는 Socket::AncillaryData#unix_rights 메서드를 위해 각 파일 디스크립터의 IO 객체를 만들어요.

반환값은 4-요소 배열이에요.

mesg 는 받은 메시지의 문자열이에요.

sender_addrinfo 는 커넥션 없는 소켓의 발신자 소켓 주소예요. Addrinfo 객체이죠. TCP 같은 커넥션 지향 소켓에서 sender_addrinfo는 플랫폼에 따라 달라요.

rflags 는 받은 메시지의 플래그로, Socket::MSG_TRUNC 같은 MSG_* 상수들의 비트 OR예요. 시스템이 4.3BSD 스타일의 옛 recvmsg 시스템 콜을 쓰면 nil이 돼요.

controlsSocket::AncillaryData 객체들의 배열인 제어 데이터로, 예를 들면:

#<Socket::AncillaryData: AF_UNIX SOCKET RIGHTS 7>

maxmesglenmaxcontrollen 은 nil일 수 있어요. 그 경우 버퍼가 메시지가 잘리지 않을 때까지 커져요. 내부적으로 MSG_PEEK가 사용돼요. 잘림은 버퍼 전체와 MSG_CTRUNC로 검사해요.

recvmsg는 다음과 같이 recv_io를 구현하는 데 쓸 수 있어요:

mesg, sender_sockaddr, rflags, *controls = sock.recvmsg(:scm_rights=>true)
controls.each {|ancdata|
  if ancdata.cmsg_is?(:SOCKET, :RIGHTS)
    return ancdata.unix_rights[0]
  end
}

recvmsg_nonblock(maxdatalen=nil, flags=0, maxcontrollen=nil, opts={}) → [data, sender_addrinfo, rflags, *controls]

recvmsg는 recvmsg(2) 시스템 콜로 메시지를 비블로킹 방식으로 받아요.

BasicSocket#recvmsg 와 비슷하지만, 시스템 콜 전에 비블로킹 플래그가 설정되고 시스템 콜을 재시도하지 않아요.

키워드 인자 exceptionfalse로 지정하면 recvmsg_nonblockIO::WaitReadable 예외를 발생시키지 않고 심볼 :wait_readable을 돌려주게 할 수 있어요.

remote_address → addrinfo

getpeername으로 얻은 원격 주소에 대한 Addrinfo 객체를 돌려줘요. addrinfo.protocol은 0으로 채워진다는 점에 주의하세요.

TCPSocket.open("www.ruby-lang.org", 80) {|s|
  p s.remote_address #=> #<Addrinfo: 221.186.184.68:80 TCP>
}

TCPServer.open("127.0.0.1", 1728) {|serv|
  c = TCPSocket.new("127.0.0.1", 1728)
  s = serv.accept
  p s.remote_address #=> #<Addrinfo: 127.0.0.1:36504 TCP>
}

send(mesg, flags [, dest_sockaddr]) → numbytes_sent

basicsocket 를 통해 mesg 를 보내요. mesg 는 문자열이어야 해요. flagsSocket::MSG_* 상수들의 비트 OR이어야 해요. dest_sockaddr 은 패킹된 sockaddr 문자열 또는 addrinfo여야 해요.

TCPSocket.open("localhost", 80) {|s|
  s.send "GET / HTTP/1.0\r\n\r\n", 0
  p s.read
}

sendmsg(mesg, flags=0, dest_sockaddr=nil, *controls) → numbytes_sent

sendmsg는 sendmsg(2) 시스템 콜로 메시지를 블로킹 방식으로 보내요.

mesg 는 보낼 문자열이에요. flagsSocket::MSG_OOB 같은 MSG_* 상수들의 비트 OR이에요. dest_sockaddr 은 커넥션 없는 소켓의 목적지 소켓 주소예요. Socket.sockaddr_in 결과 같은 sockaddr이어야 해요. Addrinfo 객체도 쓸 수 있어요.

controls 는 제어 데이터 목록이에요. controls 의 요소는 Socket::AncillaryData 또는 3-요소 배열이어야 해요. 3-요소 배열은 cmsg_level, cmsg_type, data를 담아야 해요.

반환값 numbytes_sent 는 보낸 바이트 수인 정수예요.

sendmsg는 다음과 같이 send_io를 구현하는 데 쓸 수 있어요:

# use Socket::AncillaryData.
ancdata = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, io.fileno)
sock.sendmsg("a", 0, nil, ancdata)

# use 3-element array.
ancdata = [:SOCKET, :RIGHTS, [io.fileno].pack("i!")]
sock.sendmsg("\0", 0, nil, ancdata)

sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls, opts={}) → numbytes_sent

sendmsg_nonblock은 sendmsg(2) 시스템 콜로 메시지를 비블로킹 방식으로 보내요.

BasicSocket#sendmsg 와 비슷하지만, 시스템 콜 전에 비블로킹 플래그가 설정되고 시스템 콜을 재시도하지 않아요.

키워드 인자 exceptionfalse로 지정하면 sendmsg_nonblockIO::WaitWritable 예외를 발생시키지 않고 심볼 :wait_writable을 돌려주게 할 수 있어요.

setsockopt(level, optname, optval)

setsockopt(socketoption)

소켓 옵션을 설정해요. 이들은 프로토콜·시스템마다 다르니 자세한 내용은 로컬 시스템 문서를 보세요.

파라미터

  • level은 정수로, 보통 Socket::SOL_SOCKET 같은 SOL_ 상수 중 하나 또는 프로토콜 레벨이에요. 접두사 없는 이름의 문자열이나 심볼도 받아들여져요.
  • optname은 정수로, 보통 Socket::SO_REUSEADDR 같은 SO_ 상수 중 하나예요. 접두사 없는 이름의 문자열이나 심볼도 받아들여져요.
  • optval은 옵션의 값으로, 기본 setsockopt()에 특정 바이트 수에 대한 포인터로 전달돼요. 타입에 따라 처리 방식이 달라져요:
    • Integer: 값이 int에 할당되고, sizeof(int) 길이로 int에 대한 포인터가 전달돼요.
    • true 또는 false: 1 또는 0(각각)이 int에 할당되고, Integer처럼 전달돼요. nil이 아니라 false를 전달해야 한다는 점에 주의하세요.
    • String: 문자열의 데이터와 길이가 소켓에 전달돼요.
  • socketoptionSocket::Option의 인스턴스예요.

예시

불리언 값을 가진 정수인 소켓 옵션은 setsockopt을 이렇게 호출할 수 있어요:

sock.setsockopt(:SOCKET, :REUSEADDR, true)
sock.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
sock.setsockopt(Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true))

숫자 값을 가진 정수인 소켓 옵션은 이렇게:

sock.setsockopt(:IP, :TTL, 255)
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, 255)
sock.setsockopt(Socket::Option.int(:INET, :IP, :TTL, 255))

옵션 값은 구조체일 수도 있어요. 전달이 복잡할 수 있는데, 시스템 헤더를 들여다보고 올바른 정의를 알아내야 하거든요. 예를 들어 시스템 헤더에 ip_mreq가 이렇게 정의되어 있을 수 있어요:

struct ip_mreq {
  struct  in_addr imr_multiaddr;
  struct  in_addr imr_interface;
};

이 경우 setsockopt을 이렇게 호출할 수 있어요:

optval = IPAddr.new("224.0.0.251").hton +
         IPAddr.new(Socket::INADDR_ANY, Socket::AF_INET).hton
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, optval)

shutdown([how]) → 0

shutdown(2) 시스템 콜을 호출해요.

  • s.shutdown(Socket::SHUT_RD) 는 더 이상의 읽기를 막아요.
  • s.shutdown(Socket::SHUT_WR) 는 더 이상의 쓰기를 막아요.
  • s.shutdown(Socket::SHUT_RDWR) 는 읽기와 쓰기를 모두 막아요.

how 는 심볼이나 문자열일 수 있어요:

  • :RD, :SHUT_RD, "RD", "SHUT_RD"Socket::SHUT_RD 로 받아들여져요.
  • :WR, :SHUT_WR, "WR", "SHUT_WR"Socket::SHUT_WR 로 받아들여져요.
  • :RDWR, :SHUT_RDWR, "RDWR", "SHUT_RDWR"Socket::SHUT_RDWR 로 받아들여져요.
UNIXSocket.pair {|s1, s2|
  s1.puts "ping"
  s1.shutdown(:WR)
  p s2.read          #=> "ping\n"
  s2.puts "pong"
  s2.close
  p s1.read          #=> "pong\n"
}