简介
- TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在C#中,TCP程序设计是指利用Socket类、TcpClient类和 TcpListener类编写的网络通信程序,这3个类都位于System.Net.Sockets命名空间中。利用TCP协议进行通信的两个应用程序是有主次之分的,一个称为服务器端程序,另一个称为客户端程序。本文主要讲解TcpClient和TcpListener。
- 代码示例要求:局域网内一个服务端对多个客户端的tcp通讯,服务端监听展示目前上线的设备,同时需要时向某个设备发送一些指令,设备上线后每隔5秒进行一次心跳通讯,心跳数据为基本字符串信息。
TcpClient 类
- TcpClient 类提供了一个更高级别的抽象,它封装了底层的 Socket 功能,使得TCP网络通信的编写更加简单和直观。TcpClient 主要用于客户端/服务器模式中的客户端部分。
属性及方法 | 说明 |
---|---|
Available属性 | 获取已经从网络接收且可供读取的数据量 |
Client属性 | 获取或设置基础Socket |
Connected属性 | 获取一个值,该值指示TepClient的基础Socket是否已连接到远程主机 |
RecieveBufferSize属性 | 获取或设置接收缓冲区的大小 |
RecieveTimeout属性 | 获取或设置在初始化一个读取操作后TcpClient等待接收数据的时间量 |
SendBufferSize属性 | 获取或设置发送缓冲区的大小 |
SendTimeout属性 | 获取或设置TcpClient等待发送操作成功完成的时间量 |
BeginConnect方法 | 开始一个对远程主机连接的异步请求 |
Close方法 | 释放此TcpClient实例,而不关闭基础连接 |
Connec方法 | 使用指定的主机名和端口号将客户端连接到TCP主机 |
EndConnect方法 | 异步接受传入的连接尝试 |
GetStream方法 | 返回用于发送和接收数据的NetworkStream |
示例代码
public class YZTCPClient
{
// 单例模式
private static readonly YZTCPClient instance = new YZTCPClient();
// TcpClient对象
private TcpClient tcpClient;
private string ipAddress;
private int port;
// Task取消令牌
private CancellationTokenSource cts;
// 读取长度
private readonly int readLength = 1024;
// 客户端用户名
private readonly string userName = ConfigurationManager.AppSettings["UserName"];
// 事件
public event Action<string> EventReceiveMessage;// 接收到服务端消息
public event Action<bool> EventConnectState;// 连接状态
private YZTCPClient()
{
tcpClient = new TcpClient();
}
public static YZTCPClient GetInstance()
{
return instance;
}
public void Connect(string ipAddress, int port)
{
this.ipAddress = ipAddress;
this.port = port;
if (tcpClient.Connected)
{
Debug.WriteLine($"Already connected to server:{tcpClient.Connected}");
EventConnectState?.Invoke(tcpClient.Connected);
return;
}
Debug.WriteLine("Begin connect to server");
Task.Factory.StartNew(() =>
{
while (!tcpClient.Connected)
{
try
{
// 客户端连接
tcpClient.Connect(ipAddress, port);
}
catch (Exception ex)
{
Debug.WriteLine("Failed to connect to server:{0}", ex.Message);
EventConnectState?.Invoke(tcpClient.Connected);
}
finally
{
Thread.Sleep(1000);// 连接失败后停止1秒
}
}
// todo:三次握手处理
EventConnectState?.Invoke(tcpClient.Connected);
Debug.WriteLine("Successed to connect to server:{0}", tcpClient.Connected);
try
{
cts = new CancellationTokenSource();
Task.Factory.StartNew(() =>
{
// 开始心跳
SendHeartBeat();
}, cts.Token);
Task.Factory.StartNew(() =>
{
// 处理收到的服务端消息
ListenForMessage();
}, cts.Token);
}
catch (Exception ex)
{
Debug.WriteLine("Task.Factory.StartNew Fail:{0}", ex.Message);
throw ex;
}
});
}
private void ListenForMessage()
{
try
{
NetworkStream stream = tcpClient.GetStream();
while (!cts.Token.IsCancellationRequested)
{
byte[] bytes = new byte[4096];
try
{
int bytesRead = stream.Read(bytes, 0, readLength);
if (bytesRead == 0)
{
break;
}
string message = "";
try
{
message = Encoding.UTF8.GetString(bytes, 0, bytesRead);
}
catch (Exception ex)
{
Debug.WriteLine($"GetString Fail:{ex.Message}");
}
Console.WriteLine($"Read Message:{message}");
EventReceiveMessage?.Invoke(message);
}
catch (Exception ex)
{
Debug.WriteLine($"NetworkStream Read Fail:{ex.Message}");
if (!tcpClient.Connected)
{
Debug.WriteLine("Server disconnect");
EventConnectState?.Invoke(tcpClient.Connected);
// 释放资源
cts?.Cancel();
tcpClient?.Close();
stream.Close();
tcpClient = null;
stream = null;
tcpClient = new TcpClient();
// 重新开启连接
Connect(this.ipAddress,this.port);
}
}
}
//stream.Close();
}
catch (Exception ex)
{
Debug.WriteLine($"GetStream Fail:{ex.Message}");
}
}
public void WriteMessage(byte[] data)
{
// todo: 发送数据
}
// 发送心跳
private void SendHeartBeat()
{
// 组装心跳信息
string message = AssembleHeartBeat();
// 获取一个网络流对象
try
{
NetworkStream stream = tcpClient.GetStream();
while (!cts.Token.IsCancellationRequested)
{
try
{
byte[] data = Encoding.UTF8.GetBytes(message);
//byte[] data = Encoding.ASCII.GetBytes(message);
try
{
Debug.WriteLine("Send HeartBeat");
stream.Write(data, 0, data.Length);
Thread.Sleep(5000);
}
catch (Exception ex)
{
Debug.WriteLine($"NetworkStream Write Fail: {ex.Message}");
if (!tcpClient.Connected)
{
Debug.WriteLine("Server disconnect");
//EventConnectState?.Invoke(tcpClient.Connected);
//cts?.Cancel();
}
break;
}
}
catch (Exception ex)
{
Debug.WriteLine($"GetBytes Fail:{ex.Message}");
break;// 如果发现GetBytes失败,还是建议退出循环
}
}
//stream.Flush();
//stream.Close();
}
catch (Exception ex)
{
Debug.WriteLine($"GetStream Fail:{ex.Message}");
}
}
// 组装心跳数据
private string AssembleHeartBeat()
{
// todo:定义心跳数据,后期有时间可以使用二进制定协议
string message = ":";
try
{
var hostName = Dns.GetHostName();
// 设备名(主机名)
//message = hostName;
message = userName;
var ipAddresses = Dns.GetHostAddresses(hostName);
foreach (var ipAddress in ipAddresses)
{
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
// ipv4协议地址
var localIp = ipAddress.ToString();
message += ":" + localIp;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"AssembleHeartBeat Fail:{ex.Message}");
}
return message;
}
// 停止连接
public void Disconnect()
{
try
{
cts?.Cancel();
}
catch (Exception ex)
{
Debug.WriteLine($"Disconnect Fail:{ex.Message}");
throw ex;
}
//tcpClient?.Close();
}
}
TcpListener类
- 与TcpClient相似,TcpListener 主要用于客户端/服务器模式中的服务端部分。
属性及方法 | 说明 |
---|---|
LocalEndpoint属性 | 获取当前TcpListener的基础EndPoint |
Server属性 | 获取基础网络Socket |
AcceptSocket/AcceptTcpClient方法 | 接受挂起的连接请求 |
BeginAcceptSocket/BeginAcceptTcpClient方法 | 开始一个异步操作来接受一个传入的连接尝试 |
EndAcceptSocket方法 | 异步接受传入的连接尝试,并创建新的Socket来处理远程主机通信 |
EndAcceptTcpClient方法 | 异步接受传入的连接尝试,并创建新的TcpClient来处理远程主机通信 |
Start方法 | 开始侦听传入的连接请求 |
Stop方法 | 关闭侦听器 |
示例代码
public class YZTCPServer
{
// 单例模式
private static readonly YZTCPServer instance = new YZTCPServer();
private readonly string ipAddress = ConfigurationManager.AppSettings["IPAddress"];
private readonly int port = int.Parse(ConfigurationManager.AppSettings["Port"]);
private YZTCPServer()
{
try
{
tcpListener = new TcpListener(IPAddress.Parse(ipAddress), port);
}
catch (Exception ex)
{
Debug.WriteLine($"YZTCPServer Init Fail:{ex.Message}");
throw ex;
}
// 监听客户端列表
tcpClients.CollectionChanged += (sender, e) =>
{
Debug.WriteLine($"tcpclients change:{tcpClients.Count()}");
List<ClientInfo> list = tcpClients.Cast<ClientInfo>().ToList();
EventClients?.Invoke(list);
};
}
public static YZTCPServer GetInstance()
{
return instance;
}
private readonly TcpListener tcpListener;
// Task取消令牌
private CancellationTokenSource cts;
// 读取长度
private readonly int readLength = 1024;
// 客户端列表
//private readonly ObservableCollection<TcpClient> tcpClients = new ObservableCollection<TcpClient>();
private readonly ObservableCollection<ClientInfo> tcpClients = new ObservableCollection<ClientInfo>();
// 线程锁
private static readonly object lockObj = new object();
// 事件
public event Action<List<ClientInfo>> EventClients;
// 构造函数
//public YZTCPServer(string ipAddress, int port)
//{
// try
// {
// tcpListener = new TcpListener(IPAddress.Parse(ipAddress), port);
// }
// catch (Exception ex)
// {
// Debug.WriteLine($"YZTCPServer Init Fail:{ex.Message}");
// throw ex;
// }
// // 监听客户端列表
// tcpClients.CollectionChanged += (sender, e) =>
// {
// Debug.WriteLine($"tcpclients change:{tcpClients.Count()}");
// List<ClientInfo> list = tcpClients.Cast<ClientInfo>().ToList();
// EventClients?.Invoke(list);
// };
//}
// 开启服务
public void StartServer()
{
cts = new CancellationTokenSource();
try
{
Task.Factory.StartNew(ListenForClients, cts.Token);
}
catch (Exception ex)
{
Debug.WriteLine($"StartServer Fail:{ex.Message}");
throw ex;
}
}
// 监听客户端
private void ListenForClients()
{
try
{
tcpListener.Start();
while (!cts.Token.IsCancellationRequested)
{
Debug.WriteLine("Waiting for a new client connection");
try
{
TcpClient client = tcpListener.AcceptTcpClient();//AcceptTcpClient 是一个阻塞调用,它会等待直到有新的客户端连接
Debug.WriteLine("There is a client connection");
if (client != null)
{
ClientInfo info = new ClientInfo()
{
Client = client,
};
tcpClients.Add(info);
//tcpClients.Add(client);
try
{
Task.Factory.StartNew(() => HandleClientComm(client), cts.Token);
}
catch (Exception ex)
{
CloseClient(client);
Debug.WriteLine($"Task.Factory.StartNew HandleClientComm Fail:{ex.Message}");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"AcceptTcpClient Fail:{ex.Message}");
}
}
//tcpListener.Stop();这里永远不会执行,因为AcceptTcpClient阻塞了while循环
}
catch (Exception ex)
{
Debug.WriteLine($"TcpListener Start Fail:{ex.Message}");
}
}
// 处理客户端消息
private void HandleClientComm(TcpClient client)
{
try
{
// 获取网络流对象
NetworkStream stream = client.GetStream();
while (!cts.Token.IsCancellationRequested)
{
// 读取数据
byte[] bytes = new byte[readLength];
//byte[] bytes = new byte[client.ReceiveBufferSize];
try
{
int bytesRead = stream.Read(bytes, 0, bytes.Length);
if (bytesRead == 0)
{
break;
}
// todo:解包问题
// todo: 判断数据类型(心跳或别的消息)
string message = "";
try
{
message = Encoding.UTF8.GetString(bytes, 0, bytesRead);
//string message = Encoding.ASCII.GetString(bytes, 0, bytesRead);
}
catch (Exception ex)
{
Debug.WriteLine($"GetString Fail:{ex.Message}");
}
Console.WriteLine($"Read Message:{message}");
try
{
lock (lockObj)
{
ClientInfo item = tcpClients.FirstOrDefault(p => p.Client == client);
if (item != null)
{
int itemIndex = tcpClients.IndexOf(item);
string[] fruits = message.Split(':');
if (fruits.Length > 1)
{
item.Name = fruits[0];
item.IPAddress = fruits[1];
}
tcpClients[itemIndex] = item;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"FirstOrDefault Fail:{ex.Message}");
}
}
catch (Exception ex)
{
//在TCP/IP协议中,并没有直接的方法来检查连接的状态(如“已连接”、“已关闭”等)。
//连接状态是通过TCP协议栈的状态机来管理的,并且对于应用程序来说,通常只能通过尝试读写操作或接收异常来间接地了解连接的状态。
Debug.WriteLine($"NetworkStream Read Fail:{ex.Message}");
break;
}
}
stream.Close();
CloseClient(client);
Debug.WriteLine("Client Close");
}
catch (Exception ex)
{
CloseClient(client);
Debug.WriteLine($"GetStream Fail:{ex.Message}");
}
}
// 关闭客户端连接,从客户端列表删除
private void CloseClient(TcpClient client)
{
client.Close();
//tcpClients.Remove(client);
try
{
ClientInfo item = tcpClients.FirstOrDefault(p => p.Client == client);
lock (lockObj)
{
tcpClients.Remove(item);
}
}
catch (Exception ex)
{
Debug.WriteLine($"FirstOrDefault Fail:{ex.Message}");
}
}
// 结束服务
public void StopServer()
{
try
{
cts?.Cancel();
}
catch (Exception ex)
{
Debug.WriteLine($"Disconnect Fail:{ex.Message}");
throw ex;
}
//tcpListener.Stop();
//tcpClients.Clear();
}
}
public class ClientInfo
{
public TcpClient Client { get; set; }
// 客户端名字
public string Name { get; set; }
// 客户端ip地址
public string IPAddress { get; set; }
}
客户端使用示例:
private readonly string ipAddress = ConfigurationManager.AppSettings["IPAddress"];
private readonly int port = int.Parse(ConfigurationManager.AppSettings["Port"]);
public HomeWin()
{
InitializeComponent();
// 开启连接服务端
YZTCPClient.GetInstance().Connect(ipAddress, port);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
YZTCPClient.GetInstance().EventReceiveMessage += Client_EventReceiveMessage;
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
{
YZTCPClient.GetInstance().EventReceiveMessage -= Client_EventReceiveMessage;
}
private void Client_EventReceiveMessage(string obj)
{
Debug.WriteLine("处理服务端的消息:{0}", obj);
// 回到主线程
Application.Current.Dispatcher.Invoke(new Action(() =>
{
// 解析数据
}));
}
服务器端使用示例:
private List<ClientInfo> clientInfos = new List<ClientInfo>();
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("UserControl_Unloaded");
YZTCPServer.GetInstance().EventClients -= Server_EventClients;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("UserControl_Loaded");
// 开启tcp监听
YZTCPServer.GetInstance().StartServer();
YZTCPServer.GetInstance().EventClients += Server_EventClients;
}
private void Server_EventClients(List<ClientInfo> obj)
{
//Dispatcher.Invoke(new Action(() =>
Application.Current.Dispatcher.Invoke(new Action(() =>
{
clientInfos = obj;
clientDG.ItemsSource = obj;
}));
}
//Common.SysParas.client:需要发送消息到客户端时使用,此处是全局变量保存了某个客户端实例
private void bthPlayClient_Click(object sender, RoutedEventArgs e)
{
// 传递参数 让客户端执行播放
string tcpParam = "执行命令";
try
{
NetworkStream stream = Common.SysParas.client.GetStream();
byte[] data = Encoding.UTF8.GetBytes(tcpParam);
stream.Write(data, 0, data.Length);
//消息已发送到客户端
}
catch (Exception ex)
{
Debug.WriteLine($"Send TCP Message Fail:{ex.Message}");
}
网友评论