Socket Reference

创建 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_INETAF_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 类型。
    • flagsman 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一次接收数据的最大字节数
    • flagsman 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
    12
    import 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import socket

# server
def server():
host = '127.0.0.1'
port = 8080
sock = socket.socket()
# Python >= 3.2, support context manager: with socket.socket as sock
sock.bind((host, port))
sock.listen(5)
conn, address = sock.accept()
while True:
data = conn.recv(1024)
if data:
print('Data receive from client: %s' % data)
conn.sendall('Server receive data succefull!')

def client():
host = '127.0.0.1'
port = 8080
sock = socket.socket()
sock.connect((host, port))
while True:
data = input('Please input data that send to server: ')
sock.sendall(str(data))
msg = sock.recv(1024)
if msg:
print('Message from server: %s' % data)
  • 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 同时接收多个连接请求,可以用多线程分发接收连接的新 socket conn,或用多路复用 selectpollepoll 来实现。

  • accept() 方法会阻塞当前进程,直到接收到一个连接请求,然后返回新的 socket conn 和客户端地址 address
  • recv() 方法会阻塞当前进程,直到接收到数据才执行下去。
  • 注意:Python3 发送的数据要转换为 bytes;接收到的数据也是 bytes 类型。

UDP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import socket

def server():
host = '127.0.0.1'
port = 8080
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
sock.bind((host, port))
while True:
data, address = sock.recvfrom(1024)
print('Data from client: %s' % data)
sock.sendto('This is a message from server', address)

def client():
host = '127.0.0.1'
port = 8080
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
while True:
msg = input('Please input data that send to server: ')
sock.sendto(str(msg), (host, port))
data, address = sock.recvfrom(1024)
print(data)
print(address)
  • UDP 通信不需要建立连接
  • recvfrom() 方法会一直阻塞到接收数据为止。

socket 发送 HTTP/HTTPS 请求

URL:http://www.baidu.com

1
2
3
4
5
6
7
8
9
10
11
12
13
import 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.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 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)