个人对 Socket 和协议的理解 以及 使用 System.Net.Sockets 命名空间编写简单的 TCP 通讯程序

Socket

我们要想实现两个程序在不同主机上进行相互通讯,我们就必须准确得标识这两个程序。我们知道对于一个程序来说其都有一个PID(即进程控制符),虽然对于同一台主机上来说PID是唯一的,但是在不同主机之间,两个程序的PID那就不一定是唯一的了,其极有可能会发生重复,因此我们无法使用PID来标识不同主机上的程序。

于是Socket变应运而生,其使用IP地址标识了主机后,再使用端口标识了程序,也许你会问:既然IP标识了主机,那为什么不继续使用PID标识程序,那是因为当我们开启一个程序,但系统分配给这个程序的PID是一个随机的值,这样就导致了我们本地的主机程序 无法得知 我们所需连接的 远程主机程序 的PID,从而无法完成连接。而端口却是固定的且是唯一的,一个端口只能被一个进程所占用,因此在网络通信中我们使用的端口标识程序。

综上所述,Socket是指 本地IP地址 和 远程IP地址 以及 本地端口号 和 远程端口号 的组合,其作用是标识不同主机间的程序,在计算机专业术语中它的意思是套接字,但我们光靠一个套接字显然不可能实现网络通讯,我们需要一些方法,这些方法最初起源于1983年加利福尼亚大学伯克利分校发布的Berkeley Sockets API,后经微软、英特尔等大型公司的完善及规范,形成了一套标准,并在Windows上推出了WinSock API,因此在网络编程中,其也被常常称为套接字接口,简称套接口。在.Net Framework的基础类库中,微软对WinSock API进行了进一步的包装,并为我们提供了强大的System.Net.Sockets命名空间,我们可以利用该命名空间轻松地完成网络编程。

协议

最近发现有很多人认为Socket是指协议,在这儿我想很明确地告诉你们,那是错误的,记住Socket不是协议
协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合(百度百科上是这么写的)。其中它有三个要素:语义、语法、时序,以下是我个人对这三个要素的理解:

语义:是指发送的每个数据包中的数据所代表的含义,例如第一个数据包发送的数据表示的是协议编号,第二个数据包发送的数据表示的是真正所需传递的数据1,第三个数据包发送的数据表示的是真正所需传递的数据2,这就是语义。其也可以看做是发送数据包的顺序。

语法:是指单个数据包的格式,例如每个数据包的前8个字节表示的是数据包的大小,而后面的N个字节表示的是该数据包所传递的数据,这就是语法,当然这语法也包括数据在转换成字节时所用到的编码类型等。

时序:是指整个服务器和客户端交互的流程,其中包括长连接与短连接、同步与异步等。

协议指的是通过语义+语法+时序所建立起来的规则、标准或约定的集合,而Socket仅仅是实现网络通讯的工具而已。

Socket不是协议!Socket不是协议!Socket不是协议!

重要的事说三遍

相关知识补充

套接字的分类:System.Net.Sockets命名空间中,微软为我们提供了一个名为SocketType的枚举,其表示的是套接字的类型。SocketType枚举中共有6个成员,其中常用的有流式套接字(Stream)、数据报套接字(Dgram)、原始套接字(Raw)。流式套接字(Stream)必须被用于TCP协议中,这已经被微软写死,如果该套接字类型与协议类型不对应会报错;数据报套接字(Dgram)也已经被微软写死,只能用于UDP协议中;原始套接字(Raw)可以操纵网络层和传输层,一般被用于自定义协议中。

TCP是一种面向连接的协议:由于TCP是一种面向连接的协议,因此对于TCP服务端来说,其必须创建用于监听的Socket用于连接的Socket才能完成通讯,而由于UDP是一种面向无连接的协议,因此对于UDP服务端来说,其不需要监听连接。

网络通讯中所有数据都是以字节的形式进行传输:由于网络通讯中所有数据都是以字节的形式进行传输的,因此我们必须把相关数据都转化为byte[]的类型。其中System命名空间中的BitConverter类,为我们提供了一系列基础数据类型与字节数组相互转换的方法;另外对于字符串与字节数组的相互转换,我们可以使用System.Text命名空间中的Encoding类,并根据相应的编码方式来实现。

服务端和客户端通讯的基本流程(TCP)

服务端:创建用于监听的Socket ListenSocket

方法一:

Socket ListenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

方法二:

Socket ListenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);

AddressFamily:是一个枚举,指的是服务端地址的类型,对于TCP来说,我们提供的服务端地址一般是IPV4地址或IPV6地址,他们分别对应的是InterNetwork和InterNetworkV6。
SocketType:是一个枚举,指的是套接字的类型,一般常用的有Stream、Dgram和Raw,TCP是使用流式套接字来实现传输的,因此我们在这里使用的是Stream,如果我们使用了SocketType.Stream那ProtocolType必须使用ProtocolType.Tcp,另外Dgram是指数据报套接字,其被用于UDP,如果我们使用了SocketType.Dgram那ProtocolType必须使用ProtocolType.Udp
ProtocolType:是一个枚举,指的是协议类型,这里我们要用的是Tcp,当然这个枚举里是没有HTTP的,由于HTTP是基于TCP的,因此如果要发HTTP,我们需要使用的还是Tcp。

服务端:将Socket ListenSocket与 服务端的IP及端口 进行绑定
IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
ListenSocket.Bind(ServerIPEndPoint);

IPAddress:表示的是一个IP地址,我们可以使用IPAddress.Parse(String)的方法,将一个字符串形式的IP地址转换为一个IPAddress类。
IPEndPoint:表示的是一个由IP及端口组成的一个网络终结点,我们可以使用构造函数IPEndPoint(IPAddress, Int32)来进行创建,其所需的第二个参数指的是端口号。
Socket.Bind(EndPoint):该方法可以将Socket ListenSocket与 服务端的IP及端口 进行绑定,其中EndPoint表示的是一个网络地址,由于IPEndPoint继承自EndPoint,所以我们我们可以直接将IPEndPoint带入方法中来完成该步骤。

服务端:将Socket ListenSocket设为监听状态,开始监听
ListenSocket.Listen(MaxListenNumber);

Socket.Listen(Int32):该方法可以将服务端的Socket ListenSocket设为监听状态,其所需的参数Int32表示的是连接队列的最大连接数。

客户端:创建连接Socket ConnectSocket

方法一:

Socket ConnectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

方法二:

Socket ConnectSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);

该方法与之前说的创建服务端的Socket ListenSocket相同因此不再叙述。

客户端:向服务器发起连接请求,并完成连接

设定服务端的IP及端口号

IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);

IPAddress和IPEndPoint:在发起连接前,我们需要设定服务端的地址,而设定服务端地址的方法也是利用IPAddress和IPEndPoint这两个类,由于该方法与之前讲的相同,我便不再叙说。
同步:

ConnectSocket.Connect(ServerIPEndPoint);

Socket.Connect(EndPoint):该方法可以使客户端向服务端以同步的方式发起一个连接请求。其所需的参数EndPoint表示的是服务端的地址,我们可以将IPEndPoint来带入。
异步:

    object[] ConnectState = {
      
       ConnectSocket };
    ConnectSocket.BeginConnect(ServerIPEndPoint, ConnectCallBack, ConnectState);

private static void ConnectCallBack(IAsyncResult AsyncResult)
{
      
      
    object[] ConnectState = (object[])AsyncResult.AsyncState;
    Socket ConnectSocket = (Socket)ConnectState[0];
    ConnectSocket.EndConnect(AsyncResult);
}//void ConnectCallBack(IAsyncResult AsyncResult)

Socket.BeginConnect(EndPoint, AsyncCallback, Object):该方法可以使客户端向服务器以异步的方式发起一个连接请求。该方法需要3个参数:第一个参数EndPoint,表示的是服务端的地址,我们可以将IPEndPoint来带入;第二个参数AsyncCallback表示的是一个异步委托,其原型是 delegate void AsyncCallback(IAsyncResult ar),因此回调函数应是 一个无返回值void类型 且 所需参数是一个IAsyncResult的函数,由于该参数是一个委托,因此我们可以直接将回调函数名直接带入即可;第三个参数Object表示所需传递至回调函数的参数,由于这里只能带入一个参数,我们需要将多个参数打包成一个object[],然后带入。
IAsyncResult和IAsyncResult.AsyncState:IAsyncResult是一个接口,表示的是一个异步操作的状态,我们可以使用IAsyncResult的AsyncState属性以及强制转换类型的方法来获取 在BeginConnect(EndPoint, AsyncCallback, Object)函数中所带入的第三个参数object的 值。
Socket.EndConnect(IAsyncResult):该方法可以结束一个客户端向服务端发起的异步连接请求,其所需的参数是一个IAsyncResult

服务端:接收客户端发来的请求并创建一个用于连接该客户端的Socket ConnectSocket

同步:

Socket ConnectSocket = ListenSocket.Accept();

Socket.Accept():该方法可以使服务端接受客户端的连接请求,并生成一个服务端用于连接客户端的Socket ConnectSocket。
异步:

    object[] AcceptState = {
      
       ListenSocket };
    ListenSocket.BeginAccept(AcceptCallBack, AcceptState);

private static void AcceptCallBack(IAsyncResult AsyncResult)
{
      
      
    object[] AcceptState = (object[])AsyncResult.AsyncState;
    Socket ListenSocket = (Socket)AcceptState[0];
    Socket ConnectSocket = ListenSocket.EndAccept(AsyncResult);
}//void AcceptCallBack(IAsyncResult AsyncResult)

Socket.BeginAccept(AsyncCallback, Object):该方法可以 以异步的方式 使服务端接收来自客户端发来的连接请求,并生成一个服务端用于连接客户端的Socket ConnectSocket。该方法需要2个参数:第一个参数AsyncCallback和之前说的一样,表示是的一个异步委托,我们只需带入回调函数名即可;第二个参数Object表示所需传递至回调函数的参数。
Socket.EndAccept(IAsyncResult):该方法可以终止 使服务端接受客户端的连接请求 的异步操作,其所需的参数是一个IAsyncResult,该方法具有一个返回值,返回值即为Socket ConnectSocket。

客户端:开始向服务端发送数据(服务端向客户端发送数据也是同理)

同步:

ConnectSocket.Send(ClientData, 0, ClientData.Length, SocketFlags.None);

Socket.Send(Byte[], Int32, Int32, SocketFlags):该方法可以将要发送的byte[]形式的数据写入连接套接字中,从而实现数据的发送。该方法需要4个参数:第一个参数Byte[]表示的是所需发送的数据对象;第二个参数Int32表示的是发送数据的起始位置;第三个参数Int32表示的是发送数据的大小;第四个参数SocketFlags是一个枚举,其指的是套接字的收发行为,一般我们只需使用None即可。
异步:

    object[] SendState = {
      
       ConnectSocket, ClientData };
    ConnectSocket.BeginSend(ClientData, 0, ClientData.Length, SocketFlags.None, SendCallBack, SendState);

private static void SendCallBack(IAsyncResult AsyncResult)
{
      
      
    object[] SendState = (object[])AsyncResult.AsyncState;
    Socket ConnectSocket = (Socket)SendState[0];
    byte[] ClientData = (byte[])SendState[1];
    ConnectSocket.EndSend(AsyncResult);
}//void SendCallBack(IAsyncResult AsyncResult)

Socket.BeginSend(Byte[], Int32, Int32, SocketFlags, AsyncCallback, Object):该方法可以 以异步的方式 将要发送的数据写入连接套接字中。该方法需要6个参数::第一个参数Byte[]表示的是所需发送的数据对象;第二个参数Int32表示的是发送数据的起始位置;第三个参数Int32表示的是发送数据的大小;第四个参数SocketFlags是一个枚举,其指的是套接字的收发行为,一般我们只需使用None即可;第五个参数AsyncCallback表示的是一个异步委托,我们只需带入回调函数名即可;第六个参数Object表示的是所需传递给回调函数的数据。
Socket.EndSend(IAsyncResult):该方法可以终止 将数据byte[]写入套接字 的异步操作,其所需的参数是一个IAsyncResult,该方法具有一个int类型的返回值,表示的是已经写入套接字的数据大小。

服务端:开始接收客户端发来的数据(客户端接收服务端发来的数据也是同理)

同步:

ConnectSocket.Receive(ClientData, 0, ClientData.Length, SocketFlags.None);

Socket.Receive(Byte[], Int32, Int32, SocketFlags):该方法可以从连接套接字中取出远程发来的数据byte[]。该方法需要4个参数:第一个参数Byte[]表示的是接收数据的数据对象;第二个参数Int32表示的是接收数据的起始存放位置;第三个参数Int32表示的是所需接收数据的大小;第四个参数SocketFlags是一个枚举,其指的是套接字的收发行为,一般我们只需使用None即可。
异步:

    object[] ReceiveState = {
      
       ConnectSocket, ClientData };
    ConnectSocket.BeginReceive(ClientData, 0, ClientData.Length, SocketFlags.None, ReceiveCallBack, ReceiveState);

private static void ReceiveCallBack(IAsyncResult AsyncResult)
{
      
      
    object[] ReceiveState = (object[])AsyncResult.AsyncState;
    Socket ConnectSocket = (Socket)ReceiveState[0];
    byte[] ClientData = (byte[])ReceiveState[1];
    ConnectSocket.EndReceive(AsyncResult);
}//void ReceiveCallBack(IAsyncResult AsyncResult)

Socket.BeginReceive(Byte[], Int32, Int32, SocketFlags, AsyncCallback, Object):该方法可以 以异步的方式 从连接套接字中取出远程发来的数据byte[]。该方法需要6个参数:第一个参数Byte[]表示的是接收数据的数据对象;第二个参数Int32表示的是接收数据的起始存放位置;第三个参数Int32表示的是所需接收数据的大小、第四个参数SocketFlags是一个枚举,其指的是套接字的收发行为,一般我们只需使用None即可;第五个参数AsyncCallback表示的是一个异步委托,我们只需带入回调函数名即可;第六个参数Object表示的是所需传递给回调函数的数据。
Socket.EndReceive(IAsyncResult):该方法可以终止 从连接套接字中取出远程发来的数据 的异步操作,其所需的参数是一个IAsyncResult

<think>好的,我现在需要回答用户关于C#中是否存在UDPServer类的问题。首先,用户之前询问了如何通过客户端端口发送数据触发监听,现在转向C#的特定命名空间。 用户的问题明确询问System.NetSystem.Net.Sockets中是否有UDPServer类。根据我的知识,C#的标准库中没有直接名为UDPServer的类。不过,可能存在相关的类用于实现UDP服务器功能。我需要确认这两个命名空间中的类,尤其是UdpClient,因为它是处理UDP通信的主要类。 接下来,我需要回忆System.NetSystem.Net.Sockets的内容。System.Net包含网络协议相关的类,如IP地址处理,而System.Net.Sockets提供更底层的套接字操作。UdpClient位于System.Net.Sockets中,用于简化UDP通信。虽然没有UDPServer类,但可以通过UdpClient来创建服务器客户端。 我需要解释清楚这一点,并给出示例代码,说明如何用UdpClient实现服务器端监听。同时,对比之前的Python示例,保持一致性,帮助用户理解不同语言间的差异。还要注意用户可能对C#不太熟悉,所以代码需要简洁明了,并注释关键步骤。 另外,用户可能希望了解如何绑定特定端口IP,以及如何处理接收数据。需要确保示例覆盖这些方面,并提到异常处理,比如端口占用问题。最后,可以提及TcpListenerSocket类,说明不同选项的适用场景,供用户进一步探索。 需要验证自己的记忆是否正确,确保UdpClient确实在System.Net.Sockets中,并且用于UDP服务器客户端的实现。如果有不确定的地方,可能需要查阅文档,但根据已有知识,UdpClient是正确的选择。此外,提醒用户注意防火墙权限问题,这与之前的回答一致,保持连贯性。 总结回答结构:首先明确不存在UDPServer类,然后介绍替代方案UdpClient,提供服务器端代码示例,说明使用方法,最后扩展相关类注意事项。确保语言简洁,技术细节准确,帮助用户解决问题。</think>在 C# 的标准库中,**`System.Net` `System.Net.Sockets` 命名空间中没有直接名为 `UDPServer` 的类**。不过,可以通过 `UdpClient` 类(位于 `System.Net.Sockets` 命名空间)轻松实现 UDP 服务器功能。以下是具体说明示例: --- ### **1. 核心类 `UdpClient`** `UdpClient` 是 C# 中用于简化 UDP 通信的类,既可用于客户端发送数据,也可用于服务端监听接收数据。它的核心方法是: - **`Receive(ref IPEndPoint remoteEP)`**: 阻塞并等待接收数据。 - **`Send(byte[] data, int length, IPEndPoint remoteEP)`**: 发送数据到指定端点。 --- ### **2. 如何实现 UDP 服务端** 以下是一个简单的 UDP 服务端示例,监听指定端口并接收数据: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; class UDPServer { static void Main() { // 绑定本地IP端口(服务端监听地址) int port = 9999; IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port); // 创建 UdpClient 并绑定到指定端口 using (UdpClient udpServer = new UdpClient(localEndPoint)) { Console.WriteLine($"服务端已启动,正在监听端口 {port}..."); while (true) { // 等待接收数据(阻塞方法) IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); byte[] receivedBytes = udpServer.Receive(ref remoteEndPoint); // 解析数据 string receivedMessage = Encoding.ASCII.GetString(receivedBytes); Console.WriteLine($"来自 {remoteEndPoint} 的消息: {receivedMessage}"); } } } } ``` --- ### **3. 关键点说明** - **绑定端口**:通过 `new UdpClient(localEndPoint)` 绑定到指定端口,启动监听。 - **接收数据**:`Receive` 方法会阻塞线程,直到收到数据,并返回发送方的 `IPEndPoint`。 - **无连接特性**:UDP 是无连接的,服务端不需要显式维护客户端连接状态。 --- ### **4. 客户端发送数据示例** 客户端可以复用 `UdpClient` 发送数据到服务端: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; class UDPClient { static void Main() { using (UdpClient udpClient = new UdpClient()) { // 目标服务端地址端口 IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999); // 发送数据 string message = "Hello, UDP Server!"; byte[] data = Encoding.ASCII.GetBytes(message); udpClient.Send(data, data.Length, serverEndPoint); Console.WriteLine("数据已发送。"); } } } ``` --- ### **5. 扩展说明** - **`Socket` 类**:如果需要更底层控制,可直接使用 `System.Net.Sockets.Socket` 类(`UdpClient` 是对其的封装)。 - **异步操作**:可通过 `ReceiveAsync` `SendAsync` 方法实现异步通信,避免阻塞线程。 - **防火墙与权限**:确保操作系统防火墙允许端口的入站/出站通信。 --- ### **常见问题** - **Q**: 如果端口被占用怎么办? **A**: 初始化 `UdpClient` 时若端口被占用,会抛出 `SocketException`,需捕获异常并处理。 - **Q**: 如何发送到特定客户端? **A**: UDP 是无连接的,服务端需记录客户端的 `IPEndPoint`(通过 `Receive` 方法获取),再调用 `Send` 时指定目标地址。 --- 通过 `UdpClient` 类,可以灵活实现 UDP 服务端客户端功能,无需依赖特定命名的 `UDPServer` 类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值