2.网络编程

 

进程间通信方式大概有 管道通信(又分为匿名管道,命名管道,高级管道),信号,消息队列,共享内存,信号量,套接字

1.管道:

  1).匿名管道是一个未命名的,单向管道,通过父进程和一个子进程之间传输数据。只能实现本地机器上两个进程之间的通信,而不能实现跨网络的通信。常用的比如Linux命令。

  2).命名管道是进程间单向或双向管道,建立时指定一个名字,任何进程都可以通过该名字打开管道的另一端,可跨网络通信

  3).高级管道是将一个程序在另一个进程中打开,作为它的子进程,从而实现管道通信。

2.信号:是这些方式中唯一的异步通信机制。

  信号(signal):其实是软中断信号的简称。用来通知进程发生了异步事件。

  在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求是一样的。

  信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达。

  收到信号的进程对各种信号有不同的处理方法,主要是三类:

  1).类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。

  2).忽略某个信号,对该信号不做任何处理。

  3).对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是让进程终止。

  进程通过系统调用signal来指定进程对某个信号的处理行为。

3.消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。

  每个数据块都被认为含有一个类型,接收进程可以独立的接收含有不同类型的数据结构。

  可以通过发送消息来避免命名管道的同步和阻塞问题。

  但是消息队列和命名管道一样,每个数据块都有一个最大长度的限制。

4.共享内存就是允许两个不相关的进程访问同一个逻辑内存。

  共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。

  不同进程之间共享的内存通常安排为同一段物理内存。

  进程可以将同一段共享内存连接到他们自己的地址空间中,所有进程都可以访问共享内存中的地址。

5.信号量:为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。

  临界区域是指执行数据更新的代码需要独占式的执行。

  而信号量就可以提供这样的一种访问机制。

  让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来协调对共享资源访问的。

6.套接字:这种通信机制使得客户端/服务器的开发工作既可以在本地单机上进行,也可以跨网络进行

 

一、套接字socket基本概念

  1.套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)及原始套接字

套接字:最初用于一台主机进程与进程之间的通信(非网络套接字),后来用于网络之间的通信(网络套接字)。
  任何类型的通信开始之前,程序必须先创建套接字,即通信端点!
2.套接字有2种类型:基于文件的和面向网络的
3.套接字有众多家族,以下为python支持的套接字:
* UNIX套接字家族,名称为AF_UNIX
* 网络套接字家族,或叫因特网套接字家族,名称为AF_INET,基于IP寻址
* AF_INET6套接字家族,IPV6
* AF_NETLINK家族:允许使标准的BSD套接字进行用户级别和内核代码级别代码之间的进程间通信--IPC。比如,添加新系统调用,/proc支持,对操作系统的IOCTL
 * AF_TIPC家族:支持透明的进程间通信--TIPC协议,允许计算机集群之间的机器相互通信,无须基于IP寻址方式
套接字与(主机-端口对)的关系:套接字就像电话服务器中心的电话插孔,是允许通信的基础设施;主机-端口对就像区号和号码的组合

4.面向连接和无连接套接字:
* 面向连接的套接字----在通信之前,必须先建立一个连接。面向连接的通信提供序列化的、可靠的、不重复的数据交付,而没有记录边界。这意味着每条消息可以拆分成多个片段,且每一条消息片段都确保能够到达目的地,然后将它们按顺序组合在一起,再形成完整的消息交付
* 实现面向连接通信的主要协议是传输控制协议TCP,使用SOCK_STREAM作为套接字类型
* 面向连接的通信,也称为虚拟电路或流套接字
* 整个面向连接的通信,通常使用TCP/IP两种协议(网络套接字时)或者TCP/AF_LOCAL、TCP/AF_UNIX两种协议(非网络套接字时)

* 无连接的套接字--在通信开始之前不需要建立连接。在数据传输过程中,无法保证它的顺序性、可靠性、重复性,但是它保存了记录边界。这意味着消息是以整体发送的,而非片段
*  实现无连接通信的主要协议是用户数据报协议UDP,使用SOCK_DGRAM作为套接字类型 
* 面向无连接的通信,也称为数据报类型的套接字
* 整个面向无连接的通信,通常使用UDP/IP两种协议(网络套接字时)
TCP:Transmission Control Protocol 传输控制协议
UDP:User Datagram Protocol,用户数据报协议

二、socket
(一)套接字方法:

1.创建套接字socket
python创建套接字的一般语法:socket.socket(socket_family,socket_type,protocol=0)
 * socket_family :套接字家族,如AF_UNIX,AF_INET
 * socket_type:套接字类型,如SOCK_STREAM,SOCK_DGRAM
 * protocol通常省略,默认为0
import socket
# * 创建TCP/IP套接字示例:
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# * 创建UDP/IP套接字示例:
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

2.服务器/客户端套接字方法

# ### 服务器套接字方法:
s.bind()  # 将地址(主机名、端口号对)绑定到套接字上
s.listen()  # 设置并启动TCP监听器
s.accept()  # 被动接受TCP客户端连接,一直等待直到连接到达(阻塞)

# ### 客户端套接字方法:
s.connect()  # 主动发起TCP服务器连接
s.connect_ex()  # 如果出现错误,会以错误码的形式返回问题,而不是抛出一个异常

3.普通的套接字方法

# ### 普通的套接字方法:
# * s.recv()   接收TCP消息
# * s.recv_into()  接收TCP消息到指定的缓冲区
# * s.send() 发送TCP消息
# * s.sendall()  完整地发送TCP消息
# *  
# * s.recvfrom()   接收UDP消息
# * s.recvfrom_into 接收UDP消息到指定的缓冲区
# * s.sendto()  发送UDP消息
# *   
# * s.getpeername()  连接到TCP套接字的远程地址
# * s.getsockname()  当前套接字的地址
# * s.getsockopt()  返回给定套接字选项的值
# * s.setsockopt()  设置给定套接字选项的值
# * s.shutdown()  关闭连接
# * s.close()  关闭套接字
# * s.detach() 在未关闭文件描述符的情况下关闭套接字,返回文件描述符

 

 4.面向阻塞的套接字方法
# ### 面向阻塞的套接字方法
# * s.setblocking()  设置套接字为阻塞True或非阻塞模式False
# * s.settimeout()  设置阻塞套接字操作的超时时间
# * s.gettimeout()  获取阻塞套接字操作的超时时间
5.面向文件的套接字方法
# ### 面向文件的套接字方法
# * s.fileno()  套接字的文件描述符
# * s.makefile()  创建与套接字关联的文件对象
6. 数据属性
# ### 数据属性
# * s.family   套接字家族
# * s.type  套接字类型
# * s.proto  套接字协议

 7.TCP/UDP SERVER

import socket
from time import ctime
class Server(object):   # 注释掉的部分为TCPServer
    def __init__(self, host='127.0.0.1', port=8000, bufsize=512):
        self.BUFSIZE = bufsize
        self.ADDR = (host, port)
    def _run(self):
        # self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # self.s.bind(self.ADDR)
        # self.s.listen(5)
        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.s.bind(self.ADDR)
    def run(self):
        self._run()
        # while True:
        #     try:
        #         con, addr = self.s.accept()
        #         print '......connected from : ', addr
        #         while True:
        #             try:
        #                 data = con.recv(self.BUFSIZE)
        #                 if not data or data == 'exit':
        #                     # con.send('bye!!')
        #                     con.close()
        #                     break
        #                 con.send('received--%s---%s' % (ctime(), data))
        #                 print 'send','='*5, data
        #             except:
        #                 print '....exit from: ', addr
        #                 break
        #     except KeyboardInterrupt:
        #         break
        while True:
            try:
                data, addr = self.s.recvfrom(self.BUFSIZE)
                print '......connected from : ', addr
                self.s.sendto('received--%s---%s' % (ctime(), data), addr)
                print 'send','='*5, data
            except KeyboardInterrupt:
                break
            except Exception:
                print '.....exit from: ',addr
                pass
        self.s.close()
if __name__ == '__main__':
    Server().run()

 

 TCP/UDP 客户端
# coding=utf8
import socket
import time
class Client(object):   # 注释掉的部分为TCPSClient
    def __init__(self, host='127.0.0.1', port=8000, bufsize=1024):
        self.BUFFSIZE = bufsize
        self.ADDR = (host, port)
    def run(self):
        # s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # s.connect(self.ADDR)
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        while True:
            try:
                data = raw_input('>>>')
                if not data or data == 'exit':
                    # s.send('bye!!')
                    s.sendto('bye!!', self.ADDR)
                    time.sleep(0.5)
                    break
                # s.send(data)
                # receive_data = s.recv(self.BUFFSIZE)
                s.sendto(data, self.ADDR)
                receive_data, addr = s.recvfrom(self.BUFFSIZE)
                if not receive_data or receive_data == 'exit':
                    break
                print receive_data
            except KeyboardInterrupt:
                break
        s.close()
if __name__ == '__main__':
    Client().run()

 


(二)socket模块的属性
8.socket模块的数据属性:
# * 套接字地址家族:AF_UNIXAF_INETAF_INET6AF_NETLINKAF_TIPC
# * 套接字类型:SOCK_STREAMSOCK_DGRAM
# * has_ipv6:是否支持IPV6的布尔标记
9.socket模块的异常:
# * error:套接字相关错误
# * herror:主机和地址相关错误
# * gaierror:地址相关错误
# * timeout:超时时间

10.socket模块的函数:
# * socket():以给定的地址家族、套接字类型和协议类型(可选),创建一个套接字对象
# * socketpair():以给定的地址家族、套接字类型和协议类型(可选),创建一对套接字对象
# * create_connect():接收一个地址对(主机名、端口号),返回一个套接字对象
# * fromfd():以一个打开的文件描述符,创建一个套接字对象
# * ssl():通过套接字启动一个安全套接字层连接;不执行证书验证
# * getaddrinfo():获取一个五元组序列形式的地址信息
# * getnameinfo():给定一个套接字地址,返回(主机名、端口号)二元组
# * getfqdn():返回完整的域名
# * gethostname():返回当前主机名
# * gethostbyname():将一个主机名映射到它的IP地址
# * gethostbyname_ex():返回主机名、别名主机集合和IP地址列表
# * gethostbyaddr():将一个IP地址映射到DNS信息;返回与gethostbyname_ex相同的3元组
# * getprotobyname():将一个协议名,如'tcp'映射到一个数字
# * getservbyname()/getservbyport():将一个服务器名映射到一个端口号或者反过来;对于任何一个函数来说,协议名都是可选的
# * ntohl()/ntohs():将来自网络的整数转换为主机字节顺序
# * htonl()/htons():将来自主机的整数转换为网络字节顺序
# * getdefaulttimeout()/setdefaulttimeout():以秒(浮点数)为单位返回或者设置默认套接字超时时间

三、SocketServer模块
SocketServer模块:简化了很多样板代码,它们是创建网络客户端和服务器所必需的代码。
SocketServer模块创建了各种各样的类,如下:
* BaseServer:包含核心服务器功能和mix-in类的钩子;仅用于推导,并不会创建这个类的实例;用TCPServer和UDPServer创建类的实例
* TCPServer/UDPServer:基础的网络同步TCP/UDP服务器
* UnixStreamServer/UnixDatagramServer:基于文件的基础的网络同步TCP/UDP服务器
* ForkingMixIn/ThreadingMixIn:多进程或多线程功能的钩子,需与一个服务器类配合实现
*
* ForkingTCPServer/ForkingUDPServerForkingMixInTCPServer/UDPServer的组合
* ThreadingTCPServer/ThreadingUDPServerThreadingMixInTCPServer/UDPServer的组合
*
* BaseRequestHandler:包含处理服务请求的核心功能:仅用于推导,并不会创建这个类的实例;用StreamRequestHandler和DatagramRequestHandler创建类的实例
* StreamRequestHandler/DatagramRequestHandler:实现TCP/UDP服务器的服务处理器
SocketServer实现TCP服务器:
* 在使用socket实现TCP服务器时,在服务器循环中,我们阻塞等待请求,当接收到请求时就对其提供服务,然后继续等待下一个请求
* 在使用SocketServer实现TCP服务器时,在服务器循环中,并非在服务器中创建代码去接收和发送数据,而是定义一个requestHandler的处理函数,这样当服务器接收到一个传入的请求后,服务器调用自定义的requestHandler处理函数去发送和接收数据。自定义的requestHandler,通常继承StreamRequestHandler,重写它的handle方法;在handle方法中,编写发送数据的代码即可,其它的由父类完成了

from time import ctime
from SocketServer import TCPServer, StreamRequestHandler
class MyRequestHandle(StreamRequestHandler):
    def handle(self):
     # addr = self.request.getpeername() 或者下一行
print '....connect from: %s' % (self.client_address,) line = self.rfile.readline() self.wfile.write('[%s] %s' % (ctime(), line)) print line MyTCPServer = TCPServer(('', 8802), MyRequestHandle) print 'your tcpserver is starting......' MyTCPServer.serve_forever()
SocketServer与socket的客户端连接的实现是一样的。略。
但是,需要注意的是:连接SocketServer的客户端每发送一条消息,是一次连接TCP服务器。而上面用socket实现的TCPServer,是多次发送消息,只到断开TCP连接,算一次TCP连接。
SocketServer实现实现异步,支持多连接TCP服务器:
要想支持异步可以利用多继承从ForkingMixIn 或ThreadingMixInmix-in classes和一个基本的服务类继承来定义一个支持异步的服务类。

ForkingMixIn 要考虑进程间的通信。ThreadingMixIn要考虑线程访问同一变量时的同步和互斥。


一个使用了多线程处理的服务器示例:

from SocketServer import TCPServer, ThreadingMixIn, StreamRequestHandler
#定义支持多线程的服务类,注意是多继承
class Server(ThreadingMixIn, TCPServer): pass
#定义请求处理类
class Handler(StreamRequestHandler):
    def handle(self):
        addr = self.request.getpeername()
        print 'Got connection from ',addr
        self.wfile.write('Thank you for connection')
server = Server(('', 1234), Handler)#实例化服务类
server.serve_forever()#开启服务

 


四、第三方模块:Twisted框架
Twisted是一个完整的事件驱动的异步网络框架。它像SocketServer一样,提供了很多完整的功能,甚至比SocketServer丰富得多的功能。Twisted对于其支持的所有协议都带有客户端和服务器实现。
* 使用SocketServer框架实现TCP服务器的时侯,1.通过继承StreamRequestHandler请求处理类,实现一个自定义的RequestHandler处理类,重写handle方法---发送数据的一行代码;
  2.以addr和自定义的Request处理类对象(这里是使用类对象)为参数,实例化一个SERVER类(如TCPServer、ThreadingTCPServer等)实现相应的服务器,最后用服务器实例的serve_forever()启动服务器
* 使用Twisted框架实现TCP服务器的时侯,1.通过继承Twisted.internet.protocol.Protocol协议类,实现一个自定义的Protocol协议类,重写connectionData(获取客户端名称一行代码)和dataReceived(发送数据的一行代码)
  2.自定义的协议类(这里是使用类对象)作为protocol.Factory()工厂协议类的实例对象(这里是使用实例对象)的protocol属性,以此工厂协议类的实例对象和端口为参数,传递给reactor对象的listenTCP方法,实现一个服务器,最后reactor.run()启动服务器
使用twisted实现TCP服务器
from twisted.internet import protocol
class MyProtocol(protocol.Protocol):
    def connectionMade(self):
        self.clnt = self.transport.getPeer().host
    def dataReceived(self, data):
        self.transport.write(data)
factory = protocol.Factory()
factory.protocol = MyProtocol
from twisted.internet import reactor
reactor.listenTCP(8823, factory)
reactor.run()

使用twisted实现客户端

* socketsocketserver和客户端实现是一样的

* twisted的客户端实现与socket、socketserver有很大的差异

* Twisted的客户端和它的服务器的实现类似:1.通过继承Twisted.internet.protocol.Protocol协议类,实现一个自定义的Protocol协议类,重写connectionMade(调用自己写的sendData方法)和dataReceived(调用自己写的sendData方法),以及sendData方法(发送数据一行代码)

 2.通过继承protocol.ClientFactory协议工厂类,实现一个自定义的客户端工厂类,将1中自定义的协议类(这里是使用类对象)作为此自定义客户端工厂类的protocol属性,

自定义一个错误处理函数。以HOST、PORT、刚定义的客户端工厂类的实例对象(这里是实例对象)为参数,传递给reactor对象的connectTCP方法,实现一个客户端,最后reactor.run()启动客户端

from twisted.internet import protocol
class TSclientProtocol(protocol.Protocol):
    def sendData(self):  # 自己的方法
        data = raw_input('>')
        if data:
            print '...sending %s.....' % data
            self.transport.write(data)
        else:
            self.transport.loseConnection()
    def connectionMade(self):
        self.sendData()
    def dataReceived(self, data):
        print data
        self.sendData()
from twisted.internet import reactor
class TSClientFactory(protocol.ClientFactory):
    protocol = TSclientProtocol
    clientConnectionLost = clientConnectionFailed = lambda self, connector, reason: reactor.stop()
reactor.connectTCP('127.0.0.1', 8822, TSClientFactory())
reactor.run()

 

 

五、总结

网络/套接字编程相关模块
# * scoket 低级网络编程接口
# * select 在一个单线程的网络服务器应用中同时管理多个套接字连接
# * asyncore/asynchat 提供创建网络应用程序的基础设施,并异步地处理客户端
# * SocketServer 高级网络编程模块,提供网络应用程序的服务器类,包括forkingthreading
# * twisted 高级网络编程模块,异步处理客户端,比`async*`提供了更加强大和灵活的框架
# * Concurrence 是一个更现代化的高级网络编程框架,它搭配了一个libevent的高性能IO系统;也是一个异步模块,它使用轻量级线程执行回调以事件驱动的方式进行线程间的通信和消停传递工作。
# * 现代网络框架遵循众多异步模型之一(greenletgenerator)来提供高性能异步服务器。

# 在创建服务器方面:async*SocketServer模块都供更高级的功能,它们以socket和或select模块为基础编写,能够使C/S系统开发更加迅速,因为这两个模块都已经自动处理了所有底层的代码

# 通常,我们创建或继承适当的高级网络编程模块即可。

 

 

 

 

 

 

 

 

 

 

 

 


 
 












 
 


 

 

posted on 2018-04-25 08:57  myworldworld  阅读(148)  评论(0)    收藏  举报

导航