吵吵   2015-08-25  阅读:790

c#用来做c/s架构的程序确实是把好手,经吵吵测试,连上几百个客户端的实时通信,也没有产生啥问题。

讨论异步Sokcet,我们大多时候讨论的是异步的接收,并且主要是用于服务端的。当有数据到达服务端的时候,程序会新建一个线程(如果线程池的话会动态调用空闲的线程)用于处理接收的数据。c#的线程池技术也确实强大,它能够有效的管理线程数量,既能让CPU不会耗尽,又有队列机制,将一个个回调的任务完成。

异步接收的大概思路是:建立一个buffer,在接收完回调的函数里面从sokcet里面读取的数据写入buffer,如果写入的数据不足一个Pakcet的话,就接着接收,知道满足至少一个packet,然后就去处理这个packet的内容。

但是异步写入呢?我看看异步写入代码,最主要的是这个部分:

private static void Send(Socket handler, String data)
{
    // Convert the string data to byte data using ASCII encoding.     
    byte[] byteData = Encoding.ASCII.GetBytes(data);
    // Begin sending the data to the remote device.     
    handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
}
private static void SendCallback(IAsyncResult ar)
{
    try
    {
        // Retrieve the socket from the state object.     
        Socket handler = (Socket)ar.AsyncState;
        // Complete sending the data to the remote device.     
        int bytesSent = handler.EndSend(ar);
        Console.WriteLine("Sent {0} bytes to client.", bytesSent);
        handler.Shutdown(SocketShutdown.Both);
        handler.Close();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }
}

异步写入也有回调,回调解决的问题是写入完成之后通知你,通知你有啥用呢?如果写入的数据比较大的话,你可以腾出时间来干其它的事情,写完之后它就告诉你写完了,例如上传完了。(这里我认为其实也没有太大的必要,如果是大一点的文件读写发送的话,都会启动新的线程进行读写了,如果你有应用场景,麻烦告知一声)。

如果不考虑发送回调的话,最大的问题就是线程同步了,如果一个线程在写入数据,而另外一个线程也在写入数据,那么如果写入的数据有交叉的话,我们发送的数据就全乱了,因此线程的同步写入尤为重要,而我们今天要讨论的问题也是线程写入的同步:

一、缓冲区。

缓冲区是我第一个想到的设计同步的模式,为什么呢?因为本身网卡就有写入和读取的缓冲区,当然,现在是磁盘、cpu都会有缓冲区的概念,缓冲区工作的逻辑:

1、定义一个缓冲区,这个大小视其需求而定。

2、将写入的数据不断的写入缓冲区,有多个线程则不断的往后追加。

3、写入的线程不断把缓冲区里面的数据写入到socket里面,知道缓冲区写完并清空。

缓冲区模型其实是不用解决同步问题的,因为你可以持续的写入,不断的往后追加就行了,但是也会有两个问题:

1、定义多大的缓冲区啊,如果写入的数据太快的话,缓冲区就会要爆了!

2、这个方法等于是在sokcet本身具备写入缓存的情况下再来个二级缓存,这数据的写入和读取,虽然是内存,但是也费时费力的。

二、单线程写入。

如果一件事情,涉及到了多个人的话,沟通和协调就比较麻烦,我们就倾向于把它交给一个人干,这样可能就会更有效率一点。我去网上查多线程写入的问题,结果就有人回答,既然多个线程写入要同步,那么你不如把写入交给一个线程,其它线程负责通知它就可以了啊!

这让我想起vc时代做的线程同步,让大家看看一段代码:

// 事件句柄
HANDLE hEvent = NULL;
// 共享资源
char g_cArray[10];
……
UINT ThreadProc(LPVOID pParam)
{
 // 等待事件置位
 WaitForSingleObject(hEvent, INFINITE);
 // 对共享资源进行写入操作

 // 处理完成后即将事件对象置位
 SetEvent(hEvent);
 return 0;
}

我们通过事件来通知该线程来写入,或者我们用线程消息也是一样的,但是这会引发一个问题,就是多个线程同时告诉写入线程我有数据写入怎么办?线程的顺序要如何协调?

为了解决这个问题,你会每一个线程都会写入,只是用hEvent进行同步罢了,于是会有ThreadProc1、ThreadProc2、ThreadProc3等等。

你突然发现,如果真的只让一个线程去写,就只能是采用缓存机制,如果是多个线程去写,你就只能做同步,排顺序,因此也会引出最后最方便的解决方案:

三、临界区。

临界区就是一扇门,多个人都想过去的话,一个人进去了,其它人就在门外等待,做线程同步的话,c#提供了一个超级简单的函数Lock。

Lock(A)
{
代码B
}
那么不同的线程走到A的时候,执行代码B的只有一个线程。

你可以Lock(Socket A),也可以Lock(object A),我建议你用Lock(object A),并将A定义为static,这样就真的是单例了!

Lock的机制实际上是Monitor.Enter(this),这些机制和vc时代的事件、互斥事件最终实现多线程同步的思路都是一样的,你也可以用事件的,不勉强,切记用Lock,别搞得死锁了!

吵吵微信朋友圈,请付款实名加入:

吵吵 吵吵

发表评论

电子邮件地址不会被公开。 必填项已用*标注