C# Socket Tcp 语音通讯程序设计

来源:互联网 发布:网络客服工资怎么算 编辑:程序博客网 时间:2024/06/09 20:51
核心关键词:压缩、序列化、粘包半包、封包拆包
整个流程就是:
一般事先新建一个类库,做好要发送的数据类,编写完成后打包成类库dll 提供给服务端和客户端程序封装使用(为程序添加引用请自行百度)

 

客户端u3d: 
 步骤:
1.获取声音数据(byte[])
       用自带的Microphone组件获取要传送的声音压缩成byte[]                    
2.将数据序列化
    利用上面自己封装好的dll库中数据类作为对象序列化成buffer字节数组,如下:


注意上面的红色框框处就是我们下一步的封包处理了

3.进行封包处理
    首先了解一下为什么要封包?
    基于socket TCP信息交互的两个问题:接收方不能主动识别发送方发送的消息类型  接收方也不能主动拆分发送方发送的多条消息
    基于以上两个问题会出现所谓的粘包和半包问题(因为在TCP中只有流的概念,没有包的概念)
Socket长连接:建立连接后可以连续发送多个数据包并进行维持
Socket短连接:建立Tcp连接,数据发送完成后立即断开Tcp连接
粘包:可能由发送方造成,也可能由接收方造成。TCP为提高传输效率,发送方往往要收集到足够多的数据才发送一包数据,造成多个数据包的粘连。如果接收进程不及时接收数据,已收       
         到的数据就放在系统接收缓冲区,用户进程读取数据就可能督导多个数据包。【长连接会出现】
半包:并发量较大的情况下,接收方可能没有接收到一个完整的包,只接受了部分,这种情况主要是由于TCP为提高传输效率,将一个包分配的足够大,导致接收方没有一次接受完

    解决以上的粘包和半包问题,我们可以约定好一个通讯协议,利用协议来进行分包和解包
   应用层数据包格式:

包头
包体 
数据包长度Len:数据包内容,长度为Len
包头存储的是数据包长度,这样做的长度是我们接收方接收到数据的时候可以根据包头的数据长度对数据进行循环接收,直到包体长度达到包头所指定的数据包长度时及判定为一个包。收到包之后就是拆包和反序列化的过程了。

如下封装好后发往服务端:



服务端:
先用socket建立简单的监听连接:大概就是绑定端口和IP,创建监听Socket等待客户端接入,一般都是新开一个后台线程接收客户端连接并建立新的socket,异步接收消息。
这里代码略过。。。

服务端开始接收:
代码块:
public void ReceiveMessage(Socket socket)        {            while(true){                if (!socket.Connected)                    break;                MessageData md = null;                //================================================ 接收包头 ==============================================
int headLength = 4;                //存储包头的所有字节数(这里的包头内容只包含了总数据包的长度)byte[] recvBytesHead = new byte[headLength];                while (headLength > 0)                {                    byte[] recvBytes1 = new byte[4];                    //将本次传输所接收的字节数置0int iBytesHead = 0;                    //如果当前需要接收的字节数大于缓存区大小,则按缓存区大小进行接收,相反则按剩余需要接收的字节数进行接收 
    if (headLength >= recvBytes1.Length)                    {                        iBytesHead = socket.Receive(recvBytes1, recvBytes1.Length, 0);                    }                    else {                        iBytesHead = socket.Receive(recvBytes1, headLength, 0);                    }                    //将接收到的字节数保存recvBytes1.CopyTo(recvBytesHead, recvBytesHead.Length - headLength);                    //减去已经接收到的字节数headLength -= iBytesHead;                }                //========================================== 接收包体 ==================================================
//从recvBytesHead中获取包体长度
byte[] bytes = new byte[4];                Array.Copy(recvBytesHead, 0, bytes, 0, 4);                int bodyLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(bytes, 0));                //存储数据内容的字节数组byte[] recvBytesBody = new byte[bodyLength];                //如果当前需要接收的字节数大于0,则循环接收while (bodyLength > 0)                {                    byte[] recvBytes2 = new byte[bodyLength < 1024 ? bodyLength : 1024];                    //将本次传输接收到的字节置0int iBytesBody = 0;                    //如果当前需要接收的字节数大于缓存区大小,则按缓存区大小进行接收,相反则按剩余需要接收的字节数进行接收  if (bodyLength >= recvBytes2.Length)                    {                        iBytesBody = socket.Receive(recvBytes2, recvBytes2.Length, 0);                    }                    else {                        iBytesBody = socket.Receive(recvBytes2, bodyLength, 0);                    }                    //将接收到的字节数保存recvBytes2.CopyTo(recvBytesBody, recvBytesBody.Length - bodyLength);                    //减去已经接收到的字节数bodyLength -= iBytesBody;                }                //一个消息包接收完毕,开始解析消息包UnpackData(recvBytesHead, recvBytesBody, out md);                 //处理一些逻辑if (md != null)                    HandleMessage(md, socket);            }        }


接收到一个完整包后开始拆包处理


接下来我们就可以根据拆包并反序列化得到的的数据类MessageData进行处理转发了:



Demo测试:
两个客户端连入服务器


客户端发送1条语音消息100条普通消息:





还有,部署到服务器上的时候注意:IPAddress枚举类型为Any,否则客户端访问时会被积极拒绝。如果还不行尝试关闭服务器的防火墙。







1 0