使用C#在.NET框架中进行插座编程
假设网络上有两个设备需要相互通信。为了加强这些设备之间的通信,我们将一个设备设置在一个特定端口和一个IP地址的套接字上。
如果另一个设备在同一个套接字或网络上,它将能够通过相同的端口和IP地址与第一个设备进行通信。在.NET框架中,我们有Socket 类,可以进行网络编程。这个类同时处理网络编程的同步和异步模式。
在异步编程中,我们的程序可以继续做其他任务,并在等待其他任务执行时接收数据。在同步编程中,任务每次只执行一个,并按照它们进来的顺序进行。
在这种状态下,我们的程序只能处理一个任务,直到它完成,然后接受另一个任务。因此,这就要求我们看一下异步套接字编程是如何处理的,以及我们如何在C#中开发一个。为了做到这一点,我们将建立一个简单的服务器端控制台应用程序。
前提条件
要完成本教程,你必须。
- 具备[C#]编程语言的基本知识。
- 熟悉[.NET框架]
- 安装有一个IDE。
开始工作
为了创建我们的应用程序,我们将从创建一个新的项目开始。打开Microsoft Visual Studio,点击create new project ,如下图所示。

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

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

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

在这个应用程序中,我们将主要研究服务器端的套接字编程。
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 发送数据,另一端就可以收到数据。这是因为我们的设备将作为一个接收者,而发送者将作为一个服务器。