简体   繁体   中英

Determine end of message in socket stream

I have a project with a server and a client using asynchronous socket connections.

I want to transmit an object from server to client. Unfortunately I have the problem, that sometimes the object isn't fully transmitted in one go. Therefore I need a way to determine when an object was fully transmitted. So I added a four-byte-head to the data transmitted to tell, how long the stream will be.

Server

private void Send(Socket handler, Packet packet)
    {
        byte[] byteData = ByteHelper.Serialize(packet).Data;

        byte[] byteDataLength = BitConverter.GetBytes(byteData.Length);

        byte[] transmissionData = new byte[byteDataLength.Length + byteData.Length];
        byteDataLength.CopyTo(transmissionData, 0);
        byteData.CopyTo(transmissionData, byteDataLength.Length);

        if (debug)
        {
            Status = "Sending "+packet.Type+"-packet to client.";
        }

        try
        {
            handler.BeginSend(transmissionData, 0, transmissionData.Length, 0, new AsyncCallback(SendCallback), handler);
        }
        catch (Exception ex)
        {
            Status = "[EXCEPTION]" + ex.Message.ToString();
        }
    }

The client receives the stream and evaluates the first four bytes to create a StateObject which has the right size. But I have the feeling that this is not really a good way to solve my problem. Is there a better way to do this?

Client

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

            int bytesRead = client.EndReceive(ar);

            if (bytesRead > 0)
            {
                do
                {
                    state = AdjustState(state);
                    if (state != null)
                    {
                        if (state.buffer.Length == state.bufferSize)
                        {
                            ProcessPacket(state);
                            receiveDone.Set();
                        }
                    }
                    else
                    {
                        receiveDone.Set();
                    }
                }
                while (state != null && state.tempBuffer.Length >= state.bufferSize);
                if (state != null)
                {
                    client.BeginReceive(state.tempBuffer, 0, state.tempBuffer.Length, 0, new AsyncCallback(ReceiveCallback), state);
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show("ReceiveCallback: " + ex.ToString(), "Client-Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

private static StateObject AdjustState(StateObject state)
    {
        StateObject tempState = state;
        if (tempState.tempBuffer.Length >= 4)
        {
            byte[] sizeBytes = tempState.tempBuffer.Take(4).ToArray();
            int bufferSize = BitConverter.ToInt32(sizeBytes, 0);
            if (bufferSize == 0)
            {
                return null;
            }
            byte[] temp = tempState.tempBuffer.Skip(4).ToArray();
            Socket tempSocket = tempState.workSocket;
            tempState = new StateObject(bufferSize);
            tempState.BufferSet();
            if (temp.Length >= bufferSize)
            {
                tempState.buffer = temp.Take(bufferSize).ToArray();
                tempState.tempBuffer = temp.Skip(bufferSize).ToArray();
            }
            tempState.workSocket = tempSocket;
        }
        return tempState;
    }

Solution

Thanks to usr I've changed the bytesRead-Part in the ReceiveCallbackCode of the client to this. It seems to work now.

if (bytesRead > 0)
            {
                if (!state.bufferSet)
                {
                    state = AdjustState(state);
                    client.BeginReceive(state.buffer, 0, state.bufferSize, 0, new AsyncCallback(ReceiveCallback), state);
                }
                else
                {
                    if (state.buffer.Length == state.bufferSize)
                    {
                        ProcessPacket(state);
                        receiveDone.Set();
                    }
                    else
                    {
                        client.BeginReceive(state.buffer, state.buffer.Length, state.bufferSize - state.buffer.Length, 0, new AsyncCallback(ReceiveCallback), state);
                    }
                }
            }

You are not making use of the return value from EndReceive ( bytesRead ). It signals how many bytes were received.

And in case bytesRead == 0 you just do nothing which is probably not a proper response to the connection having ended.

And then, there is this unsynchronized busy-loop:

while (state != null && state.tempBuffer.Length >= state.bufferSize);

That's going to burn one CPU core for each client connection. Must solve this differently.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM