Socket 클래스
Socket 클래스
Socket은 운영체제의 실제 소켓 구현에 직접 접근할 수 있게 해 주는 클래스예요. 프로토콜 특화 소켓 클래스들보다 더 시스템 특화된 기능을 쓸 때 유용하죠.
출처: Ruby 4.0 API
본문
클래스 Socket은 운영체제의 소켓 구현에 접근할 수 있게 해 줘요. 프로토콜 특화 소켓 클래스들보다 더 운영체제 특화된 기능을 다룰 때 사용해요.
Socket::Constants 아래 정의된 상수들은 Socket 아래에도 정의되어 있어요. 예를 들어 Socket::Constants::AF_INET뿐 아니라 Socket::AF_INET도 쓸 수 있죠. 상수 목록은 Socket::Constants 참고하세요.
소켓이 뭔가요? (What's a socket?)
소켓은 양방향 통신 채널의 끝점(endpoint)이에요. 프로세스 안에서, 같은 머신의 프로세스 사이에서, 또는 서로 다른 머신 사이에서 통신할 수 있어요. 소켓에는 여러 타입이 있어요: TCPSocket, UDPSocket, UNIXSocket 등이 대표적이죠.
소켓에는 고유한 용어가 있어요:
- domain(도메인): 프로토콜의 패밀리(family)예요:
Socket::PF_INETSocket::PF_INET6Socket::PF_UNIX- 등등.
- type(타입): 두 끝점 사이의 통신 타입. 보통:
Socket::SOCK_STREAMSocket::SOCK_DGRAM.
- protocol(프로토콜): 보통 0. 프로토콜의 변형을 식별하는 데 쓸 수 있어요.
- hostname(호스트명): 네트워크 인터페이스의 식별자:
- 문자열(호스트명, IPv4 또는 IPv6 주소, 또는 브로드캐스트를 지정하는 broadcast),
INADDR_ANY를 지정하는 길이 0의 문자열,- 정수(호스트 바이트 순서의 바이너리 주소로 해석됨).
빠른 시작 (Quick start)
TCPSocket, UDPSocket, UNIXSocket 같은 클래스들이 C 프로그래밍 인터페이스에 비해 소켓을 훨씬 쉽게 쓰게 해 줘요.
IPv4 프로토콜로 인터넷 소켓을 C스럽게 만들어 볼게요:
require 'socket'
s = Socket.new Socket::AF_INET, Socket::SOCK_STREAM
s.connect Socket.pack_sockaddr_in(80, 'example.com')
TCPSocket 클래스로도 쓸 수 있어요:
s = TCPSocket.new 'example.com', 80
간단한 서버는 이렇게 생겼어요:
require 'socket'
server = TCPServer.new 2000 # Server bound 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'
s = TCPSocket.new 'localhost', 2000
while line = s.gets # Read lines from socket
puts line # and print them
end
s.close # close socket when done
예외 처리 (Exception Handling)
Ruby의 소켓 구현은 시스템 특화 구현이 만든 오류에 따라 예외를 발생시켜요. 그래서 메서드들을 Unix 기반 예외와 Windows 기반 예외로 나눠 문서화했어요. 특정 예외에 대한 더 자세한 정보는 Unix 매뉴얼 페이지나 Windows WinSock 참조를 보세요.
편의 메서드 (Convenience methods)
소켓을 만드는 일반적인 방법은 Socket.new지만, 대부분의 경우에 쓸 수 있는 소켓 생성 메서드가 여럿 있어요:
TCP 클라이언트 소켓, TCP 서버 소켓, UNIX 클라이언트 소켓, UNIX 서버 소켓.
Class Methods
tcp(host, port, local_host=nil, local_port=nil, [opts]) { |socket| ... }
TCP/IP로 host:port에 연결된 새 소켓 객체를 만들어요.
Ruby 3.4부터 이 메서드는 기본적으로 Happy Eyeballs Version 2(RFC 8305) 알고리즘에 따라 동작해요.
Happy Eyeballs Version 2에 대한 자세한 내용은 Socket.tcp_fast_fallback=를 보세요.
Ruby 3.3 이전처럼 동작하게 하려면 옵션 fast_fallback:false를 명시하거나, Socket.tcp_fast_fallback=false로 설정하면 전역적으로 Happy Eyeballs Version 2가 꺼져요.
local_host:local_port를 주면 소켓을 그 주소에 바인딩해요.
선택적 마지막 인자 opts는 해시로 표현되는 옵션이에요. opts에는 다음 옵션이 있을 수 있어요:
:resolv_timeout:connect_timeout:open_timeout:fast_fallback
블록을 주면 블록이 소켓과 함께 호출돼요. 블록의 값이 반환돼요. 이 메서드가 반환될 때 소켓은 닫혀요.
블록이 없으면 소켓이 반환돼요.
Socket.tcp("www.ruby-lang.org", 80) {|sock|
sock.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
sock.close_write
puts sock.read
}
tcp_fast_fallback → true or false
Ruby 3.4부터 TCPSocket.new와 Socket.tcp에서 제공되는 Happy Eyeballs Version 2(RFC 8305)가 켜져 있는지 꺼져 있는지 반환해요.
true면 TCPSocket.new와 Socket.tcp에 활성화돼요. (참고: Windows에서 TCPSocket.new를 쓰면 Happy Eyeballs Version 2가 제공되지 않아요.)
false면 Happy Eyeballs Version 2가 꺼져요.
자세한 내용은 Socket.tcp_fast_fallback= 참고.
tcp_fast_fallback= → true or false
Ruby 3.4부터 TCPSocket.new와 Socket.tcp에서 제공되는 Happy Eyeballs Version 2(RFC 8305)를 전역적으로 켜거나 꺼요.
true로 설정하면 TCPSocket.new와 Socket.tcp 둘 다에 활성화돼요. (참고: Windows에서 TCPSocket.new를 쓰면 이 기능을 쓸 수 없어요.)
false로 설정하면 Ruby 3.3 이전의 동작으로 되돌아가요.
이 메서드를 호출해 명시적으로 설정하지 않으면 기본값은 true예요. 다만 환경 변수 RUBY_TCP_NO_FAST_FALLBACK=1이 설정돼 있으면 기본값은 false예요.
메서드 단위로 조절하려면 각 메서드의 fast_fallback 키워드 인자를 쓰세요.
Happy Eyeballs Version 2
Happy Eyeballs Version 2(RFC 8305)는 클라이언트 소켓 연결성을 개선하기 위해 설계된 알고리즘이에요. 호스트 이름 해석과 연결 시도를 직렬로 하는 대신 병렬로 수행해 더 안정적이고 효율적인 연결을 노려요.
Ruby 3.4부터 이 메서드는 이 알고리즘에 따라 이렇게 동작해요:
- IPv6와 IPv4 주소를 동시에 해석하기 시작해요.
- 먼저 얻어진 주소 중 하나에 연결하기 시작해요. IPv4 주소가 먼저 얻어지면, IPv6 연결을 우선시하기 위해 50ms 동안 IPv6 이름 해석을 기다려요.
- 연결 시도를 시작한 뒤 연결이 맺어질 때까지 250ms를 기다려요. 그 시간 안에 연결이 맺어지지 않으면, 연결이 맺어지거나 더 이상 후보 주소가 없을 때까지 250ms마다 새 연결을 시작해요. (RFC 8305는 엄격히 주소 정렬을 명시하지만, 성능 문제로 이 메서드는 IPv6/IPv4 주소 사이만 번갈아 가요.)
- 연결이 맺어지면 나머지 모든 연결 시도를 취소해요.
tcp_server_loop(host=nil, port) { |sock, client_addrinfo| ... }
port에 TCP/IP 서버를 만들고, 수락된 각 연결마다 블록을 호출해요. 블록은 소켓과 Addrinfo 객체인 클라이언트 주소를 인자로 받아요.
host를 지정하면 port와 함께 서버 주소를 결정해요.
블록이 반환될 때 소켓은 닫히지 않아요. 그래서 애플리케이션이 명시적으로 닫아야 해요.
이 메서드는 블록을 순차적으로 호출해요. 즉 블록이 반환될 때까지 다음 연결은 수락되지 않아요. 그래서 여러 클라이언트를 동시에 서비스하려면 스레드 같은 동시성 메커니즘을 써야 해요.
서버 소켓 주소 결정에 Addrinfo.getaddrinfo가 쓰여요. Addrinfo.getaddrinfo가 두 개 이상의 주소(가령 IPv4와 IPv6 주소)를 반환하면 그 모두를 사용해요. Socket.tcp_server_loop은 소켓 하나라도 쓸 수 있으면 성공해요.
# Sequential echo server.
# It services only one client at a time.
Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
begin
IO.copy_stream(sock, sock)
ensure
sock.close
end
}
# Threaded echo server
# It services multiple clients at a time.
# Note that it may accept connections too much.
Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
Thread.new {
begin
IO.copy_stream(sock, sock)
ensure
sock.close
end
}
}
tcp_server_sockets(host=nil, port)
host와 port용 TCP/IP 서버 소켓을 만들어요. host는 선택이에요.
블록이 없으면 수신(listening) 소켓 배열을 반환해요.
블록을 주면 블록이 소켓들과 함께 호출돼요. 블록의 값이 반환돼요. 이 메서드가 반환될 때 소켓은 닫혀요.
port가 0이면 실제 포트 번호가 동적으로 선택돼요. 하지만 결과의 모든 소켓은 같은 포트 번호를 가져요.
# tcp_server_sockets returns two sockets.
sockets = Socket.tcp_server_sockets(1296)
p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>]
# The sockets contains IPv6 and IPv4 sockets.
sockets.each {|s| p s.local_address }
#=> #<Addrinfo: [::]:1296 TCP>
# #<Addrinfo: 0.0.0.0:1296 TCP>
# IPv6 and IPv4 socket has same port number, 53114, even if it is chosen dynamically.
sockets = Socket.tcp_server_sockets(0)
sockets.each {|s| p s.local_address }
#=> #<Addrinfo: [::]:53114 TCP>
# #<Addrinfo: 0.0.0.0:53114 TCP>
udp_server_loop(port) { |msg, msg_src| ... }
port에 UDP/IP 서버를 만들고 도착한 각 메시지마다 블록을 호출해요. 블록은 메시지와 그 출처 정보를 인자로 받아요.
이 메서드는 내부적으로 port로 소켓을 할당해요. host를 지정하면 port와 함께 서버 주소를 결정해요.
msg는 문자열이에요.
msg_src는 Socket::UDPSource 객체예요. 답장에 사용돼요.
# UDP/IP echo server.
Socket.udp_server_loop(9261) {|msg, msg_src|
msg_src.reply msg
}
udp_server_loop_on(sockets) { |msg, msg_src| ... }
주어진 소켓들에서 UDP/IP 서버 루프를 실행해요.
Socket.udp_server_sockets의 반환값이 인자로 적합해요.
수신한 각 메시지마다 블록을 호출해요.
udp_server_recv(sockets) { |msg, msg_src| ... }
주어진 sockets에서 UDP/IP 패킷을 받아요. 각 패킷마다 블록을 호출해요.
블록은 msg와 msg_src를 받아요. msg는 받은 패킷의 페이로드인 문자열이에요. msg_src는 답장에 쓰이는 Socket::UDPSource 객체예요.
Socket.udp_server_loop은 이 메서드로 이렇게 구현할 수 있어요:
udp_server_sockets(host, port) {|sockets|
loop {
readable, _, _ = IO.select(sockets)
udp_server_recv(readable) {|msg, msg_src| ... }
}
}
udp_server_sockets([host, ] port)
UDP 서버용 UDP/IP 소켓을 만들어요.
블록이 없으면 소켓 배열을 반환해요.
블록을 주면 블록이 소켓들과 함께 호출돼요. 블록의 값이 반환돼요. 이 메서드가 반환될 때 소켓들은 닫혀요.
port가 0이면 어떤 포트가 선택돼요. 선택된 포트는 모든 소켓에 쓰여요.
# UDP/IP echo server
Socket.udp_server_sockets(0) {|sockets|
p sockets.first.local_address.ip_port #=> 32963
Socket.udp_server_loop_on(sockets) {|msg, msg_src|
msg_src.reply msg
}
}
unix(path) { |sock| ... }
UNIX 소켓으로 path에 연결된 새 소켓을 만들어요.
블록을 주면 블록이 소켓과 함께 호출돼요. 블록의 값이 반환돼요. 이 메서드가 반환될 때 소켓은 닫혀요.
블록이 없으면 소켓이 반환돼요.
# talk to /tmp/sock socket.
Socket.unix("/tmp/sock") {|sock|
t = Thread.new { IO.copy_stream(sock, STDOUT) }
IO.copy_stream(STDIN, sock)
t.join
}
unix_server_loop(path) { |sock, client_addrinfo| ... }
path에 UNIX 소켓 서버를 만들어요. 수락된 각 소켓마다 블록을 호출해요.
블록이 반환될 때 소켓은 닫히지 않아요. 애플리케이션이 닫아야 해요.
이 메서드는 먼저 path가 가리키는 소켓 파일이 소켓 파일이고 앱 사용자 소유이면 그 파일을 삭제해요. 이는 path의 디렉터리가 악의적인 사용자에게 바뀌지 않았을 때만 안전해요. 그래서 /tmp/malicious-users-directory/socket은 쓰지 마세요. /tmp/socket과 /tmp/your-private-directory/socket은 /tmp에 sticky bit가 있다고 가정하면 안전해요.
# Sequential echo server.
# It services only one client at a time.
Socket.unix_server_loop("/tmp/sock") {|sock, client_addrinfo|
begin
IO.copy_stream(sock, sock)
ensure
sock.close
end
}
unix_server_socket(path)
path에 UNIX 서버 소켓을 만들어요.
블록이 없으면 수신 소켓을 반환해요.
블록을 주면 소켓과 함께 호출되고 블록의 값이 반환돼요. 블록이 끝나면 소켓이 닫히고 소켓 파일이 제거돼요.
socket = Socket.unix_server_socket("/tmp/s")
p socket #=> #<Socket:fd 3>
p socket.local_address #=> #<Addrinfo: /tmp/s SOCK_STREAM>
Socket.unix_server_socket("/tmp/sock") {|s|
p s #=> #<Socket:fd 3>
p s.local_address #=> # #<Addrinfo: /tmp/sock SOCK_STREAM>
}
getaddrinfo(nodename, servname[, family[, socktype[, protocol[, flags[, reverse_lookup]]]]]) → array
nodename:servname에 대한 주소 정보를 얻어요.
Addrinfo.getaddrinfo가 같은 기능을 객체 지향 스타일로 제공한다는 점을 참고하세요.
family는 :INET, :INET6 같은 주소 패밀리여야 해요.
socktype은 :STREAM, :DGRAM, :RAW 같은 소켓 타입이어야 해요.
protocol은 패밀리에 정의된 프로토콜이어야 하며, 패밀리에 대해 기본값은 0이에요.
flags는 Socket::AI_* 상수들의 비트 OR이어야 해요.
Socket.getaddrinfo("www.ruby-lang.org", "http", nil, :STREAM)
#=> [["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68", 2, 1, 6]] # PF_INET/SOCK_STREAM/IPPROTO_TCP
Socket.getaddrinfo("localhost", nil)
#=> [["AF_INET", 0, "localhost", "127.0.0.1", 2, 1, 6], # PF_INET/SOCK_STREAM/IPPROTO_TCP
# ["AF_INET", 0, "localhost", "127.0.0.1", 2, 2, 17], # PF_INET/SOCK_DGRAM/IPPROTO_UDP
# ["AF_INET", 0, "localhost", "127.0.0.1", 2, 3, 0]] # PF_INET/SOCK_RAW/IPPROTO_IP
reverse_lookup은 세 번째 요소의 형태를 지시하며, 아래 중 하나여야 해요. 생략하면 기본값은 nil이에요.
true, :hostname: hostname is obtained from numeric address using reverse lookup, which may take a time.
false, :numeric: hostname is the same as numeric address.
nil: obey to the current do_not_reverse_lookup flag.
Addrinfo 객체가 더 좋다면 Addrinfo.getaddrinfo를 쓰세요.
gethostbyaddr(address_string [, address_family]) → hostent
Addrinfo#getnameinfo를 대신 쓰세요. 이 메서드는 다음 이유로 deprecated예요:
- 흔하지 않은 주소 표현: IPv4/IPv6 주소를 나타내는 4/16바이트 바이너리 문자열.
gethostbyaddr()가 오래 걸리고 다른 스레드를 블록할 수 있어요. (gethostbyname()이 스레드 안전하지 않아 GVL을 풀 수 없어요.)- 이 메서드는 이미 POSIX에서 제거된
gethostbyname()함수를 사용해요.
이 메서드는 address에 대한 호스트 정보를 얻어요.
p Socket.gethostbyaddr([221,186,184,68].pack("CCCC"))
#=> ["carbon.ruby-lang.org", [], 2, "\xDD\xBA\xB8D"]
gethostbyname(hostname) → [official_hostname, alias_hostnames, address_family, *address_list]
Addrinfo.getaddrinfo를 대신 쓰세요. 이 메서드는 다음 이유로 deprecated예요:
- 결과의 3번째 요소는 첫 주소의 주소 패밀리예요. 나머지 주소들의 주소 패밀리는 반환되지 않아요.
- 흔하지 않은 주소 표현: 4/16바이트 바이너리 문자열.
gethostbyname()이 오래 걸리고 다른 스레드를 블록할 수 있어요.
이 메서드는 hostname에 대한 호스트 정보를 얻어요.
p Socket.gethostbyname("hal") #=> ["localhost", ["hal"], 2, "\x7F\x00\x00\x01"]
gethostname → hostname
호스트명을 반환해요.
p Socket.gethostname #=> "hal"
이 호스트명이 gethostbyname, getaddrinfo 등으로 IP 주소로 변환될 수 있다는 보장은 없어요. 로컬 IP 주소가 필요하면 Socket.ip_address_list를 쓰세요.
getifaddrs → [ifaddr1, ...]
인터페이스 주소들의 배열을 반환해요. 배열의 각 요소는 Socket::Ifaddr의 인스턴스예요.
이 메서드는 멀티캐스트 지원 인터페이스를 찾는 데도 쓸 수 있어요:
pp Socket.getifaddrs.reject {|ifaddr|
!ifaddr.addr.ip? || (ifaddr.flags & Socket::IFF_MULTICAST == 0)
}.map {|ifaddr| [ifaddr.name, ifaddr.ifindex, ifaddr.addr] }
#=> [["eth0", 2, #<Addrinfo: 221.186.184.67>],
# ["eth0", 2, #<Addrinfo: fe80::216:3eff:fe95:88bb%eth0>]]
getnameinfo(sockaddr [, flags]) → [hostname, servicename]
sockaddr에 대한 이름 정보를 얻어요.
sockaddr은 다음 중 하나여야 해요.
Socket.sockaddr_in(80, "127.0.0.1")같은 packed sockaddr 문자열.["AF_INET", 80, "127.0.0.1"]같은 3-요소 배열.["AF_INET", 80, ignored, "127.0.0.1"]같은 4-요소 배열.
flags는 Socket::NI_* 상수들의 비트 OR이어야 해요.
참고: 마지막 형태는 IPSocket#addr, IPSocket#peeraddr와 호환돼요.
Socket.getnameinfo(Socket.sockaddr_in(80, "127.0.0.1")) #=> ["localhost", "www"]
Socket.getnameinfo(["AF_INET", 80, "127.0.0.1"]) #=> ["localhost", "www"]
Socket.getnameinfo(["AF_INET", 80, "localhost", "127.0.0.1"]) #=> ["localhost", "www"]
Addrinfo 객체가 더 좋다면 Addrinfo#getnameinfo를 쓰세요.
getservbyname(service_name, protocol_name='tcp') → port_number
service_name에 대한 포트 번호를 얻어요.
protocol_name을 주지 않으면 "tcp"로 가정해요.
Socket.getservbyname("smtp") #=> 25
Socket.getservbyname("shell") #=> 514
Socket.getservbyname("syslog", "udp") #=> 514
getservbyport(port [, protocol_name]) → service
port에 대한 서비스 이름을 얻어요.
protocol_name을 주지 않으면 "tcp"로 가정해요.
Socket.getservbyport(80) #=> "www"
Socket.getservbyport(514, "tcp") #=> "shell"
Socket.getservbyport(514, "udp") #=> "syslog"
ip_address_list → array
로컬 IP 주소를 배열로 반환해요. 배열에는 Addrinfo 객체가 담겨요.
pp Socket.ip_address_list
#=> [#<Addrinfo: 127.0.0.1>,
#<Addrinfo: 192.168.0.128>,
#<Addrinfo: ::1>,
...]
new(domain, socktype [, protocol]) → socket
새 소켓 객체를 만들어요.
domain은 :INET, :INET6, :UNIX 같은 통신 도메인이어야 해요.
socktype은 :STREAM, :DGRAM, :RAW 같은 소켓 타입이어야 해요.
protocol은 선택이며 도메인에 정의된 프로토콜이어야 해요. 주지 않으면 내부적으로 0을 사용해요.
Socket.new(:INET, :STREAM) # TCP socket
Socket.new(:INET, :DGRAM) # UDP socket
Socket.new(:UNIX, :STREAM) # UNIX stream socket
Socket.new(:UNIX, :DGRAM) # UNIX datagram socket
sockaddr_in(port, host) → sockaddr
port와 host를 AF_INET/AF_INET6 sockaddr 문자열로 패킹해요.
Socket.sockaddr_in(80, "127.0.0.1")
#=> "\x02\x00\x00P\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
Socket.sockaddr_in(80, "::1")
#=> "\n\x00\x00P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"
sockaddr_un(path) → sockaddr
path를 AF_UNIX sockaddr 문자열로 패킹해요.
Socket.sockaddr_un("/tmp/sock") #=> "\x01\x00/tmp/sock\x00\x00..."
pair(domain, type, protocol) → [socket1, socket2]
서로 연결된 소켓 쌍을 만들어요.
domain은 :INET, :INET6, :UNIX 같은 통신 도메인이어야 해요.
socktype은 :STREAM, :DGRAM, :RAW 같은 소켓 타입이어야 해요.
protocol은 도메인에 정의된 프로토콜이어야 하며 기본값은 0이에요.
s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
s1.send "a", 0
s1.send "b", 0
s1.close
p s2.recv(10) #=> "ab"
p s2.recv(10) #=> ""
p s2.recv(10) #=> ""
s1, s2 = Socket.pair(:UNIX, :DGRAM, 0)
s1.send "a", 0
s1.send "b", 0
p s2.recv(10) #=> "a"
p s2.recv(10) #=> "b"
unpack_sockaddr_in(sockaddr) → [port, ip_address]
sockaddr을 port와 ip_address로 언패킹해요.
sockaddr은 AF_INET/AF_INET6용 문자열 또는 addrinfo여야 해요.
sockaddr = Socket.sockaddr_in(80, "127.0.0.1")
p sockaddr #=> "\x02\x00\x00P\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
p Socket.unpack_sockaddr_in(sockaddr) #=> [80, "127.0.0.1"]
unpack_sockaddr_un(sockaddr) → path
sockaddr을 path로 언패킹해요.
sockaddr은 AF_UNIX용 문자열 또는 addrinfo여야 해요.
sockaddr = Socket.sockaddr_un("/tmp/sock")
p Socket.unpack_sockaddr_un(sockaddr) #=> "/tmp/sock"
Instance Methods
accept → [client_socket, client_addrinfo]
다음 연결을 수락해요. 새 Socket 객체와 Addrinfo 객체를 반환해요.
serv = Socket.new(:INET, :STREAM, 0)
serv.listen(5)
c = Socket.new(:INET, :STREAM, 0)
c.connect(serv.connect_address)
p serv.accept #=> [#<Socket:fd 6>, #<Addrinfo: 127.0.0.1:48555 TCP>]
accept_nonblock([options]) → [client_socket, client_addrinfo]
기본 파일 디스크립터에 O_NONBLOCK을 설정한 뒤 accept(2)로 들어오는 연결을 수락해요. 들어오는 연결용 수락 소켓 client_socket과 Addrinfo인 client_addrinfo를 담은 배열을 반환해요.
# In one script, start this first
require 'socket'
include Socket::Constants
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(2200, 'localhost')
socket.bind(sockaddr)
socket.listen(5)
begin # emulate blocking accept
client_socket, client_addrinfo = socket.accept_nonblock
rescue IO::WaitReadable, Errno::EINTR
IO.select([socket])
retry
end
puts "The client said, '#{client_socket.readline.chomp}'"
client_socket.puts "Hello from script one!"
socket.close
*accept_nonblock* 호출이 실패하면 던져질 수 있는 예외는 Socket#accept를 참고하세요.
Socket#accept_nonblock은 Errno::EWOULDBLOCK을 포함해 accept(2) 실패에 해당하는 어떤 오류든 발생시킬 수 있어요.
예외가 Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED 또는 Errno::EPROTO면 IO::WaitReadable로 확장돼요. 그래서 IO::WaitReadable로 예외를 잡아 accept_nonblock을 재시도할 수 있어요.
키워드 인자 exception을 false로 지정하면 IO::WaitReadable 예외를 발생시키지 않고 :wait_readable 심볼을 반환하게 할 수 있어요.
bind(local_sockaddr) → 0
주어진 로컬 주소에 바인딩해요.
파라미터: local_sockaddr — 문자열에 담긴 struct sockaddr 또는 Addrinfo 객체.
require 'socket'
# use Addrinfo
socket = Socket.new(:INET, :STREAM, 0)
socket.bind(Addrinfo.tcp("127.0.0.1", 2222))
p socket.local_address #=> #<Addrinfo: 127.0.0.1:2222 TCP>
# use struct sockaddr
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.bind( sockaddr )
Unix 기반 시스템에서 bind가 실패하면 Errno::EACCES, Errno::EADDRINUSE, Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT, Errno::EBADF, Errno::EFAULT, Errno::EINVAL, Errno::ENAMETOOLONG, Errno::ENOBUFS, Errno::ENOSR, Errno::ENOTSOCK, Errno::EOPNOTSUPP 등이 발생할 수 있어요.
connect(remote_sockaddr) → 0
주어진 remote_sockaddr에 연결을 요청해요. 성공하면 0을, 아니면 예외를 반환해요.
파라미터: remote_sockaddr — 문자열이나 Addrinfo 객체에 담긴 struct sockaddr.
# Pull down Google's web page
require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 80, 'www.google.com' )
socket.connect( sockaddr )
socket.write( "GET / HTTP/1.0\r\n\r\n" )
results = socket.read
Unix 기반 시스템에서 connect가 실패하면 Errno::EACCES, Errno::EADDRINUSE, Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT, Errno::EALREADY, Errno::EBADF, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EFAULT, Errno::EHOSTUNREACH, Errno::EINPROGRESS, Errno::EINTR, Errno::EISCONN, Errno::EINVAL, Errno::ENAMETOOLONG, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::ENOBUFS, Errno::ENOTSOCK, Errno::EOPNOTSUPP, Errno::EPROTOTYPE, Errno::ETIMEDOUT 등이 발생할 수 있어요.
connect_nonblock(remote_sockaddr, [options]) → 0
기본 파일 디스크립터에 O_NONBLOCK을 설정한 뒤 주어진 remote_sockaddr에 연결을 요청해요. 성공하면 0을, 아니면 예외를 반환해요.
# Pull down Google's web page
require 'socket'
include Socket::Constants
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(80, 'www.google.com')
begin # emulate blocking connect
socket.connect_nonblock(sockaddr)
rescue IO::WaitWritable
IO.select(nil, [socket]) # wait 3-way handshake completion
begin
socket.connect_nonblock(sockaddr) # check connection failure
rescue Errno::EISCONN
end
end
socket.write("GET / HTTP/1.0\r\n\r\n")
results = socket.read
*connect_nonblock* 호출이 실패하면 던져질 수 있는 예외는 Socket#connect를 참고하세요.
Socket#connect_nonblock은 Errno::EINPROGRESS를 포함해 connect(2) 실패에 해당하는 어떤 오류든 발생시킬 수 있어요.
예외가 Errno::EINPROGRESS면 IO::WaitWritable로 확장돼요. 그래서 IO::WaitWritable로 예외를 잡아 connect_nonblock을 재시도할 수 있어요.
키워드 인자 exception을 false로 지정하면 IO::WaitWritable 예외를 발생시키지 않고 :wait_writable 심볼을 반환하게 할 수 있어요.
listen(int) → 0
지정된 int를 백로그(backlog)로 하여 연결을 기다려요. listen 호출은 socket이 SOCK_STREAM 또는 SOCK_SEQPACKET 타입일 때만 적용돼요.
파라미터: backlog — 대기 중인 연결 큐의 최대 길이.
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 )
backlog 양이 구현에 의존하는 최대 큐 길이를 초과하면 구현의 최대 큐 길이가 사용돼요.
Unix 기반 시스템에서 listen이 실패하면 Errno::EBADF, Errno::EDESTADDRREQ, Errno::EINVAL, Errno::ENOTSOCK, Errno::EOPNOTSUPP, Errno::EACCES, Errno::ENOBUFS 등이 발생할 수 있어요.
recvfrom(maxlen) → [mesg, sender_addrinfo]
socket에서 최대 maxlen 바이트를 받아요. flags는 MSG_ 옵션 0개 이상이에요. 결과의 첫 요소 mesg는 받은 데이터예요. 두 번째 요소 sender_addrinfo는 보낸 사람의 프로토콜 특화 주소 정보를 담아요.
파라미터: maxlen — 소켓에서 받을 최대 바이트 수. flags — MSG_ 옵션 0개 이상.
# In one file, start this first
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 )
client, client_addrinfo = socket.accept
data = client.recvfrom( 20 )[0].chomp
puts "I only received 20 bytes '#{data}'"
sleep 1
socket.close
recvfrom_nonblock(maxlen[, flags[, outbuf[, opts]]]) → [mesg, sender_addrinfo]
기본 파일 디스크립터에 O_NONBLOCK을 설정한 뒤 recvfrom(2)로 socket에서 최대 maxlen 바이트를 받아요. flags는 MSG_ 옵션 0개 이상이에요. 결과의 첫 요소 mesg는 받은 데이터, 두 번째 요소 sender_addrinfo는 보낸 사람의 주소 정보예요.
recvfrom(2)가 0을 반환하면 Socket#recv_nonblock은 nil을 반환해요. 대부분의 경우 연결이 닫혔다는 뜻이지만, UDP 연결에서는 빈 패킷이 수신됐다는 뜻일 수도 있어요. 기반 API가 이 두 경우를 구분할 수 없기 때문이에요.
파라미터: maxlen — 최대 수신 바이트 수. flags — MSG_ 옵션 0개 이상. outbuf — 목적지 String 버퍼. opts — exception: false를 지원하는 키워드 해시.
*recvfrom_nonblock* 호출이 실패하면 던져질 수 있는 예외는 Socket#recvfrom을 참고하세요.
Socket#recvfrom_nonblock은 Errno::EWOULDBLOCK을 포함해 recvfrom(2) 실패에 해당하는 어떤 오류든 발생시킬 수 있어요.
예외가 Errno::EWOULDBLOCK 또는 Errno::EAGAIN이면 IO::WaitReadable로 확장돼요. 키워드 인자 exception을 false로 지정하면 IO::WaitReadable 예외 대신 :wait_readable 심볼을 반환해요.
sysaccept → [client_socket_fd, client_addrinfo]
들어오는 연결을 수락하고, 들어오는 연결용 (정수) 파일 디스크립터 client_socket_fd와 Addrinfo인 client_addrinfo를 담은 배열을 반환해요.
# In one script, start this first
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 )
client_fd, client_addrinfo = socket.sysaccept
client_socket = Socket.for_fd( client_fd )
puts "The client said, '#{client_socket.readline.chomp}'"
client_socket.puts "Hello from script one!"
socket.close
*sysaccept* 호출이 실패하면 던져질 수 있는 예외는 Socket#accept를 참고하세요.