1

这是用于 MoSync 应用程序和外部 DLL 之间的本地通信,MoSync 不允许我使用第 3 部分 DLL,这就是为什么我必须实现这个桥接软件而不是使用对 DLL 的简单调用的原因,我必须从 xml 转换为 DLL 消息格式,然后再转换为 XML。我知道这是一件愚蠢的事情,不幸的是没有改变架构的灵活性。最初我以为只有一个请求,所以我有 Sync coms,但现在我发现可以有多个请求,所以我需要再次实现 Async。

我有一个不时抛出的异常,因为我是 C# 新手,所以我无法找到内存泄漏......也许一双训练有素的眼睛可以找到问题

源代码:

我编写了以下代码,我对 C# 和 Sockets 还很陌生,所以也许我犯了一些只有更有经验的眼睛才能发现的大错误。这是在 Windows Mobile 6.1 设备中使用的,所以我试图避免使用很多线程。

using System;

using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Diagnostics;

namespace SmartDevice_Server
{
    //ClientConnection saves connection information is used to keep context in Async and Event calls
    public class ClientConnection : EventArgs
    {
        public NetworkStream NetworkStream { get; private set; }
        public byte[] Data { get; private set; }
        public int byteReadCount { get; set; }

        public ClientConnection(NetworkStream networkStream, byte[] data)
        {
            NetworkStream = networkStream;
            Data = data;
        }
    }

    //MySocket - Is a server that listens for events and triggers Events upon Request Completion 
    public class MySocketTCP
    {
        #region Class Members
        TcpListener myTcpListener;
        TcpClient myTcpClient;
        NetworkStream myNetworkStream;

        const string localHost = "127.0.0.1";
        IPAddress myAddress = IPAddress.Parse(localHost);
        int myPortNumber = 58889;
        byte[] myData;

        int bytesReadCount;
        const int MIN_REQUEST_STRING_SIZE = 10;

        int TimeStart;

        //Event
        public event socketReadCompleteHandler socketReadCompleteEvent;
        public EventArgs eventArguments = null;
        public delegate void socketReadCompleteHandler(MySocketTCP myTcpSocket, ClientConnection eventArguments);

        #endregion

        //Constructor
        public MySocketTCP()
        {
            Init();
        }

        //Constructor overloaded to receive IPAdress Host, and Port number
        public MySocketTCP(IPAddress hostAddress, int portNumber)
        {
            myAddress = hostAddress;
            myPortNumber = portNumber;

            Init();
        }

        //Initializes the TCPListner
        public void Init()
        {
            try
            {
                myTcpListener = new TcpListener(myAddress, myPortNumber);

                //myNetworkStream = myTcpClient.GetStream();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /*TODO_Listener_Timer: After you accept a connection you wait for data to be Read indefinitely
         *Possible solution: Use a timeout to close the socket connection.
         *Check WIKI, TODOS
         * */
        //Listens Asynchronously to Clients, class a recieveMessageHandler to process the read
        public void ListenAsync()
        {
            myTcpListener.Start();

            while (true)
            {
                //blocks until a client has connected to the server
                myTcpClient = myTcpListener.AcceptTcpClient();

                var client = new ClientConnection(myTcpClient.GetStream(), new byte[myTcpClient.ReceiveBufferSize]);

                // Capture the specific client and pass it to the receive handler
                client.NetworkStream.BeginRead(client.Data, 0, client.Data.Length, r => receiveMessageHandler(r, client), null);
            }
        }

        //Callback is used to Process the request Asynchronously, triggers socketReadCompleteEvent
        public void receiveMessageHandler(IAsyncResult asyncResult, ClientConnection clientInstance)
        {
            bytesReadCount = 0;

            lock (clientInstance.NetworkStream)
            {
                try
                {
                    bytesReadCount = clientInstance.NetworkStream.EndRead(asyncResult);
                    clientInstance.byteReadCount = bytesReadCount;
                }
                catch (Exception exc)
                {
                    throw exc;
                }
            }

            if (bytesReadCount < MIN_REQUEST_STRING_SIZE)
            {
                //Could not read form client.
                Debug.WriteLine("NO DATA READ");
            }
            else
            {
                if (socketReadCompleteEvent != null)
                {
                    socketReadCompleteEvent(this, clientInstance);
                }
            }
        }

        //Reads the request, uses the ClientConnection for context
        public string ReadAsync(ClientConnection connObj)
        {
            int bytesReadCount = connObj.byteReadCount;
            byte[] myData = connObj.Data;

            string xmlMessage;

            try
            {
                xmlMessage = Encoding.ASCII.GetString(myData, 0, bytesReadCount);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return xmlMessage;
        }

        //Deprecated
        public string Read()
        {
            string xmlMessage;

            try
            {
                xmlMessage = Encoding.ASCII.GetString(myData, 0, bytesReadCount);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return xmlMessage;
        }

        //Deprecated
        public void Write(byte[] outBytes)
        {
            try
            {
                myNetworkStream.Write(outBytes, 0, outBytes.Length);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        //Deprecated
        public void Write(string outMessage)
        {
            byte[] outBytes = Encoding.ASCII.GetBytes(outMessage);

            try
            {
                myNetworkStream.Write(outBytes, 0, outBytes.Length);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            int TimeEnd = Environment.TickCount;
            int TimeResult = TimeEnd - TimeStart;
        }

        //Is used to send the message to the correct socket
        public void WriteAsync(ClientConnection connObj, string outMessage)
        {
            byte[] outBytes = Encoding.ASCII.GetBytes(outMessage);

            try
            {
                connObj.NetworkStream.Write(outBytes, 0, outBytes.Length);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            int TimeEnd = Environment.TickCount;
            int TimeResult = TimeEnd - TimeStart;
        }

        //Closes the client
        public void Close()
        {
            //myNetworkStream.Close();
            try
            {
                myTcpClient.Close();
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
    }
}
4

2 回答 2

6

最可能的问题是,您期望对客户端执行的三个“写入”执行三个“读取”。

这是一个错误的假设,因为 TCP 套接字是一个字节流并且不保留您的应用程序消息边界。服务器可能会在一次、两次或十七次读取中消耗客户端发送的这三个“消息”。

您需要以某种方式告诉服务器消息在字节流中的结束位置。通常的选择是固定长度的消息、分隔符、告诉有效负载长度的消息头、像 XML 这样的自描述形式等。

因此,您继续从流中读取,直到您有完整的消息要处理,但同时您可能已经将下一条消息的一部分读入缓冲区。

于 2012-12-19T17:22:10.800 回答
3

我认为这里的问题是你只持有一个NetworkStream( myNetworkStream) ,如果第二个客户端在第一个客户端发送数据之前连接,你的接受循环将覆盖myNetworkStream第二个连接的流。当第一个客户端然后发送一些数据时,您receiveMessageHandlerEndRead第二个连接上调用NetworkStream(在第二个客户端连接时存储myNetworkStream),但从asyncResult一个客户端的读取中传递。这会导致您指出的异常。具体来说,当我测试它时,我收到以下消息:

无法从传输连接读取数据:IAsyncResult 对象未从此类的相应异步方法返回。参数名称:asyncResult。

尝试进行以下更改:

// Create a class to hold details related to a client connection
public class ClientConnection
{
    public ClientConnection(NetworkStream networkStream, byte[] data)
    {
        NetworkStream = networkStream;
        Data = data;
    }

    public NetworkStream NetworkStream { get; private set; }
    public byte[] Data { get; private set; }
}

public void Listen()
{
    myTcpListener.Start();

    while (true)
    {
        //blocks until a client has connected to the server
        myTcpClient = myTcpListener.AcceptTcpClient();

        var client = new ClientConnection(myTcpClient.GetStream(), new byte[myTcpClient.ReceiveBufferSize]);

        // Capture the specific client and pass it to the receive handler
        client.NetworkStream.BeginRead(client.Data, 0, client.Data.Length, r => receiveMessageHandler(r, client), null);
    }
}

public void receiveMessageHandler(IAsyncResult asyncResult, ClientConnection client)
{
    var byteReadCount = client.NetworkStream.EndRead(asyncResult);

    if (byteReadCount < MIN_REQUEST_STRING_SIZE)
    {
        //Could not read form client.
        //Erro - Como tratar? Close()
    }
    else
    {
        if (socketReadCompleteEvent != null)
        {
            socketReadCompleteEvent(this, eventArguments);
        }
    }
}

正如其他人所提到的,还有与您对发送方和接收方之间匹配读/写的期望相关的其他问题,但这似乎是您看到的实际问题的原因。

编辑:

这是一个服务器,它将接收数据,并callback在收到完整消息时调用一个方法。返回一个字符串,然后将其发送回客户端,客户端使用响应数据callback调用自己的。replyCallback每个连接只发送一个请求-响应(这是相当低效的,但应该作为一个很好的起点)。

public static class Server
{
    public static void Run(int port, Action<string> callback)
    {
        var listener = new TcpListener(IPAddress.Loopback, port);
        listener.Start();

        while (true)
        {
            using (var client = listener.AcceptTcpClient())
            {
                try
                {
                    var buffer = new byte[2048];
                    using (var memoryStream = new MemoryStream())
                    {
                        using (var stream = client.GetStream())
                        {
                            stream.ReadTimeout = 1000; // 1 second timeout
                            int bytesRead;
                            // Loop until Read returns 0, signalling the socket has been closed
                            while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                memoryStream.Write(buffer, 0, bytesRead);
                            }
                        }

                        // Pass the client's message to the callback and use the response as the reply message to the client.
                        var reply = Encoding.UTF8.GetBytes(callback(Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length)));
                        stream.Write(reply, 0, reply.Length);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error: {0}", e.Message);
                }
            }
        }
    }
}

这是一个小型客户端程序,它将连接、发送数据并等待响应。收到响应后,它将通过replyCallback服务器响应调用:

public static class Client
{
    public static void Run(string hostname, int port, string dataToSend, Action<string> replyCallback)
    {
        using (var client = new TcpClient(hostname, port))
        {
            using (var stream = client.GetStream())
            {
                var buffer = Encoding.UTF8.GetBytes(dataToSend);
                stream.Write(buffer, 0, buffer.Length);
                // Shutdown the send side of the socket, indicating to the server we're done sending our message
                client.Client.Shutdown(SocketShutdown.Send);
                using (var memoryStream = new MemoryStream())
                {
                    stream.ReadTimeout = 1000; // 1 second timeout
                    int bytesRead;
                    // Loop until Read returns 0, signalling the socket has been closed
                    while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        memoryStream.Write(buffer, 0, bytesRead);
                    }
                    replyCallback(Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length));
                }
            }
        }
    }
}

还有一个小测试工具将它们联系在一起:

static class Program
{
    static void Main(string[] args)
    {
        var port = 12345;
        ThreadPool.QueueUserWorkItem(o => Server.Run(port, ProcessClientMessage));
        while (true)
        {
            Console.WriteLine("Enter a message to send and hit enter (or a blank line to exit)");
            var data = Console.ReadLine();
            if (string.IsNullOrEmpty(data)) break;
            Client.Run("localhost", port, data, m => Console.WriteLine("Client received server reply: {0}", m));
        }
    }

    private static string ProcessClientMessage(string clientMessage)
    {
        Console.WriteLine("Server received client message: {0}", clientMessage);
        // This callback would ordinarily process the client message, then return a string that will be sent back to the client in response.
        // For now, we'll just return a fixed string value...
        return "This is the server reply...";
    }
}
于 2012-12-19T17:27:16.497 回答