python 网络编程学习 非阻塞soc

发布时间:2019-09-10 08:51:09编辑:auto阅读(1566)

    主要学习服务器的异步使用

    SocketServer简化了网络服务器的编写。它有4个类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。这4个类是同步进行处理的,另外通过ForkingMixIn和ThreadingMixIn类来支持异步。

    创建服务器的步骤

    1. 创建一个请求处理类,它是BaseRequestHandler的子类并重载其handle()方法。
    2. 实例化一个服务器类,传入服务器的地址和请求处理程序类。
    3. 调用handle_request()(一般是调用其他事件循环或者使用select())或serve_forever()。

    集成ThreadingMixIn类时需要处理异常关闭。daemon_threads指示服务器是否要等待线程终止,要是线程互相独立,必须要设置为True,默认是False。


    使用基于多进程,ForkingMinIn 支持异步

    import os                                                                                                                                                                          
    import socket                                                                       
    import threading                                                                    
    import SocketServer                                                                 
                                                                                        
    # 构造客户端                                                                               
    class ForkingClient():                                                              
        def __init__(self, ip, port):                                                   
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)     
            self.sock.connect((ip, port))                                               
                                                                                        
        def run(self):                                                                  
            crtpid = os.getpid()                                                        
            print "PID: %s " % crtpid                                                    
            send_length = self.sock.send("hello, world!")                               
            print "sent %d characters..." % send_length                                 
            res = self.sock.recv(2048)                                                  
            print "PID %s received: %s" % (crtpid, res)                                 
                                                                                        
        def shutdown(self):                                                             
            self.sock.close()                                                           
                                                                                        
    # 写服务端处理函数                                                                                
    class ForkingServerRequestHandler(SocketServer.BaseRequestHandler):                 
        def handle(self):                                                               
            data = self.request.recv(2048)                                              
            crtpid = os.getpid()                                                        
            res = "%s: %s" % (crtpid, data)                                             
            print res                                                                   
            self.request.send(res)                                                      
            return                                                                      
                                                                                        
    # 继承                                                                                  
    class ForkingServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):             
        pass                                                                            
                                                                                        
                                                                                        
    def main():                                                                         
        server = ForkingServer(('localhost', 8989), ForkingServerRequestHandler)        
        ip, port = server.server_address                                                
        server_thread = threading.Thread(target=server.serve_forever)                   
        server_thread.setDaemon(True)                                                   
        server_thread.start()
        print 'Server loop running PID: %s' % os.getpid()
    
        client_1 = ForkingClient(ip, port)
        client_2 = ForkingClient(ip, port)
    
        client_1.run()
        client_2.run()
                                                                                        
        client_1.shutdown()
        client_2.shutdown()
        server.socket.close()
    
                                                                              
    if __name__ == '__main__':
        main()
    

    执行可以看到

    duck@duck:~/sockdir/chapter_2$ python forkser.py 
    Server loop running PID: 22649
    PID: %s  22649
    sent 13 characters...
    22651: hello, world!
    PID 22649 received: 22651: hello, world!
    PID: %s  22649
    sent 13 characters...
    22652: hello, world!
    PID 22649 received: 22652: hello, world!

    现在用ThreadingMixIn

    优势:线程之间共享应用状态容易,避免进程间通信的复杂操作。

    import os                                                                                                                                                                          
    import socket                                                                       
    import threading                                                                    
    import SocketServer                                                                 
                                                                                        
                                                                                        
    class ForkingClient():                                                              
        def __init__(self, ip, port):                                                   
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)               
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)             
            self.sock.connect((ip, port))                                               
                                                                                        
        def run(self):                                                                  
            crtpid = os.getpid()                                                        
            print "PID: %s " % crtpid                                                   
            send_length = self.sock.send("hello, world!")                               
            print "sent %d characters..." % send_length                                 
            res = self.sock.recv(2048)                                                  
            print "PID %s received: %s" % (crtpid, res)                                 
                                                                                        
        def shutdown(self):                                                             
            self.sock.close()                                                           
                                                                                        
                                                                                        
    class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):                   
        def handle(self):                                                               
            data = self.request.recv(2048)                                              
            crtpid = threading.current_thread()                                         
            res = "%s: %s" % (crtpid, data)                                             
            print res                                                                   
            self.request.sendall("hello, client!")                                      
            return                                                                      
                                                                                        
                                                                                        
    class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):       
        pass                                                                            
                                                                                        
                                                                                        
    def main():                                                                         
        server = ThreadedTCPServer(('localhost', 8989),ThreadedTCPRequestHandler)       
        ip, port = server.server_address                                                
        server_thread = threading.Thread(target=server.serve_forever)
        # 线程互相独立,必须要设置为True                   
        server_thread.setDaemon(True)                                                   
        server_thread.start()                                                           
        print 'Server loop running PID: %s' % server_thread.name                        
                                                                                        
        client_1 = ForkingClient(ip, port)                                              
        client_2 = ForkingClient(ip, port)                                              
                                                                                        
        client_1.run()                                                                  
        client_2.run() 
        server.socket.close()                                                        
                                                                                     
    if __name__ == '__main__':                                                       
        main()                                                                                                                                                                        
    
    

    可以看到基本套路都差不多,就是替换了一些处理类

    而在大型网络服务器应用中,存在几百上千的并发连接时,为每个客户端建立单独的线程和进程不太实际。内存和主机cpu都有限,需要一种更好的办法来处理,那就是select模块。

    这里可以了解一下,select,poll ,epoll三个模块

    link

    先用select的select模块编写一个聊天室服务器。分了三个模块

    先写通讯模块

    import cPickle                                                                                                                                                                     
    import struct                                                                       
    import socket                                                                       
                                                                                        
    def send(channel, *args):                                                           
        buf = cPickle.dumps(args)                                                       
        value = socket.htonl(len(buf))                                                  
        size = struct.pack("L", value)                                                  
        channel.send(size)                                                              
        channel.send(buf)                                                               
                                                                                        
    def receive(channel):                                                               
        size = struct.calcsize("L")                                                     
        size = channel.recv(size)                                                       
        try:                                                                            
            size = socket.ntohl(struct.unpack("L", size)[0])                            
        except struct.error, e:                                                         
            return ''                                                                   
        buf = ""                                                                        
        while len(buf) < size:                                                          
            buf += channel.recv(size - len(buf))                                        
        return cPickle.loads(buf)[0]
    

    服务模块

    from com_model import send, receive                                                                                                                                                
                                                                                        
    import select                                                                       
    import socket                                                                       
    import sys                                                                          
    import signal                                                                       
                                                                                        
                                                                                        
    class ChatServer(object):                                                           
        def __init__(self, port, backlog=5):                                            
            self.clients = 0                                                            
            self.clientmap = {}                                                         
            self.outputs = []                                                           
            self.server = socket.socket(socket.AF_INET, socket.SOL_SOCKET)              
            self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           
            self.server.bind(('localhost', port))                                       
            self.server.listen(backlog)                                                 
            signal.signal(signal.SIGINT, self.sighandler)                               
                                                                                        
        def sighandler(self, signum, frame):                                            
            print "Shutting down server...."                                            
            for output in self.outputs:                                                 
                output.close()                                                          
            self.server.close()                                                         
                                                                                        
        def get_client_name(self, client):                                              
            info = self.clientmap[client]                                               
            host, name = info[0][0], info[1]                                            
            return '@'.join((name, host))                                               
                                                                                        
        def run(self):                                                                  
            inputs = [self.server, sys.stdin]                                           
            self.outputs = []                                                           
            running = True                                                              
            while running:                                                              
                try:                                                                    
                    readable, writeable, exceptional = select.select(inputs, self.outputs, []) 
                except select.error, e:                                                 
                    break                                                               
                                                                                        
                for sock in readable:                                                   
                    if sock == self.server:                                             
                        client, address = self.server.accept()                          
                        print "Chat server: got connection %d from %s" % (client.fileno(), address)
                        cname = receive(client).split('NAME: ')[1]                      
                                                                                        
                        self.clients += 1                                               
                        send(client, "CLIENT: " + str(address[0]))                      
                        inputs.append(client)                                           
                        self.clientmap[client] = (address, cname)                       
                        msg = "\n(Connected: New client (%d) from %s" % (self.clients, self.get_client_name(client))
                        for output in self.outputs: 
                                                                                    
                    elif sock == sys.stdin:                                          
                        junk = sys.stdin.readline()                                  
                        running = False                                              
                                                                                     
                    else:                                                            
                        try:                                                         
                            data = receive(sock)                                     
                            if data:                                                 
                                msg = '\n#[' + self.get_client_name(sock) + ']>>' + data
                                for output in self.outputs:                          
                                    if output != sock:                               
                                        send(output, msg)                            
                                                                                     
                            else:                                                    
                                print "Chat server: %d hung up" % sock.fileno()      
                                self.clients -= 1                                    
                                sock.close()                                         
                                inputs.remove(sock)                                  
                                self.outputs.remove(sock)                            
                                                                                     
                                msg = '\n(Now hung up: Client from %s)' % self.get_client_name(sock)
                                for output in self.outputs:                          
                                    send(output, msg)                                
                                                                                     
                        except socket.error, e:                                      
                            inputs.remove(sock)                                      
                            self.outputs.remove(sock)                                
            self.server.close()                                                      
                                                                                     
                                                                                     
    def main():                                                                      
        server = ChatServer(8989)                                                    
        server.run()                                                                 
                                                                                     
    if __name__ == '__main__':                                                       
        main()                               
    
    

    客户端模块

    from com_model import send, receive                                                                                                                                                
                                                                                        
    import os                                                                           
    import select                                                                       
    import socket                                                                       
    import sys                                                                          
    import signal                                                                       
                                                                                        
    class ChatClient(object):                                                           
        def __init__(self, name, port, host="localhost"):                               
            self.name = name                                                            
            self.connected = False                                                      
            self.host = host                                                            
            self.port = port                                                            
            self.prompt = '[' + '@'.join((name, socket.gethostname().split('.')[0])) + ']>'
                                                                                        
            try:                                                                        
                self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)           
                self.sock.connect((host, self.port))                                    
                print "Now connected to chat server@ port %d" % self.port               
                self.connected = True                                                   
                send(self.sock, 'NAME: ' + self.name)                                   
                data = receive(self.sock)                                               
                addr = data.split('CLIENT: ')[1]                                        
                self.prompt = '[' + '@'.join((self.name, addr)) + ']> '                 
            except socket.error, e:                                                     
                print "Failed connect server @port %d" % self.port                      
                sys.exit(1)                                                             
                                                                                        
        def run(self):                                                                  
            while self.connected:                                                       
                try:                                                                    
                    sys.stdout.write(self.prompt)                                       
                    sys.stdout.flush()                                                  
                    readable, writeable, exceptional = select.select([0, self.sock], [],[])
                    for sock in readable:                                               
                        if sock == 0:                                                   
                            data = sys.stdin.readline().strip()                         
                            if data:                                                    
                                send(self.sock, data)                                   
                        elif sock == self.sock:                                         
                            data = receive(self.sock)                                   
                            if not data:                                                
                                print "Client shutting down."                           
                                self.connected = False                                  
                                break                                                   
                            else:                                                       
                                sys.stdout.write(data + '\n')                           
                                sys.stdout.flush()                                      
                                                                                        
                except KeyboardInterrupt:                                               
                    print "interupt"                                                     
                    self.sock.close()                                                
                    break                                                            
    def main():                                                                      
        cli_name = str(os.getpid()) + 'client'                                       
        cli_port = raw_input("port: ")                                               
        client = ChatClient(cli_name, int(cli_port))                                 
        client.run()                                                                 
                                                                                     
    if __name__ == '__main__':                                                       
        main()  
    
    
     先启动服务端,然后启动客户端,输入服务端的port,这里写死成8989了,所以写入8989就可以连入通信了

    duck@duck:~/sockdir/chapter_2/select_ex$ python cli_chat.py 
    port: 8989
    Now connected to chat server@ port 8989
    [25395client@127.0.0.1]> haha
    [25395client@127.0.0.1]> 
    (Connected: New client (2) from 25415client@127.0.0.1
    [25395client@127.0.0.1]> 
    #[25415client@127.0.0.1]>>hee
    [25395client@127.0.0.1]> nihao

    接下来将用epoll写一个简单的web服务器,熟悉一下什么叫I/O多路复用,这名字翻译的也是一脸蒙逼

    看看大神的们的解释,秒懂

    link

关键字