文章目录
- 前言
- 一、网口通信概念
- 二、使用网口通信准备
- 三、使用步骤
前言
C#上位机之网口通信与协议!
一、网口通信概念
定义 :Socket 可以理解为一个通信端点,它提供了应用程序与网络之间的接口,使得应用程序能够在网络上发送和接收数据。通过 Socket,不同设备上的应用程序可以建立连接,实现数据的交换。
地址族 :用于确定 Socket 能够使用的协议类型,常见的有 AF_INET(IPv4 地址族)、AF_INET6(IPv6 地址族)、AF_UNIX(本地通信的 UNIX 域协议)等。
类型
流式套接字(SOCK_STREAM) :提供面向连接的、可靠的、双向的字节流服务,基于 TCP 协议。数据传输保证顺序、完整且无重复,适用于对数据可靠性要求较高的场景,如文件传输、远程登录等。
数据报套接字(SOCK_DGRAM) :提供无连接的、不可靠的、基于数据报的包传输服务,基于 UDP 协议。数据报独立传输,可能存在丢失、重复或乱序的情况,但传输效率较高,适用于对实时性要求较高、少量丢包可以接受的场景,如视频直播、在线游戏等。
原始套接字(SOCK_RAW) :允许对低层协议进行访问和操作,可直接处理 IP 数据报或更低层协议的数据,通常用于网络工具开发、协议研究等特殊场景。
二、使用网口通信准备
1、下载网络调试助手软件
2、Socket概念
地址族(AddressFamily):如 InterNetwork (IPv4)、InterNetworkV6 (IPv6)。
套接字类型(SocketType):如 Stream (TCP)、Dgram (UDP)。
协议类型(ProtocolType):如 Tcp 、Udp 。
IPEndPoint:表示网络终结点(IP 地址 + 端口)。
3、Socket工作流程
阶段 | 服务器端操作 | 客户端操作 |
---|---|---|
创建套接字(Socket) | 创建一个 Socket 对象,指定地址族、套接字类型和协议类型。 | 创建一个 Socket 对象,指定地址族、套接字类型和协议类型。 |
绑定地址(Bind) | 使用 Bind 方法将套接字绑定到本地的 IP 地址和端口号。 | 通常不需要显式绑定,除非需要指定本地地址和端口。 |
监听连接(Listen) | 调用 Listen 方法开始监听来自客户端的连接请求,进入监听状态,等待客户端连接。(仅TCP) | 无此操作。 |
建立连接(Accept) | 调用 Accept 方法接受客户端的连接请求,建立与客户端之间的连接,返回一个新的套接字用于与客户端通信(仅TCP)。 | 调用 Connect 方法向服务器端发起连接请求,尝试与服务器端建立连接。 |
数据传输 | 通过返回的套接字使用 Receive 方法接收客户端发送的数据,使用 Send 方法向客户端发送数据。 | 通过套接字使用 Send 方法向服务器端发送数据,使用 Receive 方法接收服务器端发送的数据。 |
关闭连接(Close) | 数据传输完成后,调用 Shutdown 方法关闭套接字的发送和接收功能,然后调用 Close 方法释放套接字资源,关闭连接。 | 数据传输完成后,调用 Shutdown 方法关闭套接字的发送和接收功能,然后调用 Close 方法释放套接字资源,关闭连接。 |
三、使用步骤
TCP服务器
C#创建socket服务端,但这种方法只能接收一个客户端连接和处理一次接收到的信息。解释:当启动服务端后程序会停在Socket socketClient = socketServer.Accept();处等待客户端连接,当有客户端来连接程序就会停在int bytesReader = socketClient.Receive(recvData);等待客户端发送消息。
// 1、创建Socket
Socket socketServer = new Socket(AddressFamily.InterNetwork,// 地址族IPv4SocketType.Stream, // 套接字类型ProtocolType.Tcp// TCP协议);
// 2、绑定IP和端口
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
socketServer.Bind(endPoint);// 3、开始监听
socketServer.Listen();
Debug.WriteLine($"服务器已启动,等待连接");// 4、接收客户端连接
Socket socketClient = socketServer.Accept();
Debug.WriteLine($"连接的客户端:{socketClient.RemoteEndPoint}");// 5、接收数据
byte[] recvData = new byte[1024];
int bytesReader = socketClient.Receive(recvData);// 返回接收到的字节数
Debug.WriteLine($"收到的消息:{Encoding.UTF8.GetString(recvData,0, bytesReader)}");// 6、发送响应
byte[] sendMes = Encoding.UTF8.GetBytes("Hello I am Server");
socketClient.Send(sendMes);// 7、关闭连接
//socketClient.Close();
//socketServer.Close();
如果需要连接多个客户端和接收信息,可以使用While循环来实现
// 1、创建Socket
Socket socketServer = new Socket(AddressFamily.InterNetwork,// 地址族IPv4SocketType.Stream, // 套接字类型ProtocolType.Tcp// TCP协议);
// 2、绑定IP和端口
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
socketServer.Bind(endPoint);// 3、开始监听
socketServer.Listen();
Debug.WriteLine($"服务器已启动,等待连接");
while (true)
{// 4、接收客户端连接Socket socketClient = socketServer.Accept();Debug.WriteLine($"连接的客户端:{socketClient.RemoteEndPoint}");while (true){// 5、接收数据byte[] recvData = new byte[1024];int bytesReader = socketClient.Receive(recvData);// 返回接收到的字节数Debug.WriteLine($"收到的消息:{Encoding.UTF8.GetString(recvData, 0, bytesReader)}");// 6、发送响应byte[] sendMes = Encoding.UTF8.GetBytes("Hello I am Server");socketClient.Send(sendMes);}
}
但是使用上述方法程序在
while (true)
{// 5、接收数据byte[] recvData = new byte[1024];int bytesReader = socketClient.Receive(recvData);// 返回接收到的字节数Debug.WriteLine($"收到的消息:{Encoding.UTF8.GetString(recvData, 0, bytesReader)}");// 6、发送响应byte[] sendMes = Encoding.UTF8.GetBytes("Hello I am Server");socketClient.Send(sendMes);
}
这段代码处陷入死循环,只能重复接收一个客户端的消息。
如何解决上述的问题呢?
可以使用Task线程来解决。相当于每来一个客户端就创建一个线程来处理这个客户端发送的数据。
// 1、创建Socket
Socket socketServer = new Socket(AddressFamily.InterNetwork,// 地址族IPv4SocketType.Stream, // 套接字类型ProtocolType.Tcp// TCP协议);
// 2、绑定IP和端口
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
socketServer.Bind(endPoint);// 3、开始监听
socketServer.Listen();
Debug.WriteLine($"服务器已启动,等待连接");
while (true)
{// 4、接收客户端连接Socket socketClient = socketServer.Accept();Debug.WriteLine($"连接的客户端:{socketClient.RemoteEndPoint}");Task.Factory.StartNew(() =>{while (true){// 5、接收数据byte[] recvData = new byte[1024];int bytesReader = socketClient.Receive(recvData);// 返回接收到的字节数Debug.WriteLine($"收到的消息:{Encoding.UTF8.GetString(recvData, 0, bytesReader)}");// 6、发送响应byte[] sendMes = Encoding.UTF8.GetBytes("Hello I am Server");socketClient.Send(sendMes);}});
}
// 7、关闭连接
//socketClient.Close();
//socketServer.Close();
部分代码解读:
Task.Factory.StartNew 创建一个线程并开启这个线程。
TCP客户端
// 1、创建Socket
Socket socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 2、连接服务器
IPEndPoint serverPoin = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
socketClient.Connect(serverPoin);
Debug.WriteLine($"已连接到服务器");// 3、发送数据
byte[] senfMsg = Encoding.UTF8.GetBytes("I am Client");
socketClient.Send(senfMsg);// 4、接收响应
byte[] recvMsg = new byte[1024];
int bytesReader = socketClient.Receive(recvMsg);
string response = Encoding.UTF8.GetString(recvMsg, 0,bytesReader);
Debug.WriteLine($"接收到的消息:{response}");// 5、关闭连接
//socketClient.Close();
UDP通信
// 1、创建UDP
Socket udp = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
// 2.绑定端口
udp.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888));// 1、指定地址
EndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999);
// 2、给指定地址发送信息
udp.SendTo(Encoding.UTF8.GetBytes("Hello UDP"),endPoint);// 接收信息的字节数组
byte[] buffer = new byte[1024];
// IPAddress.Any 服务器会接受来自任何网络接口的连接请求,表示端口号由操作系统自动分配
endPoint = new IPEndPoint(IPAddress.Any, 0);
// 参数ref endPoint指定接收数据的来源
int bytesReader = udp.ReceiveFrom(buffer,ref endPoint);
string message = Encoding.UTF8.GetString(buffer, 0, bytesReader);
Debug.WriteLine(message);// 2、广播模式
udp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
endPoint = new IPEndPoint(IPAddress.Parse("255.255.255.255"), 9999);
udp.SendTo(Encoding.UTF8.GetBytes("Hello UDP!-BroadCast"), endPoint);
TcpClient,简化了 TCP 协议的通信流程。
构造方法 | 说明 |
---|---|
TcpClient() | 初始化一个新的 TcpClient 实例,不指定远程主机和端口。 |
TcpClient(string hostname, int port) | 初始化一个新的 TcpClient 实例,并连接到指定的远程主机和端口。 |
属性 | 说明 | 使用方法示例 |
---|---|---|
Client | 获取底层的 Socket 对象,用于更细粒度的网络操作。 | Socket socket = tcpClient.Client; |
Connected | 获取一个布尔值,指示 TcpClient 是否连接到远程主机。 | if (tcpClient.Connected) { ... } |
Available | 获取接收缓冲区中等待接收的字节数。 | int availableBytes = tcpClient.Available; |
ReceiveBufferSize | 获取或设置接收缓冲区的大小。 | int bufferSize = tcpClient.ReceiveBufferSize; 或 tcpClient.ReceiveBufferSize = 1024; |
SendBufferSize | 获取或设置发送缓冲区的大小。 | int bufferSize = tcpClient.SendBufferSize; 或 tcpClient.SendBufferSize = 1024; |
方法 | 说明 | 使用方法示例 |
---|---|---|
Connect(string host, int port) | 连接到远程 TCP 服务器的指定主机和端口。 | tcpClient.Connect("127.0.0.1", 8888); |
GetStream() | 返回一个 NetworkStream 对象,用于在 TcpClient 上进行读写操作。 | NetworkStream stream = tcpClient.GetStream(); |
Close() | 关闭 TcpClient 和其底层的 Socket 。 | tcpClient.Close(); |
Dispose() | 释放 TcpClient 使用的资源。 | tcpClient.Dispose(); |
TCP客户端
// 1、创建TcpClient
using ( TcpClient client = new TcpClient())
{client.Connect("127.0.0.1",8888);Debug.WriteLine("已连接到服务器");// 2、获取网络流NetworkStream stream = client.GetStream();// 3、发送数据string message = "Hello Server!";byte[] sentBytes = Encoding.UTF8.GetBytes(message);stream.Write(sentBytes, 0, sentBytes.Length);Debug.WriteLine($"发送:{message}");// 4、接收响应byte[] buffer = new byte[1024];int bytesRead = stream.Read(buffer, 0, buffer.Length);string response = Encoding.UTF8.GetString(buffer);Debug.WriteLine(response);
TCP服务端
// 监听指定IP和端口
TcpListener server = new TcpListener(IPAddress.Any, 8888);
server.Start();
Debug.WriteLine($"服务器已启动,等待连接.....");try
{// 接收客户端连接using (TcpClient client = server.AcceptTcpClient()){Debug.WriteLine($"客户端已连接:{((IPEndPoint)client.Client.RemoteEndPoint).Address}");// 2、获取网络流NetworkStream stream = client.GetStream();// 3、接收数据byte[] buffer = new byte[1024];int bytesRead = stream.Read(buffer, 0, buffer.Length);string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);Debug.WriteLine(message);// 4、发送响应string response = "Hello Client!";byte[] sendData = Encoding.UTF8.GetBytes(response);stream.Write(sendData, 0, sendData.Length);}
}catch(Exception ex)
{Debug.WriteLine($"错误:{ex.Message}");
}
finally
{server.Stop();
}