I am porting an AS3 client to C# and am having huge problems with completing the login procedure to the server.
I have no access to the server, just the protocol, and have no idea if the server is expecting any particular behavior with regards the sockets.
I have managed to connect to the server and go through the login process. I have then been able to send a message requesting data which results in a whole series of messages being sent to my client.
In the AS3 version of the client I receive messages in three distinct buffers.
In my C# I only get the first 2 buffers followed by a short period of time and then the connection is reset.
The protocol is binary. First 2 bytes tells me the length of the message, 2nd 2 bytes message type. Rest is data.
On the first read I get a 91 byte policy file which I discard. After that I receive data that I am able to process and the first 20 odd messages are fine. The 3rd buffer though never arrives.
Any ideas? Is it my implementation of AsyncSocket at fault or is there some flag I should be using on my socket?
Any pointers would be much appreciated.
public abstract class AsyncSocket
{
public class StateObject
{
public Socket workSocket = null;
public const int BufferSize = 4096;
public byte[] buffer = new byte[BufferSize];
public byte[] messageBuffer = new byte[0];
}
public delegate void MessageReceivedHandler(object sender, MessageReceivedEventArgs e);
public delegate void ConnectedHandler(object sender, EventArgs e);
public event MessageReceivedHandler MessageReceived;
public event ConnectedHandler Connected;
private IPAddress[] addresses;
private int port;
private WaitHandle addressesSet;
private Socket socket;
private int failedConnectionCount;
private StateObject state;
public AsyncSocket(IPAddress address, int port) : this(new[] { address }, port) { }
public AsyncSocket(IPAddress[] addresses, int port) : this(port)
{
this.addresses = addresses;
}
public AsyncSocket(string hostNameOrAddress, int port) : this(port)
{
addressesSet = new AutoResetEvent(false);
Dns.BeginGetHostAddresses(hostNameOrAddress, GetHostAddressesCallback, null);
}
private void GetHostAddressesCallback(IAsyncResult result)
{
addresses = Dns.EndGetHostAddresses(result);
((AutoResetEvent)addressesSet).Set();
}
private AsyncSocket(int port)
{
this.port = port;
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.Encoding = Encoding.Default;
this.state = new StateObject();
state.workSocket = socket;
}
public Encoding Encoding { get; set; }
public Account Account { get; set; }
public void Connect()
{
if (addressesSet != null)
addressesSet.WaitOne();
Interlocked.Exchange(ref failedConnectionCount, 0);
socket.BeginConnect(addresses, port, ConnectCallback, socket);
}
private void ConnectCallback(IAsyncResult result)
{
try
{
Socket client = (Socket)result.AsyncState;
client.EndConnect(result);
if (Connected != null)
{
Connected(this, new EventArgs());
}
Receive(client);
}
catch
{
Interlocked.Increment(ref failedConnectionCount);
if (failedConnectionCount >= addresses.Length)
{
return;
}
}
}
public void Send(string data)
{
byte[] bytes = Encoding.GetBytes(data);
Send(bytes);
}
public void Send(MsgHead msg)
{
byte[] bytes = msg.write();
Send(bytes);
}
public void Send(byte[] bytes)
{
int messageLength = BitConverter.ToUInt16(bytes, 0);
int messageType = BitConverter.ToUInt16(bytes, 2);
Console.Out.WriteLine("Sending:len:{0} msg:{1}", messageLength, messageType);
socket.BeginSend(bytes, 0, bytes.Length, 0, new AsyncCallback(WriteCallback), socket);
}
private void WriteCallback(IAsyncResult result)
{
Socket client = (Socket)result.AsyncState;
int bytesSent = client.EndSend(result);
Console.WriteLine("Sent {0} bytes to server.", bytesSent);
}
private void Receive(Socket client)
{
try
{
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
catch (Exception e)
{
Account.Window.Exit(string.Format("Error on receive: {0}",e.Message));
}
}
private void ReceiveCallback(IAsyncResult result)
{
StateObject state = (StateObject)result.AsyncState;
Socket client = state.workSocket;
SocketError errorCode;
int bytesRead = client.EndReceive(result, out errorCode);
if (errorCode != SocketError.Success)
{
Account.Window.Exit(string.Format("Disconnected, {0}", errorCode.ToString()));
return;
}
if (bytesRead == 0)
{
Account.Window.Exit("Disconnected, zero bytes");
return;
}
state.messageBuffer = state.messageBuffer.Concat(state.buffer.Take(bytesRead).ToArray()).ToArray();
int messageLength = BitConverter.ToUInt16(state.messageBuffer, 0);
if (messageLength > 4096)
{
state.messageBuffer = state.messageBuffer.Skip(91).ToArray();
messageLength = state.messageBuffer.Length == 0 ? 0 : BitConverter.ToUInt16(state.messageBuffer, 0);
}
while (messageLength > 0 && state.messageBuffer.Length >= messageLength)
{
int messageType = BitConverter.ToUInt16(state.messageBuffer, 2);
Console.Out.WriteLine("Received:len:{0} msg:{1}", messageLength, messageType);
if (MessageReceived != null)
{
MessageReceived(this, new MessageReceivedEventArgs(state.messageBuffer.Take(messageLength).ToArray()));
}
state.messageBuffer = state.messageBuffer.Skip(messageLength).ToArray();
messageLength = state.messageBuffer.Length == 0 ? 0 : BitConverter.ToUInt16(state.messageBuffer, 0);
}
Receive(client);
}
}
Sometimes TCP Framing can be tricky. You never know how many receive calls you might get for the three buffers you are expecting. Here are a few things to check:
One problem that could occur in the ReceiveCallback() method, if you only receive 1 byte, your attempt to decode messageLength into a 2-byte Int16 will fail.
Be sure the MessageBuffer methods are all working properly.
Are the 2 bytes for MessageLength included in the value of the MessageLength? If not, be sure to Skip those 2 bytes before decoding the message bytes.
Safer to discard the full number of Message bytes just in case it's not always 91 bytes long.
I would dispatch all "full" message buffers into another method, and discard or handle there, to keep the ReceiveCallback() method nice and concise.
Related
I'm trying to learn Game Networking programming so I started a very simple async udp socket system using Unity, however when I added JSON serialization things got a little bit weird.
I can connect to the server and send packets with the SendPacket(string msg) method and it will receive it fine on the server side. And it will work fine as long as the size of the msg variable is the same size or bigger. So if I send a string "Hello" and then one "Hello World" will work fine and be able to print it on the other size. However if I was now to send another packet with the string "Hi" nothing would happen and no more connections will work on that socket neither sending new packets.
I have checked and I'm receiving the packet over the network and it's not empty it has the information however it seems to stop when it gets to the code:
Packet p = e.Buffer.FromJsonBinary<Packet>();
Which is on the OnConnect function on my server side.After that function It seems my server stops listening not accepting new connections and no other packets will be receive, I suspect it somehow stops my async process.
For the Json Serialization I'm using JsonUtility from Unity.
Any ideas what is happening?
Server Class
public class Server
{
private static string _protocolID = "hash";
private static ushort _port = 11000;
private Socket _socket;
private SocketAsyncEventArgs _event;
private List<Socket> _connections = new List<Socket>();
public void Start()
{
Listen();
}
private bool Listen()
{
// Create UDP Socket
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// Set the socket into non-blocking mode
_socket.Blocking = false;
try
{
_socket.Bind(new IPEndPoint(IPAddress.Any, _port));
_event = new SocketAsyncEventArgs();
_event.Completed += OnConnect;
byte[] buffer = new byte[1024];
_event.SetBuffer(buffer, 0, 1024);
//_socket.ReceiveAsync(_event);
StartListening(_event);
}
catch (Exception e)
{
Debug.LogException(e);
return false;
}
return true;
}
private void StartListening(SocketAsyncEventArgs e)
{
//e.AcceptSocket = null;
_socket.ReceiveAsync(e);
}
private void OnConnect(object sender, SocketAsyncEventArgs e)
{
if (e.BytesTransferred > 0)
{
if (e.SocketError != SocketError.Success)
Debug.Log("ERROR"); // TODO: Close Socket
Packet p = e.Buffer.FromJsonBinary<Packet>();
if (p._protocolID != _protocolID)
Debug.Log("Protocol Error");
Debug.Log("Connect:" + p._msg);
if (!_socket.ReceiveAsync(e))
{
// Call completed synchonously
StartListening(e);
}
}
else
{
Debug.Log("No data");
}
}
}
Client Class
public class Client
{
private static string _protocolID = "hash";
private ushort _port = 11000;
private string _ip = "127.0.0.1";
private Socket _socket;
private SocketAsyncEventArgs _event;
public bool Connect()
{
// Create UDP Socket
_socket = new Socket(SocketType.Dgram, ProtocolType.Udp);
// Set the socket into non-blocking mode
_socket.Blocking = false;
IPEndPoint ip = new IPEndPoint(IPAddress.Parse(_ip), _port);
_event = new SocketAsyncEventArgs();
//_event.Completed += Callback;
try
{
_socket.Connect(ip);
Debug.Log($"Connection was to {_ip} was sucessfull");
}
catch(Exception e)
{
Debug.LogException(e);
Debug.Log("Couldn't connect Socket");
return false;
}
return true;
}
public void SendPacket<T>(T t)
{
try
{
if (_socket == null)
Debug.Log("Null socket");
// Encode the data string into a byte array.
byte[] buffer = t.ToJsonBinary();
_event.SetBuffer(buffer, 0, buffer.Length);
// Send the data through the socket.
//_socket.SendAsync(_event);
bool willRaiseEvent = _socket.SendAsync(_event);
//if (!willRaiseEvent)
//{
// SendPacket<T>(t);
//}
}
catch (SocketException e)
{a
Debug.LogException(e);
Debug.Log("Couldn't Send Packet");
}
}
public void SendPacket(string msg)
{
Packet packet = new Packet(_protocolID, msg);
SendPacket<Packet>(packet);
}
}
Json Serialization
public static byte[] ToJsonBinary(this object obj)
{
return Encoding.ASCII.GetBytes(JsonUtility.ToJson(obj));
}
public static T FromJsonBinary<T>(this byte[] data)
{
return JsonUtility.FromJson<T>(Encoding.ASCII.GetString(data));
}
Packet
[Serializable]
public struct Packet
{
public string _protocolID;
public string _msg;
public Packet(string protocolID, string msg)
{
_protocolID = protocolID;
_msg = msg;
}
}
Edit: I found out it's crashing due to Json Utility
System.ArgumentException: JSON parse error: The document root must not follow by other values.
Json string before being sent (top), Json string reiceived at the server (bottom)
It seems on the server the buffer isn't being cleared so it still has information in it.
Okay so it seems the problem was due to how SocketAsyncEventArgs works. Since I'm calling recursively and passing the same event it never clears it's buffer.
if (!_socket.ReceiveAsync(e))
{
OnConnect(null, e);
}
I found two solutions one was to do pass a new SocketAsyncEventArgs which will have a clear buffer. But I believe a better idea would be to make a pool to handle and reuse a set of predefined SocketAsyncEventArgs instead of constantly creating new ones.
Another solution I found was to simple set a new buffer as a way to clear the last one.
byte[] buffer = t.ToJsonBinary();
_event.SetBuffer(buffer, 0, buffer.Length);
Server :
public class TcpServer
{
private TcpListener tcpListener;
private static ManualResetEvent allDone = new ManualResetEvent(false);
public TcpServer(string url, int port)
{
tcpListener = new TcpListener(IPAddress.Parse(url), port);
pingMapper = new LightPingMapper();
}
public void Run()
{
tcpListener.Start();
Console.WriteLine("Server running");
while (true)
{
allDone.Reset();
tcpListener.BeginAcceptSocket(AcceptCallback, tcpListener);
Console.WriteLine("Accepting socket");
allDone.WaitOne();
}
Console.ReadLine();
}
private void AcceptCallback(IAsyncResult result)
{
try
{
allDone.Set();
var listener = (TcpListener) result.AsyncState;
var handler = listener.EndAcceptSocket(result);
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, state.buffer.Length, 0, ReadCallback, state);
}
catch (Exception e)
{
Console.WriteLine($"Error accepting callback. {e.Message}");
}
}
private void ReadCallback(IAsyncResult asyncResult)
{
try
{
string content = string.Empty;
Console.WriteLine("Read data from socket");
StateObject state = (StateObject) asyncResult.AsyncState;
Socket handler = state.workSocket;
int bytesRead = handler.EndReceive(asyncResult);
if (bytesRead > 0)
{
state.sb.Append(Encoding.UTF8.GetString(state.buffer));
content = state.sb.ToString();
Console.WriteLine(content + " " + DateTime.Now);
}
}
catch (Exception e)
{
Console.WriteLine($"Error reading socket. {e.Message}");
}
}
}
class StateObject
{
// Client socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 256;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public StringBuilder sb = new StringBuilder();
}
Client:
public class TCPClientWrapper
{
private TcpClient tcpClient;
private readonly string address;
private readonly int port;
public TCPClientWrapper(string address, int port)
{
InitTcpClient();
this.address = address;
this.port = port;
}
public void SendMessage()
{
for(int i=0; i < 10; i++)
{
if (!SocketConnected())
{
TryConnect();
}
byte[] buffer = Encoding.UTF8.GetBytes("Hello");
tcpClient.Client.Send(buffer);
Thread.Sleep(60000);
}
}
private void TryConnect()
{
bool isConnected = false;
while (true)
{
try
{
InitTcpClient();
tcpClient.Connect(IPAddress.Parse(address), port);
if (SocketConnected())
{
Console.WriteLine("TcpClient, Connected");
isConnected = true;
break;
}
}
catch (Exception e)
{
Console.WriteLine("TcpClient, connection failed. Try to reconnect after 30 seconds, {0}", e.Message);
}
finally
{
if (!isConnected)
{
tcpClient.Close();
Thread.Sleep(30000);
}
}
}
}
private void InitTcpClient()
{
tcpClient = new TcpClient();
tcpClient.SendTimeout = 15;
}
private bool SocketConnected()
{
var s = tcpClient.Client;
if (!s.Connected)
return false;
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = s.Available == 0;
return !(part1 && part2);
}
}
The problem is that server read only first message , each next message is not received by server . The tcpClient is connected , but server doesn't receive any message . Could anyone suggest what is wrong with my code ?
In ReadCallback, you don't start the next read - so yes, your code only reads once.
Adding
handler.BeginReceive(state.buffer, 0, state.buffer.Length, 0, ReadCallback, state);
to the bottom of ReadCallback (when bytesRead > 0) should work. However! You aren't implementing proper framing, so you should be very cautious of that. A basic framing implementation for a text-based protocol (like this) would be to use some kind of line-end sentinel, and buffer data until you see a line-end, then process the line.
On TCP, you are only guaranteed to get the right bytes in the right order (or a failed socket eventually) - you are not guaranteed to get them in the same composition in terms of calls to Send exactly matching calls to Receive in terms of the numbers of bytes in each.
I'm creating a Server Socket in C# for my Unity Application. I have created Asynchronous Server Socket from below link
Asynchronous Server Socket
Using which I can successfully able to connect with client and receive data from client. But after few data received the socket is not receiving data even though the client is sending the data (I'm testing client and server in same machine).
Also there was no exception thrown in try catch either. So I'm not able to identify the root cause.
Please find my code below
public class NetworkStateObject
{
// Client socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 1024;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public List<byte> receivedBytes = new List<byte>();
}
private readonly ManualResetEvent allDone = new ManualResetEvent(false);
private readonly ManualResetEvent receiveDone = new ManualResetEvent(false);
private readonly string receiveDelimitter = "<EOF>";
socketThread = new Thread(new ThreadStart(StartSocketServer));
socketThread.Priority = System.Threading.ThreadPriority.BelowNormal;
socketThread.IsBackground = true;
socketThread.Start();
protected void Receive(Socket socket)
{
ReceiveData(socket);
receiveDone.WaitOne();
}
private void ReceiveData(Socket socket)
{
try
{
// Create the state object.
NetworkStateObject state = new NetworkStateObject();
state.workSocket = socket;
// Begin receiving the data from the remote device.
socket.BeginReceive(state.buffer, 0, NetworkStateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
catch (Exception e)
{
Debug.Log(e.ToString());
}
}
private void ReceiveCallback(IAsyncResult ar)
{
try
{
// Retrieve the state object and the client socket
// from the asynchronous state object.
NetworkStateObject state = (NetworkStateObject)ar.AsyncState;
Socket socket = state.workSocket;
// Read data from the remote device.
int bytesRead = socket.EndReceive(ar);
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
//state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
var bytesArray = state.buffer;
if (bytesRead < NetworkStateObject.BufferSize)
{
Array.Resize(ref bytesArray, bytesRead);
}
var bytesList = new List<byte>(bytesArray);
state.receivedBytes.AddRange(bytesList);
var receivedBytes = state.receivedBytes;
var bytesCount = receivedBytes.Count;
if (receivedBytes.Count > 1)
{
var receivedString = Encoding.ASCII.GetString(receivedBytes.ToArray(), 0, bytesCount);
if (receivedString.IndexOf(receiveDelimitter, StringComparison.CurrentCulture) > -1)
{
var message = receivedString.Replace(receiveDelimitter, String.Empty);
message = Regex.Unescape(message);
socketBaseDelegate.ReceivedMessage(message);
state.receivedBytes.Clear();
receiveDone.Set();
}
else
{
// Get the rest of the data.
socket.BeginReceive(state.buffer, 0, NetworkStateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
}
}
else
{
receiveDone.Set();
}
}
catch (Exception e)
{
Debug.Log(e.ToString());
}
}
public void ReceivedMessage(string data)
{
socketInputParser.Parse(data);
asynchronousSocketListener.ReceiveMessage();
}
In the above code the ReceiveCallback is not triggered after some time.
Even though client sends data.
else
{
// Get the rest of the data.
socket.BeginReceive(state.buffer, 0, NetworkStateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
This part shouldn't be conditional. You should always begin receiving again once the receive callback is complete. Unless of course the connection is terminated.
I have a client and server class in C# that uses socket communication. The Server looks like this:
public class AsyncTcpServer
{
private Socket _server_socket;
private Socket _client_socket;
private byte[] _receive_buffer;
private byte[] _send_buffer;
private NetworkStream _ns;
public void Start()
{
try
{
_server_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_server_socket.Bind(new IPEndPoint(IPAddress.Any, 17999));
_server_socket.Listen(0);
_server_socket.BeginAccept(new AsyncCallback(BeginAccept), null);
}
catch(Exception e)
{
Debug.Print(e.Message);
}
}
private void BeginAccept(IAsyncResult ar)
{
try
{
_client_socket = _server_socket.EndAccept(ar);
_receive_buffer = new byte[_client_socket.ReceiveBufferSize];
_send_buffer = new byte[_client_socket.ReceiveBufferSize];
_ns = new NetworkStream(_client_socket);
_client_socket.BeginReceive(_receive_buffer, 0, _receive_buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallback), null);
}
catch(Exception e)
{
Debug.Print(e.Message);
}
}
private void RecieveCallback(IAsyncResult ar)
{
try
{
string text = Encoding.ASCII.GetString(_receive_buffer);
Debug.Print("Server Received: " + text);
}
catch (Exception e)
{
Debug.Print("Unexpected exception: " + e.Message);
}
}
public void Send(byte [] bytes)
{
try
{
_ns.Write(bytes, 0, bytes.Length);
}
catch (Exception e)
{
Debug.Print("Unexpected exception: " + e.Message);
}
}
}
And the client looks like this:
public class AsyncTcpClient
{
private Socket _client_socket;
private byte[] _buffer;
private const int HEADER_SIZE = sizeof(int);
public void Start()
{
_client_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_client_socket.BeginConnect(new IPEndPoint(IPAddress.Loopback, 17999), new AsyncCallback(ConnectCallback), null);
}
private void ConnectCallback(IAsyncResult ar)
{
try
{
_client_socket.EndConnect(ar);
_buffer = new byte[_client_socket.ReceiveBufferSize];
StartReceive();
byte[] buffer = Encoding.ASCII.GetBytes("Connected!");
_client_socket.Send(buffer);
}
catch (Exception e)
{
Debug.Print(e.Message);
}
}
private void StartReceive(int offset = 0)
{
try
{
_client_socket.BeginReceive(_buffer, offset, _buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallback), null);
}
catch (Exception e)
{
Debug.Print(e.Message);
}
}
private void RecieveCallback(IAsyncResult ar)
{
try
{
int bytes_processed = 0;
int bytes_read = _client_socket.EndReceive(ar);
if (bytes_read > 0)
{
NetworkStream ns = new NetworkStream(_client_socket);
while (ns.DataAvailable && (bytes_processed < bytes_read))
{
byte[] len_bytes = new byte[HEADER_SIZE];
ns.Read(len_bytes, 0, HEADER_SIZE);
int current_chunk_size = BitConverter.ToInt32(len_bytes, 0);
if (current_chunk_size > 0)
{
byte[] data_buff = new byte[current_chunk_size];
ns.Read(data_buff, 0, current_chunk_size);
string s = Encoding.ASCII.GetString(data_buff);
bytes_processed += (HEADER_SIZE + current_chunk_size);
Debug.WriteLine(s);
}
}
}
StartReceive();
}
catch (Exception e)
{
Debug.Print(e.Message);
}
StartReceive();
}
}
They work together as follows:
Server starts
Client connects
Server sends custom packets to the client for its comsumption
I use the following 'data structure' to package my transmission data on the server side to send to the client:
{[DATA_LENGTH_IN_BYTES][PAYLOAD_BYTES]}
On the client side, I parse the first 4 bytes (sizeof(int)) to determine the payload length and then parse the payload itself. This works the first time I do it but after that the DataAvailable member of the NetworkStream is false and I can't parse the rest of the payload.
Why is DataAvailable false? I'm pretty new to doing this stuff in C# - am I approaching it the wrong way entirely?
Thanks in Advance!
Here is the solution I settled on:
Server:
public class Listener
{
private TcpListener _listener;
private TcpClient _client;
public void Start()
{
_listener = new TcpListener(IPAddress.Loopback, 17999);
_listener.Start();
_listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), _listener);
}
private void AcceptTcpClientCallback(IAsyncResult ar)
{
try
{
Debug.WriteLine("Accepted tcp client callback");
_client = _listener.EndAcceptTcpClient(ar);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
public void Write(string data)
{
try
{
NetworkStream ns = _client.GetStream();
byte[] buffer = Encoding.ASCII.GetBytes(data);
ns.Write(buffer, 0, buffer.Length);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
}
And the client:
public class Client
{
private TcpClient _client;
private byte[] _buffer;
public void Start()
{
try
{
_client = new TcpClient();
_client.BeginConnect(IPAddress.Loopback, 17999, new AsyncCallback(ConnectCallback), _client);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
private void ConnectCallback(IAsyncResult ar)
{
try
{
NetworkStream ns = _client.GetStream();
_buffer = new byte[_client.ReceiveBufferSize];
ns.BeginRead(_buffer, 0, _buffer.Length, new AsyncCallback(ReadCallback), null);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
private void ReadCallback(IAsyncResult ar)
{
try
{
NetworkStream ns = _client.GetStream();
int read = ns.EndRead(ar);
string data = Encoding.ASCII.GetString(_buffer, 0, read);
var res = data.Split(new [] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var r in res)
{
Debug.WriteLine(r); // process messages
}
ns.BeginRead(_buffer, 0, _buffer.Length, ReadCallback, _client);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
}
Where messages from the server to the client are formed as follows:
string message = "This is a message" + "\r\n";
_listener.Send(message);
I like this alternative for its simplicity. Its a lot shorter and (to me at least) much easier to manage.
HTH
I think you forget the EndReceive in the RecieveCallback. (server code)
private void RecieveCallback(IAsyncResult ar)
{
try
{
int bytesReceived = _client_socket.EndReceive(ar); // <---
string text = Encoding.ASCII.GetString(_receive_buffer);
Debug.Print("Server Received: " + text);
}
catch (Exception e)
{
Debug.Print("Unexpected exception: " + e.Message);
}
}
I advise to create one class that reads/writes to sockets (implementing the protocol). A Base class that handles reads/writes to Sockets, a client socket that is derived from the SocketConnection, but first will connect to an ipendpoint. A server that has a List of SocketConnection. This way you keep your client/server functionality separated of the message handling on socket. But both using the same code to receive/send messages. Here is an example:
Pseudo:
// base class that handle receive/sent packets
class SocketConnection
{
// the reason for a Start method is that event can be bound before the Start is executed.
void Start(Socket socket)
{
StartReceive();
}
void StartReceive()
{
socket.BeginReceive(...);
}
void EndReceive()
{
socket.EndReceive(...);
// handle received message.
// call the ondata event or something
StartReceive();
}
}
class ClientSocket : SocketConnection
{
void Connect()
{
Socket socket = new Socket(...);
socket.Connect(..);
// start receiving from the client socket.
base.Start(socket);
}
}
class Server
{
List<SocketConnection> _clients;
void Start()
{
// Create server socket + listening etc..
StartAccept();
}
void StartAccept()
{
serverSocket.BeginAccept(...);
}
void EndAccept()
{
Socket serverClientSocket = EndAccept(...);
// create a base socket handler.....
SocketConnection clientSocket = new SocketConnection();
_clients.Add(clientSocket);
// bind the ondata event of the client and pass it to the clientondata event of the server.
// Start receiving from the socket.
clientSocket.Start(serverClientSocket);
// accept new clients.
StartAccept();
}
}
UPDATE:
"Regarding how to handle buffers, what would you suggest as the best way to not miss data in separate packets?"
I would send a size first:
// sending part.
string message = "This is a message";
byte[] buffer = Encoding.ASCII.GetBytes(message);
_client_socket.Send(BitConverter.GetBytes(buffer.Length)); // sends a int as 4 bytes.
_client_socket.Send(data);
// receiving part.
// try to receive at least 4 bytes. (no more/ no less)
int length = BitConverter.ToInt32(buffer, 0);
// try to receive data with `length` size, (no more/ no less)
This will separate different packets.
Asynchronous example:
You still need to add some exception handling code.
public static class SocketReader
{
public static void ReadFromSocket(Socket socket, int count, Action<byte[]> endRead)
{
// read from socket, construct a new buffer.
DoReadFromSocket(socket, 0, count, new byte[count], endRead);
}
public static void ReadFromSocket(Socket socket, int count, ref byte[] buffer, Action<byte[]> endRead)
{
// if you do have a buffer available, you can pass that one. (this way you do not construct new buffers for receiving.
// the ref is because if the buffer is too small, it will return the newly created buffer.
// if the buffer is too small, create a new one.
if (buffer.Length < count)
buffer = new byte[count];
DoReadFromSocket(socket, 0, count, buffer, endRead);
}
// This method will continues read until count bytes are read. (or socket is closed)
private static void DoReadFromSocket(Socket socket, int bytesRead, int count, byte[] buffer, Action<byte[]> endRead)
{
// Start a BeginReceive.
socket.BeginReceive(buffer, bytesRead, count - bytesRead, SocketFlags.None, (result) =>
{
// Get the bytes read.
int read = socket.EndReceive(result);
// if zero bytes received, the socket isn't available anymore.
if (read == 0)
{
endRead(new byte[0]);
return;
}
// increase the bytesRead, (index point for the buffer)
bytesRead += read;
// if all bytes are read, call the endRead with the buffer.
if (bytesRead == count)
endRead(buffer);
else
// if not all bytes received, start another BeginReceive.
DoReadFromSocket(socket, bytesRead, count, buffer, endRead);
}, null);
}
}
Example how to use it:
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// read header. (length of data) (4 bytes)
SocketReader.ReadFromSocket(socket, 4, (headerBuffer) =>
{
if (headerBuffer.Length == 0)
{
// disconnected;
return;
}
int length = BitConverter.ToInt32(headerBuffer, 0);
// read bytes specified in length.
SocketReader.ReadFromSocket(socket, length, (dataBuffer) =>
{
if (dataBuffer.Length == 0)
{
// disconnected;
return;
}
// if you want this in a stream, you can do: This way the stream is readonly and only wraps arround the bytearray.
using (MemoryStream stream = new MemoryStream(dataBuffer, 0, length))
using (StreamReader reader = new StreamReader(stream))
while (!reader.EndOfStream)
Debug.WriteLine(reader.ReadLine());
});
});
Ive got an async server set up, it works perfectly by connecting, receiving and sending back a message to the connecting client.
The server itself is a Game-World-Server (mmorpg style). When a user sends its position to where its located, I need to push this out to all the clients with a PlayerPositionNotice. I know I'm missing some basic stuff here, but when i try to save the StateObject that was created in the accept method, and use that socket to send back information to the player at any given time it fails because the socket is closed. =/ Don't know why this happens and would I've searched a couple of engines on this but came back empty.
This is how i created my server:
First off we have the global stuff:
public StateManager _stateManager = new StateManager();
public bool IsClosing = false;
private const int _port = 1025;
private IPHostEntry _localhost;
private IPEndPoint _endpoint;
private Socket _serverSocket;
private Thread _serverThread;
Second of we have the initialize stuff:
public void Start()
{
_serverThread = new Thread(Initialize);
_serverThread.Start();
}
/// <summary>
/// Main entry point for the server
/// </summary>
private void Initialize()
{
Console.WriteLine("Server Main Socket Thread Initialized.");
_localhost = Dns.GetHostEntry(Dns.GetHostName());
try
{
_endpoint = new IPEndPoint(_localhost.AddressList[0], _port);
_serverSocket = new Socket(_endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_serverSocket.Bind(_endpoint);
_serverSocket.Listen(100);
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (ArgumentOutOfRangeException)
{
Console.WriteLine(" >> Port number " + _port + " would seem to be invalid, should be between 1024 and 65000");
}
catch (SocketException)
{
Console.WriteLine(" >> Could not create socket, check to make sure not duplicating port");
}
catch (Exception e)
{
Console.WriteLine(" >> Error occured while binding socket, IE:" + e.InnerException);
}
}
So far so good, i expect.. And now to the rest of the server class.
private void acceptCallback(IAsyncResult result)
{
Console.WriteLine("Connection Accepted");
StateObject state = null;
try
{
state = new StateObject
{
workSocket = ((Socket)result.AsyncState).EndAccept(result)
};
_stateManager.AddConnection(state);
state.workSocket.BeginReceive(state.buffer, 0, state.buffer.Length,
SocketFlags.None, new AsyncCallback(receiveCallback), state);
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (SocketException)
{
_stateManager.RemoveConnection(state);
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (Exception)
{
_stateManager.RemoveConnection(state);
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
}
private void receiveCallback(IAsyncResult result)
{
var state = (StateObject)result.AsyncState;
try
{
// Buffer and count bytes read
int bytesRead = state.workSocket.EndReceive(result);
if (!state.workSocket.Connected)
_stateManager.RemoveConnection(state);
if (bytesRead > 0)
{
// Parse the message to the protocol manager and return a reply
var replyingData = ProtocolManager.Parse(state.buffer);
if (replyingData != null)
Send(replyingData, state);
//Queue the next receive
state.workSocket.BeginReceive(state.buffer, 0, state.buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), state);
}
else
{
_stateManager.RemoveConnection(state);
}
}
catch (SocketException e)
{
_stateManager.RemoveConnection(state);
}
}
public bool Send(byte[] message, StateObject state)
{
Console.WriteLine("Sending " + message.Length + " bytes");
if (state != null && state.workSocket.Connected)
{
lock (state.workSocket)
{
//we use a blocking mode send, no async on the outgoing
//since this is primarily a multithreaded application, shouldn't cause problems to send in blocking mode
state.workSocket.Send(message, message.Length, SocketFlags.None);
}
}
else return false;
return true;
}
The stateManager contains a list of StateObject.. Below you can see how i build them.
STATE MANAGER:
public class StateManager
{
private List<StateObject> _connections = new List<StateObject>();
public void AddConnection(StateObject so)
{
lock (_connections)
{
_connections.Add(so);
}
}
public void RemoveConnection(StateObject so)
{
if (so.workSocket != null)
{
so.workSocket.Close();
lock (_connections)
{
_connections.Remove(so);
}
}
}
}
STATE OBJECT
public class StateObject
{
public Socket workSocket = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
}
My problem here is that whenever anybody in this list sends something i want to send back a notice to alot of other clients. How and where can i implement this? Anybody that can kick me in the right direction? =)
This code seem to be correct and I don't know why you get "socket is closed" error, but there is another problem: in Send(byte[] message, StateObject state) method, because you call this when receiving from user and send received data back to that user.(not to all other users to notice them)
As you said, if you need to send new location to all other users:
Call this method instead of your Send(byte[] message, StateObject state), when received new location.
public void NoticeAllusers(byte []buffer,StateObject state)
{
foreach(StateObject obj in _stateManager._connections)
{
if (obj != state)
{
obj.workSocket.BeginSend(buffer,<parameters you>...., new AsyncCallback(OnSend) state.workSocket);
}
}
}
public void OnSend(IAsyncResult ar)
{
try
{
Socket sock = (Socket)ar.AsyncState;
sock.EndSend(ar);
}
catch { }
}
I hope it will help a little :)