打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
深入探析c# Socket

深入探析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,大家明白的?

 

源码:

 

 

BufferManager

 

 

 

SocketAsyncEventArgsPool

 

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

class AsyncUserToken
{
public Socket Socket;
}

 

 

 

Server

 

 

 

 

Program

 

 

分类: Socket
15
2
(请您对文章做出评价)
博主前一篇:c# 多线程 --Mutex(互斥锁)
博主后一篇:c#实现用SQL池(多线程),定时批量执行SQL语句
Add your comment

37 条回复

  1. #1楼 dreamhappy      2010-09-08 18:48
    希望博主能写几篇关于
    c# .net环境 c/s多线程socket开发,如何及时的释放线程 关闭线程和socket的顺序应该怎样处理的博文,因为我之前socket编程时候客户端和服务器端分别有一个线程和一个socket 往往线程不知道什么时候合理的释放


     回复 引用 查看   
  2. #2楼 秋色      2010-09-08 19:27
    线程的释放,一般是定义开关变量。让线程自己退出。
    while(开关)
    {
    if(??)
    {
    开关=false;
    }
    }
     回复 引用 查看   
  3. #3楼 %admin      2010-09-09 10:36
    实际测试了一下,还真出现了楼主描述的问题,我想这问题主要就出在了Buffer池的使用上,Send的时候发送的是 从e.buff 拷贝出来的真实大小的数据,SendAsyn的时候发送的是e.buff 。 楼主还真是细心啊,不过好像实际中要发送数据给客户端的时候不应该在用e.buff了, 此问题有待继续深入
     回复 引用 查看   
  4. #4楼[楼主] 田志良      2010-09-09 10:37
    @dreamhappy
    多线程在Socket开发中尤为重要,若处理不当,会严重影响效率,接下来,我会陆续写些这类博文,谢谢大家关注。
     回复 引用 查看   
  5. #5楼[楼主] 田志良      2010-09-09 10:42
    @安度
    如果你想使你的Socket服务器非常高效,池是一定用到的,如果不用池,高级消息队列也行,这样才能极大提高并发数和最大连接数。
     回复 引用 查看   
  6. #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. #7楼[楼主] 田志良      2010-09-09 10:44
    @九九
    目前我开发的IM系统正在做压力测试,基本上最大连接数能上到20000,并发能上到3000。你所提的问题平时我也有遇到过,有空大家一起研究研究。
     回复 引用 查看   
  8. #8楼 %admin      2010-09-09 10:51
    其实把下面3处代码关联起来看下就比较容易了解为什么会出现这种情况了,

    if (Buffer.SetBuffer(e))                    {                        if (!e.AcceptSocket.ReceiveAsync(e))  //是否触发 Asyn_Commpleted事件                        {                            BeginReceive(e);                        }                    }这段是接受连接时调用Buffer类的SetBuffer方法,实际上还是操作的SocketAsyncEventArgs.SetBuffer


    internal Boolean SetBuffer(SocketAsyncEventArgs args)        {            if (this.freeIndexPool.Count > 0)            {                args.SetBuffer(this.buffer, this.freeIndexPool.Pop(), this.bufferSize);            }            else            {                if ((this.numSize - this.bufferSize) < this.currentIndex)                {                    return false;                }                args.SetBuffer(this.buffer, this.currentIndex, this.bufferSize);                this.currentIndex += this.bufferSize;            }            return true;        }看Buffer类的SetBuffer函数就很清楚了,这个Buffer池与SocketAsyncEventArgs不存在逻辑上的关联,只是外部分配缓冲区然后设置SocketAsyncEventArgs


    e.AcceptSocket.Send(data);                        System.Threading.Thread.Sleep(1000);                        e.SetBuffer(0, data.Length);                        if (!e.AcceptSocket.SendAsync(e))                        {                            Console.WriteLine("asynsend error");                        }到这里,SendAsyn 实际上使用的缓冲区是在Accept时候就设置好了的,所以此时如果不进行e.SetBuffer(0, data.Length); 就出现了,楼主描述的问题,
     回复 引用 查看   
  9. #9楼[楼主] 田志良      2010-09-09 11:10
    @%admin
    Buffer池主要用于集中管理SAEA的Buffer缓冲区,防止内存碎片过多影响效率。SetBuffer方法是为SAEA分配内存缓冲区,这个是跟基础系统缓冲区是有区别的。我们用程序可以管理SAEA内存缓冲区,但不能管理基础系统缓冲区,这个是由基础系统本身的机制决定的。还有微软提供的SendAsyn方法是鸡肋,一个SAEA在同一时间,不能既处于SendAsync状态又处于ReadAsyc状态,否则会引发"现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作"异常,这对于多线程、高并发通信毫无用处,显得很苍白无力。
     回复 引用 查看   
  10. #10楼 %admin      2010-09-09 11:29
    引用田志良:
    @%admin
    Buffer池主要用于集中管理SAEA的Buffer缓冲区,防止内存碎片过多影响效率。SetBuffer方法是为SAEA分配内存缓冲区,这个是跟基础系统缓冲区是有区别的。我们用程序可以管理SAEA内存缓冲区,但不能管理基础系统缓冲区,这个是由基础系统本身的机制决定的。还有微软提供的SendAsyn方法是鸡肋,一个SAEA在同一时间,不能既处于SendAsync状态又处于ReadAsyc状态,否则会引发"现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作"异常,这对于多线程、高并发通信毫无用处,显得很苍白无力。


    呵呵,你说的这问题也注意到了,我还是不够深入,对于SAEA还没有了解, 因为自己项目中代码跟你的很相似也就自己测试了下,分析的也不知道对不对,呵呵,继续学习! 去看看SAEA
     回复 引用 查看   
  11. #11楼 henry      2010-09-09 11:46
    其实SocketAsyncEventArgs性能不错的,在新的测试中cpu E5405 的服务器,服务端接收256byte数据并返回给client(有分包处理)其秒处理数据包的能力在2.5W. 而CPU只占用了50%,内存在300M内.
    补充:这样的处理方式在秒处理5000消息的时候估计会性能问题产生.
     回复 引用 查看   
  12. #12楼 阿三      2010-09-09 14:31
    小伙子进步不错嘛。
     回复 引用 查看   
  13. #13楼 无为无知无欲      2010-09-09 16:27
    楼主,我刚做了这个.net 3.5 完成端口方法的测试,一台普通的服务器,2G内存,可以并发接受1500条消息/秒, 这个瓶颈主要是从消息队列写进数据库的瓶颈,超过后就会造成消息队列的增长,但前端还是能不断接收数据的。所以如果数据库服务器更高效的话,能力还能大幅提高。
    最高连接我做到了20000,CPU,和内存还没怎么提高,所以应该能更高,问题是测试的时候这么多的客户端不好做啊。
     回复 引用 查看   
  14. #14楼[楼主] 田志良      2010-09-09 17:16
    @无为无知无欲
    所以你要做一个SQL池,将要执行的SQL语句放到池中,然后每隔一段时间,安排一条线程扫描SQL池,如果SQL池中有SQL语句,则批量执行,如果没有则退出。在对SQL池管理时要尤为小心,Push操作和Ececute操作要互斥,执行SQL语句时,不能Push SQL语句,相反,Push SQL语句时,也不能执行SQL语句。
     回复 引用 查看   
  15. #15楼 安度      2010-09-09 17:28
    我是最近才接触Socket的,貌似SocketAsyncEventArgs我没有在网上看到,大概用的都是BeginXXX和EndXXX,服务端的话,基本是用多线程(Accpet在一个独立的线程),然后做一个线程池的管理类(貌似.net有现成的线程池),不知道楼主这种方法有什么优点,不防解释下
     回复 引用 查看   
  16. #16楼 安度      2010-09-09 17:33
    在.NET 3.5里System.Net.Sockets空间下有一组增强功能的类,提供可供专用的高性能套接字应用程序使用的可选异步模式,SocketAsyncEventArgs 类就是这一组增强功能的一部分。该类专为需要高性能的网络服务器应用程序而设计。应用程序可以完全使用增强的异步模式,也可以仅仅在目标热点区域(例如,在接收大量数据时)使用此模式。以下是关于此类的介绍(摘自MSDN)

    原来是3.5里面的!是不是就是传说中的IOCP?有机会楼主写详细点,原来不知道楼主是用的这个!
     回复 引用 查看   
  17. #17楼[楼主] 田志良      2010-09-09 18:07
    @安度
    我的做法是开通300或更多个Socket用于接受侦听Socket传递的SAEA,为什么要开通这么多?你在做压力测试时就明白了,开通多一点,会使你的连接效率、连接速度大幅度提高。对于SAEA,它不能同时ReceiveAsync、SendAsync,所以采用双工通信,让收发数据在同一条连接上进行,以提高效率。对于业务逻辑层上的处理,主要采用线程池、SQL池,用SQL池主要将网络层与数据访问层分离,为什么要分离?数据库操作会极大影响效率,如果不分离,数据操作会拖垮网络层。
     回复 引用 查看   
  18. #18楼 Leon Weng      2010-09-11 02:07
    前段时间搞视频通信时用到了sokect,顺便研究了一下,感觉效率的确比较高,但是在多线程方面自我感觉很差,所以没有使用socket完成改成WCF了,WCF封装了SOCKET,更好用了。
     回复 引用 查看   
  19. #19楼 ToBin      2011-03-01 11:12
    双工通信时需要一个客户端连接两个端口嘛,一个收,一个发?
    现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作。
    为什么会出现这个问题呢?
     回复 引用 查看   
  20. #20楼[楼主] 田志良      2011-03-01 11:30
    @ToBin
    当一个SAEA对象已处于StartReceive状态时,就不能用此SAEA发送消息。也就是说SAEA对象在一个时刻中只能处于StartAccept、StartReceive、StartSend状态中的一种。解决的办法就是用双工通信,为一条连接开辟两个SAEA对象,一个用于收,一个用于发。
     回复 引用 查看   
  21. #21楼 ToBin      2011-03-01 13:02
    刚又把文章仔细读了一遍,很多东西还是没有理解!
    刚好看到您的回复!很有帮助,我再研究研究您的文章!
    还有这个saea的三种状态,我现在在发送的时候报错"
    现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作"
    但我这个确实是clientsocket.sendasync(saeasender)是发生的。
    我接受的时候用的clientsocket.receiveasync(saeareceiver),两个没有冲突啊,很奇怪!
     回复 引用 查看   
  22. #22楼[楼主] 田志良      2011-03-01 14:55
    @ToBin
    建议Receive用异步模式,Send用同步模式。
     回复 引用 查看   
  23. #23楼 ToBin      2011-03-01 17:31
    错误“现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作”是不是因为异步发送了就返回了,但实际上还没有发送到,当第二次异步发送的时候,第一次还没发送完,就出了这个错误?
    我猜测!
     回复 引用 查看   
  24. #24楼[楼主] 田志良      2011-03-02 16:51
    @ToBin
    当Socket处于StartSend状态时,也不能执行发送操作,必须等到发送回调事件触发后,才能继续执行StartSend。解决的办法是:记录Socket的当前状态,并存储在Socket的UserToken对象下,当要执行StartSend时,判断状态。不过这样效率会很慢,当并发量达到3000时,会报很多错,推荐的方法是用同步发送。不要觉得同步发送就一定会比异步发送慢,事实证明,对于SocketAsyncEventArgs,同步发送比异步发送快多了。
     回复 引用 查看   
  25. #25楼 ToBin      2011-03-02 17:17
    拜读了,现在还有一个问题请教,异步的时候是“但异步发送消息的拷贝,是将Socket自带的Buffer空间内的所有数据,拷贝到基础系统发送缓冲区,并立即返回”这个基础系统缓冲区是对应winsocket的,有点疑惑,就是只有一个缓存区,接受也从基础系统缓冲区中拷贝处来,发送也是拷贝到这,如果我接收的时候同时发送,基础系统缓冲区里的数据还没取出来,将要取出来的时候发送,拷贝进去,会不是导致接收出来的数据不正确?导致脏读,数据错误!
    有这样的问题嘛?
     回复 引用 查看   
  26. #26楼[楼主] 田志良      2011-03-02 18:04
    @ToBin
    不会导致这个问题,基础系统缓冲区为每个Socket分配发送缓冲区和接受缓冲区,这两个不冲突。
     回复 引用 查看   
  27. #27楼 ToBin      2011-03-02 18:33
    要西,外瑞thank you !哈哈
     回复 引用 查看   
  28. #28楼[楼主] 田志良      2011-03-02 18:45
    @ToBin
    呵呵,不客气。
     回复 引用 查看   
  29. #29楼 ToBin      2011-03-03 17:02
    呵呵,又碰到问题了,可能我太笨了,您在这篇文章“Socket服务器整体架构概述”中说到一个“消息队列调度器”,这个东西该怎么实现,能大概说说嘛?
    谢谢了!呵呵
     回复 引用 查看   
  30. #30楼[楼主] 田志良      2011-03-04 09:03
    @ToBin
    呵呵,把你的邮箱发给我,这个周末我写个Demo给你。
     回复 引用 查看   
  31. #31楼 ToBin      2011-03-04 09:26
    太感谢了,激动!我的邮箱:tuablove@126.com
    辛苦您了!
     回复 引用 查看   
  32. #32楼 ToBin      2011-03-07 10:07
    您的邮件已经收到,思路已经了解,非常感谢能得到您的帮助,希望能继续得到您的帮助,思路也行,呵呵,非常感谢!
     回复 引用 查看   
  33. #33楼 ToBin      2011-03-08 17:26
    又碰到个问题,不知道怎么处理了,问题是这样的
    entityData data=getdata();public void getdata(){var data=null;//...socket.sendasync()//...data=socket.receivesync();return data;}

    getdata方法是socket 发送命令,接收返回值的方法,因为socket 服务器发送,接受时分开的,我怎么在getdata中让方法阻塞,让服务器接收到命令,然后再把结果发送过来!类似于javascript 的ajax!这种东西该怎么写啊?
    类似于使用memcache 里
    MemcachedClient mc = new MemcachedClient();mc.Get("key");

    这里面这个mc.Get("key") 方法是怎么实现的啊 ?
     回复 引用 查看   
  34. #34楼 ToBin      2011-03-09 21:02
    志良哥,您好,我在socket 开发中碰到了一些问题,想请教您,已经发送您邮箱了,思路解说在邮件里,代码在附件!等待您的回复!
     回复 引用 查看   
  35. #35楼 edwardxh      2011-11-16 09:33
    引用田志良:
    @ToBin
    呵呵,把你的邮箱发给我,这个周末我写个Demo给你。

    楼主您好,不知您是否可以把这个“Socket服务器整体架构概述”Demo也发给我一份,因为我最近也在研究Socket通信,很希望能得到您的技术心得分享,谢谢!
    我的邮箱:79668157@qq.com
     回复 引用 查看   
  36. #36楼 glf      2011-11-23 17:19
    现在公司要求能够接受2W左右的服务端,每5秒访问一次,高能不能给点意见啊
     回复 引用 查看   
  37. #37楼 鹏@      2012-01-12 17:03
    @ToBin
    大哥,能不能给小弟也发一个demo:lipeng1988011@126.com!!!
    多谢啦!!!
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
.net 3.5平台上的Socket开发
C#使用SocketAsyncEventArgs操作套接字的简单异步通讯
SocketAsyncEventArgs使用解说
TCP之深入浅出send和recv
这是一份很全很全的IO基础知识与概念(应用程序不能直接操作内核空间需要将数据从内核空间拷贝到用户空间才能使用无论是read操作还是write操作都只能在内核空间里执行)
阿里P7二面:聊聊零拷贝的原理
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服