现有的语言工具已经非常成熟,在做任何事件的最好是了解一下做这个功能需要用到那些基础内容。在.NETCore下编写TCP应用你需要关注一个Socket对象,它提供相关API来完成网络通讯上的所有工作。了解它最好的办法就是看SDK文件,对于.Netcore来说当然是看MSDN了(https://docs.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socket?view=netcore-3.1)。
Socket实际所承担的工作主要网络不同协议数据的服务监听,连接创建,数据读写等操作;对于不同的应用协议有着不同的操作方式,由于组件是基于TCP应用协议,所以后面所讲术的功能内容都是针对TCP的(如果你想了解其他功能如UDP等可以看上面提供的连接)。
Bind
绑定地址端口,这方法一般用于服务端,指向那个IP端口对外接受请求服务。
public void Bind(EndPoint localEP)
具体使用如下
IPEndPoint = new System.Net.IPEndPoint(IPAddress.Any, 8080);Socket = new Socket(IPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);Socket.Bind(IPEndPoint);
以上代码定义一个TCP网络套字节处理对象并绑定所有IP地址上的8080端口。
绑定IPv6
以上代码只针对的传统互联网的IPv4,但现在的IPv6已经普及,所以在服务处理上也需要对IPv6的支持;针对IPv6兼容的处理非常简单,大概代码如下:if (Socket.OSSupportsIPv6 && Server.Options.UseIPv6){address = IPAddress.IPv6Any;}else{address = IPAddress.Any;}
只需要判断一下系统否支持IPv6并结合需求的定义来设置IPv6Any即可;但有时候需要同时支持v4和v6,这个时候需要在Socket监听前设置以下属性:
Socket.DualMode = true;
客户端绑定
Bind方法大多数情况下都用在服务端,但有时也用在Client;而这种需求更多是确认自己使用那个地址端口进行对外访问,只要在Connect前先Bind一个地址端口即可。Listen
对于服务端来说绑定服务地址后还需要做监听工作,通过Listen方法来开启监听服务,这个时候服务就可以接受连接并存放到监听队列中。
Socket.Bind(IPEndPoint);Socket.Listen(512);
方法参数带上一个数值,这个值主要描述监听队列的容量,超过就会拒绝后面的连接。
Accept
当服务进行监听后,就需要通过Accept方法来接管请求的连接并进行后续的工作。Socket提供了几种Accep行为,主要包括:
同步Accept
同步接入,这种接收要占用线程等待,简单来说需要一个固定的线程来处理。while (true){try{var acceptSocket = Socket.Accept();AcceptSocketInfo item = new AcceptSocketInfo();item.Socket = acceptSocket;item.Listen = this;mAcceptCallBack(item);}catch (Exception e_){//...}}
以上是BeetleX的同步接入代码,虽然同步独占一个线程但它有着高效的Accept接管效率,可以更快速地从监听队列中获取连接;但往往在Accept后需要处理很多逻辑,所以在接管连接后不适合在当前线程处理(后面章节会讲述)。
异步AcceptAsync
基于SocketAsyncEventArgs实现的异步接入(建议使用,由于是异步处理如果想进快处理监听队列可以尝试同步)。mAcceptEventArgs.AcceptSocket = null;if (!Socket.AcceptAsync(mAcceptEventArgs)){mAsyncAccepts++;OnAcceptCompleted(this, mAcceptEventArgs);}else{mAsyncAccepts = 0;}mAccetpError = 0;
以上是组件对于异步接受连接的处理,在使用SocketAsyncEventArgs前把AcceptSocket清空。有没发现以上代码有mAsyncAccepts一个计数呢?主要原因是AcceptAsync是有可能同步完成的,当连续同步完成就可以会引发栈溢出的风险,所以组件在处理会做一个计数当达到一定量的情况就启用线程来处理来断开递归引起的栈溢出异常。
(组件同时提供两种方式选择,主要看实际需要)异步BeginAccept
这种方式基本都对旧的兼容保留,实际应用中建议用采AcceptAsyncConnect
些方法主要提供给客户端用,它是用于创建指定服务地址的连接。
Socket = new Socket(mIPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);if (LocalEndPoint != null)Socket.Bind(LocalEndPoint);Socket.Connect(mIPAddress, mPort);
以上是组件客户端部分的使用,对于Connect所提供方法比较多,主要是可以根据Host来进行连接;当使用Host解释IP的时候需要关注一个问题不要忘记处理IPv6,可以简单判断AddressFamily为InterNetworkV6和InterNetwork .
Shutdown和Disconnect
Shutdown用于停止Socket的读写操作,可以单独停止其中一个操作;而Disconnect则是断开连接,方法需要提供一个布尔值参数,如果关闭当前连接后可以重用此套接字,则为 true;否则为 false。
Dispose
释放当前Socket, Dispose 方法使 Socket 处于不可用状态,相关引用也要清空主要确保GC回收相关资源。
Send
针对当前Socket进行一个数据发送操作,些行为提供同步和异步两种不同方法。同步的优点是适合局域网小量连接高效的吞吐应用,由于同步需要挂起线程所以并不适合存大量连接的通讯交互场景,这样会导致大量线程开销影响总体性能。虽然异步的响应存会损一点点的时效性但这微小的差距确可以获取更多服务连接支撑,可以让服务得到更多在线更大吞吐的处理;综合来说选择用异步处理是相对来说是比较好的选择。所以组件处理发送都是采用基于SocketAsyncEventArgs的SendAsync方法来处理,应用代码大概如下:
if (!socket.SendAsync(saea)){onSendCompleted(null,saea);}
其实就算使用异步发送也可能存在同步完成的情况,在使用SendAsync的时候一定要注意返回值,当返回为False的情况说明已经同步完成不会触发完成事件,从需要手动调用。
注意:不管是同步或异步都要判断发送完成的字节数,当没发送完成即需要在原缓存做一下偏移,把没有发送的内容继续发关
Receive
针对当前Socket进行一个数据接收操作,和发送一样提供同步和异步两种方法。优缺点和发送前面描述的是一样,在使用中尽量采用异步来处理。以下是使用SocketAsyncEventArgs的ReciveAsync方法处理代码:
if (!socket.ReceiveAsync(saea)){onReceiveCompleted(null,saea);}
一样需要判断返回值,判断一下是不是同步完成.
以上描述的是Socket针对TCP的基础操作功能,实际上还有更多的功能配置如SetOption,NoDelay配置等等。而你并不需要完全了解所有功能才能用,当碰到需要完成一些功能的时候又不会做,那去MSDN找一下自然没错,那里有着很多你想了解的文档资料。

