Yanshof Yanshof - 3 months ago 22
C# Question

Open two tcp connection using TcpListener class on both side

I want to open tcp connection between two machine.
I want to use the class TcpListener on the client side and on the server side and by this to have the option to make the two side 'talk' with the other by sending and receiving byte[].

That mean that each side is a server and a client.

I using the code from msdn to do it.
But on this code the server start and wait till the client will connect to him.

If i doing so on the both sides i will fail.
Is there any other way ?

The code:

public static void Main()
{
TcpListener server=null;
try
{
// Set the TcpListener on port 13000.
Int32 port = 13000;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");

// TcpListener server = new TcpListener(port);
server = new TcpListener(localAddr, port);

// Start listening for client requests.
server.Start();

// Buffer for reading data
Byte[] bytes = new Byte[256];
String data = null;

// Enter the listening loop.
while(true)
{
Console.Write("Waiting for a connection... ");

// Perform a blocking call to accept requests.
// You could also user server.AcceptSocket() here.
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Connected!");

data = null;

// Get a stream object for reading and writing
NetworkStream stream = client.GetStream();

int i;

// Loop to receive all the data sent by the client.
while((i = stream.Read(bytes, 0, bytes.Length))!=0)
{
// Translate data bytes to a ASCII string.
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine("Received: {0}", data);

// Process the data sent by the client.
data = data.ToUpper();

byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);

// Send back a response.
stream.Write(msg, 0, msg.Length);
Console.WriteLine("Sent: {0}", data);
}

// Shutdown and end connection
client.Close();
}
}
catch(SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
finally
{
// Stop listening for new clients.
server.Stop();
}


Console.WriteLine("\nHit enter to continue...");
Console.Read();
}

Answer

I assume there is some missunderstanding... The TcpListener class is used to open a listener. This represents an endpoint to whom a client can connect (like e.g. a WebServer). To actually connect to such an endpoint you need to use an instance of the TcpClient class.

Following a simple example (written out of my head and NOT TESTED!), also be advised that there is no error handling included and this should just give you a hint where and how to start.

Serverside

// Create a local endpoint (all network interfaces at port 80)
// and create a listener that uses that endpoint.
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 80);
TcpListener listener = new TcpListener(localEndPoint);

// Start the listener.
listener.Start();

// Wait (blocking) until a client connects.
TcpClient client = listener.AcceptTcpClient();

// Stop the listener (so no one else can connect).
listener.Stop();

// Fetch the underlying network stream which
// allows reading and writing data between us and
// the connected client.
NetworkStream ns = client.GetStream();

// Read data from the stream.
byte[] dataBuffer = new byte[8192];
int receivedBytes = ns.Read(dataBuffer, 0, dataBuffer.Length);

// Translate it back to a text by using UTF-8 encoding.
Console.WriteLine($"I have received {receivedBytes} bytes:");
Console.WriteLine(Encoding.UTF8.GetString(dataBuffer, 0, receivedBytes));

// Write an answert to the client.
dataBuffer = Encoding.UTF8.GetBytes("Thank you for your message!");
ns.Write(dataBuffer, 0, dataBuffer.Length);

// Close everything.
ns.Flush();
ns.Close();
client.Close();

Clientside

// Create a remote endpoint (the ip you want to connect to at port 80)
// and create a client that uses that endpoint.
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse("the ip you want to connect to"), 80);
TcpClient client = new TcpClient();

// Try to connect to that endpoint.
client.Connect(remoteEndPoint);

// Fetch the underlying network stream which
// allows reading and writing data between us and
// the connected client.
NetworkStream ns = client.GetStream();

// Write something to the server.
byte[] dataBuffer = Encoding.UTF8.GetBytes("Hello, I am here.");
ns.Write(dataBuffer, 0, dataBuffer.Length);

// Read an answer back from the server.
dataBuffer = new byte[8192];
int receivedBytes = ns.Read(dataBuffer, 0, dataBuffer.Length);

// Translate it back to a text by using UTF-8 encoding.
Console.WriteLine($"I have received an answer with {receivedBytes} bytes:");
Console.WriteLine(Encoding.UTF8.GetString(dataBuffer, 0, receivedBytes));

// Close everything.
ns.Flush();
ns.Close();
client.Close();

The example above obviously does just send one message and closes the application afterwards. If you need to wait until data has arrived, you can use the DataAvailable property of the NetworkStream which indicates whether data is available or not. If not, just sleep and try again later.

Example

bool iWantToReceiveData = true;

while (iWantToReceiveData)
{
    // If no data is available...
    if (!ns.DataAvailable)
    {
        // ...wait some time and try again later.
        Thread.Sleep(100);
        continue;
    }

    // Read an answer back from the server.
    dataBuffer = new byte[8192];
    int receivedBytes = ns.Read(dataBuffer, 0, dataBuffer.Length);

    // Translate it back to a text by using UTF-8 encoding.
    Console.WriteLine($"I have received an answer with {receivedBytes} bytes:");
    Console.WriteLine(Encoding.UTF8.GetString(dataBuffer, 0, receivedBytes));
}

Of course this is some kind of blocking beaviour so you will have to handle that in a separate thread.

I would suggest building an EnhancedNetworkStream class which has a thread running in the background that does the cyclic checking for new data and fires an event once new data has arrived.