简述
TCP/IP(transmission control protocol/internet protocol)传输控制协议与互联网协议,这是第一次在项目中使用TCP/IP,用于和各种下位机进行通信。对于TCP/IP协议也只了解一些概念性的知识,在项目中主要思考的是服务端与客户端连接以及数据传输的稳定性问题。
比如服务端怎么去处理新的客户端连接?如何去接收每个客户端发送的消息?如何去处理每个客户端发送的消息?客户端怎么去连接服务端?怎么在被断开后自动重连?重连的最大限度是多少?
基础知识就不详述了,下面提供两个参考链接:
TCP/IP基础知识:link.
System.Net.Sockets:link
TCP Server
开启监听
开启监听很简单直接使用.net System.Net.Sockets下的TcpListener,实例化添加IP地址及监听断开即可。建议单启一个线程去开启监听,当然如果服务端功能单一也可以就放在主进程中。
{
IPEndPoint end_Point = new IPEndPoint(IPAddress.Parse(server_IP), server_Port);
TcpListener Listener = new TcpListener(end_Point);
Listener.Start();//开启监听
}
接收客户端
如何接收连接?
TcpListener 提供了多种方法,常用到的是 AcceptTcpClient() //接受挂起的连接请求。但是这个方法的弊端在于是同步阻塞式,你需要自己去想办法解决多个客户端大量请求下的效率问题。下面是较为常用的方法:
private Semaphore semap = new Semaphore(5, 1000); //线程量管控,控制的资源初始和最大线程并发数
public void Start()
{
try
{
Listener.Start();
Thread Accth = new Thread(new ThreadStart(
delegate
{
while(true)
{
if(IsStop!=false)
{
break;
}
GetAcceptTcpClient();
Thread.Sleep(1);
}
}
));
Accth.Start();
}
catch(SocketException ex)
{
SocketsControl sks = new SocketsControl(); //自定义的一个socket对象
sks.ex = ex;
RevData(sks); //消息处理
}
}
private void GetAcceptTcpClient()
{
try
{
if(Listener.Pending())//如果有挂起请求
{
semap.WaitOne();//阻塞当前线程
TcpClient tcpClient = Listener.AcceptTcpClient();//接收到挂起的客户端请求链接
Socket socket = tcpClient.Client;
NetworkStream stream = new NetworkStream(socket,true);获取客户端网络流
SocketsControl sks = new SocketsControl(tcpClient.Client.RemoteEndPoint as IPEndPoint,tcpClient,stream);
sks.NStream.BeginRead(sks.RecBuffer,0,sks.RecBuffer.Length,new AsyncCallback(EndReader),sks);//客户端异步接收消息处理
semap.Release();//释放当前阻塞
}
}
catch
{
return;
}
}
上面这段代码就是开启了一个线程用while(true)的方式去查看是否有客户端挂起请求,检查到有新连接就添加到监听队列。对于消息的处理采用了Network Stream自身的异步方法然后回调一个消息处理方法EndReader并在方法中自我调用,实现实时消息监听。
详细示例可以看这位兄台的文章:link
我在项目中并没有采用这种方法,而是使用了TcpListener的异步操作方法BeginAcceptTcpClient(),原因当然不是为了少写代码主要考虑到连接的客户端数量以及发报频率高的场景下系统要保证足够的稳定。
代码如下:
public event Action<TcpClient> On_Client_Connected;//声明连接事件
private ManualResetEvent _Mre = new ManualResetEvent(false);//默认阻塞
public void Start()
{
Thread th = new Thread(new ThreadStart(()=>
{
try
{
_Begin_Monitor = true;
Listener.Start();
While(_Begin_Monitor)//如果在主进程中开启监听就不需要while(true)
{
_Mre.Reset();//设置线程为非终止即可阻塞
Listener.BeginAcceptTcpClient(ClientConnected,Listener);//开启一个异步的客户端接收,回调连接方法
_Mre.WaitOne();//阻塞等待客户端连接
}
}
catch(Exception ex)
{
//自己写事件
}
}
));
th.Start();
}
private void ClientConnected(IAsyncResult result)
{
_Mre.Set();//释放阻塞
try
{
if(On_Connect_Client != null)
{
TcpListener _listener = (TcpListener)result.AsyncState;
TcpClient client = Listener.EndAcceptTcpClient(result);
On_Connect_Client.BeginInvoke(client,null,null);//把客户端委托推送到上层的操作中去,上层操作可包含很多比如消息,断连等
}
}
catch(Exception ex)
{
//自己写事件
}
}
这里代码不多因为这里只是将挂起的客户端推送到上层操作中,上层操作才是真正的重点。项目中的绝大数业务都被扩展成事件来触发,因为项目业务本身就是在不停等待和检测之间循环,封装后的事件委托可以极大的减少不必要的资源消耗也极大的简化过程。
消息处理
下面写上层的消息处理机制,项目中采用的是一个客户端连接即新启一个对应线程,当然可以像最开始一样直接用递归异步回调的方法去一直监听消息和触发事件,两种写法都在不同的项目中用到过。上传的Demo里面也包括了这两种写法的动态库,这里只介绍前者。
建立一个接口函数包括消息字段里的字节含义
public interface IContract
{
//协议头
ushort Head {get;set;}
//消息类型
CMDType cmdtype {get;set;}
//参数
byte[] Params {get;set;}
//协议尾
ushort End {get;set;)
}
public enum CMDType
{
//询问
Enquire_Packet = 0x12D,
//应答
Response_Packet = 0x12E,
NUll = -1,
}
在定义一个契约接口,用来匹配消息类型并调用处理方法
public interface IProcessor
{
//处理契约函数
void Handling(IContract contract);
}
public class ResponseProcessor : IProcessor
{
public static event Action<IContract> On_Respond_DataRecived;
public void Handling(IContract contract)
{
if(On_Respond_DataRecived != nll)
{
On_Respond_DataRecived.BeginInvoke(contract,null,null);
}
}
}
针对每一个Client端都开启一个线程所有先建立一个监听Client端的类,及消息数据分析类
public delegate void DisConnecedHandle(ListenClient sender);
public delegate void DataReceivedHandle(ListenClient sender, byte[] data);
public class ListenClient
{
public event DisConnecedHandle Disconnected;
public event DataReceivedHandle DataReceived;
public event ContractMatchSuccessHandle OnContractMatchSuccess;
private TcpClient _Client;
private DataAnalyzer _Analyzer = new DataAnalyzer();
public TcpClient Client { get { return _Client; } }
public static void StartListen(TcpClient tcpclient)
{
//开启线程实时读取该客户端消息
}
}
public class DataAnalyzer
{
private byte[] _Datas = new byte[0];
//开启线程实时解析_Datas
}
最上层的调用和开启
static void Main(string[] args)
{
AsyncTcpServer server = new AsyncTcpServer("127.0.0.1","9302");
server.Start();//开启服务
server.On_Client_Connected += _Listen_On_Client_Connected; //订阅事件
}
static void _Listen_On_Client_Connected(TcpClient obj)
{
ListenClient listenClient = new ListenClient();
listenClient.Disconnected += card_Disconnected;
listenClient.OnContractMatchSuccess += card_OnContractMatchSuccess;
listenClient.StartListen(obj);
}
//匹配契约
private void card_OnContractMatchSuccess(IContract contract)
{
GetProcessor(contract).Handling(contract);
}
//处理契约函数
private static IProcessor GetProcessor(IContract contract)
{
下载
完整的Demo项目文件放在下面的链接中,两种TCP Server的模式都写了。
下载链接link
关于知识点的备注:
Semaphore
ManualResetEvent & AutoResetEvent
event