python 网络服务器

作者: b64c74899092 | 来源:发表于2016-06-02 23:06 被阅读530次

    网络服务器(python)

    服务器的特点是等待来自客户端的请求,发送应答。通常来说,服务器可以做任何事情。某些方面,服务器程序和客户端程序很类似。很多用在客户端程序的指令同样可以用在服务器程序中,因为服务器和客户端使用同样的socket接口。但是还是有一些重要的细节是不同的,最明显的是建立socket.

    准备连接

    对客户端来说,建立一个TCP连接的过程分两步,包括建立socket对象以及调用connect()来建立一个和服务器的连接。

    对于服务器,这个过程需要如下4步:

    • 建立socket对象
    • 设置socket选项(可选)
    • 绑定到一个端口(可以指定网卡)
    • 监听连接

    下面代码可以实现这些功能:

    host=' '
    port=51423
    
    #step 1
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    #step 2
    s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    
    #step 3
    s.bind((host,port))
    
    #step 4
    s.listen(5)
    

    来详细分析下上面的代码

    建立socket对象

    为了建立一个socket对象,使用和客户端中相同的命令。

    设置和得到socket选项

    对于一个socket,可以设置很多不同的选项,对于那些一般用途的服务器,最让人感兴趣的socket选项是SO_REUSEADDR。通常一个服务器进程终止后,操作系统会保留几分钟它的端口,从而防止其他进程在超时之前使用这个端口。如果设置SO_REUSEADDR为true,操作系统就会在服务器socket被关闭或服务器终止后马上释放该服务器的端口。这样可以使调试程序更简单。

    python定义setsockopt()和getsockopt()如下:

    setsockopt(level,optname,value)

    getsockopt(level,optname,buflen)

    value参数的内容是由level和optname决定的。level定义了哪个选项将被使用。通常情况下是SOL_SOCKET。意思是正在使用socket选项。还可以通过设置一个特殊协议号码来设置协议选项。然而,对于一个给定的操作系统,大多数协议选项都是明确的,所以为了简洁,很少用于为便携移动设备设计的应用程序。

    如果level设置为SOL_SOCKET,optname参数对应的有可以使用的特殊选项。

    SOL_SOCKET常用的选项:



    下面的python程序可以给出机器上安装的python所支持的socket选项列表:

    #!/usr/bin/env python
    # Get list of avaliable socket options - socketopts.py
    import socket
    solist = [x for x in dir(socket) if x.startswith('SO_')]
    solist.sort()
    for x in solist:
        print x
    

    绑定socket

    为服务器要求一个端口号这个过程就是绑定。每个服务器程序都有自己的端口,而且这个端口是众所周知的。

    为了绑定一个端口,需要执行下面的指令:

    s.bind(('',80))

    这条指令请求80端口,是标准的HTTP端口。通常操作系统约定使用的端口号小于1024,这样只有root用户可以绑定。

    bind()函数的第一个参数是要绑定的IP地址。通常为空,意思是可以绑定到所有的接口和地址。

    有的机器会有多个网络接口,例如,一个防火墙或许会有一个以太网卡连接公共的Internet,外加另一个以太网卡连接内部网络。这种情况下,或许需要服务只对一个接口使用,所以需要提供内部网络的IP地址来绑定。这种情况下,对于通过外部接口连接的客户端来说,看上去根本没有80端口。事实上,可以运行另一台单独的服务器来绑定一台外部服务器的80端口。

    事实上,可以通过调用bind()函数来吧客户端socket把绑定到一个特定的IP地址和端口号。然而,客户端很少使用,因为操作系统会自动提供合适的值。

    监听连接

    接受客户端连接之前的最后一步就是调用listen()函数,这个调用通知操作系统准备接受连接。函数只有一个参数,这个参数指明了服务器实际处理连接的时候,允许有多少个等待的连接在队列中等待。对于现代多线程或者多任务服务器来说,这个参数意义不大,但也是必须的。

    listen()调用如下:

    s.listen(5)

    接受连接

    大多数服务器都设计成运行不确定时间和同时服务于多个连接。与此相反,客户端一般只有几个连接,并且会运行到任务完成或者用户终止。

    通常是服务器连续运行的办法是小心地设计一个无限循环。下面是一个基本服务器的例子:

    #!/usr/bin/env python 
    # Base Server - basicserver.py
    import socket
    
    host = ''
    port = 51423
    
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    s.bind((host,port))
    print "Waiting for connections..."
    s.listen(1)
    
    while 1:
        clientscok,clientaddr = s.accept()
        print "Got connection from",clientsock.getpeername()
        clientsock.close()
    

    错误处理

    对于客户端任何没有捕获到的异常都会终止程序是可以接受的,很多时候客户端程序发生错误后退出是可以理解的。但是对于服务器,这种情况很糟糕。

    在以python为基础的网络程序中,一个错误 处理就是一个简单的标准的python异常处理,可以处理网络相关的问题。

    下面的例子试图捕获所有可能的网络错误,同时保证不会终止服务器的方法来处理这些错误。在调用accept()出现的错误被打印出来,但是会被忽略,continue可以返回到循环开始的位置,开始下一次accept()调用:

    !/usr/bin/env python

    Server With Error Handling - errorserver.py

    host = ''
    port = 51423

    s = socket.socket(socket.AF_INET,socket.STREAM)
    s.sersockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    s.bind((host,port))
    s.listen(1)
    while 1:
    try:
    clientsock,clientaddr = s.accept()
    except KeyboardInterrupt:
    traceback.print_exc()
    continue

    #  connect
    try:
        print "Got connection from ",clientsock.getpeername()
    execpt (KeyboardInterrupt,SystemExit):
        raise
    except:
        traceback.print_exc()
    #  close connect
    try:
        clientsock.close()
    execpt KeyboardInterrupt:
        raise
    except:
        traceback.print_exc()
    

    上面的程序有三个独立的try块。第一个包含对accept()的调用,会产生异常。程序会重新产生KeyboardInterrupt,所以运行服务器的人按Ctrl-C同样会终止程序。所有其他的异常被打印出来,但是程序却不会终止。相反,continue会跳跃式地回到循环开始的部分,否则代码会处理不存在的客户端连接。第二个包含真正处理连接的代码,包含两个异常,一个是和前面一样的KeyboardInterrupt,还有SystemExit。SystemExit是在堆sys.exit()调用时产生的,如果没有成功的传送它就会使程序不能再需要终止的时候终止。第三个包含对close()的调用。用这种方法可以保证在需要的时候,close()总是能够被调用。如果使用文件类对象,也应该在这里关闭它。

    在大型程序中,使用python的try..finally语句来确保socket被关闭是很有意义的,在对accept()调用成功后,马上就可以插入try,最后在调用close()之前,使用一个finally来关闭socket。这种情况下,任何捕获不到的异常如果在try和finally之间发生,都会使socket关闭,同时异常被显示出来。

    使用UDP

    从客户端来看,UDP比TCP要困难,因为客户端必须要注意丢失信息的问题,另一方面,对于服务器使用UDP就容易的多。

    下面是一个简单的UDP应答服务器,可以用来测试UDP客户端。

    #!/usr/bin/env python
    # UDP Echo Server - udpechoserver.py
    import socket,traceback
    
    host = ''
    port = 51423
    
    s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    s.bind((host,port))
    
    while 1:
        try:
            message,address = s.recvfrom(8192)
            print "Got data from ",address
            s.sendto(message,address)
        except (KeyboardInterrupt,SystemExit):
            raise
        except:
            traceback.print_exc()
    

    通过syslog来记录日志

    对于服务器来说,通信状态的一个重要内容就是记录日志文件,unix和类unix系统提供了一个syslog来帮助记录日志。

    总结

    服务器主要是等待来自客户端的请求并发送响应。和客户端一样使用socket接口,但是建立过程是4步。

    socket选项是针对一个特殊连接而改变网络系统的行为,最常用的是SO_REUSEADDR,它允许端口在socket关闭后,马上被重新使用。

    TCP服务器一般会使用accept()来为每个连接的客户端建立一个新的socket。UDP服务器一般只是使用一个单一的socket,完全依靠从recvfrom()返回值来判断该往哪里发送响应。

    当服务器和客户端都停下来等待的时候,有可能出现死锁。小心设计协议,并适当的使用超时,可以把死锁出现的频率和影响减到最小。

    相关文章

      网友评论

      本文标题:python 网络服务器

      本文链接:https://www.haomeiwen.com/subject/onmkdttx.html