python 网络编程学习 非阻塞soc

2023-01-31 05:01:05 学习 阻塞 网络编程

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

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

相关文章