python 网络客户端
python 网络编程-网络客户端
理解socket
socket是操作系统中I/O系统的延伸部分,提供进程和机器之间的通信。在某些方面socket可以看成一个标准的文件描述符。
建立socket
对于一个客户端程序来说,建立一个socket需要两个步骤。
- 建立一个socket对象
- 将其连接到远程服务器上
在建立socket对象的时候,需要告诉系统两件事情:通信类型和协议族。
- 通信类型指明了用什么协议来传输数据。
- 协议族例如IPv4,IPv6,IPX/SPX(NetWare),AFP(Apple文件共享),主要是定义数据如何被传输。
对于Internet通信,类型基本上都是AF_INET(IPv4),协议族一般是TCP通信的SOCK_STREAM或者UDP通信的SOCK_DGRAM。
对于TCP通信,建立一个socket连接,一般用类似下面的代码:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
连接socket,一般需要提供一个tuple,包含远程主机名或IP地址和远程端口。连接一个socket一般用类似这样的代码:
s.connect(("www.example.com",80))
下面的程序建立一个连接并马上终止。虽然没有使用意义,但却是一个具有完整功能的例子,代码如下:
#!/usr/bin/env python
# Basic Connection Example - connect.py
import socket
print "Creating socket...",
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print "done."
print "Connecting to remote host...",
s.connect(("www.baidu.com",80))
print "done."
上面的例子连接www.baidu.com上的web服务器,然后打印状态信息,最后终止。
寻找端口号
大多数操作系统都会附带提供一份服务器端口号的列表。python的socket库包含一个getservbyname()的函数,它可以自动的地查询。在unix系统中,可以在/etc/services目录下找到这个列表。
为了查询这个列表,需要两个参数:协议名和端口名。端口名是一个字符串。
下面是对前面程序的修改,使用端口名而不是端口号。
#!/usr/bin/env python
# Revised Connection Example - connect2.py
import socket
print "Creating scoket...",
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print "done."
print "Looking up port number...",
port = socket.getservbyname('http','tcp')
print "done."
print "Connecting to remote host on port %d..." % port,
s.connect(("www.baidu.com",port)),
print "done."
可以看到不需要事先知道HTTP使用80端口。
从socket获取信息
一旦建立了一个socket连接就可以从它那里获得一些有用的信息,下面的例子演示了这些功能:
#!/usr/bin/env python
#Information Example - connect3.py
import socket
print "Creating socket..."
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print "done."
print "Looking up port number...",
port = socket.getservbyname('http','tcp')
print "done."
print "Connecting to remote host on port %d..." % port,
s.connect(("www.baidu.com",port))
print "done."
print "Connected from ",s.getsockname()
print "Connected to ",s.getpeername()
运行这个程序的时候,可以看到两条新的信息,第一条显示自己的IP地址和端口号,第二条显示远程机器的IP地址和端口号。对于客户端来说,端口号是有操作系统随机分配的(也许有某种规则),会发现每次运行这个程序端口号都不一样。
利用socket通信
python 提供了两种方法:
- socket对象
- 文件类对象
socket对象提供了操作系统的send(),sendto(),recv(),recvfrom()调用的接口。
文件类对象提供了read(),write(),readline()这些更典型的pyhon接口。
文件类对象一般用于面向线性的协议,因为它能通过提供的readline()函数自动地处理大多数解析。然而,文件类对象一般只对TCP连接工作的很好,对UDP反而不是很好。因为TCP连接的行为更像是标准的文件,保证数据接收的准确性,并且和文件一样是以字节流形式。而UDP并不像文件那样以字节流形式传输,而是基于信息包的通信。文件类对象没有办法操作每个基本的信息包,所以建立,发送和接收UDP信息包的基本机制是不能工作的,同时错误检查也很困难。
错误处理
当网络出现错误的时候,socket代码会产生异常。具体的错误反馈取决于具体的应用程序。例如:下载中通信中断,应该尝试在该点重新开始下载。
socket异常
不同的网络调用会产生不同的异常。下面的例子演示了当处理socket对象时,如何捕获每一个普通的异常。这个例子需要3个命令行参数:要连接的主机名,服务器的端口号或名,想从服务器下载的文件。程序将连接服务器,对特定的文件发送一个简单的HTTP请求同时显示结果。在整个过程,将尝试处理各种类型潜在的错误。
#!/usr/bin/env python
# Error Handling Example - socketerrors.py
import socket,sys
#下面的参数可以自己填
host = sys.argv[1] #服务器
textport = sys.argv[2] #协议名
filename = sys.argv[3] #要下载文件名
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
except socket.error,e:
print "Strange error creating scoket: %s" %e
sys.exit(1)
# Try parsing it as a numeric port number.
try:
port = int(textport)
except ValueError:
# That didn't work,so it's probably a protocol name.
# Look it up instead.
try:
port = socket.getservbyname(textport,'tcp')
except socket.error,e:
print "Couldn't find your port:%s" %e
sys.exit(1)
try:
s.connect((host,port))
except socket.gaierror,e:
print "Address-related error connecting to server: %s" %e
sys.exit(1)
except socket.error,e:
print "Connecting error:%s" %e
sys.exit(1)
try:
s.sendall("GET %s HTTP/1.0\r\n\r\n" %filename)
except socket.error,e:
print "Error sending data:%s" %e
sys.exit(1)
while 1:
try:
buf = s.recv(2048)
except socket.error,e:
print "Error receving data:%s" %e
sys.exit(1)
if not len(buf):
break
sys.stdout.write(buf)
在这个程序中,异常处理只是简单地打印出一个友好的信息然后终止运行。
python的socket模块实际上定义了4种可能的异常:
- 一般I/O和通信问题有关的socket.error;
- 查询地址有关socket.gaierror;
- 其他地址错误有关的socket.herror;
- 处理超时有关的socket.timeout;
遗漏的错误
上面的程序中错误处理有一个问题。有时候通信处理问题,但是没有产生异常,因为没有从操作系统传回错误。
在客户端连接与服务器写客户端请求的这段时间里,如果远端服务器断开连接,就会出现这种问题。上面的从程序中recv()的调用就接收不到数据,程序会成功终止。
对于很多操作系统,有时候在网络上发送数据的调用会在远程服务器确保已经收到信息之前返回。因此,很有可能一个来自对sendall()成功调用返回的数据,实际上永远没有被收到。
为了解决这个问题,一旦结束写操作,应该立刻调用shutdown()函数。。
对于有些协议,在开始的时候,不止一次写操作,而每次写之后都调用shutdown()则不现实。所以在最后一次写之后,应该执行一次。这样就能保证所有的写操作在该点上都是成功的
文件类对象引起的错误
可以使用makefile()函数从socket得到一个文件类对象。实际上,这个文件类对象调用实际的socket,所以由文件类对象产生的异常和socket自己的send()和recv()函数产生的是一样的。
使用UDP
UDP通信几乎不适用文件类对象。下面是一个基本的UDP客户端例子:
#!/usr/bin/env pyhton
# UDP Example -udp.py
import socket,sys
host = sys.argv[1]
textport = sys.argv[2]
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
try:
port = int(textport)
except ValueError:
port = socket.getservbyname(textport,'udp')
s.connect((host,port))
print "Enter data to transmit:"
data = sys.stdin.readline().strip()
s.sendall(data)
print "Looking for replies;press Ctrl-C or Ctrl-Break to stop."
while 1:
buf = s.recv(2048)
if not len(buf):
break
sys.stdout.write(buf)
上面的例子需要两个命令行参数:一个主机名和一个服务器端口号。它将连接服务器,然后提示输入一行要发送的文字。数据发送后进入一个无线循环来等待回复,可以用Ctrl-C或Ctrl-Break来终止程序。
总结
网络通信的基本接口是socket,它扩展了操作系统基本I/O到网络通信。socket可以通过socket()函数来建立,通过connect()函数来连接。得到了socket可以确定本地和远程的ip地址和端口号。socket对不同的协议来说是一种通用的接口,可以处理tcp和udp通信。
在远程通信的时候可能会发生很多错误,所有错误检查是很重要的。很多时候异常不会马上出现,要使用shutdown()来确保每当有写错误发生时,能够提醒。
python提供了两种socket工作的接口:用于UDP和高级TCP目的的标准socket接口,以及用于简单TCP通信的文件类接口。