【问题标题】:C# Sockets and MultithreadingC# 套接字和多线程
【发布时间】:2012-09-07 04:41:07
【问题描述】:

我正在尝试更多地了解 c# 中的套接字和线程。 我在网上找到了很多很好的资源来帮助我入门。 到目前为止,我制作的程序是一个简单的“中间人”应用程序。 它的设计如下:客户端 [应用程序] 服务器

鉴于以下代码,如何防止该线程以 100% CPU 运行? 当客户端/服务器空闲时,如何让线程等待并阻塞数据而不退出?

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”类,我正在玩弄。

【问题讨论】:

  • 你能发布更多代码吗?您的中间人是否接受多个客户?您是否使用 BeginAccept 进行连接?我还认为您可以在检查数据是否可用之前在套接字上执行 BeginReceive - 它会等到有一些数据后再继续。目前,我正在做与您和我的中间人相同的项目,几乎不使用 CPU。愿意分享代码/想法吗?

标签: c# multithreading sockets tcpclient


【解决方案1】:

处理此问题的最有效方法是在每个流上发出带有回调的读取。

发出两次读取后,在一个对象上永远等待,您可以用它来表示线程应该停止其工作(ManualResetEvent 是传统的使用方法 - 可用于一次向多个线程发出信号)。

收到数据后,操作系统将调用您的回调函数,您将在其中进行处理,然后(重要地)将另一次读取排队。

这意味着你的线程永远处于空闲状态,等待一个信号对象告诉它是时候离开了(以“唤醒 - 死亡时间”的方式),并且只有在操作系统告诉它有数据要处理。

为了真正友好,您还可以异步执行写入,这样一个连接就不会占用另一个连接的处理时间(在当前实现中,如果一个写入阻塞,另一个流永远不会得到服务)。

最后,为了超级好,您可以将此行为封装在一个对象中,该对象将要使用的流作为参数,然后简单地实例化其中的两个,而不是拥有两个流并在主代码中两次执行所有操作。

【讨论】:

  • 感谢您的宝贵建议!正如你所说,我必须进行一些修改以使这段代码更好。
  • 将代码重新设计为完全异步,并删除各种循环(注意,在我描述的机制中,没有循环:操作系统调用数据,因此您发出异步写入然后另一个异步读取)可能导致不必要的 CPU 使用,这可能会让人头疼。但这几乎总是完全值得的。
【解决方案2】:

接受中间人的套接字后,我执行以下操作:

 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”就会对其进行处理。我没有遇到任何 CPU 问题。

客户端和中间人使用相同的代码。

【讨论】:

  • 谢谢!我读过关于 AsyncCallback 的文章,但对此或事件没有太多经验。我会试一试,然后发回我的结果。
猜你喜欢
  • 2016-03-16
  • 1970-01-01
  • 2011-02-11
  • 2013-10-14
  • 1970-01-01
  • 2013-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多