算法之路helloworld被隐藏了的过程玩耍Python

python 网络服务器

2016-06-02  本文已影响530人  b64c74899092

网络服务器(python)

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

准备连接

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

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

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

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()返回值来判断该往哪里发送响应。

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

上一篇下一篇

猜你喜欢

热点阅读