Python网络编程模块

Posted by LudoArt on July 8, 2019

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_INETSOCK_STREAM ,说明初始化的网络参数是TCP协议。第三个参数,可以选择 IPPROTO_TCPIPPROTO_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 接受三个参数:leveloptnamevalue

  • 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

recvsend两个函数是供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

recvfromsendto函数主要应用于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()

小结:

recvsendrecvfromsendto是通用的Socket方法,这些方法是组成基础Socket代码所必须使用的,包括客户端和服务器端,在UDP中,由于目标地址并不明确,所以要选择recvfromsendto方法,当然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)

1.7 事件驱动框架Twisted

1.7.1 Reactor模式

1.7.2 run、stop和callLater

1.7.3 Transports、Protocols、Protocol Factoies以及Deferred