深入探析c# Socket
2010-09-08 17:28 by 田志良, 14562 visits, 收藏, 编辑最近浏览了几篇有关Socket发送消息的文章,发现大家对Socket Send方法理解有所偏差,现将自己在开发过程中对Socket的领悟写出来,以供大家参考。
(一)架构
基于TCP协议的Socket通信,架构类似于B/S架构,一个Socket通信服务器,多个Socket通信客户端。Socket通信服务器启动时,会建立一个侦听Socket,侦听Socket将侦听到的Socket连接传给接受Socket,然后由接受Socket完成接受、发送消息,当Socket存在异常时,断开连接。在实际开发项目中,往往要求Socket通信服务器能提供高效、稳定的服务,一般会用到以下技术:双工通信、完成端口、SAEA、池、多线程、异步等。特别是池,用的比较多,池一般包括一下几种:
1)Buffer池,用于集中管控Socket缓冲区,防止内存碎片。
2)SAEA池,用于集中管控Socket,重复利用Socket。
3)SQL池,用于分离网络服务层与数据访问层(SQL的执行效率远远低于网络层执行效率)。
4)线程池,用于从线程池中调用空闲线程执行业务逻辑,进一步提高网络层运行效率。
(二)Send
主服务器接受Socket为一端口,客户端Socket为一端口,这两个端口通过TCP协议建立连接,通信基础系统负责管理此连接,它有两个功能:
1)发送消息
2)接受消息
Socket的Send方法,并非大家想象中的从一个端口发送消息到另一个端口,它仅仅是拷贝数据到基础系统的发送缓冲区,然后由基础系统将发送缓冲区的数据到连接的另一端口。值得一说的是,这里的拷贝数据与异步发送消息的拷贝是不一样的,同步发送的拷贝,是直接拷贝数据到基础系统缓冲区,拷贝完成后返回,在拷贝的过程中,执行线程会IO等待, 此种拷贝与Socket自带的Buffer空间无关,但异步发送消息的拷贝,是将Socket自带的Buffer空间内的所有数据,拷贝到基础系统发送缓冲区,并立即返回,执行线程无需IO等待,所以异步发送在发送前必须执行SetBuffer方法,拷贝完成后,会触发你自定义回调函数ProcessSend,在ProcessSend方法中,调用SetBuffer方法,重新初始化Buffer空间。
口说无凭,下面给个例子:
服务器端:
客户端:
解释:
客户端第一次发送数据:1234567890。
客户端第一个接受数据:1234567890,该数据由服务端用Send同步方法发送返回。
客户端第二个接受数据:1234567890,该数据由服务端用Send异步方法发送返回。
以上似乎没什么异常,好,接下来,我只发送abc。
客户端第一个接受数据:abc,理所当然,没什么问题。
客户端第二个接受数据:abc4567890!为什么呢?应该是abc才对呀!
好,现在为大家解释一下:
异步发送是将其Buffer空间中所有数据拷贝到基础系统发送缓冲区,第一次拷贝1234567890到发送缓冲区,所以收到1234567890,第二次拷贝abc到发送缓冲区,替换了先前的123,所以收到abc4567890,大家明白的?
源码:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
class AsyncUserToken
{
public Socket Socket;
}


#1楼 dreamhappy 2010-09-08 18:48
希望博主能写几篇关于c# .net环境 c/s多线程socket开发,如何及时的释放线程 关闭线程和socket的顺序应该怎样处理的博文,因为我之前socket编程时候客户端和服务器端分别有一个线程和一个socket 往往线程不知道什么时候合理的释放
#2楼 秋色 2010-09-08 19:27
线程的释放,一般是定义开关变量。让线程自己退出。while(开关)
{
if(??)
{
开关=false;
}
}
#3楼 %admin 2010-09-09 10:36
实际测试了一下,还真出现了楼主描述的问题,我想这问题主要就出在了Buffer池的使用上,Send的时候发送的是 从e.buff 拷贝出来的真实大小的数据,SendAsyn的时候发送的是e.buff 。 楼主还真是细心啊,不过好像实际中要发送数据给客户端的时候不应该在用e.buff了, 此问题有待继续深入#4楼[楼主] 田志良 2010-09-09 10:37
@dreamhappy多线程在Socket开发中尤为重要,若处理不当,会严重影响效率,接下来,我会陆续写些这类博文,谢谢大家关注。
#5楼[楼主] 田志良 2010-09-09 10:42
@安度如果你想使你的Socket服务器非常高效,池是一定用到的,如果不用池,高级消息队列也行,这样才能极大提高并发数和最大连接数。
#6楼 %admin 2010-09-09 10:42
还好SocketAsyncEventArgs 提供了SetBuffer ,遇到这种情况是,不妨在SendAsyn之前 动态的 e.SetBuffer 一下,就没问题了~~示例:
e.AcceptSocket.Send(data);
System.Threading.Thread.Sleep(1000);
e.SetBuffer(0, data.Length);
if (!e.AcceptSocket.SendAsync(e))
{
Console.WriteLine("asynsend error");
}
#7楼[楼主] 田志良 2010-09-09 10:44
@九九目前我开发的IM系统正在做压力测试,基本上最大连接数能上到20000,并发能上到3000。你所提的问题平时我也有遇到过,有空大家一起研究研究。
#8楼 %admin 2010-09-09 10:51
其实把下面3处代码关联起来看下就比较容易了解为什么会出现这种情况了,#9楼[楼主] 田志良 2010-09-09 11:10
@%adminBuffer池主要用于集中管理SAEA的Buffer缓冲区,防止内存碎片过多影响效率。SetBuffer方法是为SAEA分配内存缓冲区,这个是跟基础系统缓冲区是有区别的。我们用程序可以管理SAEA内存缓冲区,但不能管理基础系统缓冲区,这个是由基础系统本身的机制决定的。还有微软提供的SendAsyn方法是鸡肋,一个SAEA在同一时间,不能既处于SendAsync状态又处于ReadAsyc状态,否则会引发"现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作"异常,这对于多线程、高并发通信毫无用处,显得很苍白无力。
#10楼 %admin 2010-09-09 11:29
呵呵,你说的这问题也注意到了,我还是不够深入,对于SAEA还没有了解, 因为自己项目中代码跟你的很相似也就自己测试了下,分析的也不知道对不对,呵呵,继续学习! 去看看SAEA
#11楼 henry 2010-09-09 11:46
其实SocketAsyncEventArgs性能不错的,在新的测试中cpu E5405 的服务器,服务端接收256byte数据并返回给client(有分包处理)其秒处理数据包的能力在2.5W. 而CPU只占用了50%,内存在300M内.补充:这样的处理方式在秒处理5000消息的时候估计会性能问题产生.
#12楼 阿三 2010-09-09 14:31
小伙子进步不错嘛。#13楼 无为无知无欲 2010-09-09 16:27
楼主,我刚做了这个.net 3.5 完成端口方法的测试,一台普通的服务器,2G内存,可以并发接受1500条消息/秒, 这个瓶颈主要是从消息队列写进数据库的瓶颈,超过后就会造成消息队列的增长,但前端还是能不断接收数据的。所以如果数据库服务器更高效的话,能力还能大幅提高。最高连接我做到了20000,CPU,和内存还没怎么提高,所以应该能更高,问题是测试的时候这么多的客户端不好做啊。
#14楼[楼主] 田志良 2010-09-09 17:16
@无为无知无欲所以你要做一个SQL池,将要执行的SQL语句放到池中,然后每隔一段时间,安排一条线程扫描SQL池,如果SQL池中有SQL语句,则批量执行,如果没有则退出。在对SQL池管理时要尤为小心,Push操作和Ececute操作要互斥,执行SQL语句时,不能Push SQL语句,相反,Push SQL语句时,也不能执行SQL语句。
#15楼 安度 2010-09-09 17:28
我是最近才接触Socket的,貌似SocketAsyncEventArgs我没有在网上看到,大概用的都是BeginXXX和EndXXX,服务端的话,基本是用多线程(Accpet在一个独立的线程),然后做一个线程池的管理类(貌似.net有现成的线程池),不知道楼主这种方法有什么优点,不防解释下#16楼 安度 2010-09-09 17:33
在.NET 3.5里System.Net.Sockets空间下有一组增强功能的类,提供可供专用的高性能套接字应用程序使用的可选异步模式,SocketAsyncEventArgs 类就是这一组增强功能的一部分。该类专为需要高性能的网络服务器应用程序而设计。应用程序可以完全使用增强的异步模式,也可以仅仅在目标热点区域(例如,在接收大量数据时)使用此模式。以下是关于此类的介绍(摘自MSDN)原来是3.5里面的!是不是就是传说中的IOCP?有机会楼主写详细点,原来不知道楼主是用的这个!
#17楼[楼主] 田志良 2010-09-09 18:07
@安度我的做法是开通300或更多个Socket用于接受侦听Socket传递的SAEA,为什么要开通这么多?你在做压力测试时就明白了,开通多一点,会使你的连接效率、连接速度大幅度提高。对于SAEA,它不能同时ReceiveAsync、SendAsync,所以采用双工通信,让收发数据在同一条连接上进行,以提高效率。对于业务逻辑层上的处理,主要采用线程池、SQL池,用SQL池主要将网络层与数据访问层分离,为什么要分离?数据库操作会极大影响效率,如果不分离,数据操作会拖垮网络层。
#18楼 Leon Weng 2010-09-11 02:07
前段时间搞视频通信时用到了sokect,顺便研究了一下,感觉效率的确比较高,但是在多线程方面自我感觉很差,所以没有使用socket完成改成WCF了,WCF封装了SOCKET,更好用了。#19楼 ToBin 2011-03-01 11:12
双工通信时需要一个客户端连接两个端口嘛,一个收,一个发?现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作。
为什么会出现这个问题呢?
#20楼[楼主] 田志良 2011-03-01 11:30
@ToBin当一个SAEA对象已处于StartReceive状态时,就不能用此SAEA发送消息。也就是说SAEA对象在一个时刻中只能处于StartAccept、StartReceive、StartSend状态中的一种。解决的办法就是用双工通信,为一条连接开辟两个SAEA对象,一个用于收,一个用于发。
#21楼 ToBin 2011-03-01 13:02
刚又把文章仔细读了一遍,很多东西还是没有理解!刚好看到您的回复!很有帮助,我再研究研究您的文章!
还有这个saea的三种状态,我现在在发送的时候报错"
现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作"
但我这个确实是clientsocket.sendasync(saeasender)是发生的。
我接受的时候用的clientsocket.receiveasync(saeareceiver),两个没有冲突啊,很奇怪!
#22楼[楼主] 田志良 2011-03-01 14:55
@ToBin建议Receive用异步模式,Send用同步模式。
#23楼 ToBin 2011-03-01 17:31
错误“现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作”是不是因为异步发送了就返回了,但实际上还没有发送到,当第二次异步发送的时候,第一次还没发送完,就出了这个错误?我猜测!
#24楼[楼主] 田志良 2011-03-02 16:51
@ToBin当Socket处于StartSend状态时,也不能执行发送操作,必须等到发送回调事件触发后,才能继续执行StartSend。解决的办法是:记录Socket的当前状态,并存储在Socket的UserToken对象下,当要执行StartSend时,判断状态。不过这样效率会很慢,当并发量达到3000时,会报很多错,推荐的方法是用同步发送。不要觉得同步发送就一定会比异步发送慢,事实证明,对于SocketAsyncEventArgs,同步发送比异步发送快多了。
#25楼 ToBin 2011-03-02 17:17
拜读了,现在还有一个问题请教,异步的时候是“但异步发送消息的拷贝,是将Socket自带的Buffer空间内的所有数据,拷贝到基础系统发送缓冲区,并立即返回”这个基础系统缓冲区是对应winsocket的,有点疑惑,就是只有一个缓存区,接受也从基础系统缓冲区中拷贝处来,发送也是拷贝到这,如果我接收的时候同时发送,基础系统缓冲区里的数据还没取出来,将要取出来的时候发送,拷贝进去,会不是导致接收出来的数据不正确?导致脏读,数据错误!有这样的问题嘛?
#26楼[楼主] 田志良 2011-03-02 18:04
@ToBin不会导致这个问题,基础系统缓冲区为每个Socket分配发送缓冲区和接受缓冲区,这两个不冲突。
#27楼 ToBin 2011-03-02 18:33
要西,外瑞thank you !哈哈#28楼[楼主] 田志良 2011-03-02 18:45
@ToBin呵呵,不客气。
#29楼 ToBin 2011-03-03 17:02
呵呵,又碰到问题了,可能我太笨了,您在这篇文章“Socket服务器整体架构概述”中说到一个“消息队列调度器”,这个东西该怎么实现,能大概说说嘛?谢谢了!呵呵
#30楼[楼主] 田志良 2011-03-04 09:03
@ToBin呵呵,把你的邮箱发给我,这个周末我写个Demo给你。
#31楼 ToBin 2011-03-04 09:26
太感谢了,激动!我的邮箱:tuablove@126.com辛苦您了!
#32楼 ToBin 2011-03-07 10:07
您的邮件已经收到,思路已经了解,非常感谢能得到您的帮助,希望能继续得到您的帮助,思路也行,呵呵,非常感谢!#33楼 ToBin 2011-03-08 17:26
又碰到个问题,不知道怎么处理了,问题是这样的getdata方法是socket 发送命令,接收返回值的方法,因为socket 服务器发送,接受时分开的,我怎么在getdata中让方法阻塞,让服务器接收到命令,然后再把结果发送过来!类似于javascript 的ajax!这种东西该怎么写啊?
类似于使用memcache 里
这里面这个mc.Get("key") 方法是怎么实现的啊 ?
#34楼 ToBin 2011-03-09 21:02
志良哥,您好,我在socket 开发中碰到了一些问题,想请教您,已经发送您邮箱了,思路解说在邮件里,代码在附件!等待您的回复!#35楼 edwardxh 2011-11-16 09:33
楼主您好,不知您是否可以把这个“Socket服务器整体架构概述”Demo也发给我一份,因为我最近也在研究Socket通信,很希望能得到您的技术心得分享,谢谢!
我的邮箱:79668157@qq.com
#36楼 glf 2011-11-23 17:19
现在公司要求能够接受2W左右的服务端,每5秒访问一次,高能不能给点意见啊#37楼 鹏@ 2012-01-12 17:03
@ToBin大哥,能不能给小弟也发一个demo:lipeng1988011@126.com!!!
多谢啦!!!