1require 'socket.so' 2 3class Addrinfo 4 # creates an Addrinfo object from the arguments. 5 # 6 # The arguments are interpreted as similar to self. 7 # 8 # Addrinfo.tcp("0.0.0.0", 4649).family_addrinfo("www.ruby-lang.org", 80) 9 # #=> #<Addrinfo: 221.186.184.68:80 TCP (www.ruby-lang.org:80)> 10 # 11 # Addrinfo.unix("/tmp/sock").family_addrinfo("/tmp/sock2") 12 # #=> #<Addrinfo: /tmp/sock2 SOCK_STREAM> 13 # 14 def family_addrinfo(*args) 15 if args.empty? 16 raise ArgumentError, "no address specified" 17 elsif Addrinfo === args.first 18 raise ArgumentError, "too many arguments" if args.length != 1 19 addrinfo = args.first 20 if (self.pfamily != addrinfo.pfamily) || 21 (self.socktype != addrinfo.socktype) 22 raise ArgumentError, "Addrinfo type mismatch" 23 end 24 addrinfo 25 elsif self.ip? 26 raise ArgumentError, "IP address needs host and port but #{args.length} arguments given" if args.length != 2 27 host, port = args 28 Addrinfo.getaddrinfo(host, port, self.pfamily, self.socktype, self.protocol)[0] 29 elsif self.unix? 30 raise ArgumentError, "UNIX socket needs single path argument but #{args.length} arguments given" if args.length != 1 31 path, = args 32 Addrinfo.unix(path) 33 else 34 raise ArgumentError, "unexpected family" 35 end 36 end 37 38 # creates a new Socket connected to the address of +local_addrinfo+. 39 # 40 # If _local_addrinfo_ is nil, the address of the socket is not bound. 41 # 42 # The _timeout_ specify the seconds for timeout. 43 # Errno::ETIMEDOUT is raised when timeout occur. 44 # 45 # If a block is given the created socket is yielded for each address. 46 # 47 def connect_internal(local_addrinfo, timeout=nil) # :yields: socket 48 sock = Socket.new(self.pfamily, self.socktype, self.protocol) 49 begin 50 sock.ipv6only! if self.ipv6? 51 sock.bind local_addrinfo if local_addrinfo 52 if timeout 53 begin 54 sock.connect_nonblock(self) 55 rescue IO::WaitWritable 56 if !IO.select(nil, [sock], nil, timeout) 57 raise Errno::ETIMEDOUT, 'user specified timeout' 58 end 59 begin 60 sock.connect_nonblock(self) # check connection failure 61 rescue Errno::EISCONN 62 end 63 end 64 else 65 sock.connect(self) 66 end 67 rescue Exception 68 sock.close 69 raise 70 end 71 if block_given? 72 begin 73 yield sock 74 ensure 75 sock.close if !sock.closed? 76 end 77 else 78 sock 79 end 80 end 81 private :connect_internal 82 83 # :call-seq: 84 # addrinfo.connect_from([local_addr_args], [opts]) {|socket| ... } 85 # addrinfo.connect_from([local_addr_args], [opts]) 86 # 87 # creates a socket connected to the address of self. 88 # 89 # If one or more arguments given as _local_addr_args_, 90 # it is used as the local address of the socket. 91 # _local_addr_args_ is given for family_addrinfo to obtain actual address. 92 # 93 # If _local_addr_args_ is not given, the local address of the socket is not bound. 94 # 95 # The optional last argument _opts_ is options represented by a hash. 96 # _opts_ may have following options: 97 # 98 # [:timeout] specify the timeout in seconds. 99 # 100 # If a block is given, it is called with the socket and the value of the block is returned. 101 # The socket is returned otherwise. 102 # 103 # Addrinfo.tcp("www.ruby-lang.org", 80).connect_from("0.0.0.0", 4649) {|s| 104 # s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n" 105 # puts s.read 106 # } 107 # 108 # # Addrinfo object can be taken for the argument. 109 # Addrinfo.tcp("www.ruby-lang.org", 80).connect_from(Addrinfo.tcp("0.0.0.0", 4649)) {|s| 110 # s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n" 111 # puts s.read 112 # } 113 # 114 def connect_from(*args, &block) 115 opts = Hash === args.last ? args.pop : {} 116 local_addr_args = args 117 connect_internal(family_addrinfo(*local_addr_args), opts[:timeout], &block) 118 end 119 120 # :call-seq: 121 # addrinfo.connect([opts]) {|socket| ... } 122 # addrinfo.connect([opts]) 123 # 124 # creates a socket connected to the address of self. 125 # 126 # The optional argument _opts_ is options represented by a hash. 127 # _opts_ may have following options: 128 # 129 # [:timeout] specify the timeout in seconds. 130 # 131 # If a block is given, it is called with the socket and the value of the block is returned. 132 # The socket is returned otherwise. 133 # 134 # Addrinfo.tcp("www.ruby-lang.org", 80).connect {|s| 135 # s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n" 136 # puts s.read 137 # } 138 # 139 def connect(opts={}, &block) 140 connect_internal(nil, opts[:timeout], &block) 141 end 142 143 # :call-seq: 144 # addrinfo.connect_to([remote_addr_args], [opts]) {|socket| ... } 145 # addrinfo.connect_to([remote_addr_args], [opts]) 146 # 147 # creates a socket connected to _remote_addr_args_ and bound to self. 148 # 149 # The optional last argument _opts_ is options represented by a hash. 150 # _opts_ may have following options: 151 # 152 # [:timeout] specify the timeout in seconds. 153 # 154 # If a block is given, it is called with the socket and the value of the block is returned. 155 # The socket is returned otherwise. 156 # 157 # Addrinfo.tcp("0.0.0.0", 4649).connect_to("www.ruby-lang.org", 80) {|s| 158 # s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n" 159 # puts s.read 160 # } 161 # 162 def connect_to(*args, &block) 163 opts = Hash === args.last ? args.pop : {} 164 remote_addr_args = args 165 remote_addrinfo = family_addrinfo(*remote_addr_args) 166 remote_addrinfo.send(:connect_internal, self, opts[:timeout], &block) 167 end 168 169 # creates a socket bound to self. 170 # 171 # If a block is given, it is called with the socket and the value of the block is returned. 172 # The socket is returned otherwise. 173 # 174 # Addrinfo.udp("0.0.0.0", 9981).bind {|s| 175 # s.local_address.connect {|s| s.send "hello", 0 } 176 # p s.recv(10) #=> "hello" 177 # } 178 # 179 def bind 180 sock = Socket.new(self.pfamily, self.socktype, self.protocol) 181 begin 182 sock.ipv6only! if self.ipv6? 183 sock.setsockopt(:SOCKET, :REUSEADDR, 1) 184 sock.bind(self) 185 rescue Exception 186 sock.close 187 raise 188 end 189 if block_given? 190 begin 191 yield sock 192 ensure 193 sock.close if !sock.closed? 194 end 195 else 196 sock 197 end 198 end 199 200 # creates a listening socket bound to self. 201 def listen(backlog=Socket::SOMAXCONN) 202 sock = Socket.new(self.pfamily, self.socktype, self.protocol) 203 begin 204 sock.ipv6only! if self.ipv6? 205 sock.setsockopt(:SOCKET, :REUSEADDR, 1) 206 sock.bind(self) 207 sock.listen(backlog) 208 rescue Exception 209 sock.close 210 raise 211 end 212 if block_given? 213 begin 214 yield sock 215 ensure 216 sock.close if !sock.closed? 217 end 218 else 219 sock 220 end 221 end 222 223 # iterates over the list of Addrinfo objects obtained by Addrinfo.getaddrinfo. 224 # 225 # Addrinfo.foreach(nil, 80) {|x| p x } 226 # #=> #<Addrinfo: 127.0.0.1:80 TCP (:80)> 227 # # #<Addrinfo: 127.0.0.1:80 UDP (:80)> 228 # # #<Addrinfo: [::1]:80 TCP (:80)> 229 # # #<Addrinfo: [::1]:80 UDP (:80)> 230 # 231 def self.foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=nil, &block) 232 Addrinfo.getaddrinfo(nodename, service, family, socktype, protocol, flags).each(&block) 233 end 234end 235 236class BasicSocket < IO 237 # Returns an address of the socket suitable for connect in the local machine. 238 # 239 # This method returns _self_.local_address, except following condition. 240 # 241 # - IPv4 unspecified address (0.0.0.0) is replaced by IPv4 loopback address (127.0.0.1). 242 # - IPv6 unspecified address (::) is replaced by IPv6 loopback address (::1). 243 # 244 # If the local address is not suitable for connect, SocketError is raised. 245 # IPv4 and IPv6 address which port is 0 is not suitable for connect. 246 # Unix domain socket which has no path is not suitable for connect. 247 # 248 # Addrinfo.tcp("0.0.0.0", 0).listen {|serv| 249 # p serv.connect_address #=> #<Addrinfo: 127.0.0.1:53660 TCP> 250 # serv.connect_address.connect {|c| 251 # s, _ = serv.accept 252 # p [c, s] #=> [#<Socket:fd 4>, #<Socket:fd 6>] 253 # } 254 # } 255 # 256 def connect_address 257 addr = local_address 258 afamily = addr.afamily 259 if afamily == Socket::AF_INET 260 raise SocketError, "unbound IPv4 socket" if addr.ip_port == 0 261 if addr.ip_address == "0.0.0.0" 262 addr = Addrinfo.new(["AF_INET", addr.ip_port, nil, "127.0.0.1"], addr.pfamily, addr.socktype, addr.protocol) 263 end 264 elsif defined?(Socket::AF_INET6) && afamily == Socket::AF_INET6 265 raise SocketError, "unbound IPv6 socket" if addr.ip_port == 0 266 if addr.ip_address == "::" 267 addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol) 268 elsif addr.ip_address == "0.0.0.0" # MacOS X 10.4 returns "a.b.c.d" for IPv4-mapped IPv6 address. 269 addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol) 270 elsif addr.ip_address == "::ffff:0.0.0.0" # MacOS X 10.6 returns "::ffff:a.b.c.d" for IPv4-mapped IPv6 address. 271 addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol) 272 end 273 elsif defined?(Socket::AF_UNIX) && afamily == Socket::AF_UNIX 274 raise SocketError, "unbound Unix socket" if addr.unix_path == "" 275 end 276 addr 277 end 278end 279 280class Socket < BasicSocket 281 # enable the socket option IPV6_V6ONLY if IPV6_V6ONLY is available. 282 def ipv6only! 283 if defined? Socket::IPV6_V6ONLY 284 self.setsockopt(:IPV6, :V6ONLY, 1) 285 end 286 end 287 288 # :call-seq: 289 # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... } 290 # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) 291 # 292 # creates a new socket object connected to host:port using TCP/IP. 293 # 294 # If local_host:local_port is given, 295 # the socket is bound to it. 296 # 297 # The optional last argument _opts_ is options represented by a hash. 298 # _opts_ may have following options: 299 # 300 # [:connect_timeout] specify the timeout in seconds. 301 # 302 # If a block is given, the block is called with the socket. 303 # The value of the block is returned. 304 # The socket is closed when this method returns. 305 # 306 # The optional last argument _opts_ is options represented by a hash. 307 # _opts_ may have following options: 308 # 309 # [:timeout] specify the timeout in seconds. 310 # 311 # If no block is given, the socket is returned. 312 # 313 # Socket.tcp("www.ruby-lang.org", 80) {|sock| 314 # sock.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n" 315 # sock.close_write 316 # puts sock.read 317 # } 318 # 319 def self.tcp(host, port, *rest) # :yield: socket 320 opts = Hash === rest.last ? rest.pop : {} 321 raise ArgumentError, "wrong number of arguments (#{rest.length} for 2)" if 2 < rest.length 322 local_host, local_port = rest 323 last_error = nil 324 ret = nil 325 326 connect_timeout = opts[:connect_timeout] 327 328 local_addr_list = nil 329 if local_host != nil || local_port != nil 330 local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil) 331 end 332 333 Addrinfo.foreach(host, port, nil, :STREAM) {|ai| 334 if local_addr_list 335 local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily } 336 next if !local_addr 337 else 338 local_addr = nil 339 end 340 begin 341 sock = local_addr ? 342 ai.connect_from(local_addr, :timeout => connect_timeout) : 343 ai.connect(:timeout => connect_timeout) 344 rescue SystemCallError 345 last_error = $! 346 next 347 end 348 ret = sock 349 break 350 } 351 if !ret 352 if last_error 353 raise last_error 354 else 355 raise SocketError, "no appropriate local address" 356 end 357 end 358 if block_given? 359 begin 360 yield ret 361 ensure 362 ret.close if !ret.closed? 363 end 364 else 365 ret 366 end 367 end 368 369 # :stopdoc: 370 def self.ip_sockets_port0(ai_list, reuseaddr) 371 sockets = [] 372 begin 373 sockets.clear 374 port = nil 375 ai_list.each {|ai| 376 begin 377 s = Socket.new(ai.pfamily, ai.socktype, ai.protocol) 378 rescue SystemCallError 379 next 380 end 381 sockets << s 382 s.ipv6only! if ai.ipv6? 383 if reuseaddr 384 s.setsockopt(:SOCKET, :REUSEADDR, 1) 385 end 386 if !port 387 s.bind(ai) 388 port = s.local_address.ip_port 389 else 390 s.bind(ai.family_addrinfo(ai.ip_address, port)) 391 end 392 } 393 rescue Errno::EADDRINUSE 394 sockets.each {|s| s.close } 395 retry 396 rescue Exception 397 sockets.each {|s| s.close } 398 raise 399 end 400 sockets 401 end 402 class << self 403 private :ip_sockets_port0 404 end 405 406 def self.tcp_server_sockets_port0(host) 407 ai_list = Addrinfo.getaddrinfo(host, 0, nil, :STREAM, nil, Socket::AI_PASSIVE) 408 sockets = ip_sockets_port0(ai_list, true) 409 begin 410 sockets.each {|s| 411 s.listen(Socket::SOMAXCONN) 412 } 413 rescue Exception 414 sockets.each {|s| s.close } 415 raise 416 end 417 sockets 418 end 419 class << self 420 private :tcp_server_sockets_port0 421 end 422 # :startdoc: 423 424 # creates TCP/IP server sockets for _host_ and _port_. 425 # _host_ is optional. 426 # 427 # If no block given, 428 # it returns an array of listening sockets. 429 # 430 # If a block is given, the block is called with the sockets. 431 # The value of the block is returned. 432 # The socket is closed when this method returns. 433 # 434 # If _port_ is 0, actual port number is chosen dynamically. 435 # However all sockets in the result has same port number. 436 # 437 # # tcp_server_sockets returns two sockets. 438 # sockets = Socket.tcp_server_sockets(1296) 439 # p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>] 440 # 441 # # The sockets contains IPv6 and IPv4 sockets. 442 # sockets.each {|s| p s.local_address } 443 # #=> #<Addrinfo: [::]:1296 TCP> 444 # # #<Addrinfo: 0.0.0.0:1296 TCP> 445 # 446 # # IPv6 and IPv4 socket has same port number, 53114, even if it is chosen dynamically. 447 # sockets = Socket.tcp_server_sockets(0) 448 # sockets.each {|s| p s.local_address } 449 # #=> #<Addrinfo: [::]:53114 TCP> 450 # # #<Addrinfo: 0.0.0.0:53114 TCP> 451 # 452 # # The block is called with the sockets. 453 # Socket.tcp_server_sockets(0) {|sockets| 454 # p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>] 455 # } 456 # 457 def self.tcp_server_sockets(host=nil, port) 458 if port == 0 459 sockets = tcp_server_sockets_port0(host) 460 else 461 last_error = nil 462 sockets = [] 463 begin 464 Addrinfo.foreach(host, port, nil, :STREAM, nil, Socket::AI_PASSIVE) {|ai| 465 begin 466 s = ai.listen 467 rescue SystemCallError 468 last_error = $! 469 next 470 end 471 sockets << s 472 } 473 if sockets.empty? 474 raise last_error 475 end 476 rescue Exception 477 sockets.each {|s| s.close } 478 raise 479 end 480 end 481 if block_given? 482 begin 483 yield sockets 484 ensure 485 sockets.each {|s| s.close if !s.closed? } 486 end 487 else 488 sockets 489 end 490 end 491 492 # yield socket and client address for each a connection accepted via given sockets. 493 # 494 # The arguments are a list of sockets. 495 # The individual argument should be a socket or an array of sockets. 496 # 497 # This method yields the block sequentially. 498 # It means that the next connection is not accepted until the block returns. 499 # So concurrent mechanism, thread for example, should be used to service multiple clients at a time. 500 # 501 def self.accept_loop(*sockets) # :yield: socket, client_addrinfo 502 sockets.flatten!(1) 503 if sockets.empty? 504 raise ArgumentError, "no sockets" 505 end 506 loop { 507 readable, _, _ = IO.select(sockets) 508 readable.each {|r| 509 begin 510 sock, addr = r.accept_nonblock 511 rescue IO::WaitReadable 512 next 513 end 514 yield sock, addr 515 } 516 } 517 end 518 519 # creates a TCP/IP server on _port_ and calls the block for each connection accepted. 520 # The block is called with a socket and a client_address as an Addrinfo object. 521 # 522 # If _host_ is specified, it is used with _port_ to determine the server addresses. 523 # 524 # The socket is *not* closed when the block returns. 525 # So application should close it explicitly. 526 # 527 # This method calls the block sequentially. 528 # It means that the next connection is not accepted until the block returns. 529 # So concurrent mechanism, thread for example, should be used to service multiple clients at a time. 530 # 531 # Note that Addrinfo.getaddrinfo is used to determine the server socket addresses. 532 # When Addrinfo.getaddrinfo returns two or more addresses, 533 # IPv4 and IPv6 address for example, 534 # all of them are used. 535 # Socket.tcp_server_loop succeeds if one socket can be used at least. 536 # 537 # # Sequential echo server. 538 # # It services only one client at a time. 539 # Socket.tcp_server_loop(16807) {|sock, client_addrinfo| 540 # begin 541 # IO.copy_stream(sock, sock) 542 # ensure 543 # sock.close 544 # end 545 # } 546 # 547 # # Threaded echo server 548 # # It services multiple clients at a time. 549 # # Note that it may accept connections too much. 550 # Socket.tcp_server_loop(16807) {|sock, client_addrinfo| 551 # Thread.new { 552 # begin 553 # IO.copy_stream(sock, sock) 554 # ensure 555 # sock.close 556 # end 557 # } 558 # } 559 # 560 def self.tcp_server_loop(host=nil, port, &b) # :yield: socket, client_addrinfo 561 tcp_server_sockets(host, port) {|sockets| 562 accept_loop(sockets, &b) 563 } 564 end 565 566 # :call-seq: 567 # Socket.udp_server_sockets([host, ] port) 568 # 569 # Creates UDP/IP sockets for a UDP server. 570 # 571 # If no block given, it returns an array of sockets. 572 # 573 # If a block is given, the block is called with the sockets. 574 # The value of the block is returned. 575 # The sockets are closed when this method returns. 576 # 577 # If _port_ is zero, some port is chosen. 578 # But the chosen port is used for the all sockets. 579 # 580 # # UDP/IP echo server 581 # Socket.udp_server_sockets(0) {|sockets| 582 # p sockets.first.local_address.ip_port #=> 32963 583 # Socket.udp_server_loop_on(sockets) {|msg, msg_src| 584 # msg_src.reply msg 585 # } 586 # } 587 # 588 def self.udp_server_sockets(host=nil, port) 589 last_error = nil 590 sockets = [] 591 592 ipv6_recvpktinfo = nil 593 if defined? Socket::AncillaryData 594 if defined? Socket::IPV6_RECVPKTINFO # RFC 3542 595 ipv6_recvpktinfo = Socket::IPV6_RECVPKTINFO 596 elsif defined? Socket::IPV6_PKTINFO # RFC 2292 597 ipv6_recvpktinfo = Socket::IPV6_PKTINFO 598 end 599 end 600 601 local_addrs = Socket.ip_address_list 602 603 ip_list = [] 604 Addrinfo.foreach(host, port, nil, :DGRAM, nil, Socket::AI_PASSIVE) {|ai| 605 if ai.ipv4? && ai.ip_address == "0.0.0.0" 606 local_addrs.each {|a| 607 next if !a.ipv4? 608 ip_list << Addrinfo.new(a.to_sockaddr, :INET, :DGRAM, 0); 609 } 610 elsif ai.ipv6? && ai.ip_address == "::" && !ipv6_recvpktinfo 611 local_addrs.each {|a| 612 next if !a.ipv6? 613 ip_list << Addrinfo.new(a.to_sockaddr, :INET6, :DGRAM, 0); 614 } 615 else 616 ip_list << ai 617 end 618 } 619 620 if port == 0 621 sockets = ip_sockets_port0(ip_list, false) 622 else 623 ip_list.each {|ip| 624 ai = Addrinfo.udp(ip.ip_address, port) 625 begin 626 s = ai.bind 627 rescue SystemCallError 628 last_error = $! 629 next 630 end 631 sockets << s 632 } 633 if sockets.empty? 634 raise last_error 635 end 636 end 637 638 sockets.each {|s| 639 ai = s.local_address 640 if ipv6_recvpktinfo && ai.ipv6? && ai.ip_address == "::" 641 s.setsockopt(:IPV6, ipv6_recvpktinfo, 1) 642 end 643 } 644 645 if block_given? 646 begin 647 yield sockets 648 ensure 649 sockets.each {|s| s.close if !s.closed? } if sockets 650 end 651 else 652 sockets 653 end 654 end 655 656 # :call-seq: 657 # Socket.udp_server_recv(sockets) {|msg, msg_src| ... } 658 # 659 # Receive UDP/IP packets from the given _sockets_. 660 # For each packet received, the block is called. 661 # 662 # The block receives _msg_ and _msg_src_. 663 # _msg_ is a string which is the payload of the received packet. 664 # _msg_src_ is a Socket::UDPSource object which is used for reply. 665 # 666 # Socket.udp_server_loop can be implemented using this method as follows. 667 # 668 # udp_server_sockets(host, port) {|sockets| 669 # loop { 670 # readable, _, _ = IO.select(sockets) 671 # udp_server_recv(readable) {|msg, msg_src| ... } 672 # } 673 # } 674 # 675 def self.udp_server_recv(sockets) 676 sockets.each {|r| 677 begin 678 msg, sender_addrinfo, _, *controls = r.recvmsg_nonblock 679 rescue IO::WaitReadable 680 next 681 end 682 ai = r.local_address 683 if ai.ipv6? and pktinfo = controls.find {|c| c.cmsg_is?(:IPV6, :PKTINFO) } 684 ai = Addrinfo.udp(pktinfo.ipv6_pktinfo_addr.ip_address, ai.ip_port) 685 yield msg, UDPSource.new(sender_addrinfo, ai) {|reply_msg| 686 r.sendmsg reply_msg, 0, sender_addrinfo, pktinfo 687 } 688 else 689 yield msg, UDPSource.new(sender_addrinfo, ai) {|reply_msg| 690 r.send reply_msg, 0, sender_addrinfo 691 } 692 end 693 } 694 end 695 696 # :call-seq: 697 # Socket.udp_server_loop_on(sockets) {|msg, msg_src| ... } 698 # 699 # Run UDP/IP server loop on the given sockets. 700 # 701 # The return value of Socket.udp_server_sockets is appropriate for the argument. 702 # 703 # It calls the block for each message received. 704 # 705 def self.udp_server_loop_on(sockets, &b) # :yield: msg, msg_src 706 loop { 707 readable, _, _ = IO.select(sockets) 708 udp_server_recv(readable, &b) 709 } 710 end 711 712 # :call-seq: 713 # Socket.udp_server_loop(port) {|msg, msg_src| ... } 714 # Socket.udp_server_loop(host, port) {|msg, msg_src| ... } 715 # 716 # creates a UDP/IP server on _port_ and calls the block for each message arrived. 717 # The block is called with the message and its source information. 718 # 719 # This method allocates sockets internally using _port_. 720 # If _host_ is specified, it is used conjunction with _port_ to determine the server addresses. 721 # 722 # The _msg_ is a string. 723 # 724 # The _msg_src_ is a Socket::UDPSource object. 725 # It is used for reply. 726 # 727 # # UDP/IP echo server. 728 # Socket.udp_server_loop(9261) {|msg, msg_src| 729 # msg_src.reply msg 730 # } 731 # 732 def self.udp_server_loop(host=nil, port, &b) # :yield: message, message_source 733 udp_server_sockets(host, port) {|sockets| 734 udp_server_loop_on(sockets, &b) 735 } 736 end 737 738 # UDP/IP address information used by Socket.udp_server_loop. 739 class UDPSource 740 # +remote_address+ is an Addrinfo object. 741 # 742 # +local_address+ is an Addrinfo object. 743 # 744 # +reply_proc+ is a Proc used to send reply back to the source. 745 def initialize(remote_address, local_address, &reply_proc) 746 @remote_address = remote_address 747 @local_address = local_address 748 @reply_proc = reply_proc 749 end 750 751 # Address of the source 752 attr_reader :remote_address 753 754 # Local address 755 attr_reader :local_address 756 757 def inspect # :nodoc: 758 "\#<#{self.class}: #{@remote_address.inspect_sockaddr} to #{@local_address.inspect_sockaddr}>" 759 end 760 761 # Sends the String +msg+ to the source 762 def reply(msg) 763 @reply_proc.call msg 764 end 765 end 766 767 # creates a new socket connected to path using UNIX socket socket. 768 # 769 # If a block is given, the block is called with the socket. 770 # The value of the block is returned. 771 # The socket is closed when this method returns. 772 # 773 # If no block is given, the socket is returned. 774 # 775 # # talk to /tmp/sock socket. 776 # Socket.unix("/tmp/sock") {|sock| 777 # t = Thread.new { IO.copy_stream(sock, STDOUT) } 778 # IO.copy_stream(STDIN, sock) 779 # t.join 780 # } 781 # 782 def self.unix(path) # :yield: socket 783 addr = Addrinfo.unix(path) 784 sock = addr.connect 785 if block_given? 786 begin 787 yield sock 788 ensure 789 sock.close if !sock.closed? 790 end 791 else 792 sock 793 end 794 end 795 796 # creates a UNIX server socket on _path_ 797 # 798 # If no block given, it returns a listening socket. 799 # 800 # If a block is given, it is called with the socket and the block value is returned. 801 # When the block exits, the socket is closed and the socket file is removed. 802 # 803 # socket = Socket.unix_server_socket("/tmp/s") 804 # p socket #=> #<Socket:fd 3> 805 # p socket.local_address #=> #<Addrinfo: /tmp/s SOCK_STREAM> 806 # 807 # Socket.unix_server_socket("/tmp/sock") {|s| 808 # p s #=> #<Socket:fd 3> 809 # p s.local_address #=> # #<Addrinfo: /tmp/sock SOCK_STREAM> 810 # } 811 # 812 def self.unix_server_socket(path) 813 if !unix_socket_abstract_name?(path) 814 begin 815 st = File.lstat(path) 816 rescue Errno::ENOENT 817 end 818 if st && st.socket? && st.owned? 819 File.unlink path 820 end 821 end 822 s = Addrinfo.unix(path).listen 823 if block_given? 824 begin 825 yield s 826 ensure 827 s.close if !s.closed? 828 if !unix_socket_abstract_name?(path) 829 File.unlink path 830 end 831 end 832 else 833 s 834 end 835 end 836 837 class << self 838 private 839 840 def unix_socket_abstract_name?(path) 841 /linux/ =~ RUBY_PLATFORM && /\A(\0|\z)/ =~ path 842 end 843 end 844 845 # creates a UNIX socket server on _path_. 846 # It calls the block for each socket accepted. 847 # 848 # If _host_ is specified, it is used with _port_ to determine the server ports. 849 # 850 # The socket is *not* closed when the block returns. 851 # So application should close it. 852 # 853 # This method deletes the socket file pointed by _path_ at first if 854 # the file is a socket file and it is owned by the user of the application. 855 # This is safe only if the directory of _path_ is not changed by a malicious user. 856 # So don't use /tmp/malicious-users-directory/socket. 857 # Note that /tmp/socket and /tmp/your-private-directory/socket is safe assuming that /tmp has sticky bit. 858 # 859 # # Sequential echo server. 860 # # It services only one client at a time. 861 # Socket.unix_server_loop("/tmp/sock") {|sock, client_addrinfo| 862 # begin 863 # IO.copy_stream(sock, sock) 864 # ensure 865 # sock.close 866 # end 867 # } 868 # 869 def self.unix_server_loop(path, &b) # :yield: socket, client_addrinfo 870 unix_server_socket(path) {|serv| 871 accept_loop(serv, &b) 872 } 873 end 874 875end 876 877