首页 > 代码库 > 第十八章 SOCKET类的实现

第十八章 SOCKET类的实现

                        第十八章    SOCKET类的实现

 

      这几天反复思考,到底是从上到下、还是从底层开始往上设计?最后、还是决定从上层建筑开始。APO追求的是简单、再简单!强大、再强大!高速、高效!“天下武功、无坚不破、唯快不破!”。

      APO的socket也不外是一种内存文件吧,但socket描述符和其它类型的文件描述符还是略有区别的。APO中的一个用户进程最多可打开64K个非socket类型的文件描述符,而APO系统只是最多可以打开16M个v节点,需要256个64K位的位图变量、刚好1个位图变量数据块;中模式管理。socket描述符也是在16M个文件描述符中分配,APO中的socket描述符、描述的是一个连接、可以说是连接号,与UNIX可能会有一点不同;理论上、APO可以在一个服务端口上最大支持1千6百多万个连接。APO的网络编程是非常简单的,如服务端程序、只是在初始化方法中open一个socket,余下的只是对各种连接消息的处理方法的编写。即使有1千万个连接,那又能怎样?对于它们的请求消息的处理方法都是一样的。用户无须关心打开了多少socket描述符,有多少连接,关闭连接,TCP/UDP、管理socket描述符等等,那都是用户系统网络进程的事情。对于用户的网络编程,我们就是要越简单越好。服务端程序只是关心、来了一个带请求的连接消息,需要处理该请求消息,使用消息中的socket描述符就可操作该连接对应的接收、或发送流容器;至于、该连接是如何建立起来的、当前是否有1千万个连接等等,不用管的。

     LINUX、WINSOCKET中的bind()、socket()、listen()、accept()、connect()、write()、read()、recvfrom()、sendto()、upds_respon()、recv()、send()、recvmsg()、sendmsg()、close、shutdoen()、循环服务器、并发服务器、多路I/O复用、原始套接字等等、我都看晕了,这难道是给应用程序员使用的网络编程方法库?我只能感叹!真垃圾!这是底层程序员使用的、不是应用程序员啊。发展了几十年,应该是精华的浓缩了;为何我总看不顺眼?总感觉这样做、复杂、效率低,为何聪明人总是把问题复杂化啊?我也很想兼容和适应它们,但总是有心无力;自己笨、没法把握他们的思路。不管怎样,服务端支持千万个连接的功能我是加定了;这样,1000个游戏服务器就可支持100亿人同时在线了。

     在APO中,网络编程是分为三层:第一层SOCKET应用层,第二层是TCP/UDP(用户CPU线路的系统网络服务进程)、第三层是最底层IP/ICMP(内核CPU线路的实时线程)。层之间是使用消息交互、句柄提交、事件驱动。后2层是系统实现,用户主要是使用第一层编程。SOCKET是一种内存文件,网络编程分为服务端和客户端,网络协议主要就是TCP/UDP;在open文件SOCKET时、就需指明。open、close本来就是文件系统的基本方法,本文只是描述与socket有关的内容。数据报接收是2、3层自动实现的,包含数据报内容的句柄就是相应socket连接的文件号sockfd,以消息方式提交给用户进程;用户只需使用sockfd.成员的赋值指令,就可对接收的数据报进行读、写。不同的文件号sockfd句柄、对应不同的连接,有不同的内存容器。只要本地内存空间够大,支持千万个连接就没问题。内存分配是无须用户去考虑,只要给出规划就行了。为何?当我们编写一个客户端程序时,无法预先知道你请求之后、服务器返回的数据报大小,可能2GB;也就无法一开始就声明接收流容器的本地内存空间大小。但接收到服务器返回的数据报后,底层知道;所以、就可由底层申请分配内存,并将句柄关联到相应的sockfd。那接收就似乎无须用户规划了,但你发送的请求数据报是可以预设大小的。如果客户端,请求一个4GB大小的文件,难道服务器就要即时申请一个连续4GB大小的内存空间?用户编写的服务端程序无须这样麻烦,只是向2层发一个消息就行了(sendto())、一切2层搞掂。或许APO在SOCKET层就3个方法了:open()、close()、sendto()。前2个本来就是文件系统的基本方法,open()方法的参数有所不一样吧;所以改为opens()。用户程序主要是编写消息处理;实现HTTP协议、FTP等等。其实、即使这些协议的实现;APO操作系统都包含进去了。用户主要就是编写游戏等服务端的消息处理吧。

    当是客户端时,TCP/UDP的端口可以对应到sockfd、进程号、或线程号。但如是服务端,就不一样了;虽然能对应到进程号,但可能有几百万个连接、sockfd文件号。所以,服务端应设置流标签(低24位) = sockfd文件号(连接号);这样、下次客户端的带有相同流标签(连接号)的请求到来时,服务端的底层就可迅速定位到相应的连接v节点(当然还需要比对一下)。

一、服务端程序

    APO的进程就是一个消息恒循环,用户代码入口main(); 就是执行用户编写的进程消息处理代码。我们只需要在进程共用部分的初始化方法中Process_init(); 加入以服务端方式、TCP或UDP、最大连接数BACKLOG(单位为256个连接)、客户端发来的请求报文最大长度RECVLEN(单位行E)、服务端的发送流容器变量、的opens()方法。那么,余下的就是第二层TCP/UDP、用户CPU线路的系统网络服务进程的事情了。每当有一个新的属于服务进程的TCP/UDP数据报到来,第二层会给服务进程的动态优先级加码、以便服务进程可以更快的得到CPU;同时给服务进程发出一个消息(包含连接号sockfd、状态码等等)。当服务进程获得CPU时,可能已经有n个消息了;1、系统读入一个消息到H2行寄存器,服务进程处理该消息;2、判断、如果是第二层的系统网络服务进程发出,那么是网络数据报消息,用sockfd.成员,提取数据报、分析是什么请求、处理该请求,最后回发一个服务器数据报。3、循环回到1,如果没有需处理的消息、服务进程让步挂起返回。至于是如何构建一个连接,有上千万个连接、数据报的内存分配、IP包组装等等;都不是应用层需关心的,那是底层的事情。所以,APO的服务端程序没料啊,就一个opens()和回发服务器数据报时、可能使用到的sendto()(该方法与LINUX、UINX、WINDOWS的有区别)、或关闭一个连接close()。那些、bind()、socket()、listen()、accept()、connect()、write()、read()、recvfrom()、upds_respon()、recv()、send()、recvmsg()、sendmsg()、shutdoen()、sigio、select()等等、通通清除;还应用程序员一个干净的世界。要注意的是:如果同一个连接的客户端发了一个数据报,在服务进程还没处理完毕时,又发来一个数据报;客户端将受到流量抑制。如果前面的数据报达到1GB以上,客户端将立刻受到流量抑制、不允许再发数据报;直到服务进程处理完上一个数据报。至于、黑客回溯追踪,恶意TCP/UDP包判断,连接处理,重发等定时器,IP黑名单,攻击报警、包过滤等等都是底层的事情,用户不必关注。我知道UDP、可能上千万个连接问题不大,但TCP就需另外对待了;就那几种定时器就不好处理,难道要设计几千万个定时器?这不可能;有待研究。

二、客户端编程

    用户可以在进程、或线程内设置客户端TCP/UDP程序。还是那3个方法,但opens方法的参数设置不一样;也不是在初始化方法出现;在线程、或进程打开一个连接opens,那通常是需要用户关闭连接close。

    BUxE SENDFV; // 在主程序、声明发送流容器。

Thread_client(){ // 在线程内的客户端方法、线程入口。

   SENDFV.socketaddr_in = ?; // 设置需连接的服务器端IP地址、端口等

   SENDFV.news = ?; // 填写第一个消息报文。

   sockfd = opens( FLAG.PORT, SENDFV );

// 获得socket文件号, PORT = XX指定的客户端口,0由系统指定端口;

// FLAG =0xA000(TCP)、0x8000(UDP);由第二层建立连接、发送第一个数据报。

   PRET // 分支定点等待消息、阻塞返回;新的线程入口在下一条指令。

   Msgpro(); // 服务器返回的数据报处理方法;消息在H2,线程新入口。

// 收到一个服务器数据报、由二层返回一个消息到相应进程,而进程将启动

// 相应的线程Msgpro()方法来处理。

   If FLAG.关闭标志 = 1 then goto  tclt; 

   SENDFV.news = ?; // 填写消息报文;或者设置标志、为填写报文指针msg。

   Sendto(); // 由第二层发送一个新的请求数据报。

   WRET // 等待消息、阻塞返回,准备处理下一个数据报消息。

tclt:

   close(sockfd); // 关闭连接

   TRET // 线程终止返回

三、opens()方法


待续。。。

第十八章 SOCKET类的实现