简体   繁体   English

C#套接字和多线程

[英]C# Sockets and Multithreading

I am trying to learn more about sockets and threading in c#. 我正在尝试了解有关C#中的套接字和线程的更多信息。 I have come across a lot of good resources online to help get me started. 我在网上遇到了很多很好的资源,可以帮助我入门。 The program I made so far, is a simple "man-in-the-middle" application. 到目前为止,我编写的程序是一个简单的“中间人”应用程序。 It's designed as the following: client <--> [application] <--> server 它的设计如下:客户端<-> [应用程序] <->服务器

Given the following code, how can I prevent this thread from running at 100% CPU? 给定以下代码,如何防止该线程在100%CPU上运行? How can I have the thread wait and block for data and not exit when the client / server is idle? 当客户端/服务器空闲时,如何让线程等待并阻止数据并且不退出?

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);
            }
        }

EDIT: Decided to post the entire "Connection.cs" class for anyone interested. 编辑:决定为有兴趣的人发布整个“ Connection.cs”类。 I'm a beginner programming, so I know there are some bad coding practices here. 我是一名初学者,所以我知道这里有一些不良的编码做法。 Basically this whole class is ran in another thread and should die when the connection (to either the client socket or server socket) drops. 基本上,整个类都在另一个线程中运行,并且(与客户端套接字或服务器套接字的连接)连接断开时,它们应该死亡。

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();
        }
    }       
}

} }

I also have a "Main" form and a "ConnectionManager" class that i'm playing around with. 我也有一个“ Main”窗体和一个“ ConnectionManager”类,我正在玩。

The most efficient way of handling this would be to issue a read with a callback, on each stream. 处理此问题的最有效方法是在每个流上通过回调发出读取。

After issuing both reads, sit waiting forever on an object that you use to signal that the thread should stop its work (a ManualResetEvent is the traditional one to use - can be used to signal many threads at once). 发出两次读取后,请在要用来发出线程应停止其工作信号的对象上永远等待(ManualResetEvent是传统上使用的对象-可用于一次发出多个线程信号)。

When data is received, the OS will call your callback function(s), and you would do your processing in there and then (importantly) queue another read. 收到数据后,操作系统将调用您的回调函数,您将在其中进行处理,然后(重要地)将另一个读取排队。

This means that your thread is forever idle, waiting on a signal object that tells it that it's time to go away (in a "wake up - time to die" kind of way), and is only ever doing work when the OS tells it that there is data to process. 这意味着您的线程永远是空闲的,正在等待一个信号对象,该对象告诉它该离开了(以一种“唤醒-死亡的时间”之类的方式),并且只有在OS告诉它时才做有数据要处理。

To be REALLY friendly, you would also do the writes asynchronously, so that one connection cannot starve the other of processing time (in the current implementation, if one write blocks, the other stream never gets serviced). 为真正友好起见,您还可以异步进行写操作,以使一个连接不能耗尽另一个连接的处理时间(在当前实现中,如果一个写阻塞,则另一个流永远不会得到服务)。

Finally, to be super good, you would encapsulate this behaviour in an object that takes as a parameter the stream to use, and then simply instantiate two of them, instead of having two streams and doing everything twice in the main code. 最后,要获得超好效果,您可以将此行为封装在一个对象中,该对象将要使用的流作为参数,然后简单地实例化它们中的两个,而不是拥有两个流并在主代码中进行两次操作。

After accepting the socket in the middle man I do the following: 在接受中间人的套接字后,我执行以下操作:

 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
        }

    }

Receive State is: 接收状态为:

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
   }

Once data is received my routine "OnDataReceived" processes it. 接收到数据后,我的例程“ OnDataReceived”将对其进行处理。 I'm not experiencing any CPU problems with this. 我没有遇到任何CPU问题。

Same code used for both the client and middleman. 客户端和中间人使用相同的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM