Python - Socket
创建 socket 对象
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
socket.socket() 函数将根据指定的参数创建一个 socket 对象。
family: 协议族,协议族决定了 socket 连接的地址类型。AF_INET: IPv4,(host, port).host, a string (e.g. ‘alizs.cc’, ‘127.0.0.1’);port, an integer.AF_INET6: IPv6,(host, port, flowinfo, scopeid).AF_UNIX: 用于单一的 Unix 系统的进程间通信,连接地址为绝对路径。数据传输不需要经过网络,发送的数据经过内核缓冲区后,内核根据路径将数据发送到接收方的内核缓冲区。所以,数据传输的速率远远大于AF_INET、AF_INET6,而且因为不需要经过网卡,数据传输也不受网卡带宽的限制。
type: Socket 类型。SOCK_STREAM:流式 Socket,使用 TCP 传输协议,用于提供面向连接、可靠的数据传输服务。传输数据能够实现无差错、无重复发送,并按顺序接收。SOCK_DGRAM:数据包 Socket,使用 UDP 传输协议,用于提供无连接的数据传输服务。传输数据不具备可靠性,数据有可能在传输工程中丢失或出现重复发送数据,并且无法保证顺序地接收数据。SOCK_RAM:原始 Socket,允许对较低层次的协议直接访问。如:IP、ICMP 等。SOCK_STREAM只能读取 TCP 协议的数据包,SOCK_DGRAM只能读取 UDP 协议的数据包,要访问其它协议的数据包可以用SOCK_RAM。
proto:协议类型,可以指定使用的协议。0,表示默认协议。SOCK_STREAM=>IPPROTO_TCP,SOCK_DGRAM=>IPPROTO_UDP。 当type=SOCK_RAM时,proto不能为空,即不能够用默认值0。fileno:python3 新增,如果指定fileno,其它参数将没有意义,因为 socket 对象将由该fileno创建。
socket.create_connection()
socket 对象的常用方法
1 | sock = socket.socket() |
Python3.2: Support for the context manager
with socket.socket() as sock:
Socket objects(sock) have the following methods:
sock.bind(address):将 Socket 绑定到指定的地址。
address,绑定的地址,格式取决于family。如:AF_INET=>('127.0.0.1', 8080)。
sock.listen(backlog):Socket 服务器开始监听客户端的连接请求。
backlog,表示服务器在accept连接请求之前可以挂起的最大连接数。数值必须大于 0,小于 0 将自动设为 0。Python3.5: backlog 为可选参数。
sock.accept():Socket 服务器接收一个客户端的连接请求,socket 必须要先绑定地址,并且在监听状态。
accept()方法会一直阻塞,直到接收到一个连接请求。返回返回值是一个二元组(conn, address)。conn:一个新的 Socket 对象,可以用来在该连接上发送或接收数据。address:客户端的地址。地址格式取决于family,e.g.AF_INET=>('127.0.0.1', 35423).
sock.connect(address):连接到Socket,
address格式取决于family。连接失败会引起报错。sock.connect_ex(address):Like
sock.connect(address),但是一般连接失败不会引起报错,会返回一个状态码,0表示成功。但是,像Name or server not know(address=('127.0.0.999', 8080)) 这些还是会引起报错的。sock.send(bytes[, flags]):建立 socket 连接之后,发送数据到 socket。返回发送的字节数。
bytes:发送的数据。注意:Python2,为str类型;Python3,为bytes类型。flags:man recv.
用
send()发送数据到 socket,不一定一次就发送完毕,所以,发送完一次之后要比较发送的字节数和总共要发送数据的字节数,判断是否需要进行再次发送剩余的数据。sock.sendall(bytes[, flags]):建立 socket 连接之后,发送数据到 socket。不同
send(),sendall()会一直发送数据,直到数据发送完毕,或者引起报错。成功发送完毕之后,会返回None。如果引起报错是不能够确定已经发送了多少数据的。sock.sendto(bytes, address):发送数据到
sock没有连接的地址。成功返回发送发送的字节数。地址的格式取决于family。(当
family=AF_UNIX的时候,sendto()使用出现socket.error: [Errno 95] Operation not supported的错误?)sock.recv(bufsize[, flags]):接收 Socket 发送过来的数据,作为返回值。
recv()方法会一直阻塞,直到接收到数据。bufsize:一次接收数据的最大字节数flags:man recv
sock.recvfrom(bufsize[, flags]):接收 Socket 发送过来的数据,返回值为
(bytes, address)。bytes,接收的数据;address,发送数据过来的Socket地址。bufsize,flags参数同recv()。recvfrom()方法也会一直阻塞,直到接收到数据。sock.recv_into(buffer[, nbytes[, flags]]):接收 Socket 发送的数据到
buffer,并返回实际接收到数据的字节数。buffer:接收到的 Socket 数据的保存地方。nbytes:一次接收 Socket 数据的最大字节数。falgs:同recv(),man recv
sock.recvfrom_into(buffer[, nbytes[, flags]]):接收 Socket 发送的数据到
buffer,不过返回值是(nbytes, address)。nbytes,实际接收到数据的字节数;address,发送端的地址。sock.close():关闭本进程与 Socket 的连接,并不是关闭 Socket。比如:当多个进程同时操作一个 Socket 的时候,调用
close()只是断开了当前进程与 Socket 的连接,其它进程与该 Socket 的连接是不会收到影响的。当所有的连接都close()了的时候,该 Socket 才算关闭。sock.shutdown(how):可以直接关闭关闭 Socket,也可以只关闭 Socket 的读或写功能。如:多个进程同时操作一个 Socket 的时候,其中一个进程调用了
shutdown(),其它进程也会受影响。how参数:SHUT_RD:关闭 Socket 的读功能,Socket 将不能够接收数据:recv(),recvfrom()…SHUT_WR:关闭 Socket 的写功能,Socket 将不能够发送数据:send(),sendto()…SHUT_RDWR:关闭 Socket 的读写。
sock.fileno():返回 Socket 的文件描述符。失败返回
-1。sock.getsockname():返回 Socket 绑定的地址。地址格式取决于
family,如:AF_INET=>('127.0.0.1', 8080)。sock.getpeername():返回连接到 Socket 的客户端的地址。地址格式也是取决于
family。某些系统不支持该方法。sock.gettimeout():返回 Socket 操作的超时时间。如果没有设置超时时间,则返回
None。- sock.settimeout(value):设置 Socket 操作的超时时间。
value的值:None:阻塞模式。0:非阻塞模式。- 非零:Socket 操作的阻塞时间。
sock.setblocking(flag):设置 Socket 的模式:阻塞/非阻塞。
True:阻塞模式。sock.setblocking(True)等于sock.settimeout(None)。False:非阻塞模式。sock.setblocking(False)等于sock.settimeout(0)。
sock.getsockopt(level, optname[, buflen]): 获取 socket 实例属性
- sock.setsockopt(level, optname, value): 设置 socket 实例属性
socket 模块的其它函数
socket.create_connection(address[, timeout[, source_address]])
连接到地址为(address)的 TCP 服务器,并返回 socket 对象.addressTCP服务器地址,为二元组(host, port).socket.gethostbyname(host)
返回host的 IP 地址。socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)
获取地址(host, port)的相关信息,返回一个列表,列表中每个元素都是一个五元组(family, type, proto, canonname, socketaddr),这些信息包含了创建 socket 连接所需的信息.
其实,你可以理解这是一次DNS解析请求, socket 每次创建连接之前,都调用这个函数去获取地址的相关信息.
所以,在爬虫的时候,因为要请求的URL数量很大,你可以通过打补丁把DNS解析缓存下来.1
2
3
4
5
6
7
8
9
10
11
12import socket
dns_cache = dict()
def _getaddrinfo(*args, **kwargs):
if args in dns_cache:
return dns_cache[args]
info = socket._getaddrinfo(*args, **kwargs)
dns_cache[args] = info
return info
socket.getaddrinfo, socket._getaddrinfo = _getaddrinfo, socket.getaddrinfo
基于 socket 的 TCP 编程
1 | import socket |
TCP 需要先经过三次握手建立连接
accept才能够通信。- 客户端由
connect()发起连接请求,向服务器发送 SYN (i),然后connect()阻塞; - 当服务器收到 SYN 包,向客户端回应 SYN (j) 和 ACK (i+1);
- 客户端收到 SYN 和 ACK 之后,确认之后,
connect()从阻塞状态返回,并向服务器发送 ACK (j+1)。 - 服务器收到 ACK (j+1) 之后,确认之后,
accept()建立一个新的 socket 连接服务端。
TCP 三次握手过程中,服务器的
listen()会将未完成和已完成(收到确认ACK)的握手过程分别保存到未完成和已完成的队列里,已完成队列的个数可以由listen(backlog)的backlog参数指定。accept()就不断从已完成队列中获取完成握手的连接创建新的 socket 来进行通信。- 客户端由
该
server只能够同时接收进行一个连接请求,如果想要 server 同时接收多个连接请求,可以用多线程分发接收连接的新 socketconn,或用多路复用select、poll、epoll来实现。accept()方法会阻塞当前进程,直到接收到一个连接请求,然后返回新的 socketconn和客户端地址addressrecv()方法会阻塞当前进程,直到接收到数据才执行下去。- 注意:Python3 发送的数据要转换为 bytes;接收到的数据也是 bytes 类型。
UDP
1 | import socket |
- UDP 通信不需要建立连接
recvfrom()方法会一直阻塞到接收数据为止。
socket 发送 HTTP/HTTPS 请求
URL:http://www.baidu.com1
2
3
4
5
6
7
8
9
10
11
12
13import socket
sock = socket.socket()
sock.connect(('www.baidu.com', 80))
data = "GET / HTTP/1.1\n" \
"Accept: */*\n" \
"Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh\n" \
"User-Agent: Mozilla/5.0 (X11; Linux x86_64) " \
"AppleWebKit/537.36 (KHTML, like Gecko) " \
"Chrome/58.0.3029.110 Safari/537.36\n" \
"Host: www.baidu.com\n\n"
sock.sendall(data)
response = sock.recv(1024)
URL:https://www.baidu.com1
2
3
4
5
6
7
8
9
10
11
12
13
14import socket
import ssl
sock = ssl.wrap_socket(socket.socket())
sock.connect(('www.baidu.com', 443))
data = "GET / HTTP/1.1\n" \
"Accept: */*\n" \
"Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh\n" \
"User-Agent: Mozilla/5.0 (X11; Linux x86_64) " \
"AppleWebKit/537.36 (KHTML, like Gecko) " \
"Chrome/58.0.3029.110 Safari/537.36\n" \
"Host: www.baidu.com\n\n"
sock.sendall(data)
response = sock.recv(1024)