• PYTHON网络编程笔记


    socket.socket(socket.AF_INET,socket.SOCK_STREAM)

      AF 表示ADDRESS FAMILY 地址族
      PF 表示PROTOCOL FAMILY 协议族
      但这两个宏定义是一样的
      所以使用哪个都没有关系
      Winsock2.h中
      #define AF_INET 0
      #define PF_INET AF_INET
      所以在windows中AF_INET与PF_INET完全一样
      而在Unix/Linux系统中,在不同的版本中这两者有微小差别
      对于BSD,是AF,对于POSIX是PF
      UNIX系统支持AF_INET,AF_UNIX,AF_NS等,而DOS,Windows中仅支持AF_INET,它是网际网区域。

      在函数socketpair与socket的domain参数中有AF_UNIX,AF_LOCAL,AF_INET,PF_UNIX,PF_LOCAL,PF_INET.
      这几个参数有AF_UNIX=AF_LOCAL, PF_UNIX=PF_LOCAL, AF_LOCAL=PF_LOCAL, AF_INET=PF_INET.
      建议:对于socketpair与socket的domain参数,使用PF_LOCAL系列,而在初始化套接口地址结构时,则使用AF_LOCAL.
      例如: z = socket(PF_LOCAL, SOCK_STREAM, 0); adr_unix.sin_family = AF_LOCAL;

    网络通信的基本接口--socket,它扩展了操作系统的基本I/O到网络通信,对不同协议来说是一种通用的接口,可以处理TCP和UDP通信
    Python 提供了两种利用socket发送和接收数据的方法:
    socket对象:提供了send() senfto() recv() recvfrom()

    文件类对象:提供了read() write() readline() (更适合TCP,和文件一样是以字节流形式运转)


    socket模块定义了4中可能出现的异常:
    与一般I/O和通信问题有关的socket.error
    与查询地址信息有关的socket.gaierror
    与其他地址错误有关的socket.herror
    与一个socket上调用socket.settimeout()后,处理超时有关的socket.timeout


    一旦结束写操作,应该立刻调用shutdown()函数,这样会强制清除缓存里面的内容,同时如果有任何问题就会产生一个异常

    当使用文件对象 对makefile()调用 避免指定缓冲器,如果指定了,需要调用文件对象的flush()方法

    UDP通信几乎不使用文件类对象


    #########
    服务器 建立TCP连接的步骤:

    1.建立socket对象
    2.设置socket选项
    s.setsocket(level,optname,value)
    s.getsocket(level,optname[,buflen])
    level为SOL_SOCKET(socket选型)的选项有(参考,对主机系统的依赖很高):
    SO_BINDTODEVICE
    SO_BROADCAST
    SO_DONTROUTE
    SO_KEEPALIVE:可以使TCP通信的信息包保持连续性,这些信息包可以在没有信息传输的时候,使通信的双方确定连接是保持的
    SO_OOBINLINE
    SO_REUSEADDR:当socket关闭后,本地端用于该socket的端口号立刻就可以被重用,通常来说只有经过系统定义的一段时间后,才能被重用
    SO_TYPE
    3.绑定到一个端口
    4.侦听连接
    s.listen(num) 这个调用告知操作系统准备接收连接,参数指明了服务器在实际连接时,允许有多少个未决(等待)连接在队列中等待

    通常服务器连续运行来接收连接,可以设计一个无限循环。
    通常情况下无限循环是不好的 因为他们会耗尽系统的cpu资源,然而 这里的循环是不同的:当调用accept()的时候,它只在有一个客户端连接后才返回,同时,程序停止 并不使用任何
    CPU资源。一个停止并等待输入或输出的程序称为被阻塞的程序

    建立UDP服务器:不必调用listen()和accept(),仅仅使用recvfrom()函数就可以了,该函数返回接收的数据和发送数据的地址
    TCP服务器一般会用accept()来为每个连接的客户端建立个新的socket,
    UDP服务器只是使用一个单一的socket,并完全依靠recvfrom()函数返回的值来判断往哪儿发送数据


    flush() 是把缓冲区的数据强行输出, 主要用在IO中,即清空缓冲区数据,一般在读写流(stream)的时候,数据是先被读到了内存中,再把数据写到文件中,当你数据读完的时候不代表你的数据已经写完了,因为还有一部分有可能会留在内存这个缓冲区中。这时候如果你调用了close()方法关闭了读写流,那么这部分数据就会丢失,所以应该在关闭读写流之前先flush()。
    避免死锁:
    死锁发生在当一个服务器和客户端同时试图往一个连接上写东西和同时从一个连接上读的时候。


    客户端 可以使用多线程和其他方法,可以同时接收和发送
    服务端 可以用超时 来摆脱死锁

    DNS查询:
    socket.getaddrinfo(host,port[,family[,socktype[,proto[,flags]]]])根据主机名查找IP
    socket.gethostbyname()函数与IPV6不兼容

    socket.gethostbyaddr(ip) 根据IP反向查找域名,需要确保为每一个反向查找的行为捕获和处理socket.herror异常

    socket.gethostname()获取操作系统配置中本地机器的主机名

     “配置主机名两个地方都要改 否则本地获取socket.gethostbyname(socket.gethostname())会报错
      /etc/sysconfig/network
      /etc/hosts”

    socket.getfqdn(hostname) 根据主机名获取操作系统中完整的配置信息
    很多系统是在私有网络上的,在公共的Internet上既得不到主机名,也得不到完整的名称

    pydns模块 访问DNS系统的接口
    DNS records DNS记录类型

    初始化名称服务器 DNS.DiscoverNameServers()
    建立请求对象 reqobj=DNS.Request() 这个对象用来发出任何DNS查询请求
    执行实际查询 asobj=reqobj.req(name='www.baidu.com',qtype=DNS.Type.ANY) name实际查询的名称,qtype 指定DNS记录类型,返回一个包含结果的应答对象,其属性answers 包含所有返回的应答列表

    ******************************
    DNS 相关术语

      DNS A记录 NS记录 MX记录 CNAME记录 TXT记录 TTL值 PTR值 泛域名 泛解析 域名绑定 域名转向  
      1. DNS 
      DNS:Domain Name System 域名管理系统 域名是由圆点分开一串单词或缩写组成的,每一个域名都对应一个惟一的IP地址,这一命名的方法或这样管理域名的系统叫做  域名管理系统。 
      DNS:Domain Name Server 域名服务器 域名虽然便于人们记忆,但网络中的计算机之间只能互相认识IP地址,它们之间的转换工作称为域名解析,域名解析需要由专门  的域名解析服务器来完成,DNS 就是进行域名解析的服务器。 查看DNS更详细的解释  


      2. A记录 
      A (Address)记录是用来指定主机名(或域名)对应的IP地址记录。用户可以将该域名下的网站服务器指向到自己的web server上。同时也可以设置域名的子域名。通俗  来说A记录就是服务器的IP,域名绑定A记录就是告诉DNS,当你输入域名的时候给你引导向设置在DNS的A记录所对应的服务器。 简单的说,A记录是指定域名对应的IP地址。 

     
      3. NS记录 
      NS(Name Server)记录是域名服务器记录,用来指定该域名由哪个DNS服务器来进行解析。 
      您注册域名时,总有默认的DNS服务器,每个注册的域名都是由一个DNS域名服务器来进行解析的,DNS服务器NS记录地址一般以以下的形式出现: ns1.domain.com、   ns2.domain.com等。 
      简单的说,NS记录是指定由哪个DNS服务器解析你的域名。  


      4. MX记录 MX(Mail Exchanger)记录是邮件交换记录,它指向一个邮件服务器,用于电子邮件系统发邮件时根据收信人的地址后缀来定位邮件服务器。例如,当Internet  上的某用户要发一封信给 user@mydomain.com 时,该用户的邮件系统通过DNS查找mydomain.com这个域名的MX记录,如果MX记录存在, 用户计算机就将邮件发送  到MX记录所指定的邮件服务器上。  


      5. CNAME记录 
      CNAME(Canonical Name )别名记录,允许您将多个名字映射到同一台计算机。通常用于同时提供WWW和MAIL服务的计算机。例如,有一台计算机名为
      “host.mydomain.com”(A记录),它同时提供WWW和MAIL服务,为了便于用户访问服务。可以为该计算机设置两个别名(CNAME):WWW和MAIL, 这两个别名的  全称就“www.mydomain.com”和“mail.mydomain.com”,实际上他们都指向 “host.mydomain.com”。  


      6. TXT记录 

      TXT记录,一般指某个主机名或域名的说明,如:admin IN TXT "管理员, 电话:XXXXXXXXXXX",mail IN TXT "邮件主机,存放在xxx , 管理人:AAA",         Jim IN TXT "contact: abc@mailserver.com",也就是您可以设置 TXT 内容以便使别人联系到您。  
      TXT的应用之一,SPF(Sender Policy Framework)反垃圾邮件。SPF是跟DNS相关的一项技术,它的内容写在DNS的TXT类型的记录里面。MX记录的作用是给寄信者指  明某个域名的邮件服务器有哪些。SPF的作用跟MX相反,它向收信者表明,哪些邮件服务器是经过某个域名认可会发送邮件的。SPF的作用主要是反垃圾邮件,主要针对那些  发信人伪造域名的垃圾邮件。例如:当邮件服务器收到自称发件人是spam@gmail.com的邮件,那么到底它是不是真的gmail.com的邮件服务器发过来的呢,我们可以查询  gmail.com的SPF记录,以此防止别人伪造你来发邮件。  

     

      7.SOA记录

     7.任何 DNS 记录文件(Domain Name System (DNS) Zone file)中, 都是以SOA(Start of Authority)记录开始。SOA 资源记录表明此 DNS 名称服务器是为 该 DNS 域中的数据的信息的最佳来源。SOA 记录与 NS 记录的区别:简单讲,NS记录表示域名服务器记录,用来指定该域名由哪个DNS服务器来进行解析;SOA记录设置一  些数据版本和更新以及过期时间的信息.


      7. TTL值 
      TTL(Time-To-Live)原理:TTL是IP协议包中的一个值,它告诉网络路由器包在网络中的时间是否太长而应被丢弃。有很多原因使包在一定时间内不能被传递到目的地。例  如,不正确的路由表可能导致包的无限循环。一个解决方法就是在一段时间后丢弃这个包,然后给发送者一个报文,由发送者决定是否要重发。TTL的初值通常是系统缺省值,  是包头中的8位的域。TTL的最初设想是确定一个时间范围,超过此时间就把包丢弃。由于每个路由器都至少要把TTL域减一,TTL通常表示包在被丢弃前最多能经过的路由器  个数。当记数到0时,路由器决定丢弃该包,并发送一个ICMP报文给最初的发送者。  
      简单的说,TTL就是一条域名解析记录在DNS服务器中的存留时间。当各地的DNS服务器接受到解析请求时,就会向域名指定的NS服务器发出解析请求从而获得解析记录;在  获得这个记录之后,记录会在DNS服务器中保存一段时间,这段时间内如果再接到这个域名的解析请求,DNS服务器将不再向NS服务器发出请求,而是直接返回刚才获得的记  录,而这个记录在DNS服务器上保留的时间,就是TTL值。  
      TTL值设置的应用: 
      一是增大TTL值,以节约域名解析时间,给网站访问加速。 
      一般情况下,域名的各种记录是极少更改的,很可能几个月、几年内都不会有什么变化。我们完全可以增大域名记录的TTL值让记录在各地DNS服务器中缓存的时间加长,这  样在更长的一段时间内,我们访问这个网站时,本地ISP的DNS服务器就不需要向域名的NS服务器发出解析请求,而直接从缓存中返回域名解析记录。 
      二是减小TTL值,减少更换空间时的不可访问时间。 
      更换空间99.9%会有DNS记录更改的问题,因为缓存的问题,新的域名记录在有的地方可能生效了,但在有的地方可能等上一两天甚至更久才生效。结果就是有的人可能访问  到了新服务器,有的人访问到了旧服务器。仅仅是访问的话,这也不是什么大问题,但如果涉及到了邮件发送,这个就有点麻烦了,说不定哪封重要信件就被发送到了那已经  停掉的旧服务器上。 


      为了尽可能的减小这个各地的解析时间差,合理的做法是: 第一步,先查看域名当前的TTL值,我们假定是1天。 
      第二步,修改TTL值为可设定的最小值,可能的话,建议为1分钟,就是60。 第三步,等待一天,保证各地的DNS服务器缓存都过期并更新了记录。 
      第四步,设置修改新记录,这个时候各地的DNS就能以最快的速度更新到新的记录。 
      第五步,确认各地的DNS已经更新完成后,把TTL值设置成您想要的值。  
      一般操作系统的默认TTL值如下: 

      TTL=32 Windows 9x/Me 

      TTL=64 LINUX 

      TTL=128 Windows 200x/XP

       TTL=255 Unix   


      8. PTR值 
      PTR是pointer的简写,用于将一个IP地址映射到对应的域名,也可以看成是A记录的反向,IP地址的反向解析。 
      PTR主要用于邮件服务器,比如邮箱AAA@XXX.com给邮箱BBB@yahoo.com发了一封邮件,yahoo邮件服务器接到邮件时会查看这封邮件的头文件,并分析是由哪个IP地  址发出来的,然后根据这个IP地址进行反向解析,如果解析结果对应XXX.com的IP地址就接受这封邮件,反之则拒绝接收这封邮件。  


      9. 泛域名与泛解析 
      泛域名是指在一个域名根下,以 *.Domain.com的形式表示这个域名根所有未建立的子域名。 
      泛解析是把*.Domain.com的A记录解析到某个IP 地址上,通过访问任意的前缀.domain.com都能访问到你解析的站点上。  


      10. 域名绑定 
      域名绑定是指将域名指向服务器IP的操作。  


      11. 域名转向 
      域名转向又称为域名指向或域名转发,当用户地址栏中输入您的域名时,将会自动跳转到您所指定的另一个域名。一般是使用短的好记的域名转向复杂难记的域名。

      **********************

     

    半开放socket:

    socket是双向的,数据可以在socket上双向传送。

    单向的socket 称为半开放socket

    调用socket.shutdown()可以使socket称为半开放的

    参数:0,表示禁止将来读;1,禁止将来写;2,表示禁止将来读和写

    一旦给出了关闭socket的方向,就不可逆,即不能在该方向上重新打开了,对shutdown()调用的效果是累计的



    socket.settimeout(seconds)设置超时秒数,当访问一个socket时,如果经过参数设置的时间后,什么都没有发生后,就会产生一个socket.timeout异常

    传输不定长字符串的数据结束标识:
    唯一字符串结束标识符:发送方会在发送正文后附加一个字符串结束标识符号。这个标示符是一个NULL字符(python中是'')或newline字符(python中是' ')

    转义符(Escaping): 可以在字符串正文中包含字符串结束标示符
    数据编码:这样字符串结束标识符就不会出现,但会增加传输文件的大小

    可调整的字符串结束标示符
    首部的大小指示器:

    首先发送一个固定宽度的数字,表示要发送数据的长度,接收方会根据这个数字接收该长度的数据. 但是发送发必须在发送之前就知道要发送数据的字节数。

    网络字节顺序:标准的网络传输二进制数据表示方法
    python的struct模块提供了在python和二进制数据之间转换的支持


    服务器绑定到127.0.0.1 意味着只接受本地机器的其他程序的链接。


    poll()和select()实现事件通知(I/O 复用):

    通常情况下,socket上的I/O是阻塞的
    两个标准的工具:select() 和 poll(),他们都可以通知操作系统是哪个socket对你的程序"感兴趣".当某个socket上有事件发生时,操作系统会通知你发生了什么,你就可以进行处理。
    windows不支持poll(),必须使用select()(早期使用比较普遍,但是同时观察多个socket时,会变得很慢)

    select.poll()调用返回一个poll对象,接着就可以把它用在需要观察的socket上
     1 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     2 s.connect((host, port))
     3 
     4 p = select.poll()
     5 p.register(s.fileno(), select.POLLIN | select.POLLERR | select.POLLHUP)
     6 while 1:
     7     results = p.poll(50) #参数可选,表示需要等待多少毫秒后,会发生某件事。如果什么都没发生,返回一个空的列表
     8     if len(results):
     9         if results[0][1] == select.POLLIN:
    10             data = s.recv(4096)
    11             if not len(data):
    12                 print("
    Remote end closed connection; exiting.")
    13                 break
    14             # Only one item in here -- if there's anything, it's for us.
    15             sys.stdout.write("
    Received: " + data)
    16             sys.stdout.flush()
    17         else:
    18             print "
    Problem occured; exiting."
    19             sys.exit(0)
    20     spin()#其他的处理事务

    使用select()来解决I/O阻塞是通过调用一个函数来实现的

    select(iwtd,owtd,ewtd[,timeout])

    iwtd 一个为输入而观察的文件对象列表

    owtd ...输出...

    ewtd ...错误...

    timeout 浮点类型,超时的秒数

    返回3个tuple,每个tuple都是一个准备好的列表

     1 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     2 s.connect((host, port))
     3 
     4 while 1:
     5     infds, outfds, errfds = select.select([s], [], [s], 0.05)
     6     if len(infds):
     7         # Normally, one would use something like "for fd in infds" here.
     8         # We don't bother since there will only ever be a single file
     9         # descriptor there.
    10         data = s.recv(4096)
    11         if not len(data):
    12             print("
    Remote end closed connection; exiting.")
    13             break
    14         # Only one item in here -- if there's anything, it's for us.
    15         sys.stdout.write("
    Received: " + data)
    16         sys.stdout.flush()
    17     if len(errfds):
    18         print "
    Problem occured; exiting."
    19         sys.exit(0)
    20     spin()

     

    ================

    SSL:

    socket内置的ssl对象, ssl = socket.ssl(s)

    ssl对象只提供两个方法 :read()和write(),相当于socket的recv()和read()

    和send方法一样,write()不能保证会把所有的请求数据都写出,但ssl对象的没有对象的sendall函数,

    可以自己实现:

    1 def sendall(s, buf):
    2     byteswritten = 0
    3     while byteswritten < len(buf):
    4         byteswritten += s.write(buf[byteswritten:])
    5 
    6 ssl = socket.ssl(s)
    7 
    8 sendall(ssl, "GET / HTTP/1.0
    
    ")

    SSL对象不提供一个readline()方法

     

    pyOpenSSL:

    对OpenSSL的绑定,

    支持验证远程主机的证书

     ====================================

     SocketServer 模块

    SocketServer 提供了两种不同的方法,解决同时处理多个请求的问题

    1.提供 ThreadingMixIn 类 :使用python thread 来处理连接,它并不区分不同的连接

    2.提供 ForkingMixIn类 (仅支持UNIX):forking是为每一个新来的连接开启一个新的进程,所有这些进程都是独立的

    3.nonblocking(或asynchronous通信) :(不支持)

     

    SimpleHTTPServer 类扩展了BaseHTTPServer类,

    SimpleHTTPServer.SimpleHTTPRequestHandler它可以提供当前工作目录下符合规则的文件,以及支持查找index.html ,访问目录

     CGIHTTPServer.CGIHTTPRequestHandler 可以处理cgi脚本 py脚本

     

    可以基于 SocketServer.StreamRequestHandler类 实现自己的协议

    定义一个handle()方法,在连接到来的时候和连接准备好的时候,handle()会被调用

    StreamRequestHandler 会完成rfile和wfile等初始化任务,这两个变量实质上是socket.makefile()建立的,结果是类文件对象,用于读写

     

    SocketServer.StreamRequestHandler类 的基类SocketServer.BaseRequestHandler会初始化一些变量,这些变量包含了客户端和环境变量的一些信息。

    request对象 和 client_address 实际上是在 TCPserver类中 调用“self.socket.accept()”返回的客户端socket和 client_address客户端地址,然后传给处理类。

     

    CGI 脚本的弊端:

    实现处理CGI脚本的服务器(可参考CGIHTTPServer.CGIHTTPRequestHandler),通常会forking或产生一个新的进程来处理cgi程序,每次请求进来 都会重新这样调用,这样的设计虽然使CGI脚本具有语言和服务器中立的特性。

    但是,每次启动脚本,操作系统都需要为它建立新的进程,这样对大流量的站点,会耗费大量性能.

    诸如,像Apache 的mod_python ,mod_wsgi(新的服务器与app通信的协议),这类的 都是在服务器里面内置了 一个python解释器模块,所以app脚本只是在服务器进程

    启动被载入一次,而每次的请求 ,只是调用相应的app脚本 处理方法,而不是单独启动一个进程来执行,而且这样还可以在服务器启动装载app时,设置一些全局共享变量,如数据库连接等。

     

    ================

    服务器 多任务处理,同时高效处理多个网络连接

     

    forking:(只适用于UNIX平台) fork()原理与c中的fork() 函数一样

    调用os.fork(),它返回两次,这个函数把子进程的进程ID返回给服务进程,还会把零值返回给子进程

    在使用fork的程序中,每个进程都会有自己的变量拷贝,在一个进程中改变改变量,不会影响其他进程。

    用forking处理多个请求的弊端:

    重复的文件描述符:通常只要父进程或子进程不用socket时, 就马上关闭它

    zombie进程:

    fork()的语义是建立在父进程对找出子进程什么时候,以及如何终止感兴趣的假定上的。

    父进程通过os.wait()或类似的调用来获得子进程的运行信息

    在子进程终止和父进程调用wait()之间的这段时间,子进程被称为zombie进程。它停止了运行,但是内存结构还允许父进程执行wait()保持着。

    如果父进程在子进程之前终止,子进程会一直执行,操作系统会把它们的父进程设置为init (进程1)来重新指定父进程。init进程就会负责清除zombie进程。

    性能:linux 通过copy on write (写时拷贝技术)内存来实现 fork()

     1.定义信号处理程序,使用os.wait()和os.waitpid()来搜索终止的子进程信息。(被称为收割reaping)

    waitpid()返回一个进程的PID和退出信息的tuple,否则产生一个异常

    time.sleep()有一种特殊情况,如果任意一个信号处理程序被调用,睡眠会被立刻终止,而不是继续等待剩余的时间

     2.使用轮询(poll): 定期搜集检查子进程

    这个方法不包含信号处理程序,信号处理程序在有些操作系统上会引起I/O功能的问题

     

    锁定:在forking程序中,锁定是用来控制多个进程存取文件最常用的方法,锁定可以保证同时只有一个进程执行某些操作。

    fcntl.flock 对文件进行加锁,所有得到锁,最后必须都被释放,否则产生死锁,导致进程一致等待其他进程,通常在try...finally块后加上去除锁

    错误处理:

    有时os.fork()会因为 操作系统没有足够的内存,进程表没有空间,或者超过管理员设定的进程最大值等原因失败,如果不检查错误,os.fork()的失败就会终止服务器程序

    os.fork()要么返回两次,要么会因为错误产生异常。如果有错误将不会返回PID,而且程序根本不会结束fork

    捕获异常关闭客户端连接

     

    threading:

     线程间通信比进程之间通信更容易,所有线程都有同样的全局地址空间,一个线程的改动会影响其他线程,所以确保线程间通信不会互相干扰更加重要。

    python有两个多线程的模块 thread 和threading, thread 模块是实现线程的低级接口,threading 可以提供更高级的方法。

    Python的threading模块提供了一个Lock对象,这个对象可以被用来同步访问代码。Lock对象含有两个方法:

    acquire()和release(),acquire()负责取得一个锁。如果没有线程正持有锁,acquire方法会立刻得到锁,否则需要等待锁被释放。在这两种情况下,一旦acquire()返回,调用它的线程就持有锁。

    release()会释放一个锁,如果有其他的线程正等待这个锁(通过acquire()),当release()被调用的时候,它们中的一个线程就会被唤醒,也就是说,某个线程中的acquire()将返回

     

    访问共享且缺乏的资源:(客户端连接) 

    生产者 / 消费者 模式

    Semaphore 同步对象:Semaphore也有acquire()和 release()方法,Semaphore含有一个初始化的计数器,每次调用release(),计数器就

    增加一次,每次acquire()被调用,计数器就减1.如果计数器是0值的时候acquire()被调用 ,它就只有在计数器等于或大于1的情况才返回。

    Queue 队列 模块

     

    避免死锁

    当两个或更多的线程再等待资源时会产生死锁,这种情况下它们的请求时不能得到满足的。

    死锁是很难被发现的,避免死锁的原则:

    一定要以一个固定的顺序来取得锁;

    一定要按照与取得锁相反的顺序释放锁。

     

    多线程服务器:

    多数多线程服务器的体系结构:

    主线程(Main Thread):负责侦听请求的线程,当它收到一个请求的时候,一个新的工作者线程(worker thread)会被建立起来,处理该客户端的请求。当客户端断开连接的时候,worker thread就会终止。

    使用线程可以保证主线程是可以接受UNIX信号的唯一线程

    使用线程池:

    线程池被设计成一个线程同时只为一个客户服务,但是在服务结束之后并不终止。线程池中的线程要么是事先全部建立起来,要么是在

    需要的时候被建立。

    线程池通常有一个可以使用的线程数上限。如果达到了这个上限,客户端如果有连接,那么就会出现错误

    线程池通常包含以下几个部分:

    一个主要的侦听线程来分派和接收客户端的连接;

    一些工作者线程用来处理客户端的连接;

    一个线程管理系统用来管理woker线程和处理那些意外终止的woker线程;

     

     客户端使用多线程

    threading.Condiction 对象有一个潜在的锁,一个线程调用该对象的wait(n)方法,这个方法会释放锁并等待另一个线程调用notify(),有事情发生的时候给其他线程发信号

    异步 通信(asynchronous communication)

    forking 和 thread,一次处理多个连接,都是使操作系统同时执行多重代码来实现的。

    异步通信并不同时运行多个进程(或线程),只是运行一个进程,这个进程会监视各种连接,在它们之间转换,并按照需要为每一个

    连接提供服务。

    为了实现异步通信,需要一些新的特性,其中一个特性就是不用停止所有程序就可以处理网络数据。

    常规方法中,例如recv(),只有数据被完全从网络接收到,它才会返回,在这之前进程不能做任何事情

    socket可以被设置成nonblocking方式,在这种方式下,如果一个操作不能立刻执行,对send()和recv()调用会立刻返回socket.error异常,进程就可以继续。

    但是总是试图通过没有准备好的socket来发送和接收数据时低效的,

    所以最好有操作系统来通知socket什么时候准备好,通常调用select()和poll()可以做这个事情。

    通常你需要通知操作系统对哪些socket感兴趣,在一个或多个socket可以调用之前,对它们调用是暂停的;

    然后可以发现哪些socket是准备好的,然后处理它们,并重新等待。

    缺点:

    所有异步的代码都有一个重要的特征,那就是任何被阻塞一段时间的代码都要被去掉,通常那些执行复杂运算或耗时操作的服务器(数据库服务器)通常不能使用完全的异步。

    异步通信仅仅会因为新连接而增加很少的开销。这就使它适合那些仅需要少许服务器端处理,就可以处理多个连接的服务器(web 和FTP服务器)。

    在某些方面编写异步的服务器比forking和多线程服务器复杂的多,必须自己维护很多状态信息,而不是通过操作系统来做。这对于诸如sendall()这类函数的调用必须完全避免。

    因为服务器包含在单一进程中,所以不用考虑锁定,死锁和同步的问题。但要考虑必须自己维护多少状态信息。

    python中的asyncore和asynchat,可以用来编写异步程序,Twisted框架提供了更多用于异步服务器的库。

    当poll函数返回一个tuple的列表,列表中的每一个tuple对应一个连接。表明有些感兴趣的事情在该连接上发生。接下来的任务检查每一个tuple决定做什么.

    tuple包含一个socket的文件描述符和一个事件.

    监控多个master socket


    守护进程监听多个端口,当有连接到来的时候,它会启动一个可以处理该连接的程序。通过这种方法,一个进程可以处理许多socket.

    采用polling和forking / threading 的“混合技术”

    异步通信提供了一种一次处理多个连接的方法,和forking threading不同,异步通信实际上不会使服务器同时运行不同的代码。相反当有客户端到来的时候,它使用nonblocking的I/O 和polling来为它们提供服务。

    异步 I / O 会处于一个主循环的中心,该循环等待事件的到来。当有事件发生时,例如数据可以被读取,或可以写数据----程序知道发生了什么,并执行相关的操作。poll()函数被设计成能够一次观察多个socket.

     
  • 相关阅读:
    Data Structure 之 KMC字符串匹配算法
    网站建站流程
    常用算法稳定性分析
    VSS错误:The Sourcesafe Web service cannot be accessed at the specified address
    Data Struture 之 指针
    Windows 之 CMD命令
    Data Structure 之 算法设计策略
    Data Structure 之 最优二叉树
    板级支持包(BSP)
    JPEG
  • 原文地址:https://www.cnblogs.com/aveenzhou/p/3880734.html
Copyright © 2020-2023  润新知