Xamarin/WinForms 客户端在双线程架构中发送/接收时无法连接到套接字

Posted

技术标签:

【中文标题】Xamarin/WinForms 客户端在双线程架构中发送/接收时无法连接到套接字【英文标题】:Xamarin/WinForms clients unable to connect to a socket when sending/receiving in a two threaded architecture 【发布时间】:2019-07-25 19:06:54 【问题描述】:

目标

在 UI 程序中的两个不同线程上从 TCP 套接字发送和接收消息。

问题

我无法连接到套接字并发送/接收数据仅当我在 Xamarin.Forms 和 WinForms 中时。所以这是某种线程问题。

当我尝试连接时,它只是超时here,所以没有错误或什么都没有。

我一直在尝试什么

我一直在大量使用Matt Davis solution here on ***。

我在 GitHub 上创建了一个 test code (TcpClientSendReceive),其中包含所需的所有代码。

我的代码

所有内容都在 GitHub 上,地址为 this repository。我不能把它全部放在这里。但这里的 connection code 似乎不适用于例如 Xamarin.Forms see that code here。

如果我尝试从无法连接的 UI 中 connect 到我的控制台服务器(女巫意味着我无法发送/接收)

基线代码有效吗?

是的。至少当我运行两个客户端/服务器程序时,它会在彼此之间发送数据。

可能是什么问题?

绝对是如何设置线程以及它如何与 UI 交互。我只是缺乏足够的线程经验来解决这个问题。我希望你们中的一些人这样做! :-)

编辑:

赏金信息

我创建了一个更多的detailed bounty information page on GitHub,这样我需要什么就完全清楚了。

我添加了 LOTS 图像和代码链接。获取代码并尝试一下应该很容易。 请试一试。我真的不认为这对比我级别低的人来说是个大问题。

【问题讨论】:

你能描述一下错误等吗?“我无法连接”可能意味着一个负载! 是的,抱歉。我更新了问题。它只是超时。这就是我添加超时部分的原因。我已经尝试过此代码的各种版本。但它适用于服务器/客户端控制台应用程序,但不适用于 UI 应用程序。 如果一侧是控制台,另一侧是 xamarin 怎么办? @noelicus 这是我的主要问题和似乎不起作用的设置。我运行 android 模拟器,然后运行 ​​ServiceConsole。详情请看this issue 【参考方案1】:

这个怎么样?

public class HandleClient
    
        private TcpClient clientSocket;
        private static int bufferLength = 1024;
        private byte[] readBuffer = new byte[bufferLength];

        public void StartClient(TcpClient inClientSocket)
        
            this.clientSocket = inClientSocket;

            var ctThread = new Thread(Chat);
            ctThread.Start();
        

        private void Chat()
        
            var reader = clientSocket.GetStream();

            try
            
                while (true)
                
                    var bytesRead = reader.Read(readBuffer, 0, bufferLength);

                    using (var memoryStream = new MemoryStream())
                    
                        memoryStream.Write(readBuffer, 0, bytesRead);
                        var message = System.Text.Encoding.ASCII.GetString(memoryStream.ToArray());

                        Log.Debug("Server got this message from client: message", message);

                        foreach (TcpClient client in Program.GetClients())
                        
                            var writer = new BinaryWriter(client.GetStream());
                            writer.Write($"Server got your message 'message'");
                        
                    
                
            
            catch (EndOfStreamException)
            
                Log.Error($"client disconnecting: clientSocket.Client.RemoteEndPoint");
                clientSocket.Client.Shutdown(SocketShutdown.Both);
            
            catch (IOException e)
            
                Log.Error($"IOException reading from clientSocket.Client.RemoteEndPoint: e.Message");
                Console.WriteLine();
            
            catch (ObjectDisposedException e)
            
                Log.Error(e, "Error (ObjectDisposedException) receiving from server");
            
            catch (Exception e)
            
                Log.Error(e, "Error receiving from server");
            

            clientSocket.Close();
            Program.RemoveClient(clientSocket);
            Log.Debug("count clients connected", Program.GetClientCount());
        
    

如果您的 winform 无法接收消息。 您必须将 OnClient_MainDataReceived 方法更改为

this.Invoke((MethodInvoker)delegate ()
            
                txbResponseFromServer.AppendText(Environment.NewLine);
                txbResponseFromServer.AppendText(e.Data);
            );

【讨论】:

这看起来很有希望。你介意在 GitHub 上做一个 PR 并提供完整的实现,以便我可以试用吗? 我只更改了您的 HandleClient。我用wireshark检查了数据包。现在您的客户端应用程序发送了数据包。但是您的二进制阅读器无法接收数据包。我不知道为什么....我发现很多网站。但我找不到。所以我改变了你的听众。我的英语太差了。所以这个解释是我的极限...... 啊,我明白了。太感谢了!现在服务至少接收到客户端发送的数据,所以问题变成了为什么客户端没有收到服务的响应。我们已经成功了一半! p.s 我用你的代码更新了 GitHub 代码谢谢。 您的客户端无法收到消息?【参考方案2】:

问题是客户端和服务器通信不一致。服务器使用BinaryReaderBinaryWriter,客户端按原样将字节写入/读取到“原始”流。不同之处在于BinaryWriter 不仅在string 本身之前写入string 字节,而且在BinaryReader 之前写入其长度字节,并且BinaryReader 不会从网络读取所有当前可用的字节并将它们转换为string,但它会读取首先读取长度,然后处理读取获得完整 string 所需的确切字节数。

所以最简单的方法是在任何地方使用 BinaryWriterBinaryReader,因为它们会处理读取复杂数据 (string) 字节,并为您编码和解码它们。

虽然实现您自己的通信协议并直接使用NetworkStream 读取/写入字节的方法为您提供了更大的灵活性,但需要额外的努力才能使其正常工作。例如,有一种方法可以从网络中读取多个字节

int Read(byte[] buffer, int offset, int size)

当服务器发送string 时,客户端不知道它应该读取多少字节。它可以创建可以容纳一些合理数量的字节的大缓冲区。但是,如果服务器一个接一个地发送两个字符串,则客户端可以一次将它们都读入缓冲区,并且数据可能已经损坏。此时,您需要实现您的通信协议,以便通过网络发送完整的消息。 Read 方法的另一个警告是,它可能不会一次从网络读取所有当前可用的数据,并且size 值不能保证读取字节的数量。例如你知道服务器发送客户端n字节并且客户端读取它们

stream.Read(buffer, 0, n)

客户端可能会在一次Read 调用中仅读取该字节的一部分,而其余部分仍将在可供读取的网络流上。您应该考虑到这一点并通过在读取需要量时循环读取字节来处理这种情况。

【讨论】:

我为此创建了拉取请求。【参考方案3】:

我没有测试过,但我相信我看到了这种方法的死锁: https://github.com/sturlath/TcpClientSendReceive/blob/a15c936889412e5a4ac249df2b291db817d40307/TcpClientLib/Client.cs#L37

这可能足以修复它:

await Task.Run(() => _client.GetStream()).ConfigureAwait(false);

看起来客户端和服务器正在尝试在 ui 同步上下文中进行连接。这是一个单线程,所以它不会工作。如果它仍然被锁定,您可能还有其他一些地方需要修复。

【讨论】:

以上是关于Xamarin/WinForms 客户端在双线程架构中发送/接收时无法连接到套接字的主要内容,如果未能解决你的问题,请参考以下文章

java播放MP3音乐文件(线程)

java播放MP3音乐文件(线程)

java播放MP3音乐文件(线程)

java之线程

iOS 棋盘游戏架​​构

阿里鬼道Weex在双11会场的大规模应用:业务支撑稳定性保障和秒开实战