(转)
------------------
前言
------------------
开发了这么多年,发现最困难的程序开发就是通讯系统。
其他大部分系统,例如CRM/CMS/权限框架/MIS之类的,无论怎么复杂,基本上都能够本地代码本地调试,性能也不太重要。(也许这个就是.net的企业级开发的战略吧)
可是来到通讯系统,一切变得困难复杂。原因实在太多了,如:
•性能永远是第一位:有时候一个if判断都要考虑性能,毕竟要损耗一个CPU指令,而在通讯系统服务器,每秒钟都产生上百万级别的通讯量,这样一个if就浪费了1个毫秒了。
•系统环境极其恶劣:所有我们可以想象的恶意攻击、异常输入等都要考虑;
•网络说断就断:在socket环境下,客户端可以以各种理由断开链接,而且服务器根本不会知道,连一个流水作业的业务逻辑都无法保证正常执行,因此需要设计各种辅助的协议、架构去监督。
•各种网络链接问题:例如代理、防火墙等等。。。
经过了1年的跌跌撞撞,我总算收获了点有用的经验,本文先从设计角度介绍一些我在Socket编程中的经验,下一篇在放出源代码。
------------------
现有的Socket编程资源
------------------
1. 首选推荐开源的XMPP框架,也就是Google的Gtalk的开源版本。里面的架构写的非常漂亮。特点就是:简洁、清晰。
2. 其次推荐LumaQQ.net,这套框架本身写的一般般,但是腾讯的服务器非常的猛,这样必然导致客户端也要比较猛。通过学习这套框架,能够了解腾讯的IM传输协议设计,而且他们的协议是TCP/UDP结合,一举两得。
3. 最后就是DotMsn。这个写的实在很一般般,而且也主要针对了MSN的协议特点。是能够学习到一点点的框架知识的,不过要有所鉴别。
------------------
Socket的选择
------------------
在Java,到了Java5终于出现了异步编程,NIO,于是各种所谓的框架冒了出来,例如MINA, xsocket等等;而在.NET,微软一早就为我们准备好了完善的Socket模型。主要包括:同步Socket、异步Socket;我还听说了.net 3.x之后,异步的Socket内置了完成端口。综合各种模型的性能,我总结如下:
1. 如果是短链接,使用同步socket。例如http服务器、转接服务器等等。
2. 如果是长链接,使用异步socket。例如通讯系统(QQ / Fetion)、webgame等。
3. .net的异步socket的连接数性能在 7500/s(每秒并发7500个socket链接)。而听说完成端口在1.5w所有。但是我到目前还没有正式见过所谓的完成端口,不知道到底有多牛逼。
4. 我听说了java的NIO性能在5000/s所有,我们项目内部也进行了链接测试,在4000~5000比较稳定,当然如果代码调优之后,能提高一点点。
------------------
TCP Socket协议定义
------------------
本文从这里开始,主要介绍TCP的socket编程。
新手们(例如当初的我),第一次写socket,总是以为在发送方压入一个"Helloworld",接收方收到了这个字符串,就“精通”了Socket编程了。而实际上,这种编程根本不可能用在现实项目,因为:
1. socket在传输过程中,helloworld有可能被拆分了,分段到达客户端),例如 hello + world,一个分段就是一个包(Package),这个就是分包问题。
2. socket在传输过成功,不同时间发送的数据包有可能被合并,同时到达了客户端,这个就是黏包问题。例如发送方发送了hello+world,而接收方可能一次就接受了helloworld.
3. socket会自动在每个包后面补n个 0x0 byte,分割包。具体怎么去补,这个我就没有深入了解。
4. 不同的数据类型转化为byte的长度是不同的,例如int转为byte是4位(int32),这样我们在制作socket协议的时候要特别小心了。具体可以使用以下代码去测试:
代码
public void test()
{
int myInt = 1;
byte[] bytes = new byte[1024];
BinaryWriter writer = new BinaryWriter(new MemoryStream(bytes));
writer.Write(myInt);
writer.Write("j");
writer.Close();
}
尽管socket环境如此恶劣,但是TCP的链接也至少保证了:
•包发送顺序在传输过程中是不会改变的,例如发送方发送 H E L L,那么接收方一定也是顺序收到H E L L,这个是TCP协议承诺的,因此这点成为我们解决分包、黏包问题的关键。
•如果发送方发送的是helloworld, 传输过程中分割成为hello+world,那么TCP保证了在hello与world之间没有其他的byte。但是不能保证helloworld和下一个命令之间没有其他的byte。
因此,如果我们要使用socket编程,就一定要编写自己的协议。目前业界主要采取的协议定义方式是:包头+包体长度+包体。具体如下:
1. 一般包头使用一个int定义,例如int = 173173173;作用是区分每一个有效的数据包,因此我们的服务器可以通过这个int去切割、合并包,组装出完整的传输协议。有人使用回车字符去分割包体,例如常见的SMTP/POP协议,这种做法在特定的协议是没有问题的,可是如果我们传输的信息内容自带了回车字符串,那么就糟糕了。所以在设计协议的时候要特别小心。
2. 包体长度使用一个int定义,这个长度表示包体所占的比特流长度,用于服务器正确读取并分割出包。
3. 包体就是自定义的一些协议内容,例如是对像序列化的内容(现有的系统已经很常见了,使用对象序列化、反序列化能够极大简化开发流程,等版本稳定后再转入手工压入byte操作)。
一个实际编写的例子:比如我要传输2个整型 int = 1, int = 2,那么实际传输的数据包如下:
173173173 8 1 2
|------包头------|----包体长度----|--------包体--------|
这个数据包就是4个整型,总长度 = 4*4 = 16。
说说我走的弯路:
我曾经偷懒,使用特殊结束符去分割包体,这样传输的数据包就不需要指名长度了。可是后来高人告诉我,如果使用特殊结束符去判断包,性能会损失很大,因为我们每次读取一个byte,都要做一次if判断,这个性能损失是非常严重的。所以最终还是走主流,使用以上的结构体。
------------------
Socket接收的逻辑概述
------------------
针对了我们的数据包设计+socket的传输特点,我们的接收逻辑主要是:
1. 寻找包头。这个包头就是一个int整型。但是写代码的时候要非常注意,一个int实际上占据了4个byte,而可悲的是这4个byte在传输过程中也可能被socket 分割了,因此读取判断的逻辑是:
•判断剩余长度是否大于4
•读取一个int,判断是否包头,如果是就跳出循环。
•如果不是包头,则倒退3个byte,回到第一点。
•如果读取完毕也没有找到,则有可能包头被分割了,因此当前已读信息压入接收缓存,等待下一个包到达后合并判断。
2. 读取包体长度。由于长度也是一个int,因此判断的时候也要小心,同上。
3. 读取包体,由于已知包体长度,因此读取包体就变得非常简单了,只要一直读取到长度未知,剩余的又回到第一条寻找包头。
这个逻辑不要小看,就这点东西忙了我1天时间。而非常奇怪的是,我发现c#写的socket,似乎没有我说的这么复杂逻辑。大家可以看看LumaQQ.net / DotMsn等,他们的socket接收代码都非常简单。我猜想:要么是.net的socket进行了优化,不会对int之类的进行分割传输;要么就是作者偷懒,随便写点代码开源糊弄一下。
------------------
Socket服务器参数概述
------------------
我在开篇也说了,Socket服务器的环境是非常糟糕了,最糟糕的就是客户端断线之后服务器没有收到通知。 因为socket断线这个也是个信息,也要从客户端传递到我们socket服务器。有可能网络阻塞了,导致服务器连断开的通知都没有收到。
因此,我们写socket服务器,就要面对2个环境:
1. 服务器在处理业务逻辑中的任何时候都会收到Exception, 任何时候都会因为链接中断而断开。
2. 服务器接收到的客户端请求可以是任意字符串,因此在处理业务逻辑的时候,必须对各种可能的输入都判断,防止恶意攻击。
针对以上几点,我们的服务器设计必须包含以下参数:
1. 客户端链接时间记录:主要判断客户端空连接情况,防止连接数被恶意占用。
2. 客户端请求频率记录:要防止客户端频繁发送请求导致服务器负荷过重。
3. 客户端错误记录:一次错误可能导致服务器产生一次exception,而这个性能损耗是非常严重的,因此要严格监控客户端的发送协议错误情况。
4. 客户端发送信息长度记录:有可能客户端恶意发送非常长的信息,导致服务器处理内存爆满,直接导致宕机。
5. 客户端短时间暴涨:有可能在短时间内,客户端突然发送海量数据,直接导致服务器宕机。因此我们必须有对服务器负荷进行监控,一旦发现负荷过重,直接对请求的socket返回处理失败,例如我们常见的“404”。
6. 服务器短时间发送信息激增:有可能在服务器内部处理逻辑中,突然产生了海量的数据需要发送,例如游戏中的“群发”;因此必须对发送进行队列缓存,然后进行合并发送,减轻socket的负荷。
------------------
后记
------------------
本文从架构设计分析了一个socket服务器的设计要点。如果您有其他见解,欢迎留言与讨论。
--------------------------------------------------------------------------------
我们的最新动态 (Bamboo@pixysoft.net)
1.解决comet在多页面中冲突的问题.[2011-1-23]
2.优化部分后台逻辑代码.提升首页访问性能[2011-1-20]
3.网络Comet平台再次成功对接上线.[2011-1-9]
我们每天都在努力着!
作者:美丽人生
出处:http://zc22.cnblogs.com/
技术支持:reborn_zhang@hotmail.com
分享到:
相关推荐
Socket通信是计算机网络编程中的基础概念,主要用于实现不同计算机之间的数据传输。在Windows环境下,使用Socket通信需要遵循一定的步骤和使用特定的函数。以下是对Socket通信关键知识点的详细解释: 1. **MAKEWORD...
Socket 通信异常解决总结 Socket 通信异常是指在网络通信过程中出现的各种错误和异常,包括网络连接断开、服务器端或客户端程序异常、网络链路异常等。这些异常会导致 Socket 连接断开,影响系统的稳定性和可靠性。...
总结来说,C# Socket通信稳定完整版类库实例是一个帮助开发者快速构建可靠网络通信应用的工具。通过理解Socket通信的基本原理和C#中的相关类库,开发者可以构建出高效、稳定的服务端和客户端应用程序。
总结来说,Android中的Socket通信涉及网络编程、多线程、数据处理等多个方面,需要综合运用Java和Android的相关知识。理解并熟练掌握Socket通信,对于开发实时交互的应用,如聊天、游戏等,至关重要。通过实践和不断...
总结来说,Java中的Socket通信为客户端和服务器提供了双向通信的接口,可以用于文件传输。客户端通过建立Socket连接,读取文件内容并写入Socket的输出流,而服务器端则接收这些数据并保存到本地。这个过程涉及到了...
总结来说,MFC编程实现Socket通信涉及对CAsyncSocket类的使用,包括初始化Winsock、创建Socket、绑定端口、监听/连接、发送/接收数据以及关闭Socket等步骤。通过这种方式,开发者可以构建出能在网络上进行数据交换的...
总结,Java中的Socket通信是基于TCP/IP协议的,通过创建ServerSocket和Socket实例,实现客户端与服务器间的双向数据传输。在实际编程中,我们还需要考虑异常处理、多线程、并发处理以及优化策略,以构建健壮且高效的...
总结来说,Java的Socket通信允许两个应用程序通过TCP协议交换数据,这在许多网络应用中都起着关键作用。通过创建ServerSocket监听连接,Socket建立连接,以及利用I/O流进行数据传输,我们可以实现窗口间的通信。对于...
总结一下,Socket通信在文件传输中的应用涉及到网络编程的基础知识,如创建和管理Socket对象、处理连接请求、读写文件以及错误处理。通过这种方式,我们可以实现跨网络的文件共享,特别是在需要执行远程程序的场景下...
总结一下,Android平台上的Socket通信是通过Java的Socket类实现的,它提供了连接远程服务器、读写数据的能力。在《Android游戏编程之从零开始》一书中,作者可能通过具体示例介绍了如何在Android应用中创建Socket,...
总结一下,在Windows环境下使用C语言实现Socket通信,主要步骤包括:初始化Winsock、创建Socket、连接/监听、数据传输和关闭Socket。理解这些基本操作后,可以进一步学习更复杂的网络编程概念,如多线程、异步通信、...
自己的总结,,希望对您有用,socket通信
总结来说,"C# WinForm TCP通信 UDP通信 Socket通信 vs2017 .net4.0"项目是一个学习和实践C#网络编程的好材料。通过此项目,开发者可以深入理解TCP和UDP的区别,掌握如何在C#环境中使用Socket进行网络通信,并了解...
总结来说,通过USB连接实现Android App与PC的Socket通信是一项实用的技术,尤其在特定场景下具有优势。开发者需要掌握USB主机模式、Socket编程以及数据交换的相关知识,才能成功构建这样一个系统。同时,借鉴和学习...
总结一下,Unity与Winform使用Socket通信涉及到的关键知识点包括: 1. Socket编程:理解TCP/IP协议和Socket的工作原理。 2. .NET Framework的Socket类:掌握如何在Winform中创建服务器端和处理连接。 3. Unity中的...
总结来说,VC 2008 Socket通信涉及到Winsock库的使用,Socket对象的创建、绑定、监听、连接、发送和接收,以及异常处理等多个方面。通过分析和实践server-new.cpp和client-new.cpp,可以深化这些知识点的理解和应用...
在.NET框架中,WinForm应用常常需要与外部设备或服务器进行数据交互,这时Socket通信就显得尤为重要。Socket是网络通信的基础组件,它提供了一种进程间通信(IPC)的能力,使得应用程序可以通过网络发送和接收数据。...
总结来说,“基于socket通信的多人聊天”项目涉及了Socket编程的基本原理和实践,包括服务器端的建立、客户端的连接、数据的编码与解码、多线程处理、消息格式设计以及异常和安全处理。通过学习和实践此类项目,...
总结来说,Android应用与framework的Socket通信涉及Java的Socket API、原生代码的网络编程、JNI接口以及可能的安全性和性能优化策略。理解这些概念和技术,可以帮助开发者构建可靠的跨层通信机制,实现高效的数据...
总结来说,这个项目为我们提供了一个学习和实践Socket通信的好机会,不仅让我们了解了Socket的基本工作原理,还能体验到如何运用这些知识构建实际的网络应用程序。通过分析和修改源码,我们可以加深对网络编程的理解...