C#中使用async和await实现异步Udp通讯的示例代码

目录
  • C/S架构
  • 客户端实现
    • 客户端主流程和实现
    • 客户端发送消息实现
    • 客户端监听消息实现
  • 服务器实现
    • 服务器主流程和实现
    • 服务器发送消息实现
    • 服务器监听消息实现
  • 总结

在之前的C#版本中, 如果我们想要进行异步的Udp, 需要单开线程接收消息, C#7.1开始, 我们可以使用async/await关键字来编写异步代码, 我们今天一起来探索怎么实现.

C/S架构

我们要实现两个app, 一个客户端和一个服务器, 各自都可以发消息和收消息.
发消息很简单, 收消息的话需要一直在端口上监听.

udp相比tcp来说简单了很多, 不需要一直保持连接, 也不需要处理发送回调, 因为udp不可靠, 只要发了就不管, 丢了也与我无关. 而且因为不需要保证顺序, 所以没有发送缓存, 只要请求发送, 立马就发, 收到的包也不会堆积, 肯定是整包, 所以我们也不需要处理粘包问题.

整个实现的关键点有:

  • Sockets.socket: socket类, tcp和udp共用.
  • System.Net.IPEndPoint: 端口类, tcp和udp共用.
  • Sockets.socket.Bind: 绑定本地端口方法, 主要是服务器使用.
  • Sockets.socket.Create: 绑定远端端口方法, 主要是客户端使用.
  • Sockets.socket.SendTo: 向指定端口发送数据, 主要是服务器使用.
  • Sockets.socket.ReceiveFrom: 从指定端口接收数据, 主要是服务器使用.
  • Sockets.socket.Send: 从绑定的端口发送数据, 主要是客户端使用.
  • Sockets.socket.Receive: 从绑定的端口接收数据, 主要是客户端使用.
  • async 关键字: 标识方法为异步方法.
  • await 关键字: 标识异步执行方法, 等待返回.
  • System.Threading.Tasks.Task: 异步任务类

客户端实现

我们先来研究客户端, 服务器的实现大致相同, 只是有细微的差别.

客户端主流程和实现

// 构建socket对象
Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

// 连接远端口, 用于向远端发送消息, 这里是自己的机器同时当服务器和客户端, 所以都是127...
// 注意这里的连接只是将`socket`对象与ip和端口绑定, 不是tcp中的连接概念.
// 内部会分配新的本地端口, 发送给远端, 供远端使用
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8060);
udpSocket.Connect(endPoint);

// 发送消息
SendMessageToServer("客户端说:Hello Server!");

// 监听消息
StartRecvMessage();
Console.ReadKey();

客户端发送消息实现

static void SendMessageToServer(string message)
{
    udpSocket.Send(Encoding.UTF8.GetBytes(message));
}

因为之前已经和远端口绑定了, 所以客户端可以直接发送消息, 在内部会自动分配一个客户端自己的本地端口, 服务器端使用这个端口来向本客户端发送消息, 我们会在服务器实现中看到.

客户端监听消息实现

// 从byte中转换string
static string ConverBytesToString(Decoder decoder, byte[] bytes, int len)
{
    var nchar = decoder.GetCharCount(bytes, 0, len);

    var bytesChar = new char[nchar];
    nchar = decoder.GetChars(bytes, 0, len, bytesChar, 0);

    var result = new string(bytesChar, 0, nchar);
    return result;
}

// 从连接的端口接收消息, 返回读取到的字节数
static int SocketRecvMessage()
{
    var nread = udpSocket.Receive(buffer);
    return nread;
}

// 开始异步接收消息
static async void StartRecvMessage()
{
    Console.WriteLine("客户端开始监听: " + udpSocket.LocalEndPoint);
    var decoder8 = Encoding.UTF8.GetDecoder();

    while (true)
    {
        var nread = await Task.Run<int>(SocketRecvMessage);
        var message = ConverBytesToString(decoder8, buffer, nread);

        Console.WriteLine($"收到来自服务器的消息: {message}");
    }
}

上面的代码中, 主要的部分是:

async/await/Task.Run<int>(xxx):

  • async:标识方法StartRecvMessage将采用异步方式执行
  • await: 标识要等待的操作, 而这种操作是需要耗时的, 比如socket, io等, 也可以是单纯就是要等待多久(Task.Delay(500); // 等待500ms).
  • Task.Run<int>(xxx): 将耗时的操作包装为异步任务(类似开了一个线程来执行该操作).

udpSocket.Receive(buffer): 从连接好的远端口接收消息, 这是一个阻断性的操作, 在消息回来之前会停留在这里不动.

上面的异步还能写成下面的形式, 只是将耗时操作推迟到了更具体的操作而已:

// 从连接的端口接收消息, 返回读取到的字节数
static async Task<int> SocketRecvMessage()
{
    var nread = await Task.Run<int>(() => udpSocket.Receive(buffer));
    return nread;
}

// 开始异步接收消息
static async void StartRecvMessage()
{
    Console.WriteLine("客户端开始监听: " + udpSocket.LocalEndPoint);
    var decoder8 = Encoding.UTF8.GetDecoder();

    while (true)
    {
        var nread = await SocketRecvMessage();
        var message = ConverBytesToString(decoder8, buffer, nread);

        Console.WriteLine($"收到来自服务器的消息: {message}");
    }
}

我们还能进一步简化代码:

// 开始异步接收消息
static async void StartRecvMessage()
{
    Console.WriteLine("客户端开始监听: " + udpSocket.LocalEndPoint);
    var decoder8 = Encoding.UTF8.GetDecoder();

    while (true)
    {
        var nread = await Task.Run<int>(() => udpSocket.Receive(buffer));
        var message = ConverBytesToString(decoder8, buffer, nread);

        Console.WriteLine($"收到来自服务器的消息: {message}");
    }
}

服务器实现

服务器和客户端的实现差别很小.

主要区别在于服务器针对的是很多客户端, 所以在收发消息上对于端口的处理不一样.

服务器主流程和实现

// 构建socket对象
Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

// 绑定本地端口, 监听来自于各个客户端的消息
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8060);
udpSocket.Bind(endPoint);

// 监听消息
StartRecvMessage();
Console.ReadKey();

服务器发送消息实现

// 向指定的客户端端口发送消息
// 注意这里和客户端的实现不一样, 还是因为服务器会对应多个客户端, 所以每次发送都需要指明目的地
static void SendMessageToClient(EndPoint endPoint, string message)
{
    udpSocket.SendTo(Encoding.UTF8.GetBytes(message), endPoint);
}

服务器监听消息实现

static (int, EndPoint) SocketRecvMessage()
{
    EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);

    var nread = udpSocket.ReceiveFrom(buffer, ref endPoint);
    return (nread, endPoint);
}

static async void StartRecvMessage()
{
    Console.WriteLine("服务器开始监听: " + udpSocket.LocalEndPoint);
    var decoder8 = Encoding.UTF8.GetDecoder();

    while(true)
    {
        var (nread, endPoint) = await Task.Run<(int, EndPoint)>(SocketRecvMessage);
        var message = ConverBytesToString(decoder8, buffer, nread);

        Console.WriteLine($"收到来自客户端[{endPoint}]的消息: {message}");

        SendMessageToClient(endPoint, "服务器对你说Hi!");
    }
}

上面的代码中, 主要的差别在对于端口的处理上:

  • SocketRecvMessage返回的是一个元组(int, EndPoint): 即读取到的字节数, 还有客户端的端口信息.
  • ReceiveFrom: 接收消息指定了端口, 服务器每次接收消息都要使用端口信息用来标识发送消息的客户端.

优化过后的代码为:

static async void StartRecvMessage()
{
    Console.WriteLine("服务器开始监听: " + udpSocket.LocalEndPoint);
    var decoder8 = Encoding.UTF8.GetDecoder();

    while(true)
    {
        EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);
        var nread = await Task.Run<int>(() => udpSocket.ReceiveFrom(buffer, ref endPoint));
        var message = ConverBytesToString(decoder8, buffer, nread);

        Console.WriteLine($"收到来自客户端[{endPoint}]的消息: {message}");

        SendMessageToClient(endPoint, "服务器对你说Hi!");
    }
}

下面是完整的代码:

// --- AsyncUdpClient.cs

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace test
{
    class AsyncUdpClient
    {
        static Socket udpSocket;
        static byte[] buffer = new byte[4096];

        public static void Main()
        {
            udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8060);
            //udpSocket.Bind(endPoint);
            udpSocket.Connect(endPoint);

            SendMessageToServer("客户端说:Hello Server!");
            StartRecvMessage();

            Console.ReadKey();
        }

        static void SendMessageToServer(string message)
        {
            udpSocket.Send(Encoding.UTF8.GetBytes(message));
        }

        static async void StartRecvMessage()
        {
            Console.WriteLine("客户端开始监听: " + udpSocket.LocalEndPoint);
            var decoder8 = Encoding.UTF8.GetDecoder();

            while (true)
            {
                var nread = await Task.Run<int>(() => udpSocket.Receive(buffer));
                var message = ConverBytesToString(decoder8, buffer, nread);

                Console.WriteLine($"收到来自服务器的消息: {message}");

                #region 交互
                Console.WriteLine("是否继续监听?[yes|no]");
                var str = await Task.Run<string>(() => Console.ReadLine());
                if (str == "yes")
                {
                    Console.WriteLine("继续监听...");
                    continue;
                }

                Console.WriteLine("客户端停止监听.");
                return;
                #endregion
            }
        }

        static string ConverBytesToString(Decoder decoder, byte[] bytes, int len)
        {
            var nchar = decoder.GetCharCount(bytes, 0, len);

            var bytesChar = new char[nchar];
            nchar = decoder.GetChars(bytes, 0, len, bytesChar, 0);

            var result = new string(bytesChar, 0, nchar);
            return result;
        }
    }
}

// --- AsyncUdpServer.cs

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace test
{
    static class AsyncUdpServer
    {
        static Socket udpSocket;
        static byte[] buffer = new byte[4096];

        public static void Main()
        {
            udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8060);
            udpSocket.Bind(endPoint);
            //udpSocket.Connect(endPoint);

            StartRecvMessage();
            Console.ReadKey();
        }

        static void SendMessageToClient(EndPoint endPoint, string message)
        {
            udpSocket.SendTo(Encoding.UTF8.GetBytes(message), endPoint);
        }

        static async void StartRecvMessage()
        {
            Console.WriteLine("服务器开始监听: " + udpSocket.LocalEndPoint);
            var decoder8 = Encoding.UTF8.GetDecoder();

            while(true)
            {
                EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);
                var nread = await Task.Run<int>(() => udpSocket.ReceiveFrom(buffer, ref endPoint));
                var message = ConverBytesToString(decoder8, buffer, nread);

                Console.WriteLine($"收到来自客户端[{endPoint}]的消息: {message}");

                SendMessageToClient(endPoint, "服务器对你说Hi!");

#region 交互
                Console.WriteLine("是否继续监听?[yes|no]");
                var str = await Task.Run<string>(()=> Console.ReadLine());
                if (str == "yes")
                {
                    Console.WriteLine("继续监听...");
                    continue;
                }

                Console.WriteLine("服务器停止监听.");
                return;
#endregion

            }
        }

        static string ConverBytesToString(Decoder decoder, byte[] bytes, int len)
        {
            var nchar = decoder.GetCharCount(bytes, 0, len);

            var bytesChar = new char[nchar];
            nchar = decoder.GetChars(bytes, 0, len, bytesChar, 0);

            var result = new string(bytesChar, 0, nchar);
            return result;
        }
    }
}

总结

今天我们使用aync/await关键字实现了异步的udp通讯.

主要是了解和实践异步关键字的知识和使用, 同时对传统的单开线程来进行udp通讯方式进行了优化, 这

样的好处是不需要自己维护多线程环境, 不需要保证线程安全, 各种锁之类的操作.

udp通讯本身很简单, 只要搞清楚Bind, Connect还有端口的概念即可.

aync/await对于长期写同步代码或者使用异步callback形式回调的同学来说, 可能会有一定的理解困难,

但是其实也就那么回事, 我们简单理解为协程即可(只是比协程使用起来更方便).

到此这篇关于C#中使用async和await实现异步Udp通讯的示例代码的文章就介绍到这了,更多相关C# 异步Udp通讯内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#基于UDP进行异步通信的方法

    本文实例讲述了C#基于UDP进行异步通信的方法.分享给大家供大家参考.具体如下: 服务器端: using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Sockets; using System.Threading; namespace AsyncServer { public class UdpState { public UdpClient udp

  • c#实现简单控制台udp异步通信程序示例

    实现客户端发送请求,服务器端响应机制 UDP客户端代码 复制代码 代码如下: using System;using System.Text;using System.Net;using System.Net.Sockets; namespace Client{    class Program    {        //客户端 Socket对象        private static Socket clientSocket;        //服务器端 终点        private

  • C#中使用async和await实现异步Udp通讯的示例代码

    目录 C/S架构 客户端实现 客户端主流程和实现 客户端发送消息实现 客户端监听消息实现 服务器实现 服务器主流程和实现 服务器发送消息实现 服务器监听消息实现 总结 在之前的C#版本中, 如果我们想要进行异步的Udp, 需要单开线程接收消息, C#7.1开始, 我们可以使用async/await关键字来编写异步代码, 我们今天一起来探索怎么实现. C/S架构 我们要实现两个app, 一个客户端和一个服务器, 各自都可以发消息和收消息.发消息很简单, 收消息的话需要一直在端口上监听. udp相比

  • 详解Node.js中的Async和Await函数

    在本文中,你将学习如何使用Node.js中的async函数(async/await)来简化callback或Promise. 异步语言结构在其他语言中已经存在了,像c#的async/await.Kotlin的coroutines.go的goroutines,随着Node.js 8的发布,期待已久的async函数也在其中默认实现了. Node中的async函数是什么? 当函数声明为一个Async函数它会返回一个 AsyncFunction 对象,它们类似于 Generator 因为执可以被暂停.唯

  • 浅谈C#中的Async和Await的用法详解

    众所周知C#提供Async和Await关键字来实现异步编程.在本文中,我们将共同探讨并介绍什么是Async 和 Await,以及如何在C#中使用Async 和 Await. 同样本文的内容也大多是翻译的,只不过加上了自己的理解进行了相关知识点的补充,如果你认为自己的英文水平还不错,大可直接跳转到文章末尾查看原文链接进行阅读. 写在前面 自从C# 5.0时代引入async和await关键字后,异步编程就变得流行起来.尤其在现在的.NET Core时代,如果你的代码中没有出现async或者await

  • 深入浅出探究JavaScript中的async与await

    目录 1.前言 2.详解 2.1.async 2.1.1.函数返回非Promise对象 2.1.2.函数返回Promise对象 2.2.await 2.3.async.await结合使用 2.4.async.await异常处理 3.总结 1.前言 ​ async函数,也就是我们常说的async/await,是在ES2017(ES8)引入的新特性,主要目的是为了简化使用基于Promise的API时所需的语法.async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,

  • Node.js中的async 和 await 关键字微任务和宏任务

    目录 async 和 await 关键字 async 关键字 await关键字 async 和 await 解决回调地狱 JS执行机制(事件循环) 微任务和宏任务 宏任务 微任务 宏任务和微任务执行机制 async 和 await 关键字 async 和 await 是 ES2017 中提出来的,async 和 await 两个关键字的出现,简化的 Promise 的使用. async 关键字 async关键字使用比较简单,所以 async 的使用注意以下三点即可 : async 用于修饰一个

  • C#使用async和await实现异步编程

    最近在写程序的时候,经常遇到大量需要异步访问的情况,但是对于async和await到底怎么写,还不是非常明确. 1.普通的程序怎么写? class Program { static void Main(string[] args) { MyDownLoadString ds = new MyDownLoadString(); ds.DoRun(); Console.ReadKey(); } class MyDownLoadString { Stopwatch sw = new Stopwatch

  • ASP.NET MVC4异步聊天室的示例代码

    本文介绍了ASP.NET MVC4异步聊天室的示例代码,分享给大家,具体如下: 类图: Domain层 IChatRoom.cs using System; using System.Collections.Generic; namespace MvcAsyncChat.Domain { public interface IChatRoom { void AddMessage(string message); void AddParticipant(string name); void GetM

  • 在Angular中实现一个级联效果的下拉框的示例代码

    实现一个具有级联效果的下拉搜索框,实现的结果如下图所示 我们主要通过这个组件,来学习一些细微的逻辑,比如: 如何计算input框内文字的长度: 如何获取光标的位置:如何实现滚动条随着上下键盘的按动进行移动...... 具体需求如下 级联搜索最多不超过三级,以"."作为级联搜索的连接符 搜索框跟着文本框中的"."进行向后移动,向右移动的最大距离不能超过文本框的宽度 当用户修改之前的级联内容,则不进行搜索,并隐藏搜索框:若用户在之前输入的是".",

  • vue中实现拖动调整左右两侧div的宽度的示例代码

    写在最前 最近在使用vue的时候,遇到一个需求,实现左右div可通过中间部分拖拽调整宽度,类似于这样 这是我最终的实现效果 还是老话,因为我不是专业的前端工程师,只是兼职写一些简单的前端,所以这个功能的实现得益于以下博客,<vue 拖动调整左右两侧div的宽度>.<vuejs中拖动改变元素宽度实现宽度自适应大小>,而我只是针对于他们提供的代码,加了亿点点自己所需要的细节. 实现原理 如上图所示,我们需要将要实现此功能的页面划分为三个部分,左部.调整区.右部,分别对应css样式为le

  • git中submodule子模块的添加、使用和删除的示例代码

    背景 项目中经常使用别人维护的模块,在git中使用子模块的功能能够大大提高开发效率. 使用子模块后,不必负责子模块的维护,只需要在必要的时候同步更新子模块即可. 本文主要讲解子模块相关的基础命令,详细使用请参考man page. 子模块的添加 添加子模块非常简单,命令如下: git submodule add <url> <path> 其中,url为子模块的路径,path为该子模块存储的目录路径. 执行成功后,git status会看到项目中修改了.gitmodules,并增加了一

随机推荐