-1

I created a Windows Form application that can connect to another one on the same PC but it won't work on the LAN , I used sockets but I didn't fully understood it and I am not that great at C#.

I have the following code for the server:

private byte[] _buffer = new byte[4096];
public List<SocketT2h> __ClientSockets { get; set; }
List<string> _names = new List<string>();
private Socket _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

public Main()
{
    InitializeComponent();
    CheckForIllegalCrossThreadCalls = false;
    __ClientSockets = new List<SocketT2h>();
}

private void Main_Load(object sender, EventArgs e)
{
    contextMenuStrip1.ItemClicked += new ToolStripItemClickedEventHandler(contextMenuStrip1_ItemClicked);
    SetupServer();
}

private void SetupServer()
{
    Console_textBox.Text += "Setting up server . . .\n";
    _serverSocket.Bind(new IPEndPoint(IPAddress.Any, 100));
    _serverSocket.Listen(1);
    _serverSocket.BeginAccept(new AsyncCallback(AppceptCallback), null);
    Console_textBox.Text += "Server is online !";
}

private void AppceptCallback(IAsyncResult ar)
{
    Socket socket = _serverSocket.EndAccept(ar);
    __ClientSockets.Add(new SocketT2h(socket));

    Console_textBox.Text = "Client connected. . .";
    socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket);
    _serverSocket.BeginAccept(new AsyncCallback(AppceptCallback), null);
}

And this is the code for the client:

public Form1()
{
    InitializeComponent();

    LoopConnect();
    _clientSocket.BeginReceive(receivedBuf, 0, receivedBuf.Length, SocketFlags.None, new AsyncCallback(ReceiveData), _clientSocket);
    byte[] buffer = Encoding.ASCII.GetBytes("Alex : ");
    _clientSocket.Send(buffer);
}

private Socket _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

byte[] receivedBuf = new byte[4096];

private void ReceiveData(IAsyncResult ar)
{
    try
    {
        Socket socket = (Socket)ar.AsyncState;
        int received = socket.EndReceive(ar);
        byte[] dataBuf = new byte[received];
        Array.Copy(receivedBuf, dataBuf, received);
        _clientSocket.BeginReceive(receivedBuf, 0, receivedBuf.Length, SocketFlags.None, new AsyncCallback(ReceiveData), _clientSocket);
    }
    catch { }
}

private void SendLoop()
{
    while (true)
    {
        try
        {
            byte[] receivedBuf = new byte[4096];
            int rev = _clientSocket.Receive(receivedBuf);
            if (rev != 0)
            {
                byte[] data = new byte[rev];
                Array.Copy(receivedBuf, data, rev);
            }
            else _clientSocket.Close();
        }
        catch { }

    }
}

private void LoopConnect()
{
    try
    {
        int attempts = 0;
        while (!_clientSocket.Connected)
        {
            try
            {
                attempts++;
                _clientSocket.Connect(IPAddress.Loopback, 100);
            }
            catch (SocketException)
            {
            }
        }
    }
    catch { }
}

I also added the ports used by the Apps to the FireWall.
If I'm doing it completely wrong, I'm sorry, but I'm still learning.

zx485
  • 28,498
  • 28
  • 50
  • 59
ReD
  • 13
  • 4
  • `CheckForIllegalCrossThreadCalls = false;` big red flags go up whenever I see that in a Winforms app... I honestly don't know why MS included the ability to do that. Either way, you should not have tight/closed loops in Winforms applications, you need to use a background worker, tasks, threads, or some other kind of async operations. – Ron Beyer Jan 02 '20 at 21:43
  • When i open the server and then the client it gives me this error: "System.InvalidOperationException: 'Cross-thread operation not valid: Control 'Console_textBox' accessed from a thread other than the thread it was created on.'" – ReD Jan 02 '20 at 21:53
  • Right, you need to set the controls `.Text` property only on the UI thread. You also shouldn't be using `socket.BeginReceive` before `socket.BeginAccept` on the server side. You only call the receive methods when you have an established socket connection. – Ron Beyer Jan 02 '20 at 21:54
  • How can I set the controls .Text property only on the UI thread ? – ReD Jan 02 '20 at 22:05
  • See this question: https://stackoverflow.com/questions/661561/how-do-i-update-the-gui-from-another-thread – Ron Beyer Jan 02 '20 at 22:10
  • Still doesn't work , and now i have realised that when i open my client and my server is offline the form won't appear . – ReD Jan 02 '20 at 22:29
  • You can't use loops in a Windows form. – Ron Beyer Jan 02 '20 at 22:48
  • Should I replace this loop as well `while (!_clientSocket.Connected)` ? – ReD Jan 02 '20 at 23:07

1 Answers1

0

You're probably going about this in a difficult to get working/reliable way. To read/write continually you need loops or callbacks, to have loops and a working UI you need threads, you're using IOCP callbacks which end up running on different threads. Different threads can't update the UI so you need CheckForIllegalCrossThreadCalls = false; which is bad, so you next need some other syncronisation and a way to keep it all together and have a responsive UI.

Given you said that you didn't fully understand it and are new to C# I will give you an alternative idea using the ASYNC/AWAIT pattern and Tasks. This allows you to seemingly have loops, but while the loops are waiting for network input/output they aren;t actually looping so your UI is responsive.

Firstly we create a server from your GUI:

    private async Task StartServerAsync()
    {
        var tcpListener = new TcpListener(new IPEndPoint(IPAddress.Any, 9999));
        tcpListener.Start();

        btnStartServer.Enabled = false;
        lbConnectedClients.Items.Add("TcpListener has started listening for clients.");

        while (true)
        {
            var tcpClient = await tcpListener.AcceptTcpClientAsync();

            _ = StartServerSideClient(tcpClient);
        }
    }
    private async void btnStartServer_Click(object sender, EventArgs e)
    {
        await StartServerAsync();
    }

So in your GUI clicking the Button btnStartServer adds an entry to the ListBox lbConnectedClients telling us that the server has started. We also stop the user from being able to start the server more than once by disabing the bthStartServer buttong

After the user has clicked btnStartServer the socket is now sitting there waiting for connections inside the while (true) loop. Because we're using a full async/await pattern, the loop isn't actually looping, it's sitting in the background waiting for a client to connect doing absolutely nothing. So your UI can continue doing what it does best.

Next we have another button and another listbox on UI. The second button starts a client.

    private async Task StartEchoClientAsync()
    {
        using (var tcpClient = new TcpClient("127.0.0.1", 9999))
        {

            lbEchoClients.Items.Add("TcpClient: " + tcpClient.Client.LocalEndPoint + "created.");
            var s = tcpClient.GetStream();

            var buffer = new byte[1000];

            for(var i = 0; i < 10; i++)
            {
                await Task.Delay(1000);
                await s.WriteAsync(buffer, 0, buffer.Length);
                lbEchoClients.Items.Add("Sent Bytes: " + buffer.Length);
                await s.ReadAsync(buffer, 0, buffer.Length);
                lbEchoClients.Items.Add("Read Bytes: " + buffer.Length);
            }
            tcpClient.Close();
        }
    }

    private async void btnStartAClient_Click(object sender, EventArgs e)
    {
        await StartEchoClientAsync();
    }

Now in this example all the client does is sends data to the server and reads the data back. Again there is a loop, again that loop is spending most of its time waiting 1 second with the await Task.Delay(1000) this isn't like a Thread.Sleep(1000) it lets your UI just get on with doing what it needs to do while the Send/Read loop is doing absolutely nothing.

Finally we need a "client" on the server side. This client is responsible for handling each connection to the Server. Remember once a socket starts listening it can receive as many connections as memory permits.

    private async Task StartServerSideClient(TcpClient tcpClient)
    {
        lbConnectedClients.Items.Add("Client: " + tcpClient.Client.RemoteEndPoint.ToString() + " Connected");

        var s = tcpClient.GetStream();
        var buffer = new byte[1000];

        int bytesRead = 0;
        int bytesWrote = 0;

        while (true)
        {
            int rt = await s.ReadAsync(buffer, 0, buffer.Length);
            if (rt == 0) // If ReadAsync() returns with zero then the underlying stream/socket is closed
                break;
            bytesRead += buffer.Length;
            var wt = s.WriteAsync(buffer, 0, buffer.Length);
            bytesWrote += buffer.Length;
        }

        lbConnectedClients.Items.Add("Client: " + tcpClient.Client.RemoteEndPoint.ToString() + " Wrote: " + bytesWrote + " Read: " + bytesRead);
        lbConnectedClients.Items.Add("Client: " + tcpClient.Client.RemoteEndPoint.ToString() + " disconnected");

        s.Close();

    }

So now everytime a client connects we simply read anything it gives us, and just send it straight back. Again there is another loop, but again because we are using async/await to do the reading/writing it doesn't block your UI.

Everything above is running on the UI thread but because it's using the async/await pattern it never blocks the UI.

In the real world it's unlikely that your server and client would be on the same GUI.

Rowan Smith
  • 1,815
  • 15
  • 29
  • This way doesen't work as well . – ReD Jan 03 '20 at 09:43
  • This definitely works. What problem are you having with it? – Rowan Smith Jan 03 '20 at 11:29
  • I can connect to the server if the client is on the same PC but if it's on other PC it won't work . – ReD Jan 03 '20 at 11:35
  • What error message do you receiving when you call ``var tcpClient = new TcpClient(YOUR IP ADDRESS, YOUR PORT)`` ? Have you checked local firewall settings? – Rowan Smith Jan 03 '20 at 11:53
  • I get this error "System.Net.Sockets.SocketException (0x80004005): No connection could be made because the target machine actively refused it 127.0.0.1:9999 at System.Net.Sockets.TcpClient..ctor(String hostname, Int32 port) at ClientServerSocket.Form1.d__1.MoveNext() in C:\Users\User\source\repos\ClientServerSocket\ClientServerSocket\Form1.cs:line 28" . – ReD Jan 03 '20 at 12:25
  • I will restart my router, check again the FireWall and the anti-virus software . – ReD Jan 03 '20 at 12:27
  • Nope , still nothing . – ReD Jan 03 '20 at 12:47
  • You have not changed the IP Address. You need to specify the IPAddress of the Server for the client. 127.0.0.1 is the LOOPBACK address. You need to change that IPAddress to be the IPAddress of the server. – Rowan Smith Jan 03 '20 at 12:56
  • I have changed the IPAddress of the client to mach the address of the server and still doesn't work – ReD Jan 03 '20 at 13:34
  • What error message are you seeing on the client now? – Rowan Smith Jan 03 '20 at 18:33
  • I managed to do it , but now I am facing another .I want to sent a string message from the server to a client and I'm not that sure how to do it . – ReD Jan 04 '20 at 17:52
  • another problem* – ReD Jan 04 '20 at 18:09