将AS3转换为C#Async Socket无法接收所有数据

时间:2014-02-25 20:29:26

标签: c# actionscript-3 sockets asynchronous

我正在将AS3客户端移植到C#,并且在完成到服务器的登录过程时遇到了很大的问题。

我无权访问服务器,只访问协议,并且不知道服务器是否期望任何关于套接字的特定行为。

我已设法连接到服务器并完成登录过程。然后,我就能够发送一条消息,请求数据,这会导致一系列消息被发送到我的客户端。

在客户端的AS3版本中,我在三个不同的缓冲区中接收消息。

在我的C#中,我只获得前2个缓冲区,然后是短时间,然后重置连接。

协议是二进制的。前2个字节告诉我消息的长度,第2个2字节的消息类型。休息是数据。

在第一次读取时,我得到一个91字节的策略文件,我将其丢弃。之后,我收到了我能够处理的数据,前20条奇怪的消息都没问题。第三个缓冲区虽然从未到过。

有什么想法吗?是我的AsyncSocket实现有错还是我应该在套接字上使用一些标志?

任何指针都会非常感激。

public abstract class AsyncSocket 
{
    public class StateObject
    {            
        public Socket workSocket = null;
        public const int BufferSize = 4096;
        public byte[] buffer = new byte[BufferSize];
        public byte[] messageBuffer = new byte[0];
    }

    public delegate void MessageReceivedHandler(object sender, MessageReceivedEventArgs e);

    public delegate void ConnectedHandler(object sender, EventArgs e);

    public event MessageReceivedHandler MessageReceived;

    public event ConnectedHandler Connected;

    private IPAddress[] addresses;
    private int port;
    private WaitHandle addressesSet;
    private Socket socket;
    private int failedConnectionCount;
    private StateObject state;

    public AsyncSocket(IPAddress address, int port) : this(new[] { address }, port) { }

    public AsyncSocket(IPAddress[] addresses, int port) : this(port)
    {
        this.addresses = addresses;
    }

    public AsyncSocket(string hostNameOrAddress, int port) : this(port)
    {
        addressesSet = new AutoResetEvent(false);
        Dns.BeginGetHostAddresses(hostNameOrAddress, GetHostAddressesCallback, null);
    }

    private void GetHostAddressesCallback(IAsyncResult result)
    {
        addresses = Dns.EndGetHostAddresses(result);        
        ((AutoResetEvent)addressesSet).Set();
    }

    private AsyncSocket(int port)
    {   
        this.port = port;
        this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            
        this.Encoding = Encoding.Default;
        this.state = new StateObject();
        state.workSocket = socket;
    }

    public Encoding Encoding { get; set; }

    public Account Account { get; set; }

    public void Connect()
    {
        if (addressesSet != null)
            addressesSet.WaitOne();            
        Interlocked.Exchange(ref failedConnectionCount, 0);
        socket.BeginConnect(addresses, port, ConnectCallback, socket);
    }

    private void ConnectCallback(IAsyncResult result)
    {
        try
        {
            Socket client = (Socket)result.AsyncState;
            client.EndConnect(result);
            if (Connected != null)
            {
                Connected(this, new EventArgs());
            }
            Receive(client);
        }
        catch
        {   
            Interlocked.Increment(ref failedConnectionCount);
            if (failedConnectionCount >= addresses.Length)
            {
                return;
            }
        }
    }

    public void Send(string data)
    {
        byte[] bytes = Encoding.GetBytes(data);
        Send(bytes);
    }

    public void Send(MsgHead msg)
    {
        byte[] bytes = msg.write();
        Send(bytes);
    }

    public void Send(byte[] bytes)
    {
        int messageLength = BitConverter.ToUInt16(bytes, 0);
        int messageType = BitConverter.ToUInt16(bytes, 2);

        Console.Out.WriteLine("Sending:len:{0} msg:{1}", messageLength, messageType);

        socket.BeginSend(bytes, 0, bytes.Length, 0, new AsyncCallback(WriteCallback), socket);
    }

    private void WriteCallback(IAsyncResult result)
    {
        Socket client = (Socket)result.AsyncState;
        int bytesSent = client.EndSend(result);
        Console.WriteLine("Sent {0} bytes to server.", bytesSent);
    }

    private void Receive(Socket client)
    {
        try
        {             
            client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReceiveCallback), state);
        }
        catch (Exception e)
        {
            Account.Window.Exit(string.Format("Error on receive: {0}",e.Message));
        }
    }

    private void ReceiveCallback(IAsyncResult result)
    {
        StateObject state = (StateObject)result.AsyncState;
        Socket client = state.workSocket;

        SocketError errorCode;
        int bytesRead = client.EndReceive(result, out errorCode);
        if (errorCode != SocketError.Success)
        {
            Account.Window.Exit(string.Format("Disconnected, {0}", errorCode.ToString()));
            return;
        }            

        if (bytesRead == 0)
        {
            Account.Window.Exit("Disconnected, zero bytes");
            return;
        }

        state.messageBuffer = state.messageBuffer.Concat(state.buffer.Take(bytesRead).ToArray()).ToArray();

        int messageLength = BitConverter.ToUInt16(state.messageBuffer, 0);

        if (messageLength > 4096)
        {
            state.messageBuffer = state.messageBuffer.Skip(91).ToArray();
            messageLength = state.messageBuffer.Length == 0 ? 0 : BitConverter.ToUInt16(state.messageBuffer, 0);
        }

        while (messageLength > 0 && state.messageBuffer.Length >= messageLength)
        {
            int messageType = BitConverter.ToUInt16(state.messageBuffer, 2);

            Console.Out.WriteLine("Received:len:{0} msg:{1}", messageLength, messageType);

            if (MessageReceived != null)
            {
                MessageReceived(this, new MessageReceivedEventArgs(state.messageBuffer.Take(messageLength).ToArray()));
            }

            state.messageBuffer = state.messageBuffer.Skip(messageLength).ToArray();

            messageLength = state.messageBuffer.Length == 0 ? 0 : BitConverter.ToUInt16(state.messageBuffer, 0); 
        }

        Receive(client);
    }
}

1 个答案:

答案 0 :(得分:0)

有时TCP框架可能很棘手。您永远不会知道您可能获得的三个缓冲区的预期接收次数。以下是一些要检查的事项:

在ReceiveCallback()方法中可能出现的一个问题是,如果只收到1个字节,则尝试将messageLength解码为2字节的Int16将会失败。

确保MessageBuffer方法都正常工作。

MessageLength的2个字节是否包含在MessageLength的值中?如果没有,请确保在解码消息字节之前跳过这2个字节。

更安全地丢弃完整数量的Message字节,以防它总是不超过91个字节。

我会将所有“完整”消息缓冲区分配到另一个方法中,并在那里丢弃或处理,以保持ReceiveCallback()方法简洁明了。