.NET Socket开发之异步Socket

在基于.NET的网络服务端的开发中,我们用到和听到的最多的恐怕就是异步Socket了。异步Socket的性能比同步高出很多,但是编写代码比较复杂。因此异步Socket也是网络上讨论比较多的话题。
今天,我们就来讨论一下如何用异步Socket开发网络应用。在此之前我们先讨论两个问题。
一、异步Socket是如何工作的:
那异步Socket是如何工作的呢?我以接收一条消息来说明这个问题。首先,程序向系统投递一个接收数据的请求,并为其指定一个数据缓冲区和回调函数,回调函数用来指示当数据到达后将如何处理,然后我们的程序继续执行下去,当有数据到达的时候,系统将数据读入缓冲区,并执行回调函数,处理这条消息。我们并不需要关心这条消息何时到达。
二、什么情况下我们用异步Socket:
有些人认为,异步Socket的性能比同步Socket的性能高很多,应该在各种环境下都用异步Socket,其实不然。在某些环境下面。异步反到比同步的性能低,那么在哪些情况下会这样呢?
1、客户端Socket。
2、服务端连接数比较少。
3、连接数很多,但都是短连接。
在这些环境下,我们用同步Socket不但可以简化代码,而且性能并不会比异步Socket低。但在服务端连接比较多而且是长连接的情况下,我们就要使用异步Socket。
现在我们来看看如何用异步Socket编程。
首先,我们要建立一个Socket用来监听:

            Socket _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint localEP 
= new IPEndPoint(_address, _port);
            _listener.Bind(localEP);
            _listener.Listen(
100);
然后创建一个线程来处理客户端连接请求:  我们再来看看回调函数的定义:

        private void ReceiveCallBack(IAsyncResult ar)
        
{
            UserInfo info 
= (UserInfo)ar.AsyncState;
            Socket handler 
= info.socket;
            
int readCount = 0;
            
try
            
{
                readCount 
= handler.EndReceive(ar);//调用这个函数来结束本次接收并返回接收到的数据长度。
            }

            
catch (SocketException)//出现Socket异常就关闭连接
            {
                CloseSocket(info);
//这个函数用来关闭客户端连接
                return;
            }

            
catch
            
{
            }

            
if (readCount > 0)
            
{
                
byte[] buffer = new byte[readCount];
                Buffer.BlockCopy(info.Buffer, 
0, buffer, 0, readCount);
                Analyzer(info, buffer);
//这个函数用来处理接收到的信息。
                try
                
{
                    handler.BeginReceive(info.Buffer, 
0, info.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), info);//向系统投递下一个接收请求
                }

                
catch (SocketException) //出现Socket异常就关闭连接
                {
                    CloseSocket(info);
                }

                
catch
                
{
                }

            }

            
else //如果接收到0字节的数据说明客户端关闭了Socket,那我们也要关闭Socket
            {
                CloseSocket(info);
            }

        }
接下来我们看看如何发送数据给客户端:

        public void Send(Socket socket, byte message)
        
{
            
try
            
{
                info.socket.BeginSend(message, 
0, _byte.Length, SocketFlags.None, new AsyncCallback(SendCallBack), info);//这里向系统投递一个发送数据的请求,并指定一个回调函数。
            }

            
catch (SocketException ex)
            
{
                CloseSocket(info);
            }

            
catch
            
{
            }

        }
定义发送回调函数:

        private void SendCallBack(IAsyncResult ar)
        
{
            UserInfo info 
= (UserInfo)ar.AsyncState;
            
try
            
{
                info.socket.EndSend(ar);
//调用这个函数来结束本次发送。
            }

            
catch
            
{
            }

        }

 
好了,整个监听、接收、发送的过程就完成了,很简单吧。现在需要说明的是,我在这里接收客户端连接的Accept是用的同步的,我个人认为在这里用同步的会比用异步好一些。因为这样代码简单而且没有性能上的损失。
今天我就写到这里了!下次我们再继续。
posted on 2007-06-13 23:59 牧 野 阅读(2865) 评论(32)   编辑   收藏 所属分类: C#Socket
#2楼    回复  引用  查看    
通过捕获错误来关闭连接会导致性能问题,建议
使用:
Socket.BeginReceive (Byte[], Int32, Int32, SocketFlags, SocketError, AsyncCallback, Object)
Socket.EndReceive (IAsyncResult, SocketError)
来获取错误号也许性能会好一些
2007-06-14 11:24 | 老钟 [未注册用户]
#3楼    回复  引用  查看    
楼主对异步的认识大错特错
所谓的SOCKET采用异步会取得高效的性能说的就是Accept。我们应该用BeginAccept,因为如果用Accept的话,同时连接数量多的情况下就不能及时回应,而用BeginAccept就会很好的避免这个问题,所谓的SOCKET应该采用异步来提高性能,其实说的就是我们应该用BeginAccept。对于SEND和RECEIVE,你只要开一个线程在处理一个连接其实就相当于是BEGINSEND和BEGINRECEIVE,就是在异步处理了。有人说一个新连接开一个线程不好,如果有1000连接就要开1000个线程,所以应该采用异步,这简直就是天大的笑话,这些笨蛋们远不知道BEGINSEND等方法就是在线程池开的线程来处理。
BEGINSEND和BEGINRECEIVE在。NET1.1里根本就没有这两个方法,难道。NET1.1的SOCKET就不能异步了????????看了楼主的文章,其实很多东西楼主大大的理解错了。
2007-06-15 19:10 | biaobiao [未注册用户]
#4楼 [楼主]   回复  引用  查看    
@biaobiao
那你有没有查看过.NET的类库呢??
呵呵…………
BeginSned和BeginReceive可不是简单的丢在线程池里,他是投递到系统内核里去了。由系统内核来调度发送和接收的操作。
还要告诉你一点,在Socket编程中,线程不是越多越好,你知道性能最高的IOCP模式有多少个线程吗?CPU数量×2+2,就这么多。当一个程序的线程达到50个以上时,如果再增加线程,系统的性能会下降很多!看来你还要多学学!
你使用BeginAccept来接收Socket,而用Receive和Send来收发数据,这才是大错特错。回家好好看完书再来讨论。
还有,不要在我这里骂人,不然我会删除你的回复。那些被你骂成笨蛋的恰恰是比你更了解Windows开发的人。
2007-06-15 19:33 | 牧 野
#5楼 [楼主]   回复  引用  查看    
@老钟
你的建议不错,回去把例子的代码改一下,到时发上来给大家看看!
2007-06-15 19:34 | 牧 野
#6楼 [楼主]   回复  引用  查看    
@biaobiao
还有一点,.NET1.1里面的Socket有BeginSend和BeginReceive这两个方法,建议你多看看MSDN。
2007-06-15 19:40 | 牧 野
#7楼    回复  引用  查看    
呵呵,你要这样理解异步你就这样理解吧,我是给出正确的解释。你做过实际的项目吗?你了解TCP/IP吗?
2007-06-15 20:36 | biaobiao [未注册用户]
#8楼 [楼主]   回复  引用  查看    
@biaobiao
我从事软件开发7年,做过的项目不下二十个,其中有一半涉及到网络开发!
小伙子,谦虚一点!多看点书。
2007-06-16 08:49 | 牧 野
#9楼    回复  引用  查看    
顶,收藏
2007-06-16 09:00 | 忍 [未注册用户]
#10楼    回复  引用  查看    
正常交流很有趣,吵的越凶越好,当事人和观众都会有很大的收获。相互攻击就很无聊了,一方摆做过实际项目的臭脸,另一方摆年纪大经验足的老架子,有这份闲心不如把.NET网络相关的东东更深入的给我们讲讲,不是更有意义?
2007-06-16 10:07 | gussing
#11楼 [楼主]   回复  引用  查看    
@gussing
呵呵…………
谢谢你的关注,我可没摆老架子,我只是告诉他,我做过实际项目,这些都是我做项目的时候总结出来的经验,可不是想当然想出来的!
呵呵………………
你的建议很好,我也准备再写一些相关的东东!
2007-06-16 10:27 | 牧 野
#12楼 [楼主]   回复  引用  查看    
@biaobiao
删除你一篇回复,因为我说过不要在我这里骂人!
2007-06-16 10:27 | 牧 野
#13楼 [楼主]   回复  引用  查看    
@biaobiao
我的垃圾代码就在上面,不知道你的不是垃圾的代码在哪呢??

如果你再不沉下心来和我争论技术而是骂人的话,我这里不欢迎你!
2007-06-16 10:32 | 牧 野
#14楼    回复  引用  查看    
删回复,你这个人好虚伪,我的那个回复肯本就没带一个骂人的字,这个你很清楚,你太虚伪了。呵呵,你就按照你的想法做吧。说实话,你上面的代码能拿来见人,真的好笑。
2007-06-16 10:35 | biaobiao [未注册用户]
#15楼 [楼主]   回复  引用  查看    
@biaobiao
我说过,我这里不欢迎你了!如果你不讨论技术问题的话。

说别人是笨蛋,别人的代码是垃圾,这不算骂人吗??
不知道阁下的代码能不能拿出来见人呢??

PS:如果再出现非技术回复,一律删除!
2007-06-16 10:40 | 牧 野
#16楼    回复  引用  查看    
呵呵,把我回复都删除了吧。
2007-06-16 14:37 | biaobiao [未注册用户]
#17楼 [楼主]   回复  引用  查看    
@biaobiao
我欢迎技术的争论,但不欢迎骂人,我不删除你全部的回复是因为你没被删除的回复是关于技术的。
2007-06-16 14:54 | 牧 野
#18楼    回复  引用  查看    
@biaobiao
异步Socket不是简单的起多个线程
你测试一下就知道了
1200的并发 实际上 系统分配给接收 Socket的线程数 大约只有30-50个
2007-06-17 11:41 | 哈哈 [未注册用户]
#19楼    回复  引用  查看    
@biaobiao
说话不要太刻薄,@牧 野 这样做也是有他的道理的。
你如果不服气 你可以自己写啊 。别人写了 你来JJYY 的成何体统。
2007-06-18 10:56 | 嘻嘻 [未注册用户]
#20楼    回复  引用  查看    
@ 牧 野

"性能最高的IOCP模式有多少个线程吗?CPU数量×2+2"

我很想知道该公式是怎么来的,能否告知一二?谢谢
2007-06-18 16:38 | gussing
#21楼 [楼主]   回复  引用  查看    
@gussing
这个公式是前人的经验总结出来的!
2007-06-20 14:23 | 牧 野
#22楼    回复  引用  查看    
开发新手,借鉴代码。多谢!
还想请教个问题。
服务器开两个端口监听。
一个端口(8091)监听接收的是长时间不断的连接,每来一个连接,都要把它存入哈希表ht.
另一个端口(8092)监听的连接都是短连接,客户端发完消息就关闭连接。然后服务器从哈希表ht中取出一个连接,将消息转发出去.

8091端口对应的客户端多,可能上千。
8092对应的客户端可能几百。

8092端口的客户端我用同步收发.
服务器两个端口的收发,各应该用异步还是同步?
希望指教!

2007-06-21 15:32 | 刘易斯 [未注册用户]
#23楼 [楼主]   回复  引用  查看    
@刘易斯
可以分别使用同步和异步,互相并不会影响。
2007-06-21 19:44 | 牧 野
#24楼    回复  引用  查看    
牧野 的这篇文章没啥错的,我赞同他的观点,尤其是关于异步的好处,它绝非简单的把Socket操作抛到线程池里(我在刚开始用.NET 1.0的时候也有过这种想法),而是传给里系统内核,然后系统内核会把它最终传到硬件层,这时候用到的线程甚至根本不是CPU上的,而是通讯硬件中的,这种手法可以大量节约CPU资源.

还有就是关于线程的使用,应该尽量节约,尤其是在做有大量客户连接的服务器时,最好是一两个线程负责所有的客户请求. 我自己用的方法是把所有客户请求抛到一个队列里,有一个线程负责把请求从队列里取出来处理. 它好处有两个:

1. 节省并发线程
2. 是避免了多线程并发处理请求时的线程同步.

如果对异步Socket有疑惑,可以读下大师Jeffrey Richter的<<CLR via C#, Second Edition>>, 其中有一章讲.NET线程和异步操作,比MSDN讲的透彻.

最后说一下,我做开发的年头和数量不在作者之下,以上所说主要来自具体编程的实践体会.
2007-06-29 15:09 | 红金鱼 [未注册用户]
#25楼    回复  引用  查看    
终于得出一个结论了,其实讨论问题,争吵问题是很正常的,可骂人就不对了,还在学习,谢谢红金鱼与牧 野!!!!BIAOBIAO如果牛叉的话,就搞个反面例子出来,大家一起分享啦



2007-07-24 14:14 | yellowyu
#26楼    回复  引用  查看    
没做过Socket不敢乱说话。
2007-08-03 09:04 | yooono [未注册用户]
#27楼    回复  引用  查看    
牧野写得不错...
不过我还是仅同意biaobiao服务器端用BeginAccept可能性能更好,虽然是复杂了一点点....

例外如按老钟说的改用
Socket.EndReceive (IAsyncResult, SocketError)
不用还会不会有异常抛出?

2007-08-04 10:20 | 边城浪 [未注册用户]
#28楼    回复  引用  查看    
可以参考

基于事件的异步Socket(TCP连接方式)
http://blog.csdn.net/yeerh/archive/2006/09/25/1280111.aspx

很久以前的了..按老钟的说法改用了SocketError,性能应该更好了些...

2007-08-04 15:13 | 边城浪 [未注册用户]
#29楼    回复  引用  查看    
再问一下..Socket断线后 是否可以自动重连

下面是连接程序
我想在网络断开的时候重新连,怎么改,谢谢
加注释的话异常是在已连接的socket申请连接
public void Connect2Server()
{
IPAddress ipAddress = IPAddress.Parse(szSvrIPAddr);
EndPoint remoteEP = new IPEndPoint(ipAddress, SvrPort);
/*
Socket SocketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

if (SocketClient.Connected)
{
SocketClient.Shutdown(SocketShutdown.Both);
SocketClient.Close();
Thread.Sleep(100);
}
*/

while(!SocketClient.Connected)
{
try
{
SocketClient.Connect(remoteEP);
}
catch(Exception exc)
{
Log.WriteEntry("Connect to Server Error:" + exc.Message, EventLogEntryType.Error, 2);
}
Thread.Sleep(30 * 1000); //30s
}
}
2007-08-06 15:57 | 边城浪 [未注册用户]
#30楼    回复  引用  查看    
牧 野 大哥,Demo在哪呢?
能否提供下载,或者Send me (Csharp_programmer@163.com)
谢谢了!
2007-09-05 11:31 | Good_NEt [未注册用户]
#31楼    回复  引用  查看    
对了,帮忙把我的回复的@改成AT.
这样就不会有 恶意/广告 邮件了.谢谢/.嘿嘿
#1楼     回复   引用   查看    
 

            Thread _acceptWorkThread = new Thread(AcceptWorkThread);
            _acceptWorkThread.Start();

        
private void AcceptWorkThread()
        
{
            
while (_isListener)
            
{
                UserInfo info 
= new UserInfo();//这个UserInfo是用来保存客户信息的。
                info.socket = socket;
                Socket socket 
= _listener.Accept();
                
//这里进行其它处理。
                socket.BeginReceive(info.Buffer, 0, info.Buffer.Length, SocketFlags.None, ReceiveCallBack, info);//这里向系统投递一个接收信息的请求,并为其指定ReceiveCallBack做为回调函数
            }

        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值