C# detect TCP client disconnection - tcp

I am working on TCP multithread server with C# window application form, and I am trying to detect if the machine of client is shutdown and disconnects from the server. I have read some posts and have some ideas:
How to determine if the tcp is connected or not?
Instantly detect client disconnection from server socket
But I'm not sure where to call the function IsConnected
My code is like the following:
public BindingList<Tablet> tabletList = new BindingList<Tablet>();
private Socket socket_Server = null;
private Thread myThread = null;
private Socket socket_Connect = null;
private Dictionary<string, Socket> dic = new Dictionary<string, Socket> { };
private string RemoteEndPoint;
socket_Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ServerIP = IPAddress.Parse("192.168.2.146");
IPEndPoint point = new IPEndPoint(ServerIP, portNum);
socket_Server.Bind(point);
socket_Server.Listen(50);
myThread = new Thread(Listen_Disp);
myThread.IsBackground = true;
myThread.Start();
Console.WriteLine("Server start");
private void Listen_Disp()
{
try
{
while (true)
{
//This is not working
for (int i = 0; i < tabletList.Count; i++)
{
if (!SocketConnected(dic[tabletList[i].ip]))
{
Console.WriteLine(RemoteEndPoint + "disconnected");
}
}
try
{
socket_Connect = socket_Server.Accept();
RemoteEndPoint = socket_Connect.RemoteEndPoint.ToString();
Console.WriteLine(RemoteEndPoint + " is connected");
dic.Add(RemoteEndPoint, socket_Connect);
Tablet newTablet = new Tablet();
newTablet.ip = RemoteEndPoint;
newTablet.status = "Online";
tabletList.Add(newTablet);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Console.WriteLine("end of while");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
static class SocketExtensions
{
public static bool IsConnected(this Socket socket)
{
try
{
return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
}
catch (SocketException) { return false; }
}
}
Thanks for help.

There's a lot of misinformation on that subject, and some of it is present in the questions you linked. The only reliable way to detect a TCP disconnection is to try to write to the connection. A read timeout can also indicate a dropped connection, but it may mean a lot of other things too, such as a stuck server. An EOS condition when reading indicates a graceful disconnect. The IsConnected() method and friends only give you a history of what you have done to this socket: they don't give you the current state of the connection. They can't, because, absent a pending write request, there is no state to
know. TCP doesn't maintain anything akin to a dial tone.

Related

Method in event handlers are not called in SignalR after some time

On starting, the application with SignalR 2 implementation works as expected. But after sometime (it can be after 5 minutes or 30 minutes or 10 minutes), the events are not called anymore. But the Server and Client are still connected. I know it is connected because it the trace level of the client for SignalR, it can still get the commands coming from the Server.
But my messages from my client is still being received by the server and its action executed so I know that the Server - Client connection is still good.
Also, I know that it is still connected because I always check the status of the connection.
This is how I implement the SignalR
public void ConnectAsync()
{
WriteToLogs("Connecting to Server...");
var dic = new Dictionary<string, string> { { "UserName", _signalrSettings.ApplicationName } };
_hubConnection?.Dispose();
_hubConnection = new HubConnection(_signalrSettings.ServerUri, dic);
_hubConnection.StateChanged += Connection_StateChanged;
_hubProxy = _hubConnection.CreateHubProxy(_signalrSettings.HubProxy);
try
{
_hubProxy.On<string, string>("Initial", (a, b) => Initial());
_hubConnection.Start().ContinueWith((s) =>
{
if (s.IsCompleted)
{
_isCompleted = true;
}
else if (s.IsCanceled)
{
_isCompleted = false;
}
else if (s.IsFaulted)
{
_isCompleted = false;
}
_hubProxy.On<string, string>("NewMessage", (userName, message) => NewMessage(userName, message)); // THE "NewMessage" is not fired after some time.
WriteToLogs( $#"Connection status is {_hubConnection.State}");
}).Wait();
}
catch (Exception e)
{
var trace = new StackTrace(e, true);
var reflectedType = trace.GetFrame(0).GetMethod().ReflectedType;
if (reflectedType != null)
{
_messageOperation.WriteErrors($#"Error {e.StackTrace} at {trace}");
}
}
}

Xamarin Forms iOS will not fully connect to Wifi Access Point that has no internet access

I have a Xamarin forms application. The application programmatically connects to a an access to point to communicate via sockets with a host machine connected to that same access point. The access point is not required to have internet access. When testing my code in iOS - I get a socket error stating that the 'Destination is unreachable'. However, if I go to settings and click on the wifi connection I want to use - I am taken to the login page for the access point. If I click cancel I get the option to 'Use without Internet'. If I select that option, then go back to my application, I am able to connect to the host. Is there a way to programmatically tell iOS to use the connecting even though it does not have internet? I looked at the Zone property but that is read only. Here is the code I am using. Any assistance would be appreciated.
public async Task<WifiConfiguredEventArgs> ConnectToWifi()
{
try
{
var tobj_WifiManager = new NEHotspotConfigurationManager();
var tobj_SSIDs = await tobj_WifiManager.GetConfiguredSsidsAsync();
if (tobj_SSIDs != null && tobj_SSIDs.Contains(App.gvm_AppSettings.WifiSSID))
{
// We are already connected -- just return
lobj_WifiConfiguredEventArgs.ConnectionStatus = FlexConnectionStatus.eAlreadyConnected;
}
else
{
var tobj_WifiConfig = new NEHotspotConfiguration(App.gvm_AppSettings.WifiSSID, App.gvm_AppSettings.WifiPassword, false);
tobj_WifiManager.ApplyConfiguration(tobj_WifiConfig, async (error) =>
{
if (error != null)
{
lobj_WifiConfiguredEventArgs.ConnectionStatus = FlexConnectionStatus.eErrorEstablishingConnection;
lobj_WifiConfiguredEventArgs.ErrorMessage = error.ToString();
}
else
{
lobj_WifiConfiguredEventArgs.ConnectionStatus = FlexConnectionStatus.eConnectionEstablished;
}
});
}
}
catch(Exception ex)
{
lobj_WifiConfiguredEventArgs.ConnectionStatus = FlexConnectionStatus.eErrorEstablishingConnection;
lobj_WifiConfiguredEventArgs.ErrorMessage = ex.Message;
lobj_WifiConfiguredEventArgs.ErrorException = ex;
App.ProcessException(ex);
}
return lobj_WifiConfiguredEventArgs;
}
}
Someone asked for the socket code so here it is. To be clear, the socket code connects and communicates fine in iOS when the access point has an internet connection. It also works fine in all the android calls.
using FCISelfCheckIn;
using FCISelfCheckIn.Resources;
using FCISharedAll.FCICommObjects;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using static FCISharedAll.FCIEnums.FlexEnums;
namespace FCISelfCheckIn
{
// This template use base socket syntax to change Pattern. (like Send, Receive, and so on)
// Convert to Task-based Asynchronous Pattern. (TAP)
public static class AsynchronousClientSocket
{
private static bool ib_IsConnected = false;
private static Socket iobj_Client = null;
public static async Task<MasterDataObject> SendMessage(string ps_IPAddress, int pi_Port, ge_CommunicationType pe_CommicationType,
string JSONData)
{
MasterDataObject lobj_response = new MasterDataObject();
try
{
//We should already be connected by the time we get here so just continue
//However if we are not - try to connect again
if (!ib_IsConnected)
{
// Establish the remote endpoint for the socket.
IPAddress ipAddress = IPAddress.Parse(ps_IPAddress);
IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, pi_Port);
// Create a TCP/IP socket.
iobj_Client = new Socket(ipAddress.AddressFamily,
SocketType.Stream, ProtocolType.Tcp);
// Connect to the remote endpoint.
ib_IsConnected = await iobj_Client.ConnectAsync(remoteEndPoint).ConfigureAwait(false);
}
if (ib_IsConnected)
{
var bytesSent = await iobj_Client.SendAsync(JSONData).ConfigureAwait(false);
// Receive the response from the remote device.
var ls_MasterDataObject = await iobj_Client.ReceiveAsync(60).ConfigureAwait(false);
if (ls_MasterDataObject == null)
{
lobj_response = new MasterDataObject();
lobj_response.CommunicationType = ge_CommunicationType.e_Error;
}
else
{
//deserialize the master data object in order to return it to the client
lobj_response = App.gvm_CommunicationHelper.DeserializeMasterDataObject(ls_MasterDataObject);
iobj_Client.Shutdown(SocketShutdown.Both);
}
}
else
{
lobj_response.CommunicationType = ge_CommunicationType.e_Error;
lobj_response.JSONDataObject = JsonConvert.SerializeObject(AppResources.ServerConnectionError);
}
// Release the socket.
iobj_Client.Close();
}
catch (Exception ex)
{
App.ProcessException(ex);
}
return lobj_response;
}
private static Task<bool> ConnectAsync(this Socket client, IPEndPoint remoteEndPoint)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (remoteEndPoint == null) throw new ArgumentNullException(nameof(remoteEndPoint));
return Task.Run(() => Connect(client, remoteEndPoint));
}
private static bool Connect(this Socket client, EndPoint remoteEndPoint)
{
bool lb_ReturnValue = true;
try
{
if (client == null || remoteEndPoint == null)
{
lb_ReturnValue = false;
}
else
{
client.Connect(remoteEndPoint);
}
}
catch (System.Net.Sockets.SocketException Socketex)
{
lb_ReturnValue = false;
if (Socketex.ErrorCode != 10061 && Socketex.ErrorCode != 10065)
{
//Dont log if the host is not running.
App.ProcessException(Socketex);
}
}
catch (Exception ex)
{
App.ProcessException(ex);
lb_ReturnValue = false;
}
return lb_ReturnValue;
}
private static async Task<string> ReceiveAsync(this Socket client, int waitForFirstDelaySeconds = 10)
{
if (client == null) throw new ArgumentNullException(nameof(client));
Debug.WriteLine("Receive Message 1");
// Timeout for wait to receive and prepare data.
for (var i = 0; i < waitForFirstDelaySeconds; i++)
{
if (client.Available > 0)
break;
await Task.Delay(3000).ConfigureAwait(false);
}
// return null If data is not available.
if (client.Available < 1)
return null;
Debug.WriteLine("Receive Message 2");
// Size of receive buffer.
const int bufferSize = 1024;
var buffer = new byte[bufferSize];
// Get data
var response = new StringBuilder(bufferSize);
do
{
var size = Math.Min(bufferSize, client.Available);
await Task.Run(() => client.Receive(buffer)).ConfigureAwait(false);
var ts_CurrentSegment = Encoding.ASCII.GetString(buffer, 0, size);
if (ts_CurrentSegment.Length > 0)
{
response.Append(Encoding.ASCII.GetString(buffer, 0, size));
}
} while (!response.ToString().EndsWith(FCIEndOfFile));
// Return result.
return response.ToString().Replace(FCIEndOfFile, "");
}
private static async Task<int> SendAsync(this Socket client, string data)
{
var byteData = Encoding.ASCII.GetBytes(data);
return await SendAsync(client, byteData, 0, byteData.Length, 0).ConfigureAwait(false);
}
private static Task<int> SendAsync(this Socket client, byte[] buffer, int offset,
int size, SocketFlags socketFlags)
{
if (client == null) throw new ArgumentNullException(nameof(client));
return Task.Run(() => client.Send(buffer, offset, size, socketFlags));
}
public async static Task<bool> ForcePermissions(string ps_IPAddress, int pi_Port)
{
bool lb_ReturnValue = false;
try
{
IPAddress ipAddress = IPAddress.Parse(ps_IPAddress);
//This is only done to force the local network permissions access in iOS 14.
IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, pi_Port);
// Create a TCP/IP socket.
iobj_Client = new Socket(ipAddress.AddressFamily,
SocketType.Stream, ProtocolType.Tcp);
ib_IsConnected = await iobj_Client.ConnectAsync(remoteEndPoint).ConfigureAwait(false);
if (ib_IsConnected)
{
Debug.WriteLine("GEORGE Permissions Connected");
//client.Shutdown(SocketShutdown.Both);
//client.Close();
lb_ReturnValue = true;
}
else
{
Debug.WriteLine("George Permissions not Connected");
}
}
catch (Exception ex)
{
//Just skip if there is an exception
App.ProcessException(ex);
}
return lb_ReturnValue;
}
}
}

How can I read byte array coming from server in UWP app?

I have a client-server app. The client is HoloLens 2 and the server is unity running on PC. I have created a socket using HoloLens but I can't read the byte array on HoloLens. In the connect method _inputStream and _outputStream are System.IO.Streams.
PC client uses System.Net.Sockets.TcpClient, System.Net.Sockets.NetworkStream but UWP (HoloLens) uses Windows.Networking.Sockets.StreamSocket.
In the ReceiveCallback I have an issue with int byteLenght = _inputStream.EndRead(result); When I tried this code on HoloLens I got the exception: "System.ArgumentException: The specified AsyncResult does not correspond to any outstanding IO operation". But I tested it with PC client (System.Net.Socket) and everything works fine.
Btw, I gave all related permissions.
How can I read byte array in UWP app?
Update:
#if UNITY_EDITOR
private void ConnectUWP(string host, string port)
#else
private async void ConnectUWP(string host, string port)
#endif
{
#if UNITY_EDITOR
errorStatus = "UWP TCP client used in Unity!";
#else
try
{
socket = new Windows.Networking.Sockets.StreamSocket();
Windows.Networking.HostName serverHost = new Windows.Networking.HostName(host);
_receivedData = new Packet();
_receiveBuffer = new byte[DataBufferSize];
await socket.ConnectAsync(serverHost, port);
successStatus = "Connected!";
_outputStream = socket.OutputStream.AsStreamForWrite();
//_streamWriter = new StreamWriter(_outputStream) { AutoFlush = true };
_inputStream = socket.InputStream.AsStreamForRead();
Task.Run(() => StreamToBytes(socket.InputStream));
}
catch (Exception e)
{
errorStatus = e.ToString();
Debugger.GetComponent<TextMeshPro>().text += "\n Exception: " + errorStatus;
}
#endif
#if !UNITY_EDITOR
public async Task StreamToBytes(Windows.Storage.Streams.IInputStream stream)
{
using (var reader1 = new Windows.Storage.Streams.DataReader(stream))
{
reader1.InputStreamOptions = Windows.Storage.Streams.InputStreamOptions.ReadAhead;
reader1.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
reader1.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
//InBuffer is always 256,
//even if there is more data waiting. If I put a task.delay in it will always return 25
try
{
await reader1.LoadAsync(256);
}
catch (Exception ex)
{
//..
}
while (reader1.UnconsumedBufferLength > 0)
{
var bytes1 = new byte[reader1.UnconsumedBufferLength];
reader1.ReadBytes(bytes1);
_receivedData.Reset(await HandleData(bytes1));
await reader1.LoadAsync(256);
}
reader1.DetachStream();
}
}
private async Task<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;
}
#endif
When I tried this code on HoloLens I got the exception: "System.ArgumentException: The specified AsyncResult does not correspond to any outstanding IO operation".
Please refer to BeginRead method document, In the .NET Framework 4 and earlier versions, you have to use methods such as BeginRead and EndRead to implement asynchronous I/O operations. These methods are still available in the .NET Framework 4.5 to support legacy code; however, the new async methods, such as ReadAsync, WriteAsync, CopyToAsync, and FlushAsync, help you implement asynchronous I/O operations more easily. I suppose this old api could not compatible with HoloLens device, so please try to use ReadAsync to replace like the following
byte[] bytes = await StreamToBytes(_inputStream);
public async Task<byte[]> StreamToBytes(Stream stream)
{
byte[] bytes = new byte[stream.Length];
await stream.ReadAsync(bytes, 0, bytes.Length);
stream.Seek(0, SeekOrigin.Begin);
return bytes;
}
Or follow the official's processing that use DataReader to read ReadBytes, for more code please refer streamsocket.
Update
using (var reader1 = new DataReader(_inputStream)
{
reader1.InputStreamOptions = Windows.Storage.Streams.InputStreamOptions.Partial;
reader1.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
reader1.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
await reader1.LoadAsync(256); //InBuffer is always 256,
//even if there is more data waiting. If I put a task.delay in it will always return 25
while (reader1.UnconsumedBufferLength > 0)
{
var bytes1 = new byte[reader1.UnconsumedBufferLength];
reader1.ReadBytes(bytes1);
await reader1.LoadAsync(256);
}
reader1.DetachStream();
}
I tried everything for TCP but TCP is nearly impossible for HoloLens with UWP app. So I tried UDP and it works perfectly (https://github.com/mbaytas/HoloLensUDP). I hope Microsoft put a TCP example for HoloLens 1 and 2 in near future. Thanks Nico for your help.

Udp connection clients don't receive messages outside of local network even though ports are forwarded

Ok so I built a semi-simple multiplayer game that connects through UDP. However I can't get it to fully connect outside my local network. Now! I have seen this question asked and answered many times so here is where it differs from similar questions:
I have already port forwarded the correct ports.
That's right! I am a Minecraft veteran so the one thing I do know how to do is forward ports. ;)
Now, this works fine on my local network. It works if I run two instances on the same machine of course. But it also works if I use a different computer connecting to the LAN IP or if I use a different computer connecting to my routers public address. The connection only seems to fail if the messages are originating from outside the network they are being hosted on.
The host receives incoming messages from clients, but clients do not receive messages from host. And it doesn't mater who is the host and who is the client. So I had my friend try connecting to my game from his house and had a break point on the function that receives the messages. Sure enough I see the expected message come into the host, and then the host sends out the acknowledgement message, but his client never received it. Similarly, if my friend hosts and I try to connect, his host sees the message and sends the acknowledgment but my client never receives it. So at this point we both have the port forwarded but only the host ever receives anything.
So.... Anyone have any idea what I might be doing wrong? Or what Cthulhu of the internet I need to kill?
My Code Below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public enum MessageType
{
None = 0,
Connect = 1,
Command = 2,
EntityUpdate = 3,
Response = 4,
}
[System.Serializable]
public class Message
{
public MessageType _MessageType = MessageType.None;
public object Data = null;
public Guid Origin = Guid.Empty;
public Guid MessageID;
public Message(object data, MessageType type, Guid origin)
{
if (type == MessageType.None || data == null)
{
throw new Exception("Invalid Message!");
}
Data = data;
_MessageType = type;
Origin = origin;
MessageID = Guid.NewGuid();
}
}
public static class NetLayer
{
private const int FRAMES_BETWEEN_SENDS = 2;
private const int NUM_RETRIES = 10;
private const int FAMES_BEFORE_RETRY_ENTITY = 120;
private const int FAMES_BEFORE_RETRY_CLIENT = 30;
private const int BUFF_SIZE = 8 * 2048;
private const int DEFAULT_PORT = 27015;
private const string DEFAULT_ADDRESS = "localhost";
private const int MAX_BYTES = 1100;
private const int MAX_BYTES_PER_PACKET = 1100 - (322 + 14); //281 is number of existing bytes in empty serilized packet //14 is... I dunno. the overhead for having the byte aray populated I assume
private static Socket mSocket = null;
private static List<EndPoint> mClients = null;
private static Dictionary<EndPoint, PlayerData> mUnconfirmedClients = null;
private static string mServerAdress = DEFAULT_ADDRESS;
private static int mPort = DEFAULT_PORT;
public static bool IsServer { get { return mClients != null; } }
public static bool IsConnected = false;
public static bool Initiated { get { return mSocket != null; } }
private static Dictionary<Guid, Packet> mUnconfirmedPackets = null;
private static Queue<Packet> ResendQueue = new Queue<Packet>();
private static EndPoint mCurrentPacketEndpoint;
private static Guid mCurrentMessageID;
private static byte[] mDataToSend;
private static int mCurrentDataIndex = 0;
private static int mCurrentPacketIndex = 0;
private static int mTotalPacketsToSend = 0;
private static Queue<Packet> ResponseQueue = new Queue<Packet>();
private static Dictionary<Guid, Dictionary<int, Packet>> IncomingPacketGroups = new Dictionary<Guid, Dictionary<int, Packet>>();
private static Queue<QueueuedMessage> MessageQueue = new Queue<QueueuedMessage>();
public static int QueueCount { get { return MessageQueue.Count; } }
private static int mCurrentFramesToRetryClient;
private static int mCurrentFramesToRetryEntity;
private static int mLastSendFrame = 0;
[System.Serializable]
public class Packet
{
public byte[] Data;
public Guid MessageID;
public Guid PacketID;
public int PacketNumber;
public int NumPacketsInGroup;
public int NumBytesInGroup;
public bool RequireResponse;
[System.NonSerialized]
public int retries; //We don't need to send this data, just record it locally
[System.NonSerialized]
public EndPoint Destination; //We don't need to send this data, just record it locally
public Packet(byte[] data, Guid messageID, int packetNumber, int numPacketsinGroup, int numBytesInGroup, EndPoint destination, bool requireResponse)
{
Data = data;
if(data.Length > MAX_BYTES_PER_PACKET)
{
Debug.LogError("Creating a packet with a data size of " + data.Length + " which is greater then max data size of " + MAX_BYTES_PER_PACKET);
}
MessageID = messageID;
PacketID = Guid.NewGuid();
PacketNumber = packetNumber;
NumPacketsInGroup = numPacketsinGroup;
NumBytesInGroup = numBytesInGroup;
retries = 0;
Destination = destination;
RequireResponse = requireResponse;
}
}
private class QueueuedMessage
{
public Message m;
public EndPoint e;
public QueueuedMessage(Message message, EndPoint endpoint)
{
m = message;
e = endpoint;
}
}
[System.Serializable]
private class PacketResponse
{
public Guid MessageIDToConfirm;
public PacketResponse(Guid newGuid)
{
MessageIDToConfirm = newGuid;
}
}
public static void InitAsServer(string address, int port)
{
Reset();
mClients = new List<EndPoint>();
mUnconfirmedClients = new Dictionary<EndPoint, PlayerData>();
mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
mSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
mSocket.Blocking = false;
mSocket.SendBufferSize = BUFF_SIZE;
mSocket.ReceiveBufferSize = BUFF_SIZE;
mSocket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
mPort = port;
IsConnected = true;
mUnconfirmedPackets = new Dictionary<Guid, Packet>();
Packet newPacket = new Packet(new byte[MAX_BYTES_PER_PACKET], Guid.NewGuid(), 1, 15, MAX_BYTES_PER_PACKET, null, true);
byte[] serilized = Util.Serialize(newPacket);
Debug.Log("Total Packet Size = " + serilized.Length);
}
public static void InitAsClient(string address, int port)
{
Reset();
mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
mSocket.Blocking = false;
mSocket.SendBufferSize = BUFF_SIZE;
mSocket.ReceiveBufferSize = BUFF_SIZE;
mSocket.Bind(new IPEndPoint(IPAddress.Parse(GetLocalIPAddress()), port));
mServerAdress = address;
mPort = port;
mUnconfirmedPackets = new Dictionary<Guid, Packet>();
}
public static void NetTick()
{
if (Initiated)
{
//Keep retrying connection messages untill we get a response from the player, in case they miss our first message
if(IsServer)
{
mCurrentFramesToRetryClient++;
if (mCurrentFramesToRetryClient >= FAMES_BEFORE_RETRY_CLIENT)
{
mCurrentFramesToRetryClient = 0;
foreach (EndPoint ep in mUnconfirmedClients.Keys)
{
Debug.Log("Pinging client again: " + ep);
SendConnectionConfirmationToClient(ep, mUnconfirmedClients[ep]); //TODO Add timeout. Should stop pinging after 10 attempts
}
}
}
//Retry messages untill we get a responce or give up
mCurrentFramesToRetryEntity++;
if (mCurrentFramesToRetryEntity >= FAMES_BEFORE_RETRY_ENTITY)
{
List<Guid> deadMessages = new List<Guid>();
List<Guid> resendMessages = new List<Guid>();
mCurrentFramesToRetryEntity = 0;
foreach (Guid messageID in mUnconfirmedPackets.Keys)
{
Packet packet = mUnconfirmedPackets[messageID];
packet.retries++;
if (packet.retries > NUM_RETRIES)
{
Debug.LogError("Failed to send Packet " + messageID);
deadMessages.Add(messageID);
}
else
{
resendMessages.Add(messageID);
}
}
//Resend everything we have selected to resend
for (int i = 0; i < resendMessages.Count; i++)
{
Packet packet = mUnconfirmedPackets[resendMessages[i]];
ResendQueue.Enqueue(packet);
}
//Give up on messages that have been marked dead
for (int i = 0; i < deadMessages.Count; i++)
{
mUnconfirmedPackets.Remove(deadMessages[i]);
}
}
if (Time.frameCount - mLastSendFrame >= FRAMES_BETWEEN_SENDS)
{
if (ResponseQueue.Count > 0) //Send responses first
{
Packet p = ResponseQueue.Dequeue();
SendImmediate(p, p.Destination, false);
}
else if (ResendQueue.Count > 0) //Then resends
{
Packet p = ResendQueue.Dequeue();
SendImmediate(p, p.Destination, false);
}
else //Then normal messages
{
StreamSend();
}
mLastSendFrame = Time.frameCount;
}
}
}
public static string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
return ip.ToString();
}
}
return null;
}
public static void Reset()
{
if(mSocket != null)
{
mSocket.Close();
}
mSocket = null;
mClients = null;
mServerAdress = DEFAULT_ADDRESS;
mPort = DEFAULT_PORT;
IsConnected = false;
}
public static void Send(Message newMessage, EndPoint endpoint = null)
{
if (IsServer)
{
if (endpoint != null)
{
SendToQueue(new QueueuedMessage(newMessage, endpoint));
}
else
{
for (int i = 0; i < mClients.Count; i++)
{
SendToQueue(new QueueuedMessage(newMessage, mClients[i]));
}
}
}
else
{
SendToQueue(new QueueuedMessage(newMessage, new IPEndPoint(IPAddress.Parse(mServerAdress), mPort)));
}
}
private static void SendToQueue(QueueuedMessage newPacket)
{
MessageQueue.Enqueue(newPacket);
}
private static void StreamSend()
{
if(mDataToSend != null)
{
//Get number of bytes to send in this packet
int bytesToSend = Mathf.Min(mDataToSend.Length - mCurrentDataIndex, MAX_BYTES_PER_PACKET);
//Populate packet byte array
byte[] data = new byte[bytesToSend];
for(int i = 0; i < bytesToSend; i++)
{
data[i] = mDataToSend[mCurrentDataIndex + i];
}
//Increment index by the nubmer of bytes sent
mCurrentDataIndex += bytesToSend;
//Create and send packet
Packet newPacket = new Packet(data, mCurrentMessageID, mCurrentPacketIndex, mTotalPacketsToSend, mDataToSend.Length, mCurrentPacketEndpoint, true);
SendImmediate(newPacket, mCurrentPacketEndpoint, true);
//Increment packet index
mCurrentPacketIndex++;
if(mCurrentDataIndex >= mDataToSend.Length)
{
//We have finished sending. Clear data array
mDataToSend = null;
}
}
else if(MessageQueue.Count > 0)
{
//Prepare the next group for packing
QueueuedMessage queueuedMessage = MessageQueue.Dequeue();
mCurrentMessageID = queueuedMessage.m.MessageID;
mCurrentPacketEndpoint = queueuedMessage.e;
mDataToSend = Util.Serialize(queueuedMessage.m);
mCurrentDataIndex = 0;
mCurrentPacketIndex = 1;
mTotalPacketsToSend = Mathf.CeilToInt((float)mDataToSend.Length / (float)MAX_BYTES_PER_PACKET);
}
}
public static void SendImmediate(Packet packet, EndPoint endpoint, bool verify)
{
byte[] data = Util.Serialize(packet);
if(data.Length > MAX_BYTES)
{
Debug.LogError("Attempting to send packet with " + data.Length + " bytes. This will most likely fail! Please ensure all packets are limited to " + MAX_BYTES + " bytes or less!");
}
mSocket.SendTo(data, endpoint);
//Don't assume the packet is late untill we have actually sent it.
//The old way of verifying. Instead things sending imediate that don't want to be veiried should set verify to false if (newMessage._MessageType != MessageType.Response && newMessage._MessageType != MessageType.Connect && !mUnconfirmedPackets.ContainsKey(newMessage.MessageID))
if (verify)
{
mUnconfirmedPackets.Add(packet.PacketID, packet);
}
}
public static void SendConnectionMessage(PlayerData playerData)
{
Message message = new Message(playerData, MessageType.Connect, playerData.ID);
Send(message);
}
public static void SendCommand(Command command, EndPoint ep = null)
{
Message message = new Message(command, MessageType.Command, GameManager.LocalPlayer.PlayerID);
Send(message, ep);
}
public static void SendEntityUpdate(EntityData entityData, EndPoint ep = null)
{
Message message = new Message(entityData, MessageType.EntityUpdate, GameManager.LocalPlayer.PlayerID);
Send(message, ep);
}
public static void Receive()
{
if (Initiated && mSocket.Available > 0)
{
byte[] bytes = new byte[BUFF_SIZE];
EndPoint epFrom = new IPEndPoint(IPAddress.Any, 0); //TODO Do something here to stop reciving messages from dead clients...
mSocket.ReceiveFrom(bytes, ref epFrom);
Reassemble(bytes, epFrom);
}
}
public static void Reassemble(byte[] bytes, EndPoint epFrom)
{
Packet receivedPacket = Util.Deserilize(bytes) as Packet;
//Create new packet group if one does not already exist
if(!IncomingPacketGroups.ContainsKey(receivedPacket.MessageID))
{
IncomingPacketGroups.Add(receivedPacket.MessageID, new Dictionary<int, Packet>());
}
Dictionary<int, Packet> currentPacketGroup = IncomingPacketGroups[receivedPacket.MessageID];
currentPacketGroup[receivedPacket.PacketNumber] = receivedPacket;
if(currentPacketGroup.Count == receivedPacket.NumPacketsInGroup)
{
byte[] completedData = new byte[receivedPacket.NumBytesInGroup];
int index = 0;
for(int i = 0; i < receivedPacket.NumPacketsInGroup; i++)
{
Packet currentPacket = currentPacketGroup[i + 1];
for(int j = 0; j < currentPacket.Data.Length; j++)
{
completedData[index] = currentPacket.Data[j];
index++;
}
}
//Clean up the packet group now that it is compleated
IncomingPacketGroups.Remove(receivedPacket.MessageID);
//Call this once we have re-assembled a whole message
Message receivedMessage = Util.Deserilize(completedData) as Message;
ReceiveCompleateMessage(receivedMessage, epFrom);
}
//Make sure to call back to sender to let them know we got this packet
if(receivedPacket.RequireResponse)
{
SendMessageResponse(receivedPacket.PacketID, epFrom);
}
}
private static void ReceiveCompleateMessage(Message receivedMessage, EndPoint epFrom)
{
if (receivedMessage.Origin == GameManager.LocalPlayer.PlayerID)
{
//Reject messages we sent ourselves. Prevent return from sender bug.
return;
}
//TODO Consider adding a check that adds the endpoing into the connections even if we don't have a connecton message because players can sometimes re-connect.
//TODO or maybe reject messages from unknown clients becasue we shouldn't trust them
switch (receivedMessage._MessageType)
{
case MessageType.None:
Debug.LogError("Recived message type of None!");
break;
case MessageType.EntityUpdate:
GameManager.ProcessEntityUpdate(receivedMessage);
break;
case MessageType.Command:
GameManager.ProcessCommand(receivedMessage);
break;
case MessageType.Response:
ProcessResponse(receivedMessage);
break;
case MessageType.Connect:
ProcessConnection(receivedMessage, epFrom);
break;
}
//If this is the server reciving a messgae, and that message was not sent by us
if (IsServer && receivedMessage._MessageType != MessageType.Connect && receivedMessage._MessageType != MessageType.Response && receivedMessage.Origin != GameManager.LocalPlayer.PlayerID)
{
//Mirror the message to all connected clients
Send(receivedMessage);
}
}
public static void ProcessConnection(Message receivedMessage, EndPoint epFrom)
{
//This is the only time where we should process the data directly in net layer since we need the endpoint data
if (IsServer)
{
if (!mClients.Contains(epFrom))
{
if (epFrom != null)
{
mClients.Add(epFrom);
Debug.Log("Client attempting to join: " + epFrom.ToString()); //TODO becasue we are binding socket this won't work locally anymore but see if it at least works globally before trying to fix
//Get new player from recived data
PlayerData newPlayer = receivedMessage.Data as PlayerData;
newPlayer.Team = GameManager.GetNewTeam();
newPlayer.HostHasResponded = true;
if (!mUnconfirmedClients.ContainsKey(epFrom))
{
mUnconfirmedClients.Add(epFrom, newPlayer);
}
SendConnectionConfirmationToClient(epFrom, newPlayer);
}
else
{
Debug.LogError("Got connection message from null!");
}
}
else
{
if (mUnconfirmedClients.ContainsKey(epFrom))
{
PlayerData newPlayerData = receivedMessage.Data as PlayerData;
if (newPlayerData.HostHasResponded)
{
mUnconfirmedClients.Remove(epFrom);
if (epFrom != null)
{
Debug.Log("Client Confirmed, sending update: " + epFrom.ToString());
//Sent all known entitys to the new client so it's up to date
foreach (Guid key in GameManager.Entities.Keys)
{
SendEntityUpdate(GameManager.Entities[key].GetEntityData(), epFrom);
}
}
}
else
{
Debug.LogWarning("We sent client an confirmation but it is still sending us initial connection messages: " + epFrom.ToString());
}
}
else
{
Debug.LogWarning("Client Already connected but still sent connnection message: " + epFrom.ToString());
}
}
}
else
{
ReciveConnectionConfirmationFromHost(receivedMessage, epFrom);
}
}
public static void ProcessResponse(Message message)
{
PacketResponse response = (PacketResponse)message.Data;
mUnconfirmedPackets.Remove(response.MessageIDToConfirm);
}
public static void SendConnectionConfirmationToClient(EndPoint epFrom, PlayerData newPlayer)
{
//endpoint can be null if this is the server connecting to itself
if (epFrom != null)
{
//Tell client we have accepted its connection, and send back the player data with the updated team info
Message message = new Message(newPlayer, MessageType.Connect, GameManager.LocalPlayer.PlayerID);
//Send(message, epFrom);
byte[] data = Util.Serialize(message);
Packet newPacket = new Packet(data, message.MessageID, 1, 1, data.Length, epFrom, false);
ResponseQueue.Enqueue(newPacket);
}
}
public static void ReciveConnectionConfirmationFromHost(Message receivedMessage, EndPoint epFrom)
{
//Clear the loading win we had active
GameObject.Destroy(GameManager.LoadingWin);
PlayerData newPlayer = receivedMessage.Data as PlayerData;
GameManager.LocalPlayer.SetPlayerData(newPlayer);
IsConnected = true;
PingBack(epFrom);
}
public static void PingBack(EndPoint ep)
{
PlayerData localPlayer = GameManager.LocalPlayer.GetPlayerData();
Message message = new Message(localPlayer, MessageType.Connect, localPlayer.ID);
byte[] data = Util.Serialize(message);
Packet newPacket = new Packet(data, message.MessageID, 1, 1, data.Length, ep, false);
ResponseQueue.Enqueue(newPacket);
}
private static void SendMessageResponse(Guid packetIDToConfirm, EndPoint epFrom)
{
PacketResponse messageIDtoConfirm = new PacketResponse(packetIDToConfirm);
Message message = new Message(messageIDtoConfirm, MessageType.Response, GameManager.LocalPlayer.PlayerID);
byte[] data = Util.Serialize(message);
Packet newPacket = new Packet(data, message.MessageID, 1, 1, data.Length, epFrom, false);
ResponseQueue.Enqueue(newPacket);
}
}
[Edit]
So I continued to do research and refine my code. (code above has been updated to current version). One thing I was definitely doing wrong before was blowing up the MTU because I didn't realize the external limit was much smaller than the internal limit. Still getting the same problem though. Server can see the client message, uses the incoming endpoint to send a connection message back, and client never gets it. Currently the server will attempt to resend the connection confirmation indefinitely until the client responds back which never happens because none of the server responses ever reach the client.
My understanding of a UDP punch through is that both machines must first connect to a known external server. That external server then gives each incoming connection the others IP and port, allowing them to talk to each other directly. However, since I am manually feeding the client the IP and port of the game server, I shouldn't need an external server to do that handshake.
Client doesn't need to connect to an external server first to get the game server IP and port because I manual type in the IP and port of the game server and the game server network has the port already forwarded, so the servers address IS already known. I know for a fact that the client message is reaching the game server because server logs the incoming connection.
The game server gets the IP and port of the client from the incoming message, which is exactly what would be handed to it by the external server by the handshake. I have double checked the IP the server receives from the connection and it matches the external IP address of the client. I also verified that both machines firewalls are allowing the program through.
I would deeply appreciate some more help on figuring out why server is failing to deliver a message back to the client. Explanations or links to useful documents or research please. Telling me to do more research or look online for the answers isn't helpful. I am already doing that. The point of a questions and answers is to get help from people who already know the answers.
The problem is that there exists NAT between LAN and WLAN. You cannot just work through it so simple.

Sockets: .NET Core 2 server, HTML5 client cannot connect

I've run into a problem with socket server. Hope any of you might help me.
I have server built in .NET Core running on Debian which worked fine for clients made in .NET, but when I try to use HTML client it never gets past handshake.
Here's my example server:
class Program
{
public static TcpListener listener;
private static List<ClientPacket> clients;
private const int port = 4245;
private const int bufferSize = 1024;
static void Main(string[] args)
{
clients = new List<ClientPacket>();
listener = new TcpListener(IPAddress.Any, port);
listener.Start();
StartListening();
while (true)
{
}
}
private static void StartListening()
{
listener.BeginAcceptTcpClient(AcceptClient, listener);
}
private static void AcceptClient(IAsyncResult res)
{
TcpClient client = listener.EndAcceptTcpClient(res);
client.NoDelay = true;
ClientPacket packet = new ClientPacket(client);
clients.Add(packet);
Console.WriteLine("Client connected");
client.Client.BeginReceive(packet.buffer, 0, bufferSize, 0, new AsyncCallback(ReceiveMessage), packet);
StartListening();
}
private static void ReceiveMessage(IAsyncResult res)
{
ClientPacket packet = (ClientPacket)res.AsyncState;
Socket s = packet.client.Client;
try
{
if (s.EndReceive(res) > 0)
{
s.BeginReceive(packet.buffer, 0, bufferSize, 0, new AsyncCallback(ReceiveMessage), packet);
}
else
{
clients.Remove(packet);
s.Close();
packet = null;
}
}
catch (Exception e)
{
clients.Remove(packet);
s.Close();
packet = null;
}
}
It simply accepts clients, adds them to list and removes them after connection is lost. Problem is when I try it with this HTML5 client (copied from example tutorial):
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript">
function WebSocketTest()
{
if ("WebSocket" in window)
{
var ws = new WebSocket("ws://serverIP:4245");
ws.onopen = function()
{
ws.send("Message to send");
alert("Message is sent...");
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
alert("Message is received...");
};
ws.onclose = function()
{
alert("Connection is closed...");
};
window.onbeforeunload = function(event) {
socket.close();
};
}
else
{
alert("WebSocket NOT supported by your Browser!");
}
}
</script>
</head>
<body>
<div id="sse">
Run WebSocket
</div>
</body>
</html>
Problem is, that with this I never see alert from 'onopen' function so it's not opened properly and readystatus is kept as 'connecting' instead of 'connected' all the time.
I've read a about response from server with some hash to acknowledge and finish handshake, but seen it in older examples of .NET and not with this TcpListener from Core 2... so I thought it's a part of BeginAcceptTcpClient function... and since it worked with .NET client I'm not really sure where is mistake.
Can anybody help me with this or hint how to implement handshake hash response, please?
Ok I found a problem. Client really did expected a hash in response to finish handshake... still don't undeastand why HTML did and .NET Client didn't want it.
I've created a new method for handshake to send required response:
private static void CompleteHandshake(IAsyncResult res)
{
ClientPacket packet = (ClientPacket)res.AsyncState;
Socket s = packet.client.Client;
try
{
if (s.EndReceive(res) > 0)
{
var data = ByteArray.ReadString(packet.buffer);
if (new Regex("^GET").IsMatch(data))
{
Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + Environment.NewLine
+ "Connection: Upgrade" + Environment.NewLine
+ "Upgrade: websocket" + Environment.NewLine
+ "Sec-WebSocket-Accept: "
+ Convert.ToBase64String(
SHA1.Create().ComputeHash(
Encoding.UTF8.GetBytes(
new Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
)
)
) + Environment.NewLine
+ Environment.NewLine);
Console.WriteLine("Packet incoming");
s.Send(response, 0, response.Length, SocketFlags.None);
}
s.BeginReceive(packet.buffer, 0, bufferSize, 0, new AsyncCallback(ReceiveMessage), packet);
}
else
{
clients.Remove(packet);
s.Close();
packet = null;
}
}
catch (Exception e)
{
clients.Remove(packet);
s.Close();
packet = null;
}
}
Then the only change in old code is in listener method AcceptClient where simple change method parameter of BeginReceive to redirect onto CompleteHandshake.
private static void AcceptClient(IAsyncResult res)
{
TcpClient client = listener.EndAcceptTcpClient(res);
client.NoDelay = true;
ClientPacket packet = new ClientPacket(client);
clients.Add(packet);
client.Client.BeginReceive(packet.buffer, 0, bufferSize, 0, new AsyncCallback(CompleteHandshake), packet);
StartListening();
}
That new method CompleteHandshake after sending hash response will redirect back to the old ReceiveMessage method where you can handle message however you need.

Resources