C#定时器接收定时发送和处理接收socket异步通信,接收值放在静态变量里,有时候收到的数据不完整。

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#定时器接收定时发送和处理接收socket异步通信,接收值放在静态变量里,有时候收到的数据不完整。相关的知识,希望对你有一定的参考价值。

一、通讯框架类图

二、框架说明
上图是通讯框架静态类图,其抽象模型是:服务器在指定的IP和端口上进行监听,当收到一个连接请求时就会创建一个连接,然后把这个连接交给一个执行器执行处理该连接,一个连接包含一个或多个会话,每个会话在一个线程上执行,不同的会话间互相不影响,只要客户端不主动关闭连接,服务器就可以在同一连接上处理多个会话。
XServiceHost是服务主机,即监听者,它负责在指定的IP和端口上监听来自客户端的请求,它运行在一个单独的线程中;当收到来自客户端的请求时就调用XconnectionCreateor的create接口创建一个连接(XConnection的派生类,不同的服务派生不同),然后把该connection对象添加到Xconnectionmanager中,xconnectionmanager负责创建一个执行器对象即XExecuter去执行请求,XExecuter对象是一个单独的线程;XServiceHost继续监听接收来自其它客户端的请求,如此往复持续提供服务。
所有的应用服务只需派生XConnectionCreator和XConnection即可,前者负责创建具体的业务connection对象,如果逻辑在XConnection的派生类中处理较为复杂,则在connection中创建不同的XRequest派生类对象去执行具体的业务逻辑。
XHostContext是对服务框架所运行的主机对象的抽象,目前提供了一个退出方法即Exit,该接口在XServiceHost调用监听失败时调用,通知主机退出监听,结束服务。
2.1、XServiceHost类
1、构造函数
XServiceHost(XHostContext context,XConnectionCreator connectioncreator, string ip, int port)
说明:
Context 入参表示的是调用此方法的上下文,即XServiceHost对象的所有者;
Connectioncreator XConnectionCreator类的对象
Ip 表示要监听的IP
Port 表示要监听的端口;

2、Start() 函数功能是启动对应的监听线程;
3、Stop() 函数功能是停止对应的监听线程;
4、Listen() 表示此监听线程开始执行时要调用的方法;
此函数会调用C#封装好的Plasterer类来进行对指定端口的监听;使用此方法避免直接调用Socket底层的方法,简化代码,降低复杂度;需要使用到的函数AcceptSocket(),使用此函数接受挂起的连接请求;

2.2、XConnectionCreator接口
XConnectionCreator提供对连接创建的一个约束,不同的服务可以实现不同功能,从而实现封装和抽象,可以不改变XServiceHost的情况下进行良好的运行;
XConnection Create(Socket socket,string clientip)方法
声明创建连接的约定,具体的服务可以继承此接口进行相应功能的开发;并返回对应的连接实例对象;

2.3、XConnection类
XConnection类为对连接的一个抽象,为基类,具体的服务继承此基类进行对应的操作;
1、XConnection(Socket socket, string clientip) 构造函数;
2、virtual void Process(Command cmd, MemoryStream packet)为抽象函数;具体的子类进行实现;cmd是命令的枚举对象;packet是对应需要操作的数据;
3、public void Execute() 此函数会生成一个XSession类的对象,用来异步的获取各个系统的Socket连接;获取完数据后将数据传送到对应的XConnection实例对象,调用Process(Command cmd, MemoryStream packet)方法对消息进行相应的处理;

2.4、XconnectionManager类
XconnectionManager是一个使用单例模式设计的连接管理者,
Add(XConnection connection)方法使用XExecuter类的实例对象为每个连接生成一个执行器,该执行器会单独起一个线程对对应的连接进行处理;

2.5、XExecuter类
XExecuter用来执行请求,XExecuter对象是一个单独的线程;此类的对象会维护一个List<XConnection>队列;
1、public void Accept(XConnection con)方法该方法对外提供为该队列增加连接的功能;
2、拥有的独立线程会挂起,直到队列中出现连接,此时会唤起该线程进行该线程,并调用获取的连接对象的Execute()方法进行处理;

2.6、XSession类
XSession类使用Socket类的BeginReceive和EndReceive方法异步获取连接发送的数据,获取数据后使用XConnection类的Parse方法将数据传回连接对象,从而对数据进行相应的处理;

2.7、XRequest类
XRequest类主要负责不同消息的处理,此为一个基类,具体的响应需要子类完成,来负责收到不同消息的处理;

三、获取数据后的界面处理
对应界面各个模块来说,都相当于是一个单例,所以可以使用单例模式的方式对各个模块的控件进行管理,如Beam模块,可以抽象一个管理者来负责对其的控制;此单例负责所有针对此模块的逻辑;
目前有两种方案来进行界面响应:AsyncOperation和BackgroundWorker

3.1 AsyncOperation
AsyncOperation类通过回调函数可以将子线程处理完的数据传到UI线程,从而进行对应的UI操作;可以使用Post 函数来切换子线程到UI线程

具体实现:
1、当具体的消息传到具体的XRequest子类的实体对象后,XRequest子类对象可以调用对应消息的单例,在对应的单例里声明了对应的委托,并进行声明如:

public delegate void PushMessage(object msg);
PushMessagem_RouteMapSaved;
2、并且会提供具体的注册和取消注册的函数,在界面生成的时候就会调用注册函数进行注册,具体的注册和取消注册的函数如下:

public void BeamChangedMsg(PushMessage callback)

m_BeamChanged += callback;
if (null == m_Messager)

m_Messager = AsyncOperationManager.CreateOperation(null);


public void UnBeamChangedMsg(PushMessage callback)

m_BeamChanged -= callback;

注:m_BeamChanged为声明的PushMessage 委托对象;m_Messager为AsyncOperation类的对象。

1、进行完上述的步骤之后,就可以再具体的位置调用AsyncOperation类的 Post函数进行线程上下文切换,进入到UI线程进行对应的界面刷新,如:
m_Messager.Post(newSendOrPostCallback(m_BeamChanged), parameters);
2、当退出界面的时候可以调用UnBeamChangedMsg函数进行取消注册,避免发生异常;

3.2、BackgroundWorker类
BackgroundWorker类其实是对AsyncOperation类的使用的一次封装,更加简洁方便的使用异步回调的方式进行多线程编程,主要涉及到三个事件:
public event DoWorkEventHandlerDoWork; //实现开辟子线程完成耗时操作
public event ProgressChangedEventHandlerProgressChanged;//实现返回操作进度或自定义参数
public event RunWorkerCompletedEventHandlerRunWorkerCompleted;//完成后返回数据的处理;

需要注意的是DoWork事件是在子线程完成,具体函数不能包含对UI控件的操作;
具体的UI操作可以放入到ProgressChanged和RunWorkerCompleted事件中完成。

具体实现:
与使用AsyncOperation基本一致:
首先在对应的单例中声明BackgroundWorker,在创建UI的时候对BackgroundWorker对象进行各个事件的注册,最后使用RunWorkerAsync方法进行调用;

四、通信工具
通信工具类
该工具类提供网络通信的两个接口,一个是只发送请求的SendRequest函数,另一个是先发送请求然后等待服务端返回结果的GetResponse函数;SendRequestByLongConnection用于建立长连接的通讯,ReleaseConnection函数用来释放连接;
参考技术A

byte[] recebuffer = new byte[40]; //接收缓冲区,缓冲区大小自己设置

数据不完整必须要清缓冲区,或者采用滤波方式做,过滤掉字符长度小于多少的字符串

TCP Socket在C#中接收数据错误

【中文标题】TCP Socket在C#中接收数据错误【英文标题】:TCP Socket recieves data wrong in C# 【发布时间】:2018-09-17 23:42:13 【问题描述】:

我正在尝试将文件从一个 Socket 发送到另一个 Socket。套接字在两个不同的应用程序中运行,一个客户端应用程序和一个服务器应用程序。现在在同一台机器上进行测试时正在发生这种情况。当客户端准备好接收有关将发送给它的文件的第一个信息时,客户端首先发送到服务器,例如 2056 字节消息中的文件名和文件大小(以字节为单位)。然后它在每次准备好接收新的 2048 文件内容缓冲区时发送一条消息。

简而言之,这是每个文件的通信流程:

client -"CLIENT_READY_TO_RECEIVE_FILE_INFO"-------> server
client <-"File name+file size of file coming next"-  server
client -"CLIENT_READY_TO_RECEIVE_CONTENT_BUFFER" -> server
client <-"2048 byte buffer of file content"-------- server
...the same flow is repeated until all files are sent to the client.

我的问题是客户端收到错误的文件信息消息,尽管我已经仔细检查了服务器是否正确发送了它。它们都使用 ASCII 编码。这是我的代码:

客户端代码(接收文件):

private void fetchFilesThreadMethod()
    
        

        String localHostName = Dns.GetHostName();
        IPAddress[] localIPs = Dns.GetHostAddresses(localHostName);
        IPAddress localIP = localIPs[2];
        int port = 1305;
        IPEndPoint localEP = new IPEndPoint(localIP,port);
        fetchFilesSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
        fetchFilesSocket.Bind(localEP);
        fetchFilesSocket.Listen(1);
        fetchFilesSocket = fetchFilesSocket.Accept();

        while (true)
        
            byte[] receivedFileInfo = new byte[2056];
            byte[] receivedFileCont = new byte[2048];
            byte[] comMessage = new byte[100];
            byte[] comMessageBytes;
            String fileInfoStr = String.Empty;
            String fileNameStr = String.Empty; ;
            String fileExtStr = String.Empty;
            String comMessageStr = String.Empty;
            String fileSizeStr = String.Empty;
            ulong fileSize = 0;
            ulong lastBytesSize = 0;

            comMessageStr = "CLIENT_READY_TO_RECEIVE_FILE_INFO";
            comMessageBytes = Encoding.ASCII.GetBytes(comMessageStr);
            for (int i = 0; i < comMessageBytes.Length; i++)
                comMessage[i] = comMessageBytes[i];
            Console.WriteLine("Bytes available to be flushed by client: " + fetchFilesSocket.Available);
            if (fetchFilesSocket.Available > 0)
            
                Console.WriteLine(fetchFilesSocket.Available + " bytes flushed by client.");
                byte[] flusher = new byte[fetchFilesSocket.Available];
                fetchFilesSocket.Receive(flusher, 0, fetchFilesSocket.Available, SocketFlags.None);
            
            fetchFilesSocket.Send(comMessage, 0, 100, SocketFlags.None);
            Console.WriteLine("Client sent ready to receive file info.");
            fetchFilesSocket.Receive(receivedFileInfo,0,2056, SocketFlags.None);
            fileInfoStr = Encoding.ASCII.GetString(receivedFileInfo);
            Console.WriteLine("Received file info:" + fileInfoStr);
            fileNameStr = fileInfoStr.Split(new String[]  "ENDN" , StringSplitOptions.None)[0];
            Console.WriteLine("Received file name:" + fileNameStr);
            fileExtStr = fileNameStr.Split('.').Last();
            Console.WriteLine("Received file extension:" + fileExtStr);
            fileSizeStr = fileInfoStr.Split(new String[] "ENDS",StringSplitOptions.None)[0];
            fileSizeStr = fileSizeStr.Split(new String[] "ENDN",StringSplitOptions.None).Last();
            Console.WriteLine("File size string:" + fileSizeStr);
            fileSize = Convert.ToUInt64(fileSizeStr,10);
            Console.WriteLine("Received file size:" + fileSize);
            lastBytesSize = fileSize % 2048;
            ulong byteCount = 0;
            bool keepReceiving = true;
            ulong buffersReceived = 0;
            while (keepReceiving)
            
                comMessageStr = "CLIENT_READY_TO_RECEIVE_CONTENT_BUFFER";
                comMessageBytes = Encoding.ASCII.GetBytes(comMessageStr);
                for (int i = 0; i < comMessageBytes.Length; i++)
                    comMessage[i] = comMessageBytes[i];
                fetchFilesSocket.Send(comMessage, 0, 100, SocketFlags.None);
                Console.WriteLine("Console sent ready to receive buffer message.");
                if (fileSize - byteCount >= 2048)
                
                    fetchFilesSocket.Receive(receivedFileCont, 0, 2048, SocketFlags.None);
                    buffersReceived++;
                    Console.WriteLine("Buffers received:" + buffersReceived);
                    byteCount = byteCount + 2048;
                
                else
                
                    fetchFilesSocket.Receive(receivedFileCont, 0, 2048, SocketFlags.None);                        buffersReceived++;
                    byteCount = byteCount + 2048;
                    Console.WriteLine("Buffers received:" + buffersReceived);
                    keepReceiving = false;
                
                Console.WriteLine("Bytes received " + byteCount + "/" + fileSize);
                //Console.WriteLine("Received bytes in current file:" + byteCount);
            
            Console.WriteLine("File received.");
        
    

服务器代码(发送文件):

private void fetchThreadMethod(Object commandArgs)
    
        if (fetchSocket == null)
        
            fetchSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            int port = 1304;    
            IPEndPoint fetchSocketEP = new IPEndPoint(localIP,port);
            fetchSocket.Bind(fetchSocketEP);
        
        if (!fetchSocket.Connected)
            try
            
                fetchSocket.Connect(remoteIP, 1305);
                if (fetchSocket.Connected)
                    Console.WriteLine("Server fetch socket connected.");
            catch(Exception e)
            
                Console.WriteLine("Something went wrong when connecting the server fetch socket.");
            
        FileCollector fCollector = new FileCollector();
        String userName = Environment.GetEnvironmentVariable("USERPROFILE");
        String targetDirectory = userName + "\\" + commandArgs;
        Console.WriteLine("Path sent to file collector:" + targetDirectory);
        fCollector.startFileCollector(targetDirectory);
        List<FileNode> collectedFiles = fCollector.getCollectedFiles();
        String comMessageStr = String.Empty;
        foreach (FileNode fNode in collectedFiles)
        
            comMessageStr = String.Empty;
            byte[] sentFileInfo = new byte[2056];
            byte[] sentFileCont = new byte[2048];
            byte[] comMessage = new byte[100];
            String fileName = fNode.getFileName();
            String formattedFileInfo = fileName;
            formattedFileInfo += "ENDN";
            ulong fileSize = 0;
            FileStream fStream = null;
            try
            
                fStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                FileInfo fInfo = new FileInfo(fileName);
                fileSize = (ulong) fInfo.Length;
                if (fileSize == 0)
                    continue;
                formattedFileInfo += fileSize.ToString() + "ENDS";
            
            catch (Exception e)
            
                Console.WriteLine("Could not read from file:" + fileName);
                deniedAccessFiles.Add(fileName);
                continue;
            
            byte[] fileInfoBytes = Encoding.ASCII.GetBytes(formattedFileInfo);
            for (int i = 0; i < fileInfoBytes.Length; i++)
                sentFileInfo[i] = fileInfoBytes[i];
            while (!comMessageStr.Equals("CLIENT_READY_TO_RECEIVE_FILE_INFO"))
            
                Console.WriteLine("Server waiting for file info ready message from client.");
                fetchSocket.Receive(comMessage,0,100,SocketFlags.None);
                comMessageStr = Encoding.ASCII.GetString(comMessage);
                comMessageStr = comMessageStr.Substring(0,33);
                Console.WriteLine("Received parsed message from client:" + comMessageStr);
            
            Console.WriteLine("Server received file info ready message from client.");
            comMessageStr = String.Empty;
            Console.WriteLine("formattedFileInfo:" + formattedFileInfo);
            Console.WriteLine("Sent file info:" + Encoding.ASCII.GetString(sentFileInfo));
            fetchSocket.Send(sentFileInfo, 0, 2056, SocketFlags.None);
            int readByte = 0;
            ulong byteCount = 0;
            ulong buffersSent = 0;
            while (readByte != -1)
            
                if (byteCount == 2048)
                
                    while (!comMessageStr.Equals("CLIENT_READY_TO_RECEIVE_CONTENT_BUFFER"))
                    
                        Console.WriteLine("Server waiting for ready for buffer message from client.");
                        fetchSocket.Receive(comMessage, 100, SocketFlags.None);
                        comMessageStr = Encoding.ASCII.GetString(comMessage);
                        comMessageStr = comMessageStr.Substring(0,38);
                        Console.WriteLine("Received parsed message from client 1:" + comMessageStr);
                    
                    Console.WriteLine("Server received ready for buffer message from client.");
                    fetchSocket.Send(sentFileCont, 0, 2048, SocketFlags.None);
                    comMessageStr = String.Empty;
                    buffersSent++;
                    Console.WriteLine("Buffers sent:" + buffersSent);
                    byteCount = 0;
                
                else
                
                    readByte = fStream.ReadByte();
                    if (readByte != -1)
                    
                        sentFileCont[byteCount] = Convert.ToByte(readByte);
                        byteCount++;
                    
                   
            
            while (!comMessageStr.Equals("CLIENT_READY_TO_RECEIVE_CONTENT_BUFFER"))
            
                Console.WriteLine("Server waiting for ready for buffer message from client.");
                fetchSocket.Receive(comMessage, 100, SocketFlags.None);
                comMessageStr = Encoding.ASCII.GetString(comMessage);
                comMessageStr = comMessageStr.Substring(0, 38);
                Console.WriteLine("Received parsed message from client 2:" + comMessageStr);
            
            Console.WriteLine("Server received ready for buffer message from client.");
            fetchSocket.Send(sentFileCont, 0, 2048, SocketFlags.None);
            buffersSent++;
            Console.WriteLine("Buffers sent:" + buffersSent);
            comMessageStr = String.Empty;  
        
    

控制台输出:

【问题讨论】:

我能推荐你看看TCPClientTCPServer吗?他们让做这类事情变得容易得多 我去看看。谢谢。 你为什么要手动分块?底层的套接字流会为您做到这一点。无论如何,2048 字节比单个数据包大。 TCP 将消息分成最大大小为 1500 字节的数据报。消息的接收端必须知道消息的结束位置,并且可能需要进行多次读取以获取所有数据。所以通常发件人会执行以下操作之一:1)ASCII:以已知字符终止数据,而不是返回消息中的字符 2)ASCII 或二进制:在消息的开头添加字节数 3)ASCII 或二进制:使用固定大小的消息。 我在上面的评论中指的是TCPListener,而不是TCPServer,请查看我的答案以获取示例 【参考方案1】:

我的建议是使用System.Net.Sockets 中提供的TCPListenerTCPClient 类,它们确实简化了通过TCP/IP 发送消息。

服务器端你需要这样的东西:

  class MyTcpListener
    
        public static void Listen()
        
            TcpListener server = null;
            byte[] bytes = new byte[256];
            try
            
                // Set the TcpListener on port 13000.
                const int port = 13000;
                IPAddress localAddr = IPAddress.Parse("127.0.0.1");
                // TcpListener server = new TcpListener(port);
                server = new TcpListener(localAddr, port);
                // Start listening for client requests.
                server.Start();
                // Enter the listening loop.
                while (true)
                
                    Console.Write("Waiting for a connection... ");
                    // Perform a blocking call to accept requests.
                    // You could also user server.AcceptSocket() here.
                    TcpClient client = server.AcceptTcpClient();
                    Console.WriteLine("Connected!");
                    // Get a stream object for reading and writing
                    NetworkStream stream = client.GetStream();
                    int i;
                    // Loop to receive all the data sent by the client.
                    while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                    
                        // Translate data bytes to a ASCII string.
                        string data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                        Console.WriteLine($"Received: data");
                        // Process the data sent by the client.
                        data = data.ToUpper();
                        byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
                        // Send back a response.
                        stream.Write(msg, 0, msg.Length);
                        Console.WriteLine($"Sent: data");
                    
                    // Shutdown and end connection
                    client.Close();
                
            
            catch (SocketException e)
            
                Console.WriteLine($"SocketException: e");
            
            finally
            
                // Stop listening for new clients.
                server?.Stop();
            
        
    

显然,您会希望使其适应您的程序结构。我从here 中获取了这个,但稍微编辑了一下。

在你的客户端你想使用这样的东西:

    class MyTCPClient
    
        static void Connect(String server, String message)
        
            try
            
                // Create a TcpClient.
                // Note, for this client to work you need to have a TcpServer 
                // connected to the same address as specified by the server, port
                // combination.
                int port = 13000;
                TcpClient client = new TcpClient(server, port);

                // Translate the passed message into ASCII and store it as a Byte array. Any encoding can be used as long as it's consistent with the server.
                byte[] data = System.Text.Encoding.ASCII.GetBytes(message);

                // Get a client stream for reading and writing.
                //  Stream stream = client.GetStream();
                NetworkStream stream = client.GetStream();

                // Send the message to the connected TcpServer. 
                stream.Write(data, 0, data.Length);
                Console.WriteLine($"Sent: message");

                // Receive the TcpServer.response. This is all optional and can be removed if you aren't recieving a response.
                // Buffer to store the response bytes.
                data = new byte[256];

                // String to store the response ASCII representation.

                // Read the first batch of the TcpServer response bytes.
                int bytes = stream.Read(data, 0, data.Length);
                string responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
                Console.WriteLine("Received: responseData");

                // Close everything.
                stream?.Close();
                client?.Close();
            
            catch (ArgumentNullException e)
            
                Console.WriteLine($"ArgumentNullException: e");
            
            catch (SocketException e)
            
                Console.WriteLine($"SocketException: e");
            

            Console.WriteLine("\n Press Enter to continue...");
            Console.Read();
        
    


同样,这需要适应您的结构,我从here 获取。如果您不期望来自服务器的响应,则可以将响应内容全部删除。

这两个类具有极强的弹性并抽象出所有复杂的东西,数据缓冲区的大小也可以更改为您需要的任何大小。

【讨论】:

我来看看 TCPClient 和 TCPListener。 James Hughes 也提到了 TCPServer。非常感谢。 @EggBender 这是我的错字...TCPServer 在我的评论中提到了TCPListener 好的,我明白了。 TCPClient 和 TCPListener 就可以了。 如果您需要任何帮助来实现这一点,请告诉我,我整天都在附近

以上是关于C#定时器接收定时发送和处理接收socket异步通信,接收值放在静态变量里,有时候收到的数据不完整。的主要内容,如果未能解决你的问题,请参考以下文章

C#上位机开发——SerialAssistant功能优化(串口自动扫描功能接收数据保存功能加载发送文件发送历史记录打开浏览器功能定时发送功能)

Celery 基本使用

C# Sockets,接收大文件

C# UDP Socket:获取接收者地址

Socket 服务器数据接收混乱

C# Socket 如何完全发送/接收数据