科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件.NET下可复用的TCP通信层实现之TCP组件

.NET下可复用的TCP通信层实现之TCP组件

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

以前就很想将自己在Tcp通信层的开发心得、经验共享出来,但一直没有实现,究其原因,还是自己太懒了

作者:zhuweisky 来源:博客园 2007年11月4日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
3.Tcp组件基本元素实现

  在实现Tcp组件之前,有一些基本元素需要先建立起来,比如安全的网络流、Tcp监听器、用户连接上下文、上下文管理者等。(1)安全的网络流SafeNetworkStream

  前面已经提到过,为了能在Tcp组件外部 对指定的连接发送数据,必须保证我们的Tcp连接是线程安全的,而System.Net.Sockets.NetworkStream是非线程安全的,我们必须自己对其进行封装,以保证这一点。System.Net.Sockets.NetworkStream的线程安全的封装就是EnterpriseServerBase.Network.SafeNetworkStream类,它继承了ISafeNetworkStream接口:

/// <summary>
/// ISafeNetworkStream 线程安全的网络流 。
/// 注意:如果调用的异步的begin方法,就一定要调用对应的End方法,否则锁将得不到释放。
/// 作者:朱伟 sky.zhuwei@163.com
/// </summary>
public interface ISafeNetworkStream :ITcpSender ,ITcpReciever
{
 void Flush();
 void Close() ;
}

//用于在TCP连接上发送数据,支持同步和异步
public interface ITcpSender
{
 void Write(byte[] buffer ,int offset ,int size) ;
 IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state );
 void EndWrite(IAsyncResult asyncResult );
}

//用于在TCP连接上接收数据,支持同步和异步
public interface ITcpReciever
{
 int Read (byte[] buffer ,int offset ,int size) ;
 IAsyncResult BeginRead( byte[] buffer, int offset, int size, AsyncCallback callback, object state );
 int EndRead(IAsyncResult asyncResult );
}

  该接口几乎与System.Net.Sockets.NetworkStream提供的方法一样,只不过它们是线程安全的。这样,针对同一个SafeNetworkStream,我们就可以在不同的线程中同时在其上进行数据接收/发送(主要是发送)了。

  (2)Tcp监听器EnterpriseServerBase.Network.XTcpListener

  不可否认,System.Net.Sockets.TcpListener只是提供了一些最低阶的工作,为了将监听线程、端口、监听事件整合起来,我引入了EnterpriseServerBase.Network.XTcpListener类,它可以启动和停止,并且当有Tcp连接建立的时候,会触发事件。XTcpListener实现了IXTcpListener接口,其定义如下:

public interface IXTcpListener
{
 void Start() ; //开始或启动监听线程
 void Stop() ; //暂停,但不退出监听线程

 void ExitListenThread() ;//退出监听线程

 event CBackUserLogon TcpConnectionEstablished ; //新的Tcp连接成功建立
 event CallBackDynamicMsg DynamicMsgArrived ;
}

  XTcpListener可以在不同的Tcp组件中复用,这是一种更细粒度的复用。

  (3)用户连接上下文ContextKey

  ContextKey用于将所有的与一个用户Tcp连接相关的信息(比如接收缓冲区、连接的状态――空闲还是忙碌、等)封装起来,并且还能保存该用户的请求中上次未处理完的数据,将其放于接收缓冲区的头部,并与后面接收到的数据进行重组。说到这里,你可能不太明白,我需要解释一下。Tcp协议可以保证我们发出的消息完整的、有序的、正确的到达目的地,但是它不能保证,我们一次发送的数据对方也能一次接收完全。比如,我们发送了一个100Bytes的数据,对方可能要接收两次才能完全,先收到60Bytes,再收到40Bytes,这表明我们可能会收到“半条”消息。还有一种情况,你连续发了两条100Bytes的消息,而对方可能一次就接收了160Bytes,所以需要对消息进行分裂,从中分裂出完整的消息然后进行处理。这,就是前面所说的需要对消息进行分裂、重组的原因。知道这点后,IContextKey接口应该比较容易理解了,因为该接口的很多元素的存在都是为了辅助解决这个问题。IContextKey的定义如下:

public interface IContextKey
{
 NetStreamState StreamState{get ;set ;} //网络流的当前状态--空闲、忙碌
 ISafeNetworkStream NetStream{get ;set ;}

 byte[] Buffer{get ;set ;} //接收缓冲区
 int BytesRead{get ;set ;} //本次接收的字节数
 int PreLeftDataLen{get ;set ;}
 bool IsFirstMsg{get ;set ;} //是否为建立连接后的第一条消息

 int StartOffsetForRecieve{get ;}
 int MaxRecieveCapacity{get ;} //本次可以接收的最大字节数
 RequestData RequestData{get ;}

 void ResetBuffer(byte[] leftData) ;
 //leftData 表示上次没有处理完的数据,需要与后面来的数据进行重组,然后再次处理
}

  对于消息的分裂和重组是由消息分裂器完成的,由于Tcp组件的实现不需要使用消息分裂器,所以消息分裂器的说明将在后面的消息分派器实现中讲解。

  (4)上下文管理者ContextKeyManager

  ContextKeyManager用于管理所有的ContextKey,其实现的接口IContextKeyManager很容易理解:

public interface IContextKeyManager
{
 void InsertContextKey(ContextKey context_key) ;
 void DisposeAllContextKey() ;
 bool IsAllStreamSafeToStop() ; //是否可以安全退出
 void RemoveContextKey(int streamHashCode) ;
 int ConnectionCount {get ;}
 ISafeNetworkStream GetNetStream(int streamHashCode) ;
 event CallBackCountChanged StreamCountChanged ;
}

  在上述四个基本元素的支持下,再来实现Tcp组件就方便了许多,无论是以何种方式(如完成端口模型、异步方式)实现Tcp组件,这些基本元素都是可以通用的,所以如果你要实现自己的Tcp组件,也可以考虑复用上述的一些基本元素。复用可以在不同的粒度进行,复用真是无处不在,呵呵。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章