C# сокеты и многопоточность

Я пытаюсь узнать больше о сокетах и ​​потоках в C#. Я нашел много хороших ресурсов онлайн, чтобы помочь мне начать. Программа, которую я сделал до сих пор, представляет собой простое приложение "человек посередине". Он разработан следующим образом: клиент <-> [приложение] <-> сервер

Учитывая следующий код, как я могу предотвратить запуск этого потока на 100% ЦП? Как я могу заставить поток ждать и блокировать данные и не выходить, когда клиент / сервер простаивает?

while (true)
       {
            lock (ClientState)
            {
                checkConnectionStatus(client, server);
            }

            if (clientStream.CanRead && clientStream.DataAvailable)
            {
                Byte[] bytes = new Byte[(client.ReceiveBufferSize)];

                IAsyncResult result = clientStream.BeginRead(bytes, 0, client.ReceiveBufferSize, null, null);
                int size = clientStream.EndRead(result);

                sendData(bytes, serverStream, size);
            }
            if (serverStream.CanRead && serverStream.DataAvailable)
            {
                Byte[] bytes = new byte[(server.ReceiveBufferSize)];

                IAsyncResult result = serverStream.BeginRead(bytes, 0, server.ReceiveBufferSize, null, null);
                int size = serverStream.EndRead(result);

                sendData(bytes, clientStream, size);
            }
        }

РЕДАКТИРОВАТЬ: решил опубликовать весь класс "Connection.cs" для всех, кто заинтересован. Я начинающий программист, так что я знаю, что здесь есть некоторые плохие практики кодирования. По сути, весь этот класс запускается в другом потоке и должен прерваться, когда соединение (либо с сокетом клиента, либо с сокетом сервера) прервется.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace TCPRelay
{
public class Connection
{
    public delegate void delThreadSafeHandleException(System.Exception ex);
    public delegate void ConnectionDelegate(Connection conn);
    public int DataGridIndex;

    Main pMain;

    public TcpClient client { get; set; }
    public TcpClient server { get; set; }
    public String ClientState { get; set; }

    public string ListenPort { get; set; }
    public string remotePort { get; set; }

    public string listenAddress { get; set; }
    public string remoteAddress { get; set; }

    private TcpListener service { get; set; }

    private Main Form
    {
        get
        {
            return pMain;
        }
    }
    private NetworkStream clientStream { get; set; }
    private NetworkStream serverStream { get; set; }


    public Connection(TcpClient client, TcpClient server)
    {
        clientStream = client.GetStream();
        serverStream = server.GetStream();
    }

    public Connection(String srcAddress, int srcPort, String dstAddress, int dstPort, Main caller)
    {
        try
        {
            pMain = caller;
            TcpListener _service = new TcpListener((IPAddress.Parse(srcAddress)), srcPort);

            //Start the client service and add to connection property
            _service.Start();
            service = _service;


            //Set other useful parameters
            listenAddress = srcAddress;
            ListenPort = srcPort.ToString();

            remoteAddress = dstAddress;
            remotePort = dstPort.ToString();

            this.ClientState = "Listening";
        }
        catch (Exception ex)
        {
            pMain.HandleException(ex);
            Thread.CurrentThread.Abort();
        }

    }

    private TcpClient getServerConnection(String address, int port)
    {
        TcpClient client = new TcpClient(address, port);
        if (client.Connected)
        {
            return client;
        }
        else
        {
            throw new Exception(
                String.Format("Unable to connect to {0} on port {0}",
                address,
                port)
                );
        }
    }
    private void sendData(Byte[] databuf, NetworkStream stream, int size)
    {
        bool waiting = true;
        while (waiting)
        {
            if (stream.CanWrite)
            {
                waiting = false;
                stream.Write(databuf, 0, size);
            }
            else { throw new Exception("Unable to write to network stream"); }
        }
    }

    //Main Looping and data processing goes here
    public void ProcessClientRequest()
    {
        try
        {
            //Wait for a connection to the client
            TcpClient client = service.AcceptTcpClient();

            //Get the streams and set the peer endpoints
            this.clientStream = client.GetStream();
            this.client = client;

            //Now that we have a client, lets connect to our server endpoint
            TcpClient server = getServerConnection(remoteAddress, int.Parse(remotePort));

            //Set some useful parameters
            this.server = server;
            this.serverStream = server.GetStream();
        }
        catch (Exception ex)
        {
            lock (ClientState)
            {
                this.ClientState = ex.Message;    
            }

            CloseConnection();
            Thread.CurrentThread.Abort();
        }
        while (true)
       {
            lock (ClientState)
            {
                checkConnectionStatus(client, server);
            }

            if (clientStream.CanRead && clientStream.DataAvailable)
            {
                Byte[] bytes = new Byte[(client.ReceiveBufferSize)];

                IAsyncResult result = clientStream.BeginRead(bytes, 0, client.ReceiveBufferSize, null, null);
                int size = clientStream.EndRead(result);

                sendData(bytes, serverStream, size);
            }
            if (serverStream.CanRead && serverStream.DataAvailable)
            {
                Byte[] bytes = new byte[(server.ReceiveBufferSize)];

                IAsyncResult result = serverStream.BeginRead(bytes, 0, server.ReceiveBufferSize, null, null);
                int size = serverStream.EndRead(result);

                sendData(bytes, clientStream, size);
            }
        }
    }

    private void checkConnectionStatus(TcpClient _client, TcpClient _server)
    {
        try
        {
            if (_client.Client.Poll(0, SelectMode.SelectRead))
            {
                byte[] buff = new byte[1];
                if (_client.Client.Receive(buff, SocketFlags.Peek) == 0)
                {
                    this.ClientState = "Closed";
                    CloseConnection();
                    Thread.CurrentThread.Abort();
                }
            }
            else if (_server.Client.Poll(0, SelectMode.SelectRead))
            {
                byte[] buff = new byte[1];
                if (_server.Client.Receive(buff, SocketFlags.Peek) == 0)
                {
                    this.ClientState = "Closed";
                    CloseConnection();
                    Thread.CurrentThread.Abort();
                }
            }
            else { this.ClientState = "Connected"; }
        }
        catch (System.Net.Sockets.SocketException ex)
        {
            this.ClientState = ex.SocketErrorCode.ToString();
            CloseConnection();
            Thread.CurrentThread.Abort();
        }
    }

    public void CloseConnection()
    {
        if (clientStream != null)
        {
            clientStream.Close();
            clientStream.Dispose();
        }

        if (client != null)
        {
            client.Close();
        }

        if (serverStream != null)
        {
            serverStream.Close();
            serverStream.Dispose();
        }

        if (server != null)
        {
            server.Close();
        }

        if (service != null)
        {
            service.Stop();
        }
    }       
}

}

У меня также есть форма "Main" и класс "ConnectionManager", с которым я играю.

2 ответа

Наиболее эффективный способ справиться с этим - выдать чтение с обратным вызовом для каждого потока.

После выполнения обоих операций чтения, ждите вечно с объектом, который вы используете, чтобы сигнализировать о том, что поток должен прекратить свою работу (традиционное использование ManualResetEvent - может использоваться для одновременной сигнализации многих потоков).

Когда данные будут получены, ОС вызовет ваши функции обратного вызова, и вы будете выполнять их обработку, а затем (что важно) поставить в очередь очередное чтение.

Это означает, что ваш поток всегда бездействует, ожидая объекта сигнала, который сообщает ему, что пора уходить (в виде "пробуждения - время умирать"), и выполняет работу только тогда, когда ОС сообщает об этом. что есть данные для обработки.

Чтобы быть ДЕЙСТВИТЕЛЬНО дружественным, вы также должны выполнять записи асинхронно, чтобы одно соединение не могло истощить другое время обработки (в текущей реализации, если одна запись блокируется, другой поток никогда не обслуживается).

Наконец, чтобы быть супер хорошими, вы должны инкапсулировать это поведение в объекте, который принимает в качестве параметра используемый поток, а затем просто создать два из них, вместо того, чтобы иметь два потока и делать все дважды в основном коде.

После принятия розетки в середине человек я делаю следующее:

 private void WaitForData()
    {
        try
        {
            if (socketReadCallBack == null)
            {
                socketReadCallBack = new AsyncCallback(OnDataReceived);
            }

            ReceiveState rState = new ReceiveState();
            rState.Client = mySocket;

            mySocket.BeginReceive(rState.Buffer, 0, rState.Buffer.Length, SocketFlags.None,
                new AsyncCallback(socketReadCallBack), rState);

        }
        catch (SocketException excpt)
        {
            // Process Exception
        }

    }

Состояние получения:

public class ReceiveState
  {
    public byte[] Buffer = new byte[1024]; //buffer for network i/o
    public int DataSize = 0; //data size to be received by the server
    public bool DataSizeReceived = false; //whether prefix was received
    public MemoryStream Data = new MemoryStream(); //place where data is stored
    public Socket Client;   //client socket
   }

Как только данные получены, моя подпрограмма "OnDataReceived" обрабатывает их. Я не испытываю никаких проблем с процессором с этим.

Один и тот же код используется как для клиента, так и для посредника.

Другие вопросы по тегам