Sending a value from server to client with sockets
Solution 1:
Answers to the questions first:
Q: Is it necessary to convert everything to string?...In general what I want is to send a variable from one computer to two others in order a process to begin simultaneously in all computers.
A: No, it is not necessary to convert everything to string when sending using
Socket
. You may sendbyte[]
which you most probably want.Q: What I want to achieve is to send a boolean variable from the server to the client with the sockets
A: Do you mean
boolean
orbyte
? Because the basic variable type which you will get from theSocket
isbyte
. You could always changebyte
tobool
from the sender/receiver side by doing like
bool val = byteToCheck > 0;
A2: And since your server is
Console
Application, I recommend to take a look on hexstring
tobyte[]
conversion. This way, you could write something instring
but interprets it asbyte[]
. Check this. The whole idea here is pretty simple. That is: you type instring
, but it will be sent asbyte[]
. And since it isbyte[]
you can have any value in it.
And here I present my solution to handle your (1) multiple clients, (2) Async
connect & accept & receive, but having (3) send sync, as well as (4) conversion from hex string
to byte[]
(the structure & idea), and last but not least (5) working code with user input (for you to change this part) for testing!
I would solve such problem using simple Socket
class, since it is the solution I am most familiar with. But you could always do similarly if you use your TcpListener.Server
(which is the underlying network of Socket
class). And, as you desire, I would do that with ASync
.
There are several steps needed to achieve what you want in both your server and your client:
Server
-
Make your
Socket
as class field rather than method field, since you will use if everywhere and you need multiple methods to achieve what you want. And initialize it as soon as you started your main routine.const int PORT_NO = 2201; const string SERVER_IP = "127.0.0.1"; static Socket serverSocket; //put here as static static void Main(string[] args) { //---listen at the specified IP and port no.--- Console.WriteLine("Listening..."); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO)); serverSocket.Listen(4); //the maximum pending client, define as you wish //your next main routine }
-
Since the server will serve many clients, I will recommend you to use
ASync
rather thanSync
for the process. Initialize yourSocket
by usingBeginAccept
rather than usingAccept
, putacceptCallback
in yourBeginAccept
static void Main(string[] args) { //---listen at the specified IP and port no.--- Console.WriteLine("Listening..."); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO)); serverSocket.Listen(4); //the maximum pending client, define as you wish serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //other stuffs }
-
Define
acceptCallback
, which is where you will go when you accept aSocket
. PutEndAccept
there.private void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there... System.Net.Sockets.Socket socket = null; try { socket = serverSocket.EndAccept(result); // To get your client socket //do something later } catch (Exception e) { // this exception will happen when "this" is be disposed... //do something later } }
I would typically list my client sockets, and do something on client disposal (that is unlisted it) - but this depends on the need. In this case, you seem to need it. And don't forget to create buffers, etc... This is for buffering the incoming data.
-
Start to accept something received from the client, using another
ASync
BeginReceive
on the clientSocket
(and now you needreceiveCallback
). Then, very important, repeat yourBeginAccept
to accept other clients!private const int BUFFER_SIZE = 4096; private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message private static List<Socket> clientSockets = new List<Socket>(); //may be needed by you private static void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there... Socket socket = null; try { socket = serverSocket.EndAccept(result); // The objectDisposedException will come here... thus, it is to be expected! //Do something as you see it needs on client acceptance such as listing clientSockets.Add(socket); //may be needed later socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //to receive another client } catch (Exception e) { // this exception will happen when "this" is be disposed... //Do something here Console.WriteLine(e.ToString()); } }
-
Define your
receiveCallback
, that is, when you receive something from your client. This part could be quite tricky because of failures! But basically, what you need for now is simplyEndReceive
and again, very important, to repeat theBeginReceive
from the same client such that you can receive its next message!const int MAX_RECEIVE_ATTEMPT = 10; static int receiveAttempt = 0; //this is not fool proof, obviously, since actually you must have multiple of this for multiple clients, but for the sake of simplicity I put this private static void receiveCallback(IAsyncResult result) { Socket socket = null; try { socket = (Socket)result.AsyncState; //this is to get the sender if (socket.Connected) { //simple checking int received = socket.EndReceive(result); if (received > 0) { byte[] data = new byte[received]; //the data is in the byte[] format, not string! Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //There are several way to do this according to https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest //DO SOMETHING ON THE DATA IN byte[] data!! Yihaa!! Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else receiveAttempt = 0; //reset receive attempt socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //fail but not exceeding max attempt, repeats ++receiveAttempt; //increase receive attempt; socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive } else { //completely fails! Console.WriteLine("receiveCallback fails!"); //don't repeat beginReceive receiveAttempt = 0; //reset this for the next connection } } } catch (Exception e) { // this exception will happen when "this" is be disposed... Console.WriteLine("receiveCallback fails with exception! " + e.ToString()); } }
-
And suppose you want to reply your sender after you receive the message, simply do this in the
if (received > 0)
part:if (received > 0) { byte[] data = new byte[received]; //the data is in the byte[] format, not string! //DO SOMETHING ON THE DATA int byte[]!! Yihaa!! Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else //Message retrieval part //Suppose you only want to declare that you receive data from a client to that client string msg = "I receive your message on: " + DateTime.Now; socket.Send(Encoding.ASCII.GetBytes(msg)); //Note that you actually send data in byte[] Console.WriteLine("I sent this message to the client: " + msg); receiveAttempt = 0; //reset receive attempt socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive }
And after putting a little more things in your main routine, you are done(!) - IF you do not ask for sending to client as
byte[]
-
And now, if you want to send something to all your clients as
byte[]
you simply need to list all your client (see step 4-5). See this and convert theresult
string
above (remember to type it in hexstring
format as required) tobyte[]
then send it to all the clients using your client socket list (here is where it is needed!):static void Main(string[] args) { //---listen at the specified IP and port no.--- Console.WriteLine("Listening..."); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO)); serverSocket.Listen(4); //the maximum pending client, define as you wish serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //normally, there isn't anything else needed here string result = ""; do { result = Console.ReadLine(); if (result.ToLower().Trim() != "exit") { byte[] bytes = null; //you can use `result` and change it to `bytes` by any mechanism which you want //the mechanism which suits you is probably the hex string to byte[] //this is the reason why you may want to list the client sockets foreach(Socket socket in clientSockets) socket.Send(bytes); //send everything to all clients as bytes } } while (result.ToLower().Trim() != "exit"); }
And here, you are more or less done with your server. Next is your client
Client:
-
Similarly, put the
Socket
class in the class context rather than method context and initialize it as soon as you start your programconst int PORT_NO = 2201; const string SERVER_IP = "127.0.0.1"; static Socket clientSocket; //put here static void Main(string[] args) { //Similarly, start defining your client socket as soon as you start. clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //your other main routines }
-
Then start to connect by
ASync
BeginConnect
. I would normally go further byLoopConnect
just for failure handling like this.static void loopConnect(int noOfRetry, int attemptPeriodInSeconds) { int attempts = 0; while (!clientSocket.Connected && attempts < noOfRetry) { try { ++attempts; IAsyncResult result = clientSocket.BeginConnect(IPAddress.Parse(SERVER_IP), PORT_NO, endConnect, null); result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(attemptPeriodInSeconds)); System.Threading.Thread.Sleep(attemptPeriodInSeconds * 1000); } catch (Exception e) { Console.WriteLine("Error: " + e.ToString()); } } if (!clientSocket.Connected) { Console.WriteLine("Connection attempt is unsuccessful!"); return; } }
Similar concept to what you do to the server
BeginAccept
you need to defineendConnectCallback
for theASync
BeginConnect
you use. But here, unlike server which needs to re-callingBeginAccept
, once you are connected, you do not need to do any newBeginConnect
since you only need to be connected once.-
You may want to declare
buffer
etc. Then, after you connect, don't forget the nextASync
BeginReceive
to handle the message retrieval part (similar with the server)private const int BUFFER_SIZE = 4096; private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message private static void endConnectCallback(IAsyncResult ar) { try { clientSocket.EndConnect(ar); if (clientSocket.Connected) { clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), clientSocket); } else { Console.WriteLine("End of connection attempt, fail to connect..."); } } catch (Exception e) { Console.WriteLine("End-connection attempt is unsuccessful! " + e.ToString()); } }
Naturally, you need to define your
receiveCallback
, just like what you did for the server. And yes, it is as you have guessed, it is almost identical to what you did for the server!-
You can do anything you want with your data. Note that the data you receive is actually in
byte[]
, notstring
. So you can do anything with it. But for example's sake, I will just usestring
to display.const int MAX_RECEIVE_ATTEMPT = 10; static int receiveAttempt = 0; private static void receiveCallback(IAsyncResult result) { System.Net.Sockets.Socket socket = null; try { socket = (System.Net.Sockets.Socket)result.AsyncState; if (socket.Connected) { int received = socket.EndReceive(result); if (received > 0) { receiveAttempt = 0; byte[] data = new byte[received]; Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //copy the data from your buffer //DO ANYTHING THAT YOU WANT WITH data, IT IS THE RECEIVED PACKET! //Notice that your data is not string! It is actually byte[] //For now I will just print it out Console.WriteLine("Server: " + Encoding.UTF8.GetString(data)); socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //not exceeding the max attempt, try again ++receiveAttempt; socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); } else { //completely fails! Console.WriteLine("receiveCallback is failed!"); receiveAttempt = 0; clientSocket.Close(); } } } catch (Exception e) { // this exception will happen when "this" is be disposed... Console.WriteLine("receiveCallback is failed! " + e.ToString()); } }
-
And at the very very last... Yes, again, as you have already guessed, you just need to do something on your main routine - suppose you want to use it to send data. Because you use
Console
but you want it to send things asbyte[]
, you need to do the conversion (see the explanation in server 9.). And afterwards you are completely done!!static void Main(string[] args) { //Similarly, start defining your client socket as soon as you start. clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); loopConnect(3, 3); //for failure handling string result = ""; do { result = Console.ReadLine(); //you need to change this part if (result.ToLower().Trim() != "exit") { byte[] bytes = Encoding.ASCII.GetBytes(result); //Again, note that your data is actually of byte[], not string //do something on bytes by using the reference such that you can type in HEX STRING but sending thing in bytes clientSocket.Send(bytes); } } while (result.ToLower().Trim() != "exit"); }
Results:
Here you go! I tested it by sending string
for display, but I already put up what is needed when you want to change it to byte[]
Code for your test:
Server
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace TcpListenerConsoleApplication {
class Program {
const int PORT_NO = 2201;
const string SERVER_IP = "127.0.0.1";
static Socket serverSocket;
static void Main(string[] args) {
//---listen at the specified IP and port no.---
Console.WriteLine("Listening...");
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
serverSocket.Listen(4); //the maximum pending client, define as you wish
serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);
string result = "";
do {
result = Console.ReadLine();
} while (result.ToLower().Trim() != "exit");
}
private const int BUFFER_SIZE = 4096;
private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message
private static void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there...
Socket socket = null;
try {
socket = serverSocket.EndAccept(result); // The objectDisposedException will come here... thus, it is to be expected!
//Do something as you see it needs on client acceptance
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //to receive another client
} catch (Exception e) { // this exception will happen when "this" is be disposed...
//Do something here
Console.WriteLine(e.ToString());
}
}
const int MAX_RECEIVE_ATTEMPT = 10;
static int receiveAttempt = 0; //this is not fool proof, obviously, since actually you must have multiple of this for multiple clients, but for the sake of simplicity I put this
private static void receiveCallback(IAsyncResult result) {
Socket socket = null;
try {
socket = (Socket)result.AsyncState; //this is to get the sender
if (socket.Connected) { //simple checking
int received = socket.EndReceive(result);
if (received > 0) {
byte[] data = new byte[received]; //the data is in the byte[] format, not string!
Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //There are several way to do this according to https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest
//DO SOMETHING ON THE DATA int byte[]!! Yihaa!!
Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else
//Message retrieval part
//Suppose you only want to declare that you receive data from a client to that client
string msg = "I receive your message on: " + DateTime.Now;
socket.Send(Encoding.ASCII.GetBytes(msg)); //Note that you actually send data in byte[]
Console.WriteLine("I sent this message to the client: " + msg);
receiveAttempt = 0; //reset receive attempt
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
} else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //fail but not exceeding max attempt, repeats
++receiveAttempt; //increase receive attempt;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
} else { //completely fails!
Console.WriteLine("receiveCallback fails!"); //don't repeat beginReceive
receiveAttempt = 0; //reset this for the next connection
}
}
} catch (Exception e) { // this exception will happen when "this" is be disposed...
Console.WriteLine("receiveCallback fails with exception! " + e.ToString());
}
}
}
}
Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace TcpClientConsoleApplication {
class Program {
const int PORT_NO = 2201;
const string SERVER_IP = "127.0.0.1";
static Socket clientSocket; //put here
static void Main(string[] args) {
//Similarly, start defining your client socket as soon as you start.
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
loopConnect(3, 3); //for failure handling
string result = "";
do {
result = Console.ReadLine(); //you need to change this part
if (result.ToLower().Trim() != "exit") {
byte[] bytes = Encoding.ASCII.GetBytes(result); //Again, note that your data is actually of byte[], not string
//do something on bytes by using the reference such that you can type in HEX STRING but sending thing in bytes
clientSocket.Send(bytes);
}
} while (result.ToLower().Trim() != "exit");
}
static void loopConnect(int noOfRetry, int attemptPeriodInSeconds) {
int attempts = 0;
while (!clientSocket.Connected && attempts < noOfRetry) {
try {
++attempts;
IAsyncResult result = clientSocket.BeginConnect(IPAddress.Parse(SERVER_IP), PORT_NO, endConnectCallback, null);
result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(attemptPeriodInSeconds));
System.Threading.Thread.Sleep(attemptPeriodInSeconds * 1000);
} catch (Exception e) {
Console.WriteLine("Error: " + e.ToString());
}
}
if (!clientSocket.Connected) {
Console.WriteLine("Connection attempt is unsuccessful!");
return;
}
}
private const int BUFFER_SIZE = 4096;
private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message
private static void endConnectCallback(IAsyncResult ar) {
try {
clientSocket.EndConnect(ar);
if (clientSocket.Connected) {
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), clientSocket);
} else {
Console.WriteLine("End of connection attempt, fail to connect...");
}
} catch (Exception e) {
Console.WriteLine("End-connection attempt is unsuccessful! " + e.ToString());
}
}
const int MAX_RECEIVE_ATTEMPT = 10;
static int receiveAttempt = 0;
private static void receiveCallback(IAsyncResult result) {
System.Net.Sockets.Socket socket = null;
try {
socket = (System.Net.Sockets.Socket)result.AsyncState;
if (socket.Connected) {
int received = socket.EndReceive(result);
if (received > 0) {
receiveAttempt = 0;
byte[] data = new byte[received];
Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //There are several way to do this according to https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest
//DO ANYTHING THAT YOU WANT WITH data, IT IS THE RECEIVED PACKET!
//Notice that your data is not string! It is actually byte[]
//For now I will just print it out
Console.WriteLine("Server: " + Encoding.UTF8.GetString(data));
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //not exceeding the max attempt, try again
++receiveAttempt;
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
} else { //completely fails!
Console.WriteLine("receiveCallback is failed!");
receiveAttempt = 0;
clientSocket.Close();
}
}
} catch (Exception e) { // this exception will happen when "this" is be disposed...
Console.WriteLine("receiveCallback is failed! " + e.ToString());
}
}
}
}
Last Remarks (Edit)
Since the code above is run using Console Application
it must be run with static main void
keyword. And thus client Socket
defined above is of static
type. This may prevent the client Socket
to be defined multiple times as each time it is "defined", since it is of the same class
named Program
, it will refer to the same Socket
(though this may not always the case, at least according to the OP's experiment: he can run multiple clients successfully in the same computer).
Nevertheless, to overcome this is not that hard. Simply port the client application to the platform which is non-initiated as static
class (such as WinForms
) and all the above code would still run as per normal. Alternatively, if it must be run using the Console Applications
, and the problem occurs, simply copy the client application and re-define it using different namespace
or different class
name to avoid defining identical Socket
due to identical namespace
or class
.
But the most important part on this problem solving is the use of Async
and Sync
wisely to solve the given issue.
Continuation of this topic can be found here
Solution 2:
Why don't you make your life easier and use SignalR?
Below you can see a simple example where the server and the clients are console apps
Client (Run this .exe many times)
using System;
using Microsoft.AspNet.SignalR.Client;
namespace SignalRClient
{
class Program
{
private static IHubProxy HubProxy { get; set; }
const string ServerURI = "http://localhost:1234/signalr";
private static HubConnection Connection { get; set; }
static void Main(string[] args)
{
Connection = new HubConnection(ServerURI);
HubProxy = Connection.CreateHubProxy("MyHub");
HubProxy.On<string, string>("SendMessage", (name, message) => Console.WriteLine(name + ":" + message));
Connection.Start().Wait();
Console.WriteLine("Press Enter to stop client");
Console.ReadLine();
}
}
}
Server (Run this .exe before you run the clients)
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Client;
using Microsoft.Owin.Hosting;
using Owin;
namespace SignalRServer
{
class Program
{
static private IDisposable SignalR { get; set; }
const string ServerURI = "http://localhost:1234";
private static IHubProxy HubProxy { get; set; }
private static HubConnection Connection { get; set; }
static void Main(string[] args)
{
SignalR = WebApp.Start(ServerURI);
Console.WriteLine("Server running at " + ServerURI);
Connection = new HubConnection(ServerURI);
HubProxy = Connection.CreateHubProxy("MyHub");
HubProxy.On<string, string>("SendMessage", (name, message) => Console.WriteLine(name + ":" + message));
Connection.Start().Wait();
string messageToSentToClients;
do
{
Console.WriteLine("Type someting to send to clients and press enter");
messageToSentToClients = Console.ReadLine();
HubProxy.Invoke("Send", "Server", messageToSentToClients);
} while (messageToSentToClients != "exit");
}
}
public class MyHub : Hub
{
public void Send(string name, string message) { Clients.All.sendMessage(name, message); }
}
class Startup
{
public void Configuration(IAppBuilder app) { app.MapSignalR(); }
}
}
In order for the above to work you need the following NuGet packages:
Client:
Microsoft.AspNet.SignalR.Client
Server
Microsoft.AspNet.SignalR
Microsoft.AspNet.SignalR.Client
Microsoft.AspNet.SignalR.SelfHost
Microsoft.Owin.Host.HttpListener
If you want the server/clients to be on different machines all you have to do is change the ServeURI properties in both projects:
//Clients
const string ServerURI = "http://SERVER_IP:PORT/signalr";
//Server
const string ServerURI = "http://SERVER_IP:PORT";
You can find another similar example in WinForms here:
https://code.msdn.microsoft.com/windowsdesktop/Using-SignalR-in-WinForms-f1ec847b