如何在.NET框架中使用C#进行插座编程

146 阅读6分钟

使用C#在.NET框架中进行插座编程

假设网络上有两个设备需要相互通信。为了加强这些设备之间的通信,我们将一个设备设置在一个特定端口和一个IP地址的套接字上。

如果另一个设备在同一个套接字或网络上,它将能够通过相同的端口和IP地址与第一个设备进行通信。在.NET框架中,我们有Socket 类,可以进行网络编程。这个类同时处理网络编程的同步和异步模式。

在异步编程中,我们的程序可以继续做其他任务,并在等待其他任务执行时接收数据。在同步编程中,任务每次只执行一个,并按照它们进来的顺序进行。

在这种状态下,我们的程序只能处理一个任务,直到它完成,然后接受另一个任务。因此,这就要求我们看一下异步套接字编程是如何处理的,以及我们如何在C#中开发一个。为了做到这一点,我们将建立一个简单的服务器端控制台应用程序。

前提条件

要完成本教程,你必须。

  • 具备[C#]编程语言的基本知识。
  • 熟悉[.NET框架]
  • 安装有一个IDE。

开始工作

为了创建我们的应用程序,我们将从创建一个新的项目开始。打开Microsoft Visual Studio,点击create new project ,如下图所示。

New Project

由于我们正在创建一个控制台应用程序,我们将点击使用C#作为编程语言的Console Application ,然后点击下一步。

Console application

在下一个屏幕上,我们需要输入我们应用程序的名称。在这种情况下,我们将使用ConsoleApp1 作为我们应用程序的名称。

Name of our project

在下一个屏幕上,我们需要为我们的应用程序选择目标框架,我们将使用.NET 5.0 ,然后点击创建来创建我们的应用程序。

Target framework

在这个应用程序中,我们将主要研究服务器端的套接字编程。

C#命名空间

本项目将使用以下C#命名空间。

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

服务器端套接字编程

我们将从创建我们应用程序的服务器端开始。开始时,我们将在program.cs 文件中写下我们的代码。

我们需要做的第一件事是创建一个公共类,并将其称为AppState 。在这个类中,我们将定义一个套接字,将其命名为socket ,并将其设置为空。我们还将定义一个整数,给它一个1024字节的数据大小。

接下来,我们将定义一个能够容纳新数据的字节数组,并传入我们上面初始化的数据大小。在这个类中,我们需要做的最后一件事是创建一个string builder ,并初始化它new

public class ProgramState
        {
            public Socket socket = null;
            public const int dataSize = 1024;
            public StringBuilder sBuilder = new StringBuilder();
        }

我们将再次创建另一个类,它将是一个套接字监听器。在这个类中,我们需要做以下工作。

  • 为所有完成的任务设置一个重置事件,并将其初始参数设置为false。
  • 添加一个方法来启动监听器。在同一个方法中,我们还将添加一个字节数组,并将新的实例设置为1024字节。
  • 添加一个IP主机条目,通过传入DNS服务器的主机名来获得主机条目。
  • 接下来,我们设置主机的IP地址,并挑选其第一个元素。
  • 之后,我们将设置端点的IP地址,并将其设置为80端口的一个新IP。
  • 然后,我们初始化另一个套接字,调用一个监听器并传入IP地址族。之后,我们设置一个流和一个协议。
  • 最后,我们可以启动我们的应用程序并捕捉应用程序中的任何错误。我们将通过使用try...catch 方法来做到这一点。

在我们的try方法中,我们将把我们的监听器绑定到本地端点,并设置监听积压111 。我们将使用一个while循环来等待任何进入的连接。

在catch方法中,我们将使用e.Message 方法在屏幕上打印消息。

 public class AsyncSocketListener
 {
     public static ManualResetEvent completed = new ManualResetEvent(false);
     public static void StartListener()
     {
         byte[] dataSize = new byte[1024];
         IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName());
         IPAddress ip = hostEntry.AddressList[0];
         IPEndPoint localEnd = new IPEndPoint(ip, 4444);
         Socket eventListener = new Socket(ip.AddressFamily, SocketType.Stream,ProtocolType.Tcp);
         try
         {
             eventListener.Bind(localEnd);
             eventListener.Listen(111);
             while (true)
             {
                 completed.Reset();
                 Console.WriteLine($"Waiting for new connections...");
                 eventListener.BeginAccept(new AsyncCallback(AcceptCallBack), eventListener);
                 completed.WaitOne();
             }
         }
         catch(Exception e)
         {
             Console.WriteLine(e.Message);
             throw;
         }
     }

在我们上面创建的while循环中,我们意识到我们得到了一个错误,需要我们创建另一个方法,我们将创建的消除错误的方法是AcceptCallBack 。在这个方法中,我们将通过使用空括号将完成的任务设置为无效。

在同一个方法中,我们将创建一个套接字作为我们的监听器。这个监听器将被初始化为asyncState 。我们将创建另一个套接字作为我们的处理器。这个方法将通过EndAccept 方法进行初始化。告诉我们当前所拥有的连接状态的程序状态将被new 关键字所初始化。

接下来,我们设置我们的处理程序的状态,我们将用它来开始接收数据。有了这个,我们就可以使用.BeginReceive 方法,将数据、程序状态和回调事件作为参数传入。

 private static void AcceptCallBack(IAsyncResult ar)
            {
                completed.Set();
                Socket eventListener = (Socket)ar.AsyncState;
                Socket handler = eventListener.EndAccept(ar);
                ProgramState state = new ProgramState();
                state.socket = handler;
                handler.BeginReceive(state.data, 0, ProgramState.dataSize, 0, new AsyncCallback(ReadCallBack), state);
            }

从上面的方法中,我们意识到,当我们把ReadCallBack 函数作为参数传入开始接收方法时,我们得到了错误。为了消除这个错误,我们将创建另一个方法来处理ReadCallBack 甚至。在这个方法中,我们将创建一个字符串,将其称为content ,并使用Empty 方法将其设置为空。

我们将创建程序状态,并用ar.AsyncState 方法初始化它。我们需要定义连接建立后读取的数据将被存储在哪里。所以我们将用handler.EndReceive 方法初始化数据读取变量。

我们需要使用if 语句,在数据大于0的情况下,将读取的数据打印到屏幕上。我们将需要把这些数据转换成字符串,因为在套接字编程中,当数据以字节格式呈现时,数据编码是必要的。在这种情况下,我们将使用ASCII 编码方法将其转换为字符串格式,然后再打印在屏幕上。

如果连接被设置,数据被读取,我们需要向客户端发送一个响应。因为这一点,我们将创建Send 事件并传入handler ,和content 作为参数。

 private static void ReadCallBack(IAsyncResult ar)
            {
                string content = string.Empty;
                ProgramState state = (ProgramState)ar.AsyncState;
                Socket handler = state.socket;
                int dataRead = handler.EndReceive(ar);
                if(dataRead > 0)
                {
                    state.sBuilder.Append(Encoding.ASCII.GetString(state.data, 0, dataRead));
                    content = state.sBuilder.ToString();
                    if (content.IndexOf("<EOF>", StringComparison.Ordinal) > -1)
                    {
                        Console.WriteLine($"Read:{content.Length} data from \n socket data:{content}");
                        Send(handler, content);
                    }
                }
                else
                {
                    handler.BeginReceive(state.data, 0, ProgramState.dataSize, 0, new AsyncCallback(ReadCallBack), state);
                }
            }

当我们创建上述方法时,我们意识到我们得到了一个错误信息,需要我们创建Send 方法。在Send 方法中,我们将创建一个字节数组,命名为sizeOfData ,并传入我们数据的内容。

在我们的处理程序中,下面的BeginSend 函数接收sizeOfData ,数据长度和处理程序作为参数。new AsyncCallBack 是我们函数中的一个参数,接收SendCallBack 作为另一个参数。

private static void Send(Socket handler, string content)
            {
                byte[] sizeOfData = Encoding.ASCII.GetBytes(content);
                handler.BeginSend(sizeOfData, 0, sizeOfData.Length, 0, new AsyncCallback(SendCallBack), handler);
            }

上面的方法中的SendCallBack 参数产生了一个错误信息,要求我们创建另一个名为SendCallBack 的方法。在SendCallBack 方法中,我们将使用一个try...catch 方法,添加一个套接字处理程序,并定义发送至客户端的数据。

一旦数据发送完毕,我们将关闭两端的连接,catch 方法被用来检查连接中的任何错误。

private static void SendCallBack(IAsyncResult ar)
            {
                try
                {
                    Socket handler = (Socket)ar.AsyncState;
                    int dataSent = handler.EndSend(ar);
                    Console.WriteLine($"Send:{dataSent} to client");
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();
                }
                catch (Exception e)
                {
                }
            }

我们需要做的最后一件事是在main 函数中实现我们的应用程序。我们将使用Console.WriteLine 方法在屏幕上显示一条信息。

Console.ReadLine 将被用来从用户那里获取数据。有了这些,我们就可以在主函数中启动我们的监听器。

        static void Main(string[] args)
        {
            Console.WriteLine("Press enter key to continue...");
            Console.ReadLine();
            AsyncSocketListener.StartListener();
            Console.ReadLine();
        }

结论

从这篇文章中,我们可以推断出,套接字编程是设备通过网络进行通信的一种更好的方式。例如,如果一个设备使用TCP协议在端口4444 发送数据,另一端就可以收到数据。这是因为我们的设备将作为一个接收者,而发送者将作为一个服务器。