I am creating a chat application where users are behind a router (NAT). So the main problem is sending those clients a message. The server has no problem receiving messages.
UdpServer
listens for a udp packet from client A
Server
receives packet from client A
and replies with a packet to open the NATServer
now becomes a client (server constantly sends packets to client A
and the client replies to the server notifying it that it got his message) I reverse the client and server now that the NAT is open. This is because client A
can always send a message to the server through tcp.
If Server
sees that client A
does not reply back to the packets that its constantly sending it marks client A
as disconnected and stops sending packets.
// class that holds infor about client
class ClientBehindNat
{
public IPEndPoint EndpointListening;
public string Id;
}
thread that listens for new clients (connections):
while(true)
{
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 1234);
// create a new udpServer
using(var udpServer = new UdpClient())
{
// allow reusing port 1234
udpServer.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
// wait to receive data
var data = udpServer.Receive(ref ep);
// analyze data make sure it is reliable....
// maybe data is a reply from a client
if(data.IsReply())
{
// mark that client replied
// ....
continue;
}
else
{
// save new client
ConnectedClients.Add(new ClientBehindNat(){EndpointListening=ep});
}
udpServer.Connect(ep);
udpServer.Send( // data notifying client that we got his message)
}
}
thread that constantly sends udp packets to client in order to keep nat open
// send packets every 15 seconds in order to leave nat open
timer.elapsed += (a,b)=> {
foreach (var c in connectedClients)
{
// create a copy of udp server so that client can get our response
using (var copySocket = new UdpClient())
{
// allow reusing port 1234
copySocket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
copySocket.Client.Bind(new IPEndPoint(IPAddress.Any, 1234));
// log that we will send this request
// Requests[someId] = ....
// send it
copySocket.Send(someData, someData.Length, c.EndpointListening);
// first thread should be responsible for receiving this response
// dispose this client
}
}
}
Everything works great BUT is it bad to create a new Socket (UdpClient) per request? If I receive 100 requests I create 100 new sockets and dispose all that. When I try to reuse a udp client updating its endpoints I get an exception. I often need to send a message to Client A
or maybe Client Z
thats why I store its endpoint to know how to reach it. How can I reuse a udpClient updating its endpoints so that I am able to reach the client I am interested in?
I should recommend again about using Socket
s directly, it gives you a lot of flexibility. Check out the following example that uses one socket for UDP connections for both sending and receiving:
private static readonly List<EndPoint> clients = new List<EndPoint>();
private static readonly SocketAsyncEventArgs receiveEventArgs = new SocketAsyncEventArgs();
private static Socket socket;
private static void Main(string[] args)
{
receiveEventArgs.Completed += OnReceive;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.Bind(new IPEndPoint(IPAddress.Any, 6000));
Receive();
while (true)
{
Thread.Sleep(3000);
lock (clients)
{
foreach (var endPoint in clients)
socket.SendTo(Encoding.ASCII.GetBytes("PING"), endPoint);
}
}
}
private static void Receive()
{
receiveEventArgs.SetBuffer(new byte[256], 0, 256);
receiveEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 6000);
socket.ReceiveFromAsync(receiveEventArgs);
}
private static void OnReceive(object sender, SocketAsyncEventArgs e)
{
if (e.BytesTransferred > 0)
{
// Is reply?
var isReply = true/false;
if (isReply)
{
// Do domething
}
else
lock (clients)
{
clients.Add(e.RemoteEndPoint);
}
}
else
{
lock (clients)
{
clients.Remove(e.RemoteEndPoint);
}
}
Receive();
}