第四阶段通讯开发-7:TCPListener和TCPClient
1_与Socket(TCP)对比
(1)创建连接:
使用Socket类时,需要手动创建Socket对象并指定协议类型(如TCP或UDP)、本地IP地址和端口号。
而TcpClient类则是专门用于创建TCP客户端连接的,它封装了Socket类的一部分功能,并提供了更高层次的方法来处理连接、发送和接收数据。
(2)服务器监听:
使用Socket类时,需要手动监听连接请求并创建新的Socket对象来处理每个客户端连接。
而TcpListener类则是用于创建TCP服务器监听的,它封装了Socket类的一部分功能,并提供了更高层次的方法来处理客户端连接请求和接收数据。
(3)复杂性:
使用Socket类时,需要手动处理底层的网络通讯细节,包括数据的分包和组包、错误处理等。
而TcpClient和TcpListener提供了更简单、更高级别的API来实现TCP通信,隐藏了这些底层细节,并提供了更方便的方法来发送和接收数据。
(4)总结
总结来说,TcpClient和TcpListener提供了更简单、更高级别的API来实现TCP通信。它们封装了Socket类的一部分功能,隐藏了底层的网络通讯细节,使得开发人员可以更专注于业务逻辑的实现,而不必过多关注网络编程的复杂性。
(5)TcpListener类:主要负责创建服务器 TcpClient类:主要负责创建客户端
2_重要API
(1)实例化 TcpListener server = new TcpListener(ip,port);
TcpClient tcpClient = new TcpClient();
(2)启动侦听,建立连接 server.Start() tcpClient.Connect(ip,port);
(3)等待连接,接收数据,数据量,拿Socket(IP和Port)
TcpClient client = server.AcceptTcpClient() //获取客户端
stream.DataAvailable判断是否有可用数据,有返回true
client.Available客户端发送的数据量(字节数)
(4)Task,任务Token,取消任务,跨线程访问控件使用Invoke()
NetworkStream stream = client.GetStream() stream.Read(buffer, 0, buffer.Length)
client.Client属性拿到客户端对应的Socket实例。
client.Client.RemoteEndPoint 获取远程的终结点
RemoteEndPoint 获取此TCP连接另一端的终结点信息。它告诉你当前连接到的服务器(或客户端)的IP地址和端口号。
在客户端,用于确认你确实连接到了正确的服务器。
在服务器端(TcpClient来自于TcpListener.AcceptTcpClient()),用于获取连接上来的客户端的地址,以便进行日志记录、身份验证或访问控制。
client.Client.LocalEndPoint 获取本地的终结点
(5)发送数据 NetworkStream stream = client.GetStream() stream.Write(buffer, 0, buffer.Length)
(6)关闭 server.Stop() cts.Cancel()
(7)分包:获取响应报文后,解析数据的过程。
组包: 发送数据前,封装请求报文的过程。
3_实例代码
(1)服务端TcpListener tcpListener = null;
CancellationTokenSource cts = null;
//启动服务器
private void button1_Click(object sender, EventArgs e)
{if (button1.Text == "启动"){StartServer(); //启动AccepRequest();//接收请求}else StopServer();//关闭
}
}
//启动服务器
private void StartServer()
{try{//1.创建服务器IPAddress ip = IPAddress.Parse("192.168.40.1");int port = 9999;tcpListener = new TcpListener(ip, port);//2.启动tcpListener.Start(); //Socket.Bind Socket.listen()//简写// new TcpListener(IPAddress.Parse("127.0.0.1"), 9999).Start();//更改控件文本button1.Text = "关闭";}catch (Exception ex){throw ex;}
}
//接收客户端的请求
private void AccepRequest()
{//1.先开线程 获取客户端(循环获取多个客户端) 外层循环//2.先开线程 获取客户端的请求(循环获取一个客户端多个请求) 外层循环cts = new CancellationTokenSource();Task.Run(async () =>{while (!cts.IsCancellationRequested){//Pending() 判断是否有客户端连接 有 则返回trueif (!tcpListener.Pending()) continue;//AcceptTcpClient() 获取客户端 同步获取// TcpClient tcpClient = tcpListener.AcceptTcpClient();TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync();AccepData(tcpClient);}}, cts.Token);
}
//接收数据
private void AccepData(TcpClient tcpClient)
{Task.Run(async () =>{while (!cts.IsCancellationRequested){if (!tcpClient.Connected || tcpClient.Available == 0) continue;// 读取或者写入数据的前提 : 获取流//获取流的前提 获取 tcpClient//GetStream() 获取发送和接收数据流对象NetworkStream stream = tcpClient.GetStream();//tcpClient.Available 是某个客户端发送的数据量 (某个客户端可以使用的数据量)// stream.DataAvailable 判断是否有可用数据,有返回truebyte[] buffer = new byte[tcpClient.Available];//同步读取// stream.Read(buffer, 0, buffer.Length);int count = await stream.ReadAsync(buffer, 0, buffer.Length);if (count == 0) continue;//解析并显示Invoke(new Action(() =>{string data = Encoding.UTF8.GetString(buffer);// 获取到客户端的终结点(ip+port)richTextBox1.Text += $"{tcpClient.Client.RemoteEndPoint},{data}" + Environment.NewLine;}));//data 是客户端发送过来的 请求帧//使用串口发请求帧发送给设备//获取到设备的响应帧//把响应帧转发给客户端//服务器接收到数据后,原封不动返回给客户端 // stream.Write(buffer, 0, buffer.Length);}}, cts.Token);
}
private void StopServer()
{//1.停止任务cts?.Cancel();
//2.停止服务器tcpListener.Stop();//3.还原控件的文本button1.Text = "启动";
}
(2)客户端
TcpClient tcpClient = null;
IPEndPoint remoteEP = null;
CancellationTokenSource cts = null;
private async void button1_Click(object sender, EventArgs e)
{if (button1.Text == "连接"){await ConnectServer();//连接服务器AcceptResponse();//接收数据}else{DisConnectServer();//停止连接}
}
//连接服务器
private async Task ConnectServer()
{/* //终结点 身份 ip+prot IPEndPoint LocalEP =new IPEndPoint(IPAddress.Any,0);//客户端//启动服务器,暂存一下服务器的身份 展示数据的时候使用remoteEP = new IPEndPoint(IPAddress.Parse("192.168.40.1"), 9999);tcpClient = new TcpClient(LocalEP);*///连接服务器//await tcpClient.ConnectAsync(IPAddress.Parse("192.168.40.1"), 9999);tcpClient = new TcpClient();await tcpClient.ConnectAsync(IPAddress.Parse("192.168.40.1"), 9999);button1.Text = "中断";
}
//接收服务器的响应
private void AcceptResponse()
{cts = new CancellationTokenSource();Task.Run(async () =>{while (!cts.IsCancellationRequested){try{if (!tcpClient.Connected || tcpClient.Available == 0) continue;NetworkStream stream = tcpClient.GetStream();byte[] buffer = new byte[tcpClient.Available];int count = await stream.ReadAsync(buffer, 0, buffer.Length);if (count == 0) continue;Invoke(new Action(() =>{string data = Encoding.UTF8.GetString(buffer);richTextBox1.Text += $"{tcpClient.Client.RemoteEndPoint},{data}" + Environment.NewLine;}));}catch (Exception){throw;}}}, cts.Token);
}
//中断连接服务器
private void DisConnectServer()
{cts?.Cancel();tcpClient?.Close();button1.Text = "连接";
}
//发送信息
private void button2_Click(object sender, EventArgs e)
{if (tcpClient != null && tcpClient.Connected){NetworkStream stream = tcpClient.GetStream();string msg = textBox1.Text;byte[] buffer = Encoding.UTF8.GetBytes(msg);stream.Write(buffer, 0, buffer.Length);richTextBox1.Text = $"我是{tcpClient.Client.LocalEndPoint}{msg}"+Environment.NewLine;}else{MessageBox.Show("先连接服务器,再发送");return;}
}
4_建议阅读
https://www.toutiao.com/article/7269608473183322643 https://www.cnblogs.com/wxhao/p/13636401.html https://www.cnblogs.com/seabluescn/p/12972632.html https://blog.csdn.net/zhujunxxxxx/article/details/44261497 https://www.toutiao.com/article/7194274780433269303
TCP通讯过程: https://blog.csdn.net/weixin_44899642/article/details/129749935
