服务器端的高性能实现(五)——对客户端消息的响应和处理

刚从上海回来,竟然又要再去一趟,晕死了。

这次我主要添加了处理客户端消息的功能,平台是windows。

具体来说,就是添加了两个队列:接收消息队列和等待队列。队列里面都是socket,每个socket对应一个客户端连接。另外,这两个队列都是用链表实现的,实际行为相当于一个循环链表。

首先说接收消息队列。如果一个socket有数据可接收,那就把他放到接收队列里面。判断一个socket是否有数据可以接收,可以用socket下面的NetworkStream来判断,具体来说就是:
asocket.GetStream().CanRead == true和asocket.GetStream().DataAvailable == true。

现在,如果这个asocket不能接收数据,那就把它从接收队列里面去掉,放到等待队列里面。如果它能接收数据,就调用它的read方法,让它接收数据。接收完了数据,要进行一下判断。如果收到的数据数为0,则说明没有数据,那就把它放到等待队列里面,同时从接收队列里面除名。如果它受到了数据,那就把数据打包,丢给处理数据的线程池去对数据进行进一步的处理。

具体的执行过程,就是遍历接收队列里面的每一个socket,按照上面的规则进行处理。一遍处理完成之后,即到了队列的末尾,就判断一下队列是否为空。如果为空,就等待100ms。如果不为空,就继续从头开始遍历。

下面我再说一下等待队列的设计思路。对于等待队列里面的socket,同样是判断它是否可以接收数据,判断的方法跟上面一样。如果这个socket可以接收数据,那就把它从等待队列里面除名,同时放到接收队列里面。如果它没有数据可接收,就不管它。
按照这个规则,也是从头开始,遍历等待队列里面的每一个socket。当遍历完成的时候,就等待100ms,然后继续从头开始遍历。所谓等待队列,就是每次遍历完之后,都会强制等待100ms。

下面我先来说下为什么这样设计,然后再给出具体的实现代码。

第一个问题:我发现了策划的时候犯了一个错误。当初对同步和异步socket的理解有错误。最早是想用beginread方法,进行异步的数据读取。后来在查看MSDN的文档的时候,发现这根本不是我需要的功能。我需要一个快速的read方法,无论是否有数据,它都会快速的返回结果。实际查看了MSDN后发现,read方法正好可以满足我的需要。如果有数据,它当然立刻返回读取的数据,如果没有,它会立刻返回0。而beginread就不一样了,如果没有数据,或者没有足够的数据,它会进行等待,直到被挂起或者收到了足够的数据。对于服务器端来说,当然不能因为一个客户端的等待,让其他的客户端都去等待。所以,我选用了总是可以快速返回的read方法。

第二个问题:为什么要分两个队列?
假如不分成两个队列的话,让我们看看会出现什么问题。首先,我们需要对队列里面的socket逐个去判断是否有数据可以接收,如果有,就调用read,没有,就什么也不做。这样遍历了一圈之后,问题就来了?接下来做什么?看起来似乎很简单,继续遍历不就可以了么?如果继续从头开始遍历的话,我们就会立刻发现CPU已经被我们100%的占用了。对于系统来说,除了遍历socket就是遍历socket,没有任何的喘息机会。那么,每次遍历完成之后,sleep一段时间可以不。答案是可以的。只是这样一来,我们就失掉了这个框架的一个重要特性:即时响应。

所以,我分了两个队列,各自用了一个线程去跑。这样一来,当socket都没有数据可以接收的时候,大家就都去等待了。虽然这样做,在道理上来说,还是牺牲了部分时效性,但是比起用一个队列来,已经提高了很多很多,而且CPU资源也得到了释放。

下面是实现代码:
第一件事情是写一个链表类……累啊(为什么系统不提供?!)。在.net2.0版本里面,微软添加了一个LinkedList类,这个就是链表类。但是它有一个缺陷──不支持多线程。因此,我只好“抄写”了一个可以支持多线程的链表类。

为什么说是抄呢?因为我借鉴了微软编写同步hash表的套路,而且保证借鉴得很原汁原味,呵呵。
首先看类的声明吧:
public class SyncLinkedList : LinkedList
名字的前缀Sync也是“抄”来的,呵呵。
然后添加一个内部成员:
private LinkedList list;
没错,这也是抄来的。

下面是构造函数:
public SyncLinkedList(LinkedList list)
{
this.list = list;
}
反正都是抄的,我就不继续啰唆了。

那么同步体现在哪里呢?看下面的代码:
public new int Count
{
get
{
lock (this)
{
return this.list.Count;
}
}
}

如果有人去看SyncHashtable的代码,会发现微软lock的不是this,而是个什么什么“根”。不过大家不要被骗了,其实那个根就是this。关于这个问题,我在以前的文章里面已经讨论过了,此后就不多说了。我想说的是,其实我不太赞成这里lock的对象是this。为什么不赞成?请参考微软家里出版的.net编码清规……如此看来,似乎实现Sync这一类class的兄弟,没有好好遵守规矩。

不过至少目前看起来,似乎问题还不大。我们就先这么干,毕竟微软里面的那些Sync类都是这么干的。如果不打算lock住this,那就建立一个私有对象,锁它就可以了。

剩下的就简单了,想用哪个方法,就把哪个方法new一下呗,然后里面lock一下就可以了。

现在,支持多线程的链表类已经ok了,我们可以继续了。

先改造ConnectionManager,在它里面添加两个SyncLinkedList类型的成员,一个叫做receiveQueue,一个叫做waitQueue。泛型里面的T类型,当然是选择TcpClient了。如下:
private SyncLinkedList receiveQueue = new SyncLinkedList (new LinkedList ());
private SyncLinkedList waitQueue = new SyncLinkedList (new LinkedList ());

然后是添加一个Start方法,在里面启动一个线程,主要处理等待队列的任务。如下:
public void Start()
{
if (!_start)
{
_start = true;
allsockets.Clear();
receiveQueue.Clear();
waitQueue.Clear();

new Thread(new ThreadStart(startWait)).Start();
}
}

下面是startWait的代码:
TcpClient asocket = null;
LinkedListNode current = null;
LinkedListNode next = null;
while (_start)
{
try
{
current = waitQueue.First;
while (current != null)
{
asocket = current.Value;
next = current.Next;
//进行判断。如果有数据,就把这个socket转移到接收队列里面去。
if (asocket.GetStream().CanRead && asocket.GetStream().DataAvailable)
{
receiveQueue.AddLast(asocket);
waitQueue.Remove(current);
}
current = next;
}
//没错遍历完一遍后,休息100ms,释放资源。
Thread.Sleep(100);
}
catch (System.Net.Sockets.SocketException se)
{
log.Error(se.Message);
break;
}
}

现在,处理等待队列的工作已经完成了,接着是处理接收队列的相关代码:
对接收队列的处理,我创建了一个类Receiver,用它来做接收数据的事情。
首先是添加一个内部成员:connectmanager。
private IConnection connectManager;

然后是启动一个线程,处理接收队列,核心代码如下:
private void startReceive()
{
log.Debug("startReceive.....");
TcpClient asocket = null;
LinkedListNode current = null;
LinkedListNode next = null;
while (_start)
{
current = connectManager.ReceiverQueue.First;
while (current != null)
{
//取得socket
asocket = current.Value;
next = current.Next;
if (asocket.GetStream().CanRead && asocket.GetStream().DataAvailable)
{
//接收数据,如果个数为0,就放到等待队列里面。
byte[] buffer = new byte[32];
int bytes_read = asocket.GetStream().Read(buffer, 0, 10);
if (bytes_read <= 0)
{
connectManager.WaitQueue.AddLast(asocket);
connectManager.ReceiverQueue.Remove(current);
}
}
else
{
//如果不能接收数据,就放到等待队列里面
connectManager.WaitQueue.AddLast(asocket);
connectManager.ReceiverQueue.Remove(current);
}
current = next;
}
//如果没有socket,就等待100ms。
if (connectManager.ReceiverQueue.Count == 0)
{
Thread.Sleep(100);
}
}
}

做完了这些后,当然还是老规矩,提取接口。接口如下:
interface IReceiver
{
void Start();

IConnection Connection
{
get;
set;
}
}

最后,还要在Core里面调用它,修改Start方法,在listener.Start后面,添加下面的代码:
receiver.Connection = listener.Connection;
receiver.Start();

到此,这个框架已经可以实现对客户端的响应了。

ps:因为出差的原因,文章的更新进度受到了很大影响。下一次,我会把线程池添加进来,这样框架的基本功能就初具雏形了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值