In the previous C# version, if we wanted to perform asynchronous Udp, we needed to receive messages with a single thread. Starting from C# 7.1, we can use the async/await keyword to write asynchronous code. Let's explore how to implement it together.
C/S architecture
We need to implement two apps, one client and one server, each of which can send and receive messages.
Sending messages is very simple. If you receive messages, you need to listen on the port all the time.
UDP is much simpler than TCP. It does not require a connection all the time, nor does it need to handle sending callbacks. Because UDP is unreliable, it doesn't care if it is sent, and it has nothing to do with me if it is lost. Moreover, because there is no need to guarantee the order, there is no sending cache. As long as it is requested to send, it will be sent immediately. The received packets will not accumulate, and it must be a whole packet, so we don't need to deal with the problem of sticking packets.
The key points of the entire implementation are:
- : socket class, tcp and udp are shared.
- : Port class, tcp and udp are shared.
- : The method of binding local port is mainly used by the server.
- : The method of binding the remote port is mainly used by the client.
- : Send data to the specified port, mainly for the server.
- : Receive data from the specified port, mainly used by the server.
- : Send data from the bound port, mainly used by the client.
- : Receive data from the bound port, mainly used by the client.
- async keyword: The identification method is an asynchronous method.
- await keyword: Identifies the asynchronous execution method, waits for return.
- : Asynchronous task class
Client implementation
Let’s first study the client. The implementation of the server is roughly the same, but there are slight differences.
Client main process and implementation
// Build socket objectSocket udpSocket = new Socket(, , ); // Connect to the remote port, used to send messages to the remote terminal. Here is your own machine as both the server and the client, so both are 127...// Note that the connection here is just binding the `socket` object with the IP and port, not the connection concept in tcp.// A new local port will be allocated internally and sent to the remote terminal for use by the remote terminalvar endPoint = new IPEndPoint(("127.0.0.1"), 8060); (endPoint); // Send a messageSendMessageToServer("The client said: Hello Server!"); // Listen to messagesStartRecvMessage(); ();
Client sending message implementation
static void SendMessageToServer(string message) { (Encoding.(message)); }
Because it has been bound to the remote port before, the client can send messages directly, and will automatically allocate a client's own local port internally. The server uses this port to send messages to this client, which we will see in the server implementation.
Client listening message implementation
// Convert string from bytestatic string ConverBytesToString(Decoder decoder, byte[] bytes, int len) { var nchar = (bytes, 0, len); var bytesChar = new char[nchar]; nchar = (bytes, 0, len, bytesChar, 0); var result = new string(bytesChar, 0, nchar); return result; } // Receive a message from the connected port and return the number of bytes readstatic int SocketRecvMessage() { var nread = (buffer); return nread; } // Start receiving messages asynchronouslystatic async void StartRecvMessage() { ("The client starts listening: " + ); var decoder8 = Encoding.(); while (true) { var nread = await <int>(SocketRecvMessage); var message = ConverBytesToString(decoder8, buffer, nread); ($"Received a message from the server: {message}"); } }
In the above code, the main part is:
async/await/<int>(xxx):
- async: The identification method StartRecvMessage will be executed asynchronously
- await: identifies the operation to be waited for, and this operation requires time-consuming, such as socket, io, etc., or simply how long to wait ((500); // Wait for 500ms).
- <int>(xxx): wraps the time-consuming operation as an asynchronous task (similar to opening a thread to perform the operation).
(buffer): Receive messages from the connected remote port. This is a blocking operation and will stay here until the message comes back.
The above asynchronous can also be written in the following form, but only delays the time-consuming operation to a more specific operation:
// Receive a message from the connected port and return the number of bytes readstatic async Task<int> SocketRecvMessage() { var nread = await <int>(() => (buffer)); return nread; } // Start receiving messages asynchronouslystatic async void StartRecvMessage() { ("The client starts listening: " + ); var decoder8 = Encoding.(); while (true) { var nread = await SocketRecvMessage(); var message = ConverBytesToString(decoder8, buffer, nread); ($"Received a message from the server: {message}"); } }
We can further simplify the code:
// Start receiving messages asynchronouslystatic async void StartRecvMessage() { ("The client starts listening: " + ); var decoder8 = Encoding.(); while (true) { var nread = await <int>(() => (buffer)); var message = ConverBytesToString(decoder8, buffer, nread); ($"Received a message from the server: {message}"); } }
Server implementation
The implementation difference between server and client is very small.
The main difference is that the server targets many clients, so the port processing is different in sending and receiving messages.
Server main process and implementation
// Build socket objectSocket udpSocket = new Socket(, , ); // Bind the local port and listen for messages from each clientvar endPoint = new IPEndPoint(("127.0.0.1"), 8060); (endPoint); // Listen to messagesStartRecvMessage(); ();
Server sends message implementation
// Send a message to the specified client port// Note that the implementation here is different from the client, or because the server will correspond to multiple clients, so you need to specify the destination every time you send itstatic void SendMessageToClient(EndPoint endPoint, string message) { (Encoding.(message), endPoint); }
Server listening message implementation
static (int, EndPoint) SocketRecvMessage() { EndPoint endPoint = new IPEndPoint(, 0); var nread = (buffer, ref endPoint); return (nread, endPoint); } static async void StartRecvMessage() { ("The server starts listening: " + ); var decoder8 = Encoding.(); while(true) { var (nread, endPoint) = await <(int, EndPoint)>(SocketRecvMessage); var message = ConverBytesToString(decoder8, buffer, nread); ($"Received from the client[{endPoint}]News: {message}"); SendMessageToClient(endPoint, "The server said Hi to you!"); } }
In the above code, the main difference lies in the processing of ports:
- SocketRecvMessage returns a tuple (int, EndPoint): that is, the number of bytes read, and the client's port information.
- ReceiveFrom: The receiving message specifies the port. Each time the server receives a message, the port information must be used to identify the client that sent the message.
The optimized code is:
static async void StartRecvMessage() { ("The server starts listening: " + ); var decoder8 = Encoding.(); while(true) { EndPoint endPoint = new IPEndPoint(, 0); var nread = await <int>(() => (buffer, ref endPoint)); var message = ConverBytesToString(decoder8, buffer, nread); ($"Received from the client[{endPoint}]News: {message}"); SendMessageToClient(endPoint, "The server said Hi to you!"); } }
Here is the complete code:
// --- using System; using ; using ; using ; using ; namespace test { class AsyncUdpClient { static Socket udpSocket; static byte[] buffer = new byte[4096]; public static void Main() { udpSocket = new Socket(, , ); var endPoint = new IPEndPoint(("127.0.0.1"), 8060); //(endPoint); (endPoint); SendMessageToServer("The client said: Hello Server!"); StartRecvMessage(); (); } static void SendMessageToServer(string message) { (Encoding.(message)); } static async void StartRecvMessage() { ("The client starts listening: " + ); var decoder8 = Encoding.(); while (true) { var nread = await <int>(() => (buffer)); var message = ConverBytesToString(decoder8, buffer, nread); ($"Received a message from the server: {message}"); #region Interaction ("Do you continue to monitor?[yes|no]"); var str = await <string>(() => ()); if (str == "yes") { ("Continue to monitor..."); continue; } ("The client stops listening."); return; #endregion } } static string ConverBytesToString(Decoder decoder, byte[] bytes, int len) { var nchar = (bytes, 0, len); var bytesChar = new char[nchar]; nchar = (bytes, 0, len, bytesChar, 0); var result = new string(bytesChar, 0, nchar); return result; } } } // --- using System; using ; using ; using ; using ; namespace test { static class AsyncUdpServer { static Socket udpSocket; static byte[] buffer = new byte[4096]; public static void Main() { udpSocket = new Socket(, , ); var endPoint = new IPEndPoint(("127.0.0.1"), 8060); (endPoint); //(endPoint); StartRecvMessage(); (); } static void SendMessageToClient(EndPoint endPoint, string message) { (Encoding.(message), endPoint); } static async void StartRecvMessage() { ("The server starts listening: " + ); var decoder8 = Encoding.(); while(true) { EndPoint endPoint = new IPEndPoint(, 0); var nread = await <int>(() => (buffer, ref endPoint)); var message = ConverBytesToString(decoder8, buffer, nread); ($"Received from the client[{endPoint}]News: {message}"); SendMessageToClient(endPoint, "The server said Hi to you!"); #region Interaction ("Do you continue to monitor?[yes|no]"); var str = await <string>(()=> ()); if (str == "yes") { ("Continue to monitor..."); continue; } ("The server stops listening."); return; #endregion } } static string ConverBytesToString(Decoder decoder, byte[] bytes, int len) { var nchar = (bytes, 0, len); var bytesChar = new char[nchar]; nchar = (bytes, 0, len, bytesChar, 0); var result = new string(bytesChar, 0, nchar); return result; } } }
Summarize
Today we use the async/await keyword to implement asynchronous udp communication.
It mainly understands and practices the knowledge and use of asynchronous keywords, and optimizes the traditional single-threading udp communication method, which
The advantage is that you do not need to maintain a multi-threaded environment by yourself, do not need to ensure thread safety, and various locks and other operations.
UDP communication itself is very simple, just figure out the concept of Bind, Connect and ports.
async/await may have some difficulty in understanding for students who have long-term writing synchronous code or using asynchronous callbacks.
But in fact, that's the case. We simply understand it as a coroutine (it's just that it's more convenient to use than a coroutine).
This is the article about the example code of using async and await in C# to implement asynchronous Udp communication. For more related C# asynchronous Udp communication content, please search for my previous articles or continue browsing the following related articles. I hope everyone will support me in the future!