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 对象.address
TCP服务器地址,为二元组(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
和客户端地址address
recv()
方法会阻塞当前进程,直到接收到数据才执行下去。- 注意: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)