Unity Socket Port-forward
I´ve been following the series of networking tutorials from Tom Weiland, were he create a simple multiplayer game with c# sockets. I have followed the whole tutorial series, and it works fine... in my computer. It uses TCP and UDP protocols, but the IP address was the local one (127.0.0.1). It was all right, and I thought that just by changing the IP address to a IPv4 one, it would be just fine. In fact, I was right, but I have two main problems:
-1. I want to obtain the IP address of any computer running the client script, in order to set up the TCP and UDP protocols.
-2. I have a router were I am connected, so my IP address in fact still being kind of local, and the data in that socket can´t be accessed from another computer. I´ve being searching in forums for 2 weeks, and I found about port-forward, NAT punch-throwing, UDP hole punching, TCP hole punching, and quite a lot of information about it. The problem is that I don´t know how to implement those methods in my server and client scripts. And in fact I don´t even know which one to use.
In conclusion, I want to port-forward THROUGH CODE.
Thanks in advance.
Here I leave the Client.cs and Server.cs scripts:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System;
public class Client : MonoBehaviour
{
public static Client instance;
public static int dataBufferSize = 4096;
private string ip = "xxx.xxx.xxx.xxx";
public int port = 26950;
//private string ip = null;
public int myId = 0;
public TCP tcp;
public UDP udp;
private bool isConnected = false;
private delegate void PacketHandler(Packet _packet);
private static Dictionary<int, PacketHandler> packetHandlers;
private void Awake()
{
if (instance == null)
{
instance = this;
}
else if (instance != this)
{
Debug.Log("Instance already exists, destroying object!");
Destroy(this);
}
}
private void OnApplicationQuit()
{
Disconnect(); // Disconnect when the game is closed
}
/// <summary>Attempts to connect to the server.</summary>
public void ConnectToServer()
{
tcp = new TCP();
udp = new UDP();
InitializeClientData();
isConnected = true;
tcp.Connect(); // Connect tcp, udp gets connected once tcp is done
}
public class TCP
{
public TcpClient socket;
private NetworkStream stream;
private Packet receivedData;
private byte[] receiveBuffer;
/// <summary>Attempts to connect to the server via TCP.</summary>
public void Connect()
{
socket = new TcpClient
{
ReceiveBufferSize = dataBufferSize,
SendBufferSize = dataBufferSize
};
receiveBuffer = new byte[dataBufferSize];
socket.BeginConnect(instance.ip, instance.port, ConnectCallback, socket);
}
/// <summary>Initializes the newly connected client's TCP-related info.</summary>
private void ConnectCallback(IAsyncResult _result)
{
socket.EndConnect(_result);
if (!socket.Connected)
{
return;
}
stream = socket.GetStream();
receivedData = new Packet();
stream.BeginRead(receiveBuffer, 0, dataBufferSize, ReceiveCallback, null);
}
/// <summary>Sends data to the client via TCP.</summary>
/// <param name="_packet">The packet to send.</param>
public void SendData(Packet _packet)
{
try
{
if (socket != null)
{
stream.BeginWrite(_packet.ToArray(), 0, _packet.Length(), null, null); // Send data to server
}
}
catch (Exception _ex)
{
Debug.Log($"Error sending data to server via TCP: {_ex}");
}
}
/// <summary>Reads incoming data from the stream.</summary>
private void ReceiveCallback(IAsyncResult _result)
{
try
{
int _byteLength = stream.EndRead(_result);
if (_byteLength <= 0)
{
instance.Disconnect();
return;
}
byte[] _data = new byte[_byteLength];
Array.Copy(receiveBuffer, _data, _byteLength);
receivedData.Reset(HandleData(_data)); // Reset receivedData if all data was handled
stream.BeginRead(receiveBuffer, 0, dataBufferSize, ReceiveCallback, null);
}
catch
{
Disconnect();
}
}
/// <summary>Prepares received data to be used by the appropriate packet handler methods.</summary>
/// <param name="_data">The recieved data.</param>
private bool HandleData(byte[] _data)
{
int _packetLength = 0;
receivedData.SetBytes(_data);
if (receivedData.UnreadLength() >= 4)
{
// If client's received data contains a packet
_packetLength = receivedData.ReadInt();
if (_packetLength <= 0)
{
// If packet contains no data
return true; // Reset receivedData instance to allow it to be reused
}
}
while (_packetLength > 0 && _packetLength <= receivedData.UnreadLength())
{
// While packet contains data AND packet data length doesn't exceed the length of the packet we're reading
byte[] _packetBytes = receivedData.ReadBytes(_packetLength);
ThreadManager.ExecuteOnMainThread(() =>
{
using (Packet _packet = new Packet(_packetBytes))
{
int _packetId = _packet.ReadInt();
packetHandlers[_packetId](_packet); // Call appropriate method to handle the packet
}
});
_packetLength = 0; // Reset packet length
if (receivedData.UnreadLength() >= 4)
{
// If client's received data contains another packet
_packetLength = receivedData.ReadInt();
if (_packetLength <= 0)
{
// If packet contains no data
return true; // Reset receivedData instance to allow it to be reused
}
}
}
if (_packetLength <= 1)
{
return true; // Reset receivedData instance to allow it to be reused
}
return false;
}
/// <summary>Disconnects from the server and cleans up the TCP connection.</summary>
private void Disconnect()
{
instance.Disconnect();
stream = null;
receivedData = null;
receiveBuffer = null;
socket = null;
}
}
public class UDP
{
public UdpClient socket;
public IPEndPoint endPoint;
public UDP()
{
endPoint = new IPEndPoint(IPAddress.Parse(instance.ip), instance.port);
}
/// <summary>Attempts to connect to the server via UDP.</summary>
/// <param name="_localPort">The port number to bind the UDP socket to.</param>
public void Connect(int _localPort)
{
socket = new UdpClient(_localPort);
socket.Connect(endPoint);
socket.BeginReceive(ReceiveCallback, null);
using (Packet _packet = new Packet())
{
SendData(_packet);
}
}
/// <summary>Sends data to the client via UDP.</summary>
/// <param name="_packet">The packet to send.</param>
public void SendData(Packet _packet)
{
try
{
_packet.InsertInt(instance.myId); // Insert the client's ID at the start of the packet
if (socket != null)
{
socket.BeginSend(_packet.ToArray(), _packet.Length(), null, null);
}
}
catch (Exception _ex)
{
Debug.Log($"Error sending data to server via UDP: {_ex}");
}
}
/// <summary>Receives incoming UDP data.</summary>
private void ReceiveCallback(IAsyncResult _result)
{
try
{
byte[] _data = socket.EndReceive(_result, ref endPoint);
socket.BeginReceive(ReceiveCallback, null);
if (_data.Length < 4)
{
instance.Disconnect();
return;
}
HandleData(_data);
}
catch
{
Disconnect();
}
}
/// <summary>Prepares received data to be used by the appropriate packet handler methods.</summary>
/// <param name="_data">The recieved data.</param>
private void HandleData(byte[] _data)
{
using (Packet _packet = new Packet(_data))
{
int _packetLength = _packet.ReadInt();
_data = _packet.ReadBytes(_packetLength);
}
ThreadManager.ExecuteOnMainThread(() =>
{
using (Packet _packet = new Packet(_data))
{
int _packetId = _packet.ReadInt();
packetHandlers[_packetId](_packet); // Call appropriate method to handle the packet
}
});
}
/// <summary>Disconnects from the server and cleans up the UDP connection.</summary>
private void Disconnect()
{
instance.Disconnect();
endPoint = null;
socket = null;
}
}
/// <summary>Initializes all necessary client data.</summary>
private void InitializeClientData()
{
packetHandlers = new Dictionary<int, PacketHandler>()
{
{ (int)ServerPackets.welcome, ClientHandle.Welcome },
{ (int)ServerPackets.spawnPlayer, ClientHandle.SpawnPlayer },
{ (int)ServerPackets.playerPosition, ClientHandle.PlayerPosition },
{ (int)ServerPackets.playerRotation, ClientHandle.PlayerRotation },
{ (int)ServerPackets.playerDisconnected, ClientHandle.PlayerDisconnected },
{ (int)ServerPackets.playerHealth, ClientHandle.PlayerHealth },
{ (int)ServerPackets.playerRespawned, ClientHandle.PlayerRespawned },
{ (int)ServerPackets.createItemSpawner, ClientHandle.CreateItemSpawner },
{ (int)ServerPackets.itemSpawned, ClientHandle.ItemSpawned },
{ (int)ServerPackets.itemPickedUp, ClientHandle.ItemPickedUp },
{ (int)ServerPackets.spawnProjectile, ClientHandle.SpawnProjectile },
{ (int)ServerPackets.projectilePosition, ClientHandle.ProjectilePosition },
{ (int)ServerPackets.projectileExploded, ClientHandle.ProjectileExploded },
{ (int)ServerPackets.spawnEnemy, ClientHandle.SpawnEnemy },
{ (int)ServerPackets.enemyPosition, ClientHandle.EnemyPosition },
{ (int)ServerPackets.enemyHealth, ClientHandle.EnemyHealth },
};
Debug.Log("Initialized packets.");
}
/// <summary>Disconnects from the server and stops all network traffic.</summary>
private void Disconnect()
{
if (isConnected)
{
isConnected = false;
tcp.socket.Close();
udp.socket.Close();
Debug.Log("Disconnected from server.");
}
}
}
And the Server.cs script:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
public class Server
{
public static int MaxPlayers { get; private set; }
public static int Port { get; private set; }
public static Dictionary<int, Client> clients = new Dictionary<int, Client>();
public delegate void PacketHandler(int _fromClient, Packet _packet);
public static Dictionary<int, PacketHandler> packetHandlers;
private static TcpListener tcpListener;
private static UdpClient udpListener;
/// <summary>Starts the server.</summary>
/// <param name="_maxPlayers">The maximum players that can be connected simultaneously.</param>
/// <param name="_port">The port to start the server on.</param>
public static void Start(int _maxPlayers, int _port)
{
MaxPlayers = _maxPlayers;
Port = _port;
Debug.Log("Starting server...");
InitializeServerData();
tcpListener = new TcpListener(IPAddress.Any, Port);
tcpListener.Start();
tcpListener.BeginAcceptTcpClient(TCPConnectCallback, null);
udpListener = new UdpClient(Port);
udpListener.BeginReceive(UDPReceiveCallback, null);
Debug.Log($"Server started on port {Port}.");
}
/// <summary>Handles new TCP connections.</summary>
private static void TCPConnectCallback(IAsyncResult _result)
{
TcpClient _client = tcpListener.EndAcceptTcpClient(_result);
tcpListener.BeginAcceptTcpClient(TCPConnectCallback, null);
Debug.Log($"Incoming connection from {_client.Client.RemoteEndPoint}...");
for (int i = 1; i <= MaxPlayers; i++)
{
if (clients[i].tcp.socket == null)
{
clients[i].tcp.Connect(_client);
return;
}
}
Debug.Log($"{_client.Client.RemoteEndPoint} failed to connect: Server full!");
}
/// <summary>Receives incoming UDP data.</summary>
private static void UDPReceiveCallback(IAsyncResult _result)
{
try
{
IPEndPoint _clientEndPoint = new IPEndPoint(IPAddress.Any, 0);
byte[] _data = udpListener.EndReceive(_result, ref _clientEndPoint);
udpListener.BeginReceive(UDPReceiveCallback, null);
if (_data.Length < 4)
{
return;
}
using (Packet _packet = new Packet(_data))
{
int _clientId = _packet.ReadInt();
if (_clientId == 0)
{
return;
}
if (clients[_clientId].udp.endPoint == null)
{
// If this is a new connection
clients[_clientId].udp.Connect(_clientEndPoint);
return;
}
if (clients[_clientId].udp.endPoint.ToString() == _clientEndPoint.ToString())
{
// Ensures that the client is not being impersonated by another by sending a false clientID
clients[_clientId].udp.HandleData(_packet);
}
}
}
catch (Exception _ex)
{
Debug.Log($"Error receiving UDP data: {_ex}");
}
}
/// <summary>Sends a packet to the specified endpoint via UDP.</summary>
/// <param name="_clientEndPoint">The endpoint to send the packet to.</param>
/// <param name="_packet">The packet to send.</param>
public static void SendUDPData(IPEndPoint _clientEndPoint, Packet _packet)
{
try
{
if (_clientEndPoint != null)
{
udpListener.BeginSend(_packet.ToArray(), _packet.Length(), _clientEndPoint, null, null);
}
}
catch (Exception _ex)
{
Debug.Log($"Error sending data to {_clientEndPoint} via UDP: {_ex}");
}
}
/// <summary>Initializes all necessary server data.</summary>
private static void InitializeServerData()
{
for (int i = 1; i <= MaxPlayers; i++)
{
clients.Add(i, new Client(i));
}
packetHandlers = new Dictionary<int, PacketHandler>()
{
{ (int)ClientPackets.welcomeReceived, ServerHandle.WelcomeReceived },
{ (int)ClientPackets.playerMovement, ServerHandle.PlayerMovement },
{ (int)ClientPackets.playerShoot, ServerHandle.PlayerShoot },
{ (int)ClientPackets.playerThrowItem, ServerHandle.PlayerThrowItem }
};
Debug.Log("Initialized packets.");
}
public static void Stop()
{
tcpListener.Stop();
udpListener.Close();
}
}