日期:2014-05-18  浏览次数:20978 次

Socket:如何使用多个线程分别实现读取、打包、异步发送二进制数据
问题是这样的:
Socket客户端方面,偶用3个线程,分别是
1、Write():读取二进制文件、
2、Packet():打包缓冲区待发数据、
3、Send():发送缓冲区已打包数据。
自定义bufferManager类使用了lock和Monitor来控制线程的状态。
用Stream类辅助复制本地文件来测试bufferManager类准确无误,
说明bufferManager类没有问题,但是把写文件代码改为Socket
异步发送数据时,问题就来了,因为sendCallback(IAsyncResult ar)并不受
bufferManager类中的lock和Monitor来控制,不停地发送,
很快其速度就超过了读取和打包的速度,导致错误:
“NullReferenceException:未将对象引用设置到对象的实例
请各位出手相助,如何解决这个问题?
部分代码如下:
C# code

private void Write()
{
    // 将二进制数据写入缓冲区bufferManager
}
private void Packet()
{
    // 将缓冲区bufferManager中的待发数据打包(添加自定义协议)
}
private void Send()// 将缓冲区bufferManager中已打包数据异步发送
{
    Socket handler = _session.ClientSocket;
    //从缓冲区中循环读取
    //while(sentSize<fileSize)
    //{
        byte[] data = bufferManager.GetSendBuffer();
        int sSize = data.Length;//实际应该取数据有效长度,这里简单使用data.Length只为代码清晰易读
        if(sSize>0)
        {
            //测试bufferManager类时,这里用Stream类来将数据写盘
            handler.BeginSend(data, 0, data.Length, SocketFlags.None,    //开始异步发送
             new AsyncCallback(sendCallback), handler);
        }
        sentSize += sSize;
    //}
    //bufferManager.SentDone = true;
}

/// <summary>
/// 异步发送回调函数
/// </summary>
/// <param name="ar">包含要发送的数据流及数据包的包头</param>
private void sendCallback(IAsyncResult ar)
{
    try
    {
        Socket socket = (Socket)ar.AsyncState;            //得到包含Socket的对象
        int sent = socket.EndSend(ar);

        if(sentSize<fileSize)//sentSize:已发送文件的字节数;fileSize:发送文件的字节长度
        {
            byte[] data = bufferManager.GetSendBuffer();

            int sSize = data.Length;//实际应该取数据有效长度,这里简单使用data.Length只为代码清晰易读

            if(sSize>0)
            {
                socket.BeginSend(data, 0, data.Length, SocketFlags.None,    //开始异步发送
                 new AsyncCallback(sendCallback), socket);
            }
            sentSize += sSize;
        }
        else
        {
            bufferManager.SentDone = true;
        }
    }
    catch(Exception ex)//NullReferenceException
    {
        //NullReferenceException:未将对象引用设置到对象的实例。
    }
}


另外,服务端方面,担心短连接因频繁连接和断开影响效能,改为长连接。如一个连接
相应启动一线程,又担心线程开太多影响性能。因此,打算按用户数建Socket连接池,
同时开30个线程,将池中Socket平均分配给30个线程负责,以减少线程数、降低新建线
程和对象的开销、减轻服务器担负,提高用户体验。
大家对这种作法有什么建议和看法?

------解决方案--------------------
简单处理方法:sendCallback方法在发送数据之前,判断一下有没有数据,或者判断一下返回的数据是否为null,等于null则什么都不发送,
------解决方案--------------------
将Send()的BeginSend改为Send,这样就好控制了。
------解决方案--------------------
Send完一个包再Send下一个包不行吗,为什么要异步?
------解决方案--------------------
每个线程用一个while,
当有数据的时候才去操作,没有的时候一直循环,但是每次循环都要加适当的休眠
取数据的时候考虑用一个公共队列,
至少是以前是这样用过,没问题