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
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 소켓의 상대편(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 은 받을 최대 바이트 수예요. 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)로 socket에서 최대 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 # 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을 재시도할 수 있어요.
키워드 인자 exception 을 false로 지정하면 recv_nonblock이 IO::WaitReadable 예외를 발생시키지 않고 심볼 :wait_readable을 돌려주게 할 수 있어요.
참고
Socket#recvfrom
recvmsg(maxmesglen=nil, flags=0, maxcontrollen=nil, opts={}) → [mesg, sender_addrinfo, rflags, *controls]
recvmsg는 recvmsg(2) 시스템 콜로 메시지를 블로킹 방식으로 받아요.
maxmesglen 은 받을 mesg의 최대 길이예요. flags 는 Socket::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이 돼요.
controls 는 Socket::AncillaryData 객체들의 배열인 제어 데이터로, 예를 들면:
#<Socket::AncillaryData: AF_UNIX SOCKET RIGHTS 7>
maxmesglen 과 maxcontrollen 은 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 와 비슷하지만, 시스템 콜 전에 비블로킹 플래그가 설정되고 시스템 콜을 재시도하지 않아요.
키워드 인자 exception 을 false로 지정하면 recvmsg_nonblock이 IO::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 는 문자열이어야 해요. flags 는 Socket::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 는 보낼 문자열이에요. flags 는 Socket::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 와 비슷하지만, 시스템 콜 전에 비블로킹 플래그가 설정되고 시스템 콜을 재시도하지 않아요.
키워드 인자 exception 을 false로 지정하면 sendmsg_nonblock이 IO::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: 문자열의 데이터와 길이가 소켓에 전달돼요.
socketoption은Socket::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"
}