Python中Socket编程

本系列Python进阶高级编程为我购买慕课网实战课程的笔记,墙裂推荐大家购买原课程观看,课程地址:

Python高级编程和异步IO并发编程

这一篇我们学习下Python的socket编程。

弄懂HTTP、Socket、TCP这几个概念

我们先看下网络的七层结构模型。

OSI七层模型详解

下面是五层网络模型:

五层模型

上层的协议依赖于下层的协议(DNS依赖于UDP)

当我们应用层满足不了的时候我们如何和下层的TCP和UDP打交道呢?

我们可以使用Socket接口进行和传输层打交道,Socket是不属于任何协议。 我们可以使用Socket脱离应用层可以直接和传输层进行打交道,脱离与应用层,实现自己的应用层协议。

Socket的client和server实现通信

Socket服务端和客户端

我们先看下Socket通信结构,左侧是服务端,右侧是客户端。

对于服务端我们使用bind进行协议,地址,端口的指定,然后调用listen进行监听。

与HTTP传输相比Socket通信只要建立连接之后就可以无限发送信息在客户端和服务端之间,而HTTP是只能进行一次信息传输,必须由客户端发起。

真正的Socket是存在与uwsgi中的

下面我们编写两个文件一个是服务端,一个是客户端。

服务端socket_server.py代码:

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
29
30
import socket

# 指定网络模式 和 协议
# AF_INET 表示IPV4
# SOCK_STREAM 表示TCP协议
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 指定任何主机都可以连接 端口8000
# 注意这里必须传输进去一个tuple
server.bind(('0.0.0.0', 8000))

# 开启监听
server.listen()

# 使用accept可以获得socket和地址
sock, addr = server.accept()

# 下面接收客户端传来的消息
# 一次接收1024个字节
data = sock.recv(1024)

# 数据传输格式为byte类型,我们将其解码为utf8
print(data.decode("utf8"))

# 向客户端发送数据
sock.send((data.decode("utf8")).encode("utf8"))

# 关闭socket 和 server 一般情况是不关闭server的
sock.close()
server.close()

客户端socket_client.py的代码:

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

# 这里配置和服务端一致
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 客户端就要指定IP地址了
client.connect(('127.0.0.1', 8000))

# 数据传输为byte 因此我们需要将数据编码
re_data = "红烧肉"
client.send(re_data.encode("utf8"))

# 接收服务端发送来的数据
data = client.recv(1024)
print(data.decode("utf8"))

# 关闭客户端
client.close()
socket实现聊天和多用户连接

上面我们已经完成了服务端和客户端的通信,但是信息是固定的,下面我们配置可以互发消息。

服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import socket
import threading

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8000))
server.listen()

sock, addr = server.accept()

while True:
data = sock.recv(1024)
print(data.decode("utf8"))
re_data = input()
sock.send(re_data.encode("utf8"))

注意:我们的配置好的server只是用来监听,真正和客户端通信的是sock

客户端代码:

1
2
3
4
5
6
7
8
9
import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000))
while True:
re_data = input()
client.send(re_data.encode("utf8"))
data = client.recv(1024)
print(data.decode("utf8"))

依次运行服务端和客户端,就可以像聊天一样互发消息。

上面的代码只实现了一次建立socket,因此只支持单客户端,我们想要支持多客户端连接,就要建立多个socket。这里建立多个客户端我们通过多线程来实现。

服务端代码:

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

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8000))
server.listen()


def handle_sock(sock, addr):
while True:
data = sock.recv(1024)
print(data.decode("utf8"))
re_data = input()
sock.send(re_data.encode("utf8"))

while True:
# 把生成socket写在循环里 这样有多少个客户端就有多少个socket连接
sock, addr = server.accept()

#用线程去处理新接收的连接(用户)
client_thread = threading.Thread(target=handle_sock, args=(sock, addr))
client_thread.start()

客户端代码不变。

这样我们先开启服务端,就能开启多个客户端进行多客户端发消息。

Socket模拟HTTP请求

我们已经知道,socket是HTTP协议之下让我们可以使用TCP,IP的一个接口。

对于requests库是基于urlib来实现的,而urlib又是基于socket来实现的。对于网络上的大多请求(包含数据库的链接)底层都是通过socket来实现的。不同的操作系统封装了不同的socket接口。

我们先看下网页上的HTTP请求长什么样子:

请求头

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import socket
from urllib.parse import urlparse


def get_url(url):
# 通过socket请求html
# 使用urlparse解析请求URL
url = urlparse(url)

# 获得解析的域名
host = url.netloc

# 获得解析的路径
path = url.path

# 如果请求路径为空则需要将path设置为 / (域名请求方式)
# https://www.baidu.com/
if path == "":
path = "/"

# 建立socket连接
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# client.setblocking(False)

# connect 连接不仅能够制定IP还能制定域名
# 建立连接是一个耗时的过程
client.connect((host, 80)) # 阻塞不会消耗cpu

# 下面发送请求,数据格式非常重要 不同的网站需要的请求信息不同
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8"))

# 解决数据大于1024
data = b""
while True:
d = client.recv(1024)
if d:
data += d
else:
break

data = data.decode("utf8")

# 去除请求头 只保留返回的网页
# 请求头和网页内容之间是两个回车换行
html_data = data.split("\r\n\r\n")[1]
print(html_data)
client.close()


if __name__ == "__main__":

url = "https://www.baidu.com/"
get_url(url)

通过学习Socket我们发现网络请求的整个过程都是可控的。

一篇不错的参考文章:Python 中的 Socket 编程

知识就是财富
如果您觉得文章对您有帮助, 欢迎请我喝杯水!