Created
May 10, 2017 06:58
-
-
Save dcki/831df1fec4595dbbbf94586677c91cf0 to your computer and use it in GitHub Desktop.
p2p chat (with NAT hole punching, I think)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'socket' | |
| require 'io/wait' # Provides socket#ready? | |
| require 'timeout' | |
| if ARGV.length != 1 | |
| abort 'Usage: ruby p2p_chat.rb their_ip_address' | |
| end | |
| my_port = 2000 | |
| their_host = ARGV[0] | |
| their_port = 2000 | |
| socket = nil | |
| # Try to connect twice then listen, and repeat. | |
| # I *think* this achieves NAT hole punching, but I don't think I've confirmed | |
| # that yet and there are likely better hole punching algorithms. | |
| server = nil | |
| until socket | |
| server.close if server | |
| 2.times do | |
| unless socket | |
| puts 'punch' | |
| socket = Timeout::timeout(1, Timeout::Error) do | |
| begin | |
| port_available = false | |
| until port_available | |
| begin | |
| begin | |
| s = TCPSocket.new their_host, their_port, '0.0.0.0', 2000 | |
| rescue Errno::ECONNREFUSED | |
| end | |
| port_available = true | |
| rescue Errno::EADDRINUSE | |
| sleep 1 | |
| end | |
| end | |
| s | |
| rescue Timeout::Error | |
| end | |
| end | |
| end | |
| end | |
| unless socket | |
| puts 'listen' | |
| socket = Timeout::timeout(rand * 4 + 2, Timeout::Error) do | |
| begin | |
| port_available = false | |
| until port_available | |
| begin | |
| begin | |
| server = TCPServer.new my_port | |
| s = server.accept | |
| rescue Errno::ECONNREFUSED | |
| end | |
| port_available = true | |
| rescue Errno::EADDRINUSE | |
| sleep 1 | |
| end | |
| end | |
| s | |
| rescue Timeout::Error | |
| end | |
| end | |
| end | |
| end | |
| Thread.new do | |
| loop do | |
| if socket.ready? | |
| line = socket.gets | |
| puts ' >> ' + line | |
| if line.chomp == 'quit' | |
| socket.close | |
| puts '== Peer quit ==' | |
| exit | |
| end | |
| else | |
| sleep 1 | |
| end | |
| end | |
| end | |
| puts "== Ready (type 'quit' to quit) ==" | |
| # I thought some kind of keep-alive loop would be needed to send messages to keep the connection through NAT open if neither peer sends a message for a while, but that doesn't seem to be necessary. Maybe TCP sends messages that keep the NAT open, or maybe I'm not actually testing with a NAT. | |
| loop do | |
| input = $stdin.gets | |
| socket.puts input | |
| break if input.chomp == 'quit' | |
| end | |
| puts '== You quit ==' | |
| socket.close |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment