Python网络编程模块
1.1 Python Socket
1.1.1 Socket套接字
引入一个Socket模块:
import socket
Python的Socket模块的函数原型:
socket(family, type[, protocal])
其中Socket地址族和Socket类型如下表所示。
Socket地址族 | Socket类型 | 协议 |
---|---|---|
AF_INET | SOCK_STREAM | IPPROTO_TCP |
AF_INET6 | SOCK_DGRAM | IPPROTO_RAW |
AF_UNIX | SOCK_RAW | |
SOCK_SEQPACKET |
1.1.2 SOCK_STREAM、SOCK_DGRAM
参数类型 | 作用 |
---|---|
SOCK_STREAM | 其指定的是数据流Socket,一般指的是TCP/IP |
SOCK_DGRAW | 英文全称是datagrams,即数据报的形式,没有保障的面向消息的Socket,一般指的是UDP |
SOCK_RAW | 指原始套接字编程,可以接收数据帧或者数据包,可以用来监听网络的流量和进行数据包分析 |
创建Socket:
s_handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s_handle
是Socket模块初始化后返回的对象,初始化的参数是 AF_INET
和 SOCK_STREAM
,说明初始化的网络参数是TCP协议。第三个参数,可以选择 IPPROTO_TCP
或 IPPROTO_RAW
来指定所使用的协议,也可以忽略第三个参数。
若第二个参数填的是 SOCK_RAW
,在初始化之前,可使用 getprotobyname
函数来得到第三个参数指定所使用的协议。如下所示:
protocal = socket.getprotobyname('imcp')
s_handle = socket.socket(socket.AF_INET, socket.SOCK_RAW, protocal)
销毁socket对象:
s_handle.close()
1.1.3 阻塞和非阻塞模式
阻塞模式指的是在操作系统进行I/O操作完成前,执行的操作函数和内容一直会等待I/O操作完成而不会立刻返回,该函数的执行线程会阻塞在当前函数;
非阻塞模式则相反,执行函数将会立即返回而不管I/O操作是否完成,该函数线程会继续往下执行命令。
设置非阻塞代码的方法:
s_handle.setblocking(Flase)
- 设置超时时间:
s_handle.settimeout(timeout)
1.2 服务端其他Socket方法
1.2.1 bind和listen
在一段网络服务器代码中,开始运作逻辑之前,必须要保证网络地址和端口的绑定。
host = ""
port = 4096
s_handle.bind((host, port)) # 若绑定的地址为0.0.0.0,则绑定本机网卡上所有IP的地址
s_handle.listen(5)
print "start..."
while 1:
do_something
保证地址和端口的绑定:
s_handle.bind((host, port))
保证监听所绑定的地址和端口所传来的数据:
s_handle.listen(5)
Python中listen函数的参数为backlog,即在进程空间维护的请求队列的大小。
1.2.2 setsockopt
Python的 setsockopt
接受三个参数:level
、optname
、value
。
level
指的是定义的层级,其中包括:- SOL_SOCKET:基本套接字接口;
- IPPROTO_IP:IPV4套接字接口;
- IPPROTO_IPV6:IPV6套接字接口;
- IPPROTO_TCP:TCP套接字接口;
optname
指的是选项名称,当level
参数选择不同时,optname
的值又会有所不同value
指的是设置optname
选项的值
示例代码:
s_handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s_handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 说明我们需要Socket句柄关闭后能立刻被重用
# 将Socket接收和发送内容的缓冲区大小从系统的默认值替换为我们自己定义的值
s_handle.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, SEND_BUF_SIZE)
s_handle.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, RECV_BUF_SIZE)
# 将设置的值取回来
current_buf_size = s_handle.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
小结:
在服务器代码中,为了保证端口和地址的绑定操作,我们要使用
bind
函数来进行操作,如果原始Socket参数不够设置,则应使用setsockopt
函数来设置更多的内容。
1.3 客户端Socket
1.3.1 connect方法
import socket
s_handle = socket.socket(socket.AF_INET, socket.SOCK_STERAM)
s_handle.connect(("www.msn.com", 80))
connect
接收一个 tuple
参数,分别为地址和端口,如果连接出错,则返回一个Socket error。
connect
的另一个版本connect_ex
,传入的参数也是接收一个tuple
,但返回值不同,它返回的是一个C层级的返回值。
- 如果连接成功,返回一个0
- 如果连接失败,返回一个Socket系列的error错误号(如10060)或抛出一个异常(如host not found,11001异常等)
当不知道需要连接的服务器的默认端口号是什么,可以通过getservbyname
函数来获得,如下:
import socket
s_handle = sokcet.socket(socket.AF_INET, socket.SOCK_STERAM)
port = socket.getservbyname('http', 'tcp') # 查询所需要的服务名称和协议获取端口号
s_handle.connect(('www.msn.com', port))
查询主机地址可以使用函数gethostbyname
,而gethostbyname_ex
除了查询主机的IP和名称外,还有主机名列表和主机IP地址类别等信息:socket.gethostbyname_ex('www.microsoft.com')
1.4 通用的Socket方法
1.4.1 recv和send
recv
和send
两个函数是供TCP协议编程时使用的发送和接收函数。
recv
的Python原型: recv(bufsize[, flags])
recv
函数接收Socket传过来的内容,其中bufsize
为字符串缓冲区大小,返回的是字符串,而flag
则是指定有关消息的其他值,具体可以通过UNIX的manual手册的recv(2)查询,其中包含:MSG_NOWAIT、MSG_ERRQUEUE、MSG_OOB、MSG_PEEK等参数。
send
的Python原型:send(string[, flags])
send
函数接收一串待发送的字符串,返回被发送后的字节数,根据发送字节数的多少,该字节数有可能小于string字符串数量。
sendall(string[, flags])
函数保证一次性将字符串全部传完,如果出错,将抛出一个异常。
1.4.2 recvfrom和sendto
recvfrom
和sendto
函数主要应用于UDP这样面向无连接的网络编程模型,也可用于TCP编程中。
其在Python中的函数原型:
recvfrom(bufsize[, flags])
,string是接收到的内容,address是发送端Socket的地址;
sendto(string, address)
sendto(string, flags, address)
,其中flags和recv
中的flags相同。
代码示例:
import socket, sys
addr = (`<broadcast>`, 2233) # broadcast标明这是广播的代码
s_handle = socket.socket(socket.AF_INET, socket.SOCK_DGRAW)
s_handle.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
while True:
data = s_handle.recvfrom(1024)
if data:
s_handle.sendto("my message", addr)
s_handle.close()
小结:
recv
和send
,recvfrom
和sendto
是通用的Socket方法,这些方法是组成基础Socket代码所必须使用的,包括客户端和服务器端,在UDP中,由于目标地址并不明确,所以要选择recvfrom
和sendto
方法,当然TCP也可以用,只是多此一举。
1.5 SimpleHTTPServer和BaseHTTPServer
注: 在Python3中,已经将BaseHTTPServer和SimpleHTTPServer都合并入了http.server框架。
1.5.1 SimpleHTTPServer
SimpleHTTPServer包含了SimpleHTTPRequestHandle类,该类可以执行GET以及一些HTTP头部的请求。
我们可以通过命令行来呼叫SimpleHTTPServer,指定HTTP的侦听接口,来达到建立一台简易HTTP服务器的目的,在运行命令行的当前目录下,如果目录下有index.html文件的话,这个文件就会直接成为默认页面,如果没有这个页面,则会在浏览器中列出当前目录的所有内容。
当我们成功在命令行下运行python -m SimpleHTTPServer 80
的时候,会在终端看到Serving HTTP on 0.0.0.0 port 80 ...
1.5.2 BaseHTTPServer
BaseHTTPServer提供了Web服务(HTTPServer)和处理器的类(BaseHTTPRequestHandle)。
示例代码:
def run(server_class=BaseHTTPServer.HTTPServer, handler_class=BaseHTTPServer.BaseHTTPRequestHandle):
server_address = ('', 8000)
httpd = server_class(server_address, handler_class)
httpd.server_forever()
run()
该示例定义了一个默认服务的类和句柄类,都继承自BaseHTTPServer本身,打开的端口是8000,因没有任何实现代码,故运行时报错“501”,该服务器不支持GET操作。
尝试重写一份BaseHTTPRequestHandle子类的do_GET方法
class SampleGet(BaseHTTPServer.BaseHTTPRequestHandle):
#Python运行到GET方法时会自动呼叫这个函数
def do_GET(self):
#返回客户端浏览器的数据
contents = "Hello World"
#设置编码方式
enc = "UTF-8"
contents = contents.encode(enc)
#设置数值为200的返回值
self.send_response(200)
#设置编HTML的头部信息
self.send_header("Content-type", "text/html;charset=UTF-8")
self.send_header("Content-Length", str(len(contents)))
self.end_headers()
#写入contents
self.wfile.write(contents)
run(handler_class=SampleGet)
1.6 urllib和urllib2
注: urllib和urllib2这两个库在python3中已经被合并为urllib。
两个库的侧重点不同:
- urllib做的是请求URL相关的操作和内容,最主要是进行HTTP的URL的操作,urllib只能接收一个URL,可以进行urlencode方法。
- urllib2可以接收Request的对象,然后设置URL的头。
1.6.1 urllib.urlopen和urllib2.urlopen
urlopen用于操作远程获取的url内容,其原型是:
urllib.urlopen(url, data=None, proxies=None)
该函数的返回值为一个对象,返回的对象可以进行类文件的操作,如read、readline、readlines、fileno、close、info、getcode和geturl这些操作。
urllib2也有一个urlopen函数,其原型是:
urllib2.urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT)
将urllib中的proxies参数修改为timeout,可以节省CPU时间,让服务器更稳定地运行下去。
用法示例:
import urllib2
request = urllib.Request(url)
#将请求加上HTTP头信息
request.add_header('User-Agent', 'mozilla')
response = urllib2.urlopen(request, timeout=10)
page = reponse.read()
urllib2设置proxy参数:
def sample(url, enable_p):
proxy_handler = urllib2.ProxyHandler(("http" : 'http://127.0.0.1:8087'))
no_proxy_handler = urllib2.ProxyHandler(())
#使用bulid_opener和install_opener来设置urllib2的全局环境变量
if enable_p:
opener = urllib2.bulid_opener(proxy_handler)
else:
opener = urllib2.bulid_opener(np_proxy_handler)
urllib2.install_opener(opener)
request = urllib2.Request(url)
request.add_header('User-Agent', 'mozilla')
response = urllib2.urlopen(request)
print reponse.read()
#通过代理服务器获取Google网站
sample('http://www.google.com', True)
1.6.2 urllib2中的GET和POST方法
如果服务器需要经过某种登录的网页,则需要用到基础的GET或者POST方法。urllib和urllib2配合字典的请求,就可以组成浏览器的GET和POST请求,然后使用Request给远程服务器。
GET方式:
import urllib, urllib2
req_data = {}
req_data['username'] = "myaccount@163.com"
req_data['password'] = "mypassword"
#urlencode将字典编码为网页的字符串格式
url_data = urllib.urlencode(req_data)
url = "http://www.some_domain.com/login"
#使用url+"?"的形式将GET参数连接起来
full_url = url + "?" + url_data
request = urllib2.Request(full_url)
response = urllib2.urlopen(request)
print response.read()
POST方式:
import urllib, urllib2
req_data = {}
req_data['username'] = "myaccount@163.com"
req_data['password'] = "mypassword"
url_data = urllib.urlencode(req_data)
url = "http://www.some_domain.com/login"
request = urllib2.Request(url, url_data)
response = urllib2.urlopen(request)
print response.read()
Request函数的原型:
#后续几个参数可以直接填入HTTP头信息等
Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False)