BasicSocket 클래스
BasicSocket 클래스
BasicSocket는 모든 Socket 계열 클래스들의 슈퍼 클래스예요. 소켓의 공통 기능(옵션 설정, 메시지 송수신, 주소 조회 등)을 여기서 다루게 돼요.
출처: Ruby 3.3 API
본문
공개 클래스 메서드 (Public 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를 담고 있는 소켓 객체를 반환해요.
# inetd가 실행해주면 STDIN/STDOUT/STDERR이 소켓이 돼요.
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 unspecified 주소(
0.0.0.0)는 IPv4 루프백 주소(127.0.0.1)로 바꿔요. - IPv6 unspecified 주소(
::)는 IPv6 루프백 주소(::1)로 바꿔요.
로컬 주소가 connect에 적합하지 않으면 SocketError를 던져요. 포트가 0인 IPv4/IPv6 주소는 connect에 적합하지 않고, 경로가 없는 Unix 도메인 소켓도 마찬가지예요.
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>]
}
}
구현은 다음과 같아요.
# File ext/socket/lib/socket.rb, line 255
def connect_address
addr = local_address
afamily = addr.afamily
if afamily == Socket::AF_INET
raise SocketError, "unbound IPv4 socket" if addr.ip_port == 0
if addr.ip_address == "0.0.0.0"
addr = Addrinfo.new(["AF_INET", addr.ip_port, nil, "127.0.0.1"], addr.pfamily, addr.socktype, addr.protocol)
end
elsif defined?(Socket::AF_INET6) && afamily == Socket::AF_INET6
raise SocketError, "unbound IPv6 socket" if addr.ip_port == 0
if addr.ip_address == "::"
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
elsif addr.ip_address == "0.0.0.0" # MacOS X 10.4는 IPv4-mapped IPv6 주소에 "a.b.c.d"를 반환해요.
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
elsif addr.ip_address == "::ffff:0.0.0.0" # MacOS X 10.6은 IPv4-mapped IPv6 주소에 "::ffff:a.b.c.d"를 반환해요.
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
end
elsif defined?(Socket::AF_UNIX) && afamily == Socket::AF_UNIX
raise SocketError, "unbound Unix socket" if addr.unix_path == ""
end
addr
end
do_not_reverse_lookup → true or false
basicsocket의 do_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
basicsocket의 do_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 소켓 피어(상대편)의 사용자와 그룹을 반환해요. 결과는 effective uid와 effective gid를 담은 2요소 배열이에요.
Socket.unix_server_loop("/tmp/sock") {|s|
begin
euid, egid = s.getpeereid
# 연결된 클라이언트가 나 자신인지 확인해요.
next if euid != Process.uid
# 내 리소스에 대해 뭔가를 처리해요.
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_상수예요. 접두사 없는 이름의 문자열이나 심볼도 받아줘요.
예시
부울 값을 갖는 정수형 소켓 옵션은 이렇게 호출할 수 있어요.
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;
};
이 경우엔 이렇게 호출해요.
# Socket::Option이 linger 구조를 알아요.
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은 받을 최대 바이트 수예요. flags는 Socket::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)로 소켓에서 최대 maxlen 바이트를 받아요. flags는 MSG_ 옵션 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 # 블로킹 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해 재시도할 수 있어요. 키워드 인자 exception: false를 주면 IO::WaitReadable 예외를 던지는 대신 :wait_readable 심볼을 반환하게 할 수 있어요.
참고 - Socket#recvfrom
# File ext/socket/lib/socket.rb, line 376
def recv_nonblock(len, flag = 0, str = nil, exception: true)
__recv_nonblock(len, flag, str, exception)
end
recvmsg(maxmesglen=nil, flags=0, maxcontrollen=nil, opts={}) → [mesg, sender_addrinfo, rflags, *controls]
recvmsg(2) 시스템 콜을 블로킹 방식으로 호출해 메시지를 받아요. maxmesglen은 받을 mesg의 최대 길이, flags는 Socket::MSG_PEEK 같은 MSG_* 상수들의 비트 OR, maxcontrollen은 받을 컨트롤(부가 데이터)의 최대 길이예요. opts는 옵션 해시로, 현재는 :scm_rights=>bool만 지원해요.
:scm_rights 옵션은 애플리케이션이 SCM_RIGHTS 컨트롤 메시지를 기대한다는 뜻이에요. 값이 nil이나 false면 기대하지 않는 것이고, 이 경우 recvmsg는 전달받은 파일 디스크립터를 즉시 닫아요. 이것이 기본 동작이에요. 값이 nil도 false도 아니면 기대하는 것이고, recvmsg는 Socket::AncillaryData#unix_rights 메서드를 위해 각 파일 디스크립터마다 IO 객체를 만들어요.
반환값은 4요소 배열이에요.
mesg- 받은 메시지 문자열sender_addrinfo- connection-less 소켓의 발신 소켓 주소로Addrinfo객체예요. TCP 같은 connection-oriented 소켓에선 플랫폼에 따라 달라져요.rflags- 받은 메시지의 플래그로Socket::MSG_TRUNC같은MSG_*상수들의 비트 OR이에요. 4.3BSD 스타일의 옛recvmsg시스템 콜을 쓰는 시스템에선nil이 돼요.controls- 부가 데이터로Socket::AncillaryData객체들의 배열이에요.
#<Socket::AncillaryData: AF_UNIX SOCKET RIGHTS 7>
maxmesglen과 maxcontrollen은 nil이 될 수 있어요. 그 경우 메시지가 잘리지(truncate) 않을 때까지 버퍼를 키워요. 내부적으로 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
}
# File ext/socket/lib/socket.rb, line 431
def recvmsg(dlen = nil, flags = 0, clen = nil, scm_rights: false)
__recvmsg(dlen, flags, clen, scm_rights)
end
recvmsg_nonblock(maxdatalen=nil, flags=0, maxcontrollen=nil, opts={}) → [data, sender_addrinfo, rflags, *controls]
recvmsg(2) 시스템 콜을 논블로킹 방식으로 호출해 메시지를 받아요. BasicSocket#recvmsg와 비슷하지만 시스템 콜 전에 논블로킹 플래그가 설정되고 시스템 콜을 재시도하지 않아요. 키워드 인자 exception: false를 주면 IO::WaitReadable 예외 대신 :wait_readable 심볼을 반환해요.
# File ext/socket/lib/socket.rb, line 447
def recvmsg_nonblock(dlen = nil, flags = 0, clen = nil,
scm_rights: false, exception: true)
__recvmsg_nonblock(dlen, flags, clen, scm_rights, exception)
end
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는 문자열이어야 하고, flags는 Socket::MSG_* 상수들의 비트 OR이에요. dest_sockaddr은 packed 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(2) 시스템 콜을 블로킹 방식으로 호출해 메시지를 보내요. mesg는 보낼 문자열, flags는 Socket::MSG_OOB 같은 MSG_* 상수들의 비트 OR이에요. dest_sockaddr은 connection-less 소켓의 목적지 주소로, Socket.sockaddr_in 결과 같은 sockaddr이에요. Addrinfo 객체도 쓸 수 있어요. controls는 부가 데이터 목록으로, 요소는 Socket::AncillaryData이거나 cmsg_level, cmsg_type, data를 담은 3요소 배열이에요. 반환값 numbytes_sent는 보낸 바이트 수예요.
sendmsg로 send_io를 이렇게 구현할 수 있어요.
# Socket::AncillaryData 사용.
ancdata = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, io.fileno)
sock.sendmsg("a", 0, nil, ancdata)
# 3요소 배열 사용.
ancdata = [:SOCKET, :RIGHTS, [io.fileno].pack("i!")]
sock.sendmsg("\0", 0, nil, ancdata)
# File ext/socket/lib/socket.rb, line 307
def sendmsg(mesg, flags = 0, dest_sockaddr = nil, *controls)
__sendmsg(mesg, flags, dest_sockaddr, controls)
end
sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls, opts={}) → numbytes_sent
sendmsg(2) 시스템 콜을 논블로킹 방식으로 호출해 메시지를 보내요. BasicSocket#sendmsg와 비슷하지만 시스템 콜 전에 논블로킹 플래그가 설정되고 재시도하지 않아요. 키워드 인자 exception: false를 주면 IO::WaitWritable 예외 대신 :wait_writable 심볼을 반환해요.
# File ext/socket/lib/socket.rb, line 323
def sendmsg_nonblock(mesg, flags = 0, dest_sockaddr = nil, *controls,
exception: true)
__sendmsg_nonblock(mesg, flags, dest_sockaddr, controls, exception)
end
setsockopt(level, optname, optval) / setsockopt(socketoption)
소켓 옵션을 설정해요. 옵션은 프로토콜·시스템마다 정해져 있으니 로컬 시스템 문서를 참고해야 해요.
파라미터
level은 정수로, 보통Socket::SOL_SOCKET같은SOL_상수 또는 프로토콜 레벨이에요. 접두사 없는 이름의 문자열이나 심볼도 받아줘요.optname은 정수로, 보통Socket::SO_REUSEADDR같은SO_상수예요. 접두사 없는 이름의 문자열이나 심볼도 받아줘요.optval의 종류:Integer- 값이 int로 할당되고, int에 대한 포인터가sizeof(int)길이로 전달돼요.true/false- 1 또는 0이 int로 할당돼 같은 방식으로 전달돼요.nil이 아니라false를 넘겨야 한다는 점 주의하세요.String- 문자열의 데이터와 길이가 소켓에 전달돼요.
socketoption은Socket::Option인스턴스예요.
예시
부울 값을 갖는 정수형 옵션:
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;
};
이 경우엔 이렇게 호출해요.
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
s1.puts "ping"
s1.shutdown(:WR)
p s2.read #=> "ping\n"
s2.puts "pong"
s2.close
p s1.read #=> "pong\n"