Client in C# using Sockets

39 views Asked by At

I have to write a simple client for a chat server, and I want to concurrently check input from the console and from the socket connected to the server. Here is the desired behavior in C, and I want to replicate this behavior in C#:

while (true) {
    //make set of descriptors
    fd_set reads;
    FD_ZERO(&reads);
    FD_SET(client_socket, &reads);  
    FD_SET(0, &reads);
    select(client_socket+1, &reads, NULL, NULL, NULL);

    //handle user input from stdin
    if (FD_ISSET(0, &reads)) {
        ///
    }

    //handle message from server
    if (FD_ISSET(client_socket, &reads)) {
        ///
    }
}

I came up with this solution in C#, but I'm not sure if it is valild or efficient. Are there any other approaches?:

var ongoingTasks = new List<Task> {getServerMsg, getUserMsg};
    
    while (true)
    {
        var finishedTask = await Task.WhenAny(ongoingTasks);
        if (finishedTask == getServerMsg) {
            OutputServerMsg();
            ongoingTasks.Remove(getServerMsg);
            getServerMsg = ReceiveMessageFromServer();
            ongoingTasks.Add(getServerMsg);
        } else if (finishedTask == getUserInput) {
             SendMsgToServer();
             ongoingTasks.Remove(getUserInput);
             getUserInput = ReceiveInputFromStdIn();
             ongoingTasks.Add(getUserInput);
        }
    }
1

There are 1 answers

0
Alander On

Are you looking at synchronous or asynchronous operations? anyway here is the async version, your solution looks too simple

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // Assuming you've connected your clientSocket to a server beforehand, otherwise call the connect as per below
        // clientSocket.Connect(serverEndPoint);

        // Run two tasks: one for listening to user input, another for receiving data from the server
        var receiveTask = ReceiveDataAsync(clientSocket);
        var userInputTask = ReadUserInputAsync(clientSocket);

        await Task.WhenAny(receiveTask, userInputTask);
    }

    static async Task ReceiveDataAsync(Socket clientSocket)
    {
        var buffer = new byte[1024];
        while (true)
        {
            var receiveEventArgs = new SocketAsyncEventArgs();
            receiveEventArgs.SetBuffer(buffer, 0, buffer.Length);

            var receiveTask = new TaskCompletionSource<bool>();
            receiveEventArgs.Completed += (sender, e) => receiveTask.SetResult(true);

            if (!clientSocket.ReceiveAsync(receiveEventArgs))
            {
                // Operation completed synchronously
                receiveTask.SetResult(true);
            }

            await receiveTask.Task; // Wait for data to be received

            var receivedBytes = receiveEventArgs.BytesTransferred;
            if (receivedBytes > 0)
            {
                string receivedText = Encoding.UTF8.GetString(buffer, 0, receivedBytes);
                Console.WriteLine($"Received: {receivedText}");
            }
            else
            {
                // Handle the server disconnecting
                Console.WriteLine("Server has disconnected.");
                break;
            }
        }
    }

    static async Task ReadUserInputAsync(Socket clientSocket)
    {
        while (true)
        {
            string userInput = await Task.Run(() => Console.ReadLine());

            var sendEventArgs = new SocketAsyncEventArgs();
            sendEventArgs.SetBuffer(Encoding.UTF8.GetBytes(userInput));

            var sendTask = new TaskCompletionSource<bool>();
            sendEventArgs.Completed += (sender, e) => sendTask.SetResult(true);

            if (!clientSocket.SendAsync(sendEventArgs))
            {
                // Operation completed synchronously
                sendTask.SetResult(true);
            }

            await sendTask.Task; // Wait for the data to be sent

            Console.WriteLine("Sent user input to server.");
        }
    }
}

ReceiveDataAsync listens for data from the server. It uses Socket.ReceiveAsync for receiving data from the server asynchronously.

ReadUserInputAsync waits for user input in the console using Task.Run to run synchronously blocking operations (Console.ReadLine()) on a separate thread, enabling the main thread to remain responsive.

Task.WhenAny is used to await either of the tasks to complete. This approach keeps the UI responsive (if there's one) and doesn't block the main thread.