socketserver模块
基于socketserver实现并发的套接字通信 TCP UDP
# socketserver之TCP
# 引入
先要明白TCP服务需要做的两件事:
1> 建连接
2> 基于建立好的连接进行通信
前面写的TCP套接字程序,连接中包含了通信,没有将连接和通信区分开来..所以没有并发的效果.
具体来说,连接循环不停的从半连接池里取出连接请求,通过accept建立连接;
但连接循环里包含了通信循环,当前通信不完,下一个连接无法建立!!
基于TCP并发的实现逻辑:
服务端应让一个人不停的建立连接,干accept的活;
每建立好一个连接, 就 新 安排一个人与建立好的连接一一对应干通信的活!
# 实现
# 服务端
TCP服务端代码如下,客户端的代码跟原来一样,没有变化!!
"""
★ -- 服务端
"""
import socketserver
# -- 我们自定义的MyHandler类必须继承socketserver.BaseRequestHandler,不可能所有功能自己重写.
class MyHandler(socketserver.BaseRequestHandler):
# -- 自定义的MyHandler类里面必须有一个名为handle的方法,一个字都不能差!
def handle(self):
"""
self.client_address 客户端的IP和port
self.request 实则就是原先未实现并发的TCP服务端中的conn对象
注意,socketserver只是帮我们实现了并发.解决粘包问题还是得自己来!
"""
# -- 通信循环
while True:
try:
data = self.request.recv(1024)
if not data: break
self.request.send(data.upper())
except ConnectionResetError:
break
if __name__ == '__main__':
"""ThreadingTCPServer三个参数
1> server_address:服务端的地址,即IP+port
2> RequestHandlerClass: 我们自定义的类
3> bind_and_activate: 绑定并且激活 《"本质就是执行bind和listen操作"》默认值为True.
"""
s = socketserver.ThreadingTCPServer(("127.0.0.1", 8080),
MyHandler,
bind_and_activate=True)
# -- 循环建立连接 《"本质就是循环执行accept操作"》
s.serve_forever()
"""
★ -- 客户端
"""
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
msg = input('>>: ').strip()
if not msg: continue # -- 判断
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print('来自服务端的数据:', data)
phone.close()
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
# 运行过程
对象是容器,里面存了一堆数据,还捆绑了一堆方法,对象是数据和功能的结合体,用对象的思想,整合程度更高!
1> s.serve_forever()
每建好一个连接, 都会启动一个线程thread;
2> 该线程会调用我们自定义的Myhandler类产生一个实例化对象 (刚刚建立好的连接的信息都会封装到这个对象里面去 "eg 原先未实现并发的TCP服务端里调用accept方法返回的conn和client_addr")
3> 类实例化对象自动调用该对象下的handle方法,该方法专门用于实现与刚刚建立好的连接做通信循环.
# socketserver之UDP
要注意哈!
1> 原来实现的UDP套接字程序的服务端里有while True循环,收完一条C端消息后发,再收完一条再发.. 执行的过程比较快,若客户端比较少且数据量比较小的话,看起来就有并发的效果.. 实则并不是并发.
2> 在这里实现的UDP并发套接字程序的服务端里是没有的while True循环的.
从UDP的执行过程分析:
大体上跟UDP差不多.但要特别注意的是!
UDP没有连接一说,客户端在发消息的时候才会启动一个线程,实例化一个对象,触发handle方法.
"""
★ -- 服务端
"""
import socketserver
class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
"""
self.client_address 客户端的IP和port
self.request (客户端发的消息,一个套接字对象)
"""
data = self.request[0]
print("客户端消息:", data)
self.request[1].sendto(data.upper(), self.client_address)
if __name__ == '__main__':
s = socketserver.ThreadingUDPServer(("127.0.0.1", 8080), MyHandler)
# -- 比起TCP的过程,UDP少了建连接.
# UDP没有连接一说,客户端在发消息的时候才会启动一个线程,实例化一个对象,触发handle方法.
s.serve_forever()
"""
★ -- 客户端
"""
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
msg = input(">>:").strip()
client.sendto(msg.encode('utf-8'), ("127.0.0.1", 8080))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()
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
待解决: socketserver模块源码分析!!! 学完进程线程后再来(´▽`) 有时间的话.Hhh.
# 网络编程复习
只有我自己看得懂的复习总结..(´▽`)
计算机网络
IP + mac地址能标识全世界范围内独一无二的一台计算机.
(IP找到主机在哪个局域网,MAC地址找到主机在局域网的哪个位置!!)
url地址是用来标识全世界范围内独一无二的一个资源的!!
应用协议部分//域名和端口,浏览器端口默认80/路径 -- url是建立在ip+mac+port之上的!
HTTP是比TCP更高层次的应用层协议.
根据规则,只有低层协议(TCP三次握手建立双向通路)建立之后才能,才能进行高层协议的连接!
socket套接字位于应用层和传输层之间,将传输层以下的协议都封装成了接口!!!
半连接池backlog 限制的是<同一时刻>的请求数!
C端向S端发送的连接请求,要先从半连接池里走一遭,建立好TCP连接后,该连接会进入全连接池
高并发的情况下.客户端的请求连接会长时间停在半连接池里..现实中的体现就是网页一直在加载.
半连接池满了,往后的连接请求直接连接不上,超时..
半连接池跟全连接池一样占用的是内存空间,设置的连接数量受限于内存的大小.
通过netstat -an命令查看状态可以进行排错!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TCP套接字
★ 简单来记!
C端和S端都不能发空数据!!send发给的是自己的OS,发空发了个寂寞!
C端单方面断开,进行异常处理,务必同时兼容linux和window系统.
一般情况是客户端先向服务端send数据, so,客户端send-recv;服务端recv-send.
<服务端 别"收"空数据>,解决客户端单方面断开.
<客户端 别"发"空数据>,防止一直阻塞.
TCP为啥可靠,因为send到自己的OS缓存中的数据,通过网络发出,确认到达了对方后,才会删除掉!
UDP可不会,通过网络发出去后,直接删除OS缓存中的数据!
当然,recv也是从自己OS的缓存中拿!recv(1024)是指一次性最多拿1024字节的数据!
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # -- server套接字对象
server.bind(("127.0.0.1", 8080))
server.listen(5) # -- 半连接池数
while True: # -- 并没有实现并发的提供服务!用多进程多线程才行!
conn, client_addr = server.accept() # -- conn套接字对象
while True:
try:
# -- conn是三次握手建立双向通道的产物,客户端单方面挂了
# linux下S端会不停的接收空字符串,windows下S端会报错!
data = conn.recv(1024)
if not data: break # -- linux
print('客户端的数据:', data)
conn.send(data.upper())
except ConnectionResetError: # -- windows
break
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # -- client套接字对象
client.connect(('127.0.0.1', 8080))
while True:
msg = input('>>: ').strip()
if not msg: continue # -- 判断
phone.send(msg.encode('utf8'))
data = phone.recv(1024)
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
粘包问题
TCP协议称为流式协议.精髓在于流stream!!顾名思义,像流水一样传输数据.
TCP就一股脑的传真实数据.
传输过程涉及滑动窗口机制、序列号、停止等待返回ACK等等.
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段.
1> 若连续对此send的数据量小且间隔短,会用nagle算法.接收端收到的是粘包数据.
接收端无法区分合在一起的多个数据包的界限
2> 若传输一个大文件,会分段.发送时文件内容是按照一段一段的字节流发送的,
在接收方看来,根本不知道该文件的字节流从何处开始,在何处结束
解决办法:
"报头 + 数据" 接收端应该想方设法先把报头部分"精准"拿到!!!
具体来说,由于报头和要发送的数据都是属于TCP协议的一部分.也是会粘到一起的.
制作报头,将报头转换成bytes类型发送很容易实现,关键是接收端要先将报头准确的接收到..
使用struct模块可以将<整形的数据>转成<固定长度的bytes类型的数据>!!
将报头的长度做成4个bytes发送给对方,接收端先接收这4个bytes的数据,解出报头的长度..再接收报头长度的数据!
具体过程详看文件传输的函数版!
1> struct模块的i模式支持的整型数据的大小范围是有限的!超过了会报错.
SO,只传文件的字节长度是不合理的.i模式下,该文件的字节长度为10000个字节,直接报错.
2> 对于一个文件的发送,需要制作个报头/说白了,就是一个字典!
里面应该包含该文件的名字,长度,文件md5值等信息.
以服务端发文件,客户端接收文件为例!当然也可以,客户端发文件,服务端接收.一样的过程!
>> 服务端
send_dic = {k1:v1,k2:v2,...} # -- 字典
send_bytes = json.dumps(send_dic).encode('utf-8') # -- 序列化-编码 -> 字节数据
conn.send(struct.pack('i', len(send_bytes))) # -- struct打包报头长度-发送4字节的数据
conn.send(send_bytes) # -- 发送报头数据
with open(file_path, 'rb') as f:
for line in f:
conn.send(line)
>> 客户端
recv_bytes_len = struct.unpack('i', client.recv(4))[0]
recv_bytes = client.recv(header_bytes_len) # -- 不要担心,这个报头字典肯定能一次性recv取到的.
recv_dic = json.loads(header_bytes.decode('utf-8')) # -- 字节数据 解码-反序列化 -> 字典
total_size = recv_dic['file_size']
recv_size = 0
with open(file_path, 'wb') as f:
while recv_size < total_size:
line_data = client.recv(1024)
f.write(line_data)
recv_size += len(line_data)
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