I was using them to ensure that my program didn't get stuck. Would you recommend I put a wait time on a selector? I just don't want to wait too long since you don't want a game to fall 1 second behind.
Just set them to wait for 1 microsecond. That way it will run at (theoretically of course) 1000000 FPS maximum IF nothing else contributes to frame time, and I am sure there are many things that do so to much greater extents
.
Do you know of a way to solve this issue using TCP?
You don't have to solve this for TCP, because well... this problem is inherent only to UDP. The reason why
UDP hole punching is needed, is because UDP is not a connection based protocol such as TCP. A NAT does it's magic mostly through mapping external port numbers to internal addresses so that it can track each connection and only has to expose its external address to the internet pretending it is the one from which all data originates.
In the case of TCP, this is trivial since the TCP connection establishment towards the internet server already gives the NAT the information needed to insert into its table for future operation. With UDP it's a bit more tricky, since the source and destination ports don't reveal much of how the application will receive replies from the server. You could send a datagram to the server with some (source port, destination port) and get a reply on some other pair of ports. Or in the case of client to client, both would need to somehow guess the opposite sides port numbers. Since these aren't in the table NAT won't know where to send the datagrams in the internal network and just drop them. If you are communicating between 2 hosts both behind NATs if it is a simple NAT implementation without randomization, then hole punching will work. If one or both of them randomizes both source and destination ports (for security reasons) then setting up a connection can be tedious to impossible. In this case it would be easier to resort to port forwarding, even for UDP communication.
What hole punching does not solve is the fact that a firewall might still drop datagrams that it does not recognize as part of an existing connection. Stateful firewalls do so in a similar way to a NAT device, by recording the ports, addresses and states of incoming and outgoing communications. If anything doesn't match the picture perfectly it is dropped according to the security policies specified in the firewall.
You can read more about hole punching around the internet. Theoretically it is also possible with TCP, however it is done much less frequently due to the way TCP works. Typically if an application uses TCP and you want to connect to a server behind a NAT, it is the responsibility of the NAT owner to set up proper port forwarding rules to ensure incoming connection requests are forwarded to the right internal host. Outbound connections are not a problem with a NAT.
In either case, reconfiguration of your internet gateway device is mandatory unless you intend on providing a dedicated server in the internet. Since most gamers are already accustomed to setting up port forwarding rules, it is probably simpler to use TCP for the time being instead of going and getting a server merely to try to UDP hole punch (which might not even work sometimes).