【问题标题】:How to get the full message and not just a part of it via asynchronous connection如何通过异步连接获取完整消息,而不仅仅是其中的一部分
【发布时间】:2012-06-13 12:30:21
【问题描述】:

我已经实现了这个 C# 客户端,它通过异步连接与我用 Java 创建的另一台服务器进行通信。这是客户:

public class NetworkTransaction
{
    private GenericRequest request;
    private const string serverName = "192.168.1.101";
    private const int serverPort = 7777;
    private const int TIMEOUT_MILLISECONDS = 3000;
    private Delegate method;

    public NetworkTransaction(GenericRequest request, Delegate method)
    {
        this.request = request;
        this.method = method;
    }

    public void SendRequest()
    {
        SocketAsyncEventArgs connectionOperation = new SocketAsyncEventArgs();
        DnsEndPoint hostEntry = new DnsEndPoint(serverName, serverPort);
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        connectionOperation.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);
        connectionOperation.RemoteEndPoint = hostEntry;
        connectionOperation.UserToken = socket;

        try
        {
            socket.ConnectAsync(connectionOperation);
        }
        catch (SocketException ex)
        {
            throw new SocketException((int)ex.ErrorCode);
        }
    }

    private void SocketEventArg_Completed(object sender, SocketAsyncEventArgs e)
    {
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Connect:
                ProcessConnectCompleted(e);
                break;
            case SocketAsyncOperation.Receive:
                ProcessReceiveCompleted(e);
                break;
            case SocketAsyncOperation.Send:
                ProcessSendCompleted(e);
                break;
            default:
                throw new Exception("Invalid operation completed");
        }
    }

    private void ProcessConnectCompleted(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(request.ToJson() + "\n\r");
            e.SetBuffer(buffer, 0, buffer.Length);
            Socket sock = e.UserToken as Socket;
            sock.SendAsync(e);
        }
    }

    private void ProcessSendCompleted(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            Socket sock = e.UserToken as Socket;
            sock.ReceiveAsync(e);
        }
    }

    private void ProcessReceiveCompleted(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            string dataFromServer = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
            Socket sock = e.UserToken as Socket;
            sock.Shutdown(SocketShutdown.Send);
            sock.Close();

            method.DynamicInvoke(dataFromServer);
        }
    }
}

如何从服务器获取长消息?为了让客户端尝试读取更多数据以查看我是否真的阅读了整个消息,我需要更改什么?由于该行可能存在问题:string dataFromServer = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred); 仅接收消息的一部分而不是整个消息。

【问题讨论】:

    标签: c# sockets asynchronous client-server socketasynceventargs


    【解决方案1】:

    关于创建协议的答案对我帮助最大。我最终所做的是在消息中附加一个“标题”,说明它的大小。客户端然后解析标头,并根据消息大小标头和实际接收的字节数检查是否还有更多数据要发送。如果还有更多数据要读取,那么我会这样做:

    if (messageSizeRemaining > 0)
    {
          sock.ReceiveAsync(e);
          return;
    }
    

    【讨论】:

      【解决方案2】:

      为了让客户端尝试读取更多数据以查看我是否真的阅读了整个消息,我需要进行哪些更改?

      你必须实现一个协议,上面写着 “这里有 50 个字节”“这都是数据,再见”

      ProcessReceiveCompleted() 将在收到一条数据时被调用,这可以是一个字节到很多字节之间的任何值。套接字不保证将“消息”作为一个整体发送,因为该级别不存在消息。接收的数据量取决于 CPU 和网络利用率等因素。

      这就是你的协议的用武之地。这必须在服务器和客户端中定义。例如,HTTP 使用两个CRLF 让客户端说“这是我的请求,请立即响应”。另一方面,服务器(默认情况下)在发送所有数据时关闭连接,但还会发送一个Content-length 标头,因此客户端可以验证它是否已收到所有数据。

      因此,您必须将数据存储在ProcessReceiveCompleted() 中,并在收到整条消息后开始处理。

      【讨论】:

      • 这听起来合乎逻辑,我可以通过发送 2 条消息来做到这一点,其中第一个将是传入消息的大小,第二个将是消息本身。如果您能确认我对在哪里更改代码的想法,是否有可能?我想认为应该更改 ProcessConnectCompleted 以获得不同大小的缓冲区。但是假设我会这样做,那么我该如何阅读第二条消息?目前我只知道如何阅读第一条到达的消息
      • @YonatanNir 不,你可以这样做,因为你没有定义“消息”并且你不能影响你收到的数据量:这是由网络决定的堆。因此,如果您调用send("5bytes"),然后调用send("Hello"),则无法保证您会收到两个单独的事件。它可能被接收为 ("5bytesHello") 或 ("5b", "ytesH" 和 "ello")。您必须存储收到的所有内容并根据您定义的协议对其进行解析。您可以说“XXXbytes”是定义预期数据量的标题,然后检查文本“字节”并检查数字。
      • 您是否熟悉 SocketAsyncEventArgs 类,并且可能知道检查它是否还有要接收的内容的方法? (即使不知道还有多少要发送)。我的意思是类似于 while(There is more data) buffer += e.getBuffer()
      • @YonatanNir 正如我一直试图解释的那样,套接字不提供该功能;您将不得不描述一组规则(即协议)来确定您的数据如何发送。您必须执行buffer += e.GetBuffer(); 之类的操作,并且在每次接收后调用ProcessBuffer(),在其中检查缓冲区以查看是否有对您有意义的数据。
      • @YonatanNir 但你为什么不使用网络服务? Java 和 .NET 都提供了出色的框架,让您可以真正地交换 消息,而不必费心处理看似难以处理的底层内容。
      【解决方案3】:

      如果您使用网络服务,您可以使用双工通道。 客户端使用以下方式在服务器上注册数据流:

      [OperationContract(IsOneWay = true)]
      void RequestData();
      

      服务器会调用 OneWay 操作将数据发回给客户端,例如:

      [OperationContract(IsOneWay = true)]
      void SendNewData(byte[] data, bool completed)
      

      客户端会将所有数据组合在一起,并在收到完成标志时从服务器注销。

      要了解有关 DuplexChannel 的更多信息,请访问此处:Duplex Services

      【讨论】:

      • 有没有办法在 Java 中做到这一点?我在这里写的代码是客户端的,但服务器本身是用 Java 编写的
      • @YonatanNir:从来没有做过,但我相信应该是可能的:developerzone.genesyslab.com/repository/PSDK/…
      【解决方案4】:

      我已经同步使用了以下方法(我知道你想要异步)从 HTTP 获取数据

          public static Stream getDataFromHttp(string strUrl)
          {
              Stream strmOutput = null;
              try
              {
                  HttpWebRequest request;
                  Uri targetUri = new Uri(strUrl);
                  request = (HttpWebRequest)HttpWebRequest.Create(targetUri);
                  request.Timeout = 5000;
                  request.ReadWriteTimeout = 20000;
                  request.Method = "Get";
      
      
                  request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)";
                  if (request != null)
                  {
                      request.GetResponse();
                      if (request.HaveResponse)
                      {
                          HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                          strmOutput = response.GetResponseStream();
                      }
                  }
      
              }
              catch (WebException wex)
              {
      
              }
              catch (Exception ex)
              {
      
              }
              return strmOutput;
          }
      

      现在要将流转换为文本,请使用以下命令:

          Public Static String StreamToString(Stream stream)
          {
                  StreamReader SR = new StreamReader(stream);
                  try
                  {
                     String strOutput = SR.ReadToEnd();
                     Return strOutput;
                   }
                   catch(Exception ex)
                   {
                        return ex.message
                   }
         }
      

      希望这可行

      【讨论】:

      • 我写的这个客户端是为 wp7 应用程序编写的,所以同步连接是不可能的
      猜你喜欢
      • 2013-09-15
      • 2015-06-10
      • 1970-01-01
      • 1970-01-01
      • 2020-11-29
      • 2017-06-22
      • 2012-09-17
      • 1970-01-01
      • 2011-01-05
      相关资源
      最近更新 更多