[游戏开发][网络篇] Socket数据传输

阻塞与非阻塞(Blocking&Non-Blocking)

客户端与服务端为例 服务端等待客户端发来的一条新的消息 但是服务端关闭了客户端Socket 服务端却一直在等待客户端的消息  此时服务端线程阻塞 因为服务端的accept方法一直在等待新消息的到来 阻塞卡住时的两种情况 发送时(Send)比如你要按下某键才发送消息时 只有要发送的数据传到了Send函数中 才会继续向下执行;接收时(Recv) 接收一方将数据放到缓存区(byte[])中 只有收到数据放入缓存区后 才会向下执行;解决阻塞:同步+多线程(异步)
非阻塞:服务端仍在接收客户端的消息(RecvCallBack) 但无论发来新消息还是没发来(空消息)都立刻返回 不会一直将线程挂在接收函数的调用上 异步非阻塞解决了阻塞的问题 所以Socket(Client/Server结构)多用异步非阻塞

同步与异步(Sync&Async)

同步、异步、阻塞、非阻塞之间的关系

同步、异步、阻塞、非阻塞相互联系但又不等同 同步有可能导致阻塞 异步解决阻塞 同步异步主要针对client(同步是死等结果;异步是有结果了通知我)阻塞非阻塞主要针对Server(阻塞是被调用时没数据不返回;非阻塞是被调用不管如何立即返回)

前言:本篇文章主题Tcp连接Socket套接字相关内容,对基础部分、优化补充部分做了说明,最终目的是让阅读者看完本篇文章后能写一个功能完善的网络框架。

首先明确一下网络连接的基础模块有哪些

[基础部分1]:Socket初始化与连接

 Socket是网络模块封装的一个类,需要new一个对象并缓存它,连接服务器的必要信息是IP和端口,有了这些数据就可以主动发起连接了

Nodelay参数表示发送包不进入缓存池,直接发送而不是缓存发送。

//数据初始化
uint ReceiveMaxByteCount;
byte[] bufferTemp = new byte[ReceiveMaxByteCount];

pubvoid void Connect(string _ip,int _port)
{
     SocketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream,    
                           ProtocolType.Tcp);
    IPAddress ip = IPAddress.Parse(_ip);
    IPEndPoint point = new IPEndPoint(ip, _port);
    SocketClient.NoDelay = true;
    SocketClient.Connect(point);   
}

[基础部分2]数据接收

客户端Socket连接后开始接收数据,客户端接收数据的方式一般有三种

[数据接收第一种]

开线程,数据接收不需要和Mono交互,仅仅是对byte[]数据的操作,开线程提高效率,下面是代码示例,由于在线程中,while循环也不会卡死进程,但个人不咋喜欢这种方式。

pubvoid void Connect(string _ip,int _port)
{
     SocketClient = new Socket()
    //...........
    //这是第一种方式,开线程接收
    Thread c_thread = new Thread(Received);
    c_thread.IsBackground = true;
    c_thread.Start();
}
void Received()
{
    while (true)
    {
        try
        {
            int len = _SocketClient.Receive(bufferTemp);   //len 实际接收到的有效字节数
            if (len <= 0)
            {
                //异常处理
                break;
            }
            //把收到的字节解析或者塞入接收缓存池中
        }
        catch { }
    }
}

[数据接收第二种]

简单粗暴的写在Update里,如果是框架则应该有tick 接收和发送缓存池的机制,这里是基础部分,只介绍核心代码,由于是Update中执行,可以自由控制解包数量。

void Update()
{
    ProcessReceive()
}

void ProcessReceive()
{
    uint recLen = 0;
    if( client.Connected)
    //maxLen代表最大接收长度,如果有缓存池,接收长度不能超过缓存池剩余量的长度
    recLen = (uint)client.Receive(bufferTemp,maxLen, SocketFlags.None);
    //有池则入池,没池直接解析bufferTemp,数据长度为recLen
}

[数据接收第三种]

连接-连接成功回调-请求接收数据-接收数据成功回调-请求接收数据-接受数据成功回调--循环

//第一步连接Socket
private void ConnectAsync()
{
    mTcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    mSocketStatus = STATUS.ST_CONNECTING;
    mTcpSocket.BeginConnect(ipEndPoint, mAsyncConnectCallback, null);
}
//第二步,连接成功回调,开始收包
private void AsyncConnectCallback(IAsyncResult ar)
{
    //记录连接状态
    mSocketStatus = STATUS.ST_CONNECT_OK;
    //开始收包
    mTcpSocket.BeginReceive(dataBuff, 0, MAX_PACKET_SIZE, SocketFlags.None,mAsyncReceiveCallBack, null);
}

//接收成功回调
private void AsyncReceiveCallBack(IAsyncResult ar)
{
    int receiveLength = 0;
    receiveLength = mTcpSocket.EndReceive(ar);
    byte[] buff = new byte[receiveLength];
    Array.Copy(dataBuff, buff, receiveLength);
    ReceiveBytes(buff);//处理数据
    mTcpSocket.BeginReceive(dataBuff, 0, MAX_PACKET_SIZE, SocketFlags.None,mAsyncReceiveCallBack, null);
}

[基础部分3]数据发送

void Send (Socket client, Byte[] buff, uint nLen, SocketFlags flags = SocketFlags.None)
{
    try
    {
        //提示:sendLen代表向tcp底层缓冲区塞了多少个字节,并不是向服务器发送成功了多少个字节
        uint sendLen = client.Send(buff, (int)nLen, flags);
    }
    catch (SocketException e)
    {
    }
    
}

[基础部分4]:断开连接

void Close (Socket ClientSocket)
{
    try
    {
        ClientSocket.Shutdown(SocketShutdown.Both);
        ClientSocket.Close();
        
    }
    catch (SocketException e)
    {
        //LogSystem.Error(e.ToString());
    }
}

Socket基础部分外的优化发送、接收缓存池

我们可以把发送缓存池和接收缓存池都封装成一个class,将来数据发送和接收的重任就落到了它俩身上,输出叫SocketOutputPool,输入叫SocketInputPool

[补充优化1-发送缓存池]

发送缓存池是干嘛的?基本原理就是维护一个Byte数组。

当上层业务有数据向服务器发送时,会调用NetMgr的send函数,send函数再调用SocketOutputPool的Write函数向Byte数组中写入该数据。

缓存池的数据合适发送,发送多少?当Write函数写入数据后,应当记录一下当前数据长度,int变量m_PoolTail记录尾部长度,上文有过对发送时机的描述,一般来说会在Update中调用发送函数

public const uint DEFAULT_SOCKET_OUTPUT_BUFFER_SIZE = 1024 * 80;//缓存最大长度
int m_Tail = 0; //缓存池当前长度
byte[] m_Buffer = new Byte[DEFAULT_SOCKET_OUTPUT_BUFFER_SIZE];

void TickSend()
{
    //再次提示:SendLen不是向服务器发送的长度,而是往Tcp发送缓冲区塞了多少数据
    int SendLen = Socket.Send(m_Buffer,m_PoolTail, flag);
    
    //发送结束后,需要从池中把发送过得数据剔除
}

3、数据发送和数据接收可以写两个缓存池

注意事项:

1、Tcp发送是否需要底层的缓存池,可设置bool变量Nodelay,表示不缓存直接发送

1:socket.Send()方法的返回变量int值表示该send向tcp发送缓冲区塞了多少个字节,并不是向服务器发送成功了多少个字节,

2:如果出现发包失败,tcp底层会自动重发,tcp的底层有发送成功回复机制,另一端收到消息包后会回复一个确认包

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Little丶Seven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值