send方法如果同时有两个在运行那么传到缓冲区的数据会不会相互交叉现象
比如第一个send方法传送aaaaaaaaaaa,同时第二个send方法(可能是另一个线程的)传送bbbbbbbbb,那么缓冲区会不会字节队列为aaaaabbbbbaaaaabbbbbb呢?
比如第一个send方法传送aaaaaaaaaaa,同时第二个send方法(可能是另一个线程的)传送bbbbbbbbb,那么缓冲区会不会字节队列为aaaaabbbbbaaaaabbbbbb呢?
这有很多种可能的做法。一种:每次发数据其实都new一个新的socket实例,并且Bind本地IPEndPoint时使用 IPEndPoint.Any(既随机分配一个未使用的本地端口),然后连接远程IPEndPoint,然后send。这样,服务器端在不同的Accept处理中作为不同的socket来处理的,不会在同一个Receive操作时有任何交叉。当然,客户端send完了数据,接收返回值,之后就可以立即关闭连接。另一种:多线程但是共享同一个socket实例,显然send之后并不会关闭连接。这样服务器端就会在同一个接收动作中完全可能包含不只一个send内容。服务器端在Receive时,不管buffer设置的有多少,它都要循环读取tcp底层的数据,直到socket.Avaliable不大于0,写成代码类似于using(var dest=new MemoryStream())
{
while(socket.Avaliable>0)
{
len=socket.Receive(buffer);
dest.Write(buffer,0,len);
};
送去处理接收到的数据(dest.ToArray());
};
而“送去处理处理接收到的数据”并不一定仅仅有客户端一次send的值,有可能有两次send的值,甚至当网络实在是非常繁忙并且接收端的buffer的大小比发送端的buffer的大小更小时完全可能连一个send值都没有接受完全就已经满足socket.Avaliable==0的条件了!因此dest中接收到的需要放入一个更大的缓冲区中,然后从这个更大的缓冲区中取出第一个消息内容(因此设计师需要预先设计通信协议,并且从一堆字节中解析出一条单独的消息),处理消息。而取出了完整消息之后,剩下的字节仍然在缓冲区中,等待下一次再送来新的数据添加到缓冲区的时候,在去检查是否有完整的消息需要处理。基本上,第一种短连接的方式及其简单方便,并且更加可靠。缺点是可能不如第二种快速,因为tcp socket的发起、发送数据、握手和接收数据的整个机制都像一个慢性子的人,目的是为了减少网络上的数据包数量同时又提供可靠的连接。第二种长连接方式显然更复杂,需要更多编程。
最后,其实可以使用udp传送数据,udp不存在这种多个send交叉如一个receice的问题。但是udp是不可靠的通讯,不能保证数据真的传送到位(你可以假设成功率只有50%并且即使对方收不到数据你的程序也不会有任何异常抛出),因此需要你自己设计更多的业务逻辑来进行机器之间的协调控制。
心跳是应用层,完全看设计师怎么设计通信协议。假设服务器端只需要知道客户端的IPEndPoint就能记录客户端所绑定的应用程序(或者在线用户)的活跃时间(超过一定时间则可以认为是离线了),那么其实可以设计一个“什么都不做”的指令,例如一个简单的“换行回车”,客户端发送一个什么都不做的指令,服务器只是用来更新客户端的状态记录,而不会执行任何其它的动作。而所有其它业务指令其实都可以具有更新客户端活跃状态记录的作用,只不过我们特意设计一个“什么都不做”的空指令特别地叫做心跳消息罢了。
短连接比较简单,我写两三句短连接代码:客户端,假设我们要把一个门禁刷卡信息发送到服务器,这个指令的协议格式是:110 刷卡机编号 刷卡者的描述信息 刷卡时间,其中110是指令的标识,然后所有信息之间都用换行回车分割(信息内部不能包含换行回车),最后以一个换行回车结束,那么发送消息可以写(让我们使用.net专门为tcp封装的更高级一点的对象来操作):public void 发送门禁刷卡消息(long m,string p,DateTime t)
{
var x = new TcpClient(AddressFamily.InterNetwork);
x.Connect(host, port); //远程服务器地址host和port,在class中定义。
var sendString=string.Format("110/r/n{0}/r/n{1}/r/n{2}/r/n/r/n",m,p,t);
var sendDatas=Encoding.UTF8.GetBytes(sendString);
var st = x.GetStream();
st.Write(sendDatas, 0, sendDatas.Length);
st.Flush();
return st;
}而服务器端每当处理Accept,然后使用所得到的socket对象进行Receive操作,得到的就是(并且只是!)一个完整的指令,在上述“送去处理接收到的数据"操作时只要判断所接收到的数据最后是两个换行回车即可逐行取出信息,并且根据第一个指令标识(这里是110)送去不同的命令处理程序处理余下的参数。