C#
Async Programming
Socket Server
BeginReceive
Callback

C Async Socket Server - BeginReceive in Callback

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In the classic .NET socket asynchronous pattern, it is normal to call BeginReceive again inside the receive callback after you finish processing the bytes from the previous receive. The crucial rule is that every asynchronous receive must be completed with EndReceive, and a return value of 0 means the remote side has closed the connection.

The basic APM pattern

The older socket API uses the Asynchronous Programming Model, often called APM:

  • 'BeginAccept / EndAccept'
  • 'BeginReceive / EndReceive'
  • 'BeginSend / EndSend'

A receive loop in this model is callback-driven. You begin one receive, the callback fires, you process data, and then you start the next receive.

That is why calling BeginReceive in the callback is not a bug by itself. It is the intended continuation pattern.

Example pattern

csharp
1using System;
2using System.Net.Sockets;
3using System.Text;
4
5class StateObject
6{
7    public Socket WorkSocket;
8    public byte[] Buffer = new byte[1024];
9}
10
11static void StartReceive(Socket client)
12{
13    var state = new StateObject { WorkSocket = client };
14    client.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ReceiveCallback, state);
15}
16
17static void ReceiveCallback(IAsyncResult ar)
18{
19    var state = (StateObject)ar.AsyncState;
20    Socket client = state.WorkSocket;
21
22    int bytesRead = client.EndReceive(ar);
23    if (bytesRead == 0)
24    {
25        client.Close();
26        return;
27    }
28
29    string text = Encoding.UTF8.GetString(state.Buffer, 0, bytesRead);
30    Console.WriteLine("Received: " + text);
31
32    client.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ReceiveCallback, state);
33}

That is the normal receive loop shape in the old API.

Why EndReceive is mandatory

You must call EndReceive inside the callback for the matching BeginReceive. That call:

  • completes the asynchronous operation
  • reports how many bytes arrived
  • surfaces socket errors

Skipping EndReceive breaks the APM contract.

Handling partial reads

TCP is a stream, not a message protocol. One BeginReceive callback does not guarantee that a full logical message arrived.

That means production code often needs:

  • a per-connection buffer
  • framing logic such as length prefix or delimiter parsing
  • accumulation across multiple callbacks

So the callback loop is only the transport layer. Message reconstruction is a separate problem.

Do not treat callback chaining as recursion panic

Developers sometimes worry that calling BeginReceive inside ReceiveCallback causes dangerous recursion. In the ordinary APM model, this is not synchronous recursion in the usual stack-growth sense. You are scheduling the next async receive, not directly re-entering the method immediately as a normal function call.

So the pattern itself is correct.

Error handling and disconnects

A robust callback should handle:

Those are not optional details. In long-running servers, connection churn and partial failures are the normal case, not the exception.

  • 'bytesRead == 0 as remote disconnect'
  • socket exceptions
  • object disposal and shutdown races

For example, if the client disconnects abruptly, the next receive cycle may fail and should close the socket cleanly.

Modern note

For new .NET code, SocketAsyncEventArgs or async/await-style APIs are usually easier to maintain. But if you are working in the BeginReceive / EndReceive world, the callback-loop pattern is still the right mental model.

Understanding that older model is still useful because a lot of production socket code was written before the newer async abstractions became standard.

Common Pitfalls

A common mistake is forgetting to call EndReceive.

Another mistake is assuming each receive callback contains a complete application message.

A third mistake is not handling bytesRead == 0, which means the peer has closed the connection.

Summary

  • Calling BeginReceive again inside the callback is normal in the APM socket model.
  • Always complete each async receive with EndReceive.
  • Treat 0 bytes as a closed connection.
  • Build message framing separately from the raw receive loop.
  • Prefer newer async APIs for new code, but use the callback loop correctly in legacy code.

Course illustration
Course illustration

All Rights Reserved.