网络编程一直是经久不衰的话题,今天就网络编程里面一些问题作个总结,因为UDP相对于TCP的处理问题比较简单,因此此次总结的都是有关TCP的。html
不少人认为单台服务器的最大TCP链接数是65536也就是受限于服务器的端口的数量 若是服务器想开6W以上的TCP链接就要绑定多个IP。其实这是一个重大的错误认识,其实按照TCP/IP协议的规定,肯定一个链接的惟一标识是一个4元组 <本地IP,本地端口,远程IP,远程端口>,由于服务器绑定的端口和IP是固定的(通常进程都只绑定一个端口和IP),因此决定链接的因素就是客户机器的远程IP和远程端口 ,与服务器端口数量根本没有任何关系,也就是说客户机的IP范围和端口号范围才是决定链接的重要因素,咱们知道IP地址的范围是2^32个(这里IP和端口不考虑保留问题),端口的数量是2^16个 那么服务器保持的最大链接数的理论值是2^32*2^16=2^48 。不过,难道服务器端真的能开那么多链接么,答案是要看服务器有多少物理内存。也就是说物理内存的大小才是决定服务器能开多少链接的决定因数(固然也和操做系统的设置有关 例如:最大文件句柄的数量,但整体来讲是受限于物理内存),若是只有1G的物理内存想开100W的链接这是不可能的。咱们知道创建一个socket链接和打开一个文件同样,是由操做系统创建一个文件句柄,文件句柄指向一个称为文件对象的windows内核对象 ,建立文件对象的数量决定了TCP的最大链接数,也能够这么认为:对于有限的资源 服务器最大的TCP链接数是操做系统能打开文件的最大数量,若是系统资源足够大时那么最大就是2^48。node
为了证实服务端的I链接数P和端口没有关系 我写了一个测试程序,该程序为了屏蔽线程栈消耗的内存采用了.net 封装好的IOCP的方式。编程
客户端测试机使用了3台普通的PC机 ,三台台器分别与服务器保持5W,4W,1W个链接,统计服务端的链接数是不是10W。windows
程序代码以下:服务器
1. 服务端代码:网络
/// <summary> /// IOCP soket /// </summary> class Server { private Socket connSocket; //统计链接总数 private static int count; public Server() { connSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse("192.168.1.123"); IPEndPoint endPoint = new IPEndPoint(ip, 6530); connSocket.Bind(endPoint); connSocket.Listen(50000); } /// <summary> /// 接收客户端链接 /// </summary> public void Start() { IAsyncResult acceptResult=null; acceptResult = connSocket.BeginAccept(AcceptCallback, connSocket); } private void AcceptCallback(IAsyncResult ar) { Socket accetpSocket = ar.AsyncState as Socket; if(accetpSocket==null) { return; } Socket receiveSocket= accetpSocket.EndAccept(ar); //打印链接数 count++; Console.WriteLine("链接数:{0}",count); Start(); Receieve(receiveSocket); } /// <summary> /// 接收发送的数据 /// </summary> /// <param name="receiveSocket">接收数据的socket</param> private void Receieve(Socket receiveSocket) { byte[] buffer = new byte[28]; receiveSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, (state) => { Socket receive = state.AsyncState as Socket; try { int length = receive.EndReceive(state); Console.WriteLine(Encoding.Unicode.GetString(buffer)); Receieve(receive); } catch (SocketException socketEx) { receive.Dispose(); } }, receiveSocket); } }
2. 客户端代码:多线程
/// <summary> /// 测试客户端 /// </summary> class Program { static List<Socket> socketList = new List<Socket>(); static void Main(string[] args) { for (int i = 0; i < 40000;i++) { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); int port = 6530; IPAddress ip = IPAddress.Parse("192.168.1.123");//本地 socket.Connect(ip, port); socketList.Add(socket); } Console.ReadKey(); } }
3.测试结果: socket
(1)Server运行结果:性能
图3.1测试
(2)Tcpview监视的链接:
图3.2
(3)server占用资源状况:
图3.3
根据图3.1的结果能够看出, 服务端接受的链接数为10W ,因此说 最大TCP链接数与服务端的端口的数量不要紧,并且根据图3.2得知服务端的端口是保持不变的,变的只是客户端的IP和端口,因此一个链接的惟一标识就是一个4元组 <本地IP,本地端口,远程IP,远程端口>。
进一步分析,咱们假定该程序全部的内存都是链接占用的,根据图3.3能够看出10W个链接占用了 200 M内存,也就是每一个链接占用了 2kb 在物理内存4G的32操做系统下开起链接数最大约为100W(用户模式使用了2G的寻址空间),可是即便有4G的物理内存也未必能开启50W个链接,由于在内核模式的地址空间分为分页内存池和非分页内存池(用户模式下老是分页的),文件对象这个windows内核对象是存储在内核模式下的非分页内存池,所谓非分页内存池就是该内存区始终在物理内存而不会在磁盘文件的页交换文件中,而内核模式的分页内存池也占用了一部分物理内存,致使整个内核模式的非分页内存池不能达到2G,因此100W个链接在32位操做系统是也没法开启。那么在64位操做系统上,理论上内存大的话是没有限制的,亚马逊曾经测试过node.js的链接数 100W个链接占用了16G的内存(.net程序没有测过100W的内存状况,因此4G的100W个链接只是推测,和实际状况可能差异较大) http://blog.caustik.com/2012/08/19/node-js-w1m-concurrent-connections/。
最后还要补充一句:服务器的性能和处理多少链接数不要紧,即便100W个链接只是链接而不发送数据,服务器只是浪费点内存,若是100W个同时发送数据,那么服务器可能会处理不过来,处理不过来只是相对于而言,若是发送的每一个链接消息很是小,服务器的配置好,并且服务端处理消息的逻辑相对简单,那可能会处理过来,相反 一个链接只发送一条消息,但服务器处理这条消息的逻辑很是复查,好比要作个几分钟的大型的计算,那么服务器也是处理不过来的。因此对于服务端来讲 一味的追求多少链接数是不可取的,主要仍是要衡量处理消息的逻辑。
TCP/IP协议限制了每一个链接的最大传输速度。并且使各大链接的速度保持一直,因此说不考虑服务端的处理速度等其余因数,多个链接的发送数据速度要比单个链接的快,因此迅雷采用了多线程下载的方式。固然,咱们实际开发中要采用多少个链接须要根据服务端对每一个链接的处理能力进行测算,找出一个平衡点。
在.net平台之间进行通信时能够忽略主机字节序的问题,.net平台统一采用了小端法,若是须要跨平台传输,就须要统一字节序,通常有两种方案:
1.采用字符串的形式,注意UNICODE编码的字符串占2个字节,因此一样会有字节序的问题,因此应该使用ANSCI的字符串。
2.都采用大端或小端,在报文里用一个字节作个表识来代表主机的字节序。
由于TCP属于流式传输,因此没有数据边界,因此咱们不知道每次发送数据的长度,因此每次接受数据时多是两个包或者多是一个不完整的包,这就咱们须要处理TCP断包和粘包这两个场景,通常解决方案是在头部前4个字节来标识整个数据包的长度,接受数据时先接受4个字节的长度,而后根据解析出的包长进行循环接收直到数据包接完整为止,注意接收前4个字节的包长是也要进行循环接受直到接满4个字节。
有时候数据包传输过程当中会出现错误,通常都是校验CRC是否完整来校验包的正确性。一旦数据包出现错误通常有两种测试处理:第一是直接丢弃整个包, 该方法简单但可能会失去某些比较重要的数据。 第二个是对根据报文对整个包进行遍历把可能没有错误的数据保持下来,可是该方案比较麻烦。
最后一句感言:不管什么平台和语言,基础知识都是通用的,制约发展的因素不是咱们会哪几种语言,而是咱们运用基础知识去解决实际问题的能力。