本地回环地址
127.0.0.1
我们先来写一个简单地服务器和客户端
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import socket ? server = socket.socket() # 就比如买了一个手机 server.bind(( "127.0.0.1" , 8080 )) # bind中绑定的是IP地址和端口号,注意是一个元组,就比如,将手机卡,插入了手机 server.listen( 5 ) # 半连接池,最大等待连接数为5个,就比如开机 conn,address = server.accept() # 接听电话等着别人给你打电话 ? date = conn.recv( 1024 ) # 听别人说话,接收1023个字节数 print (date) conn.send(b "hello" ) # 给别人回话 ? ? conn.close() # 挂断电话 server.close() # 关机 |
1
2
3
4
5
6
7
8
9
10
11
|
import socket ? client = socket.socket() #拿电话 client.connect(( "127.0.0.1" , 8080 )) #绑定的是IP地址和端口号,也是一个元组 拨号 ? client.send(b "hello" ) # 对别人发消息 ? date = client.recv( 1024 ) #接收别人说话,没次接收1024个字节 print (date) ? client.close() # 挂电话 |
注意,在我们写服务器与客户端的时候
send与recv必须要一一对应
不能出现两边都相同的
recv是跟内存要数据,至于数据的来源你无需考虑
粘包
我们来看下面的的代码
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import socket ? server = socket.socket() # 就比如买了一个手机 server.bind(( "127.0.0.1" , 8088 )) # bind中绑定的是IP地址和端口号,注意是一个元组,就比如,将手机卡,插入了手机 server.listen( 5 ) # 半连接池,最大等待连接数为5个,就比如开机 conn,address = server.accept() # 接听电话等着别人给你打电话 ? date = conn.recv( 1024 ) # 听别人说话,接收1023个字节数 print (date) date = conn.recv( 1024 ) # 听别人说话,接收1023个字节数 print (date) ? ? conn.close() # 挂断电话 server.close() # 关机 |
客户端
1
2
3
4
5
6
7
8
9
10
|
import socket ? client = socket.socket() #拿电话 client.connect(( "127.0.0.1" , 8088 )) #绑定的是IP地址和端口号,也是一个元组 拨号 ? client.send(b "hello" ) # 对别人发消息 client.send(b "hello" ) # 对别人发消息 ? ? client.close() # 挂电话 |
服务端打印结果
1
|
b ‘hellohello‘ |
这是应为;
tcp协议会将时间间隔短的,和文件大小小的会一次打包发个对方
如果我们将代码改一下,将服务端收到的字节数1024改为我们知道的字节数,看看是否还会粘包
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import socket ? server = socket.socket() # 就比如买了一个手机 server.bind(( "127.0.0.1" , 8088 )) # bind中绑定的是IP地址和端口号,注意是一个元组,就比如,将手机卡,插入了手机 server.listen( 5 ) # 半连接池,最大等待连接数为5个,就比如开机 conn,address = server.accept() # 接听电话等着别人给你打电话 ? date = conn.recv( 5 ) # 听别人说话,接收1023个字节数 print (date) date = conn.recv( 5 ) # 听别人说话,接收1023个字节数 print (date) ? ? conn.close() # 挂断电话 server.close() # 关机 |
客户端
1
2
3
4
5
6
7
8
9
10
|
import socket ? client = socket.socket() #拿电话 client.connect(( "127.0.0.1" , 8088 )) #绑定的是IP地址和端口号,也是一个元组 拨号 ? client.send(b "hello" ) # 对别人发消息 client.send(b "hello" ) # 对别人发消息 ? ? client.close() # 挂电话 |
打印结果
1
2
|
b ‘hello‘ b ‘hello‘ |
在我们知道了,我么传输的文件大小是多大的时候,规定给接收数据的recv就会避免粘包
解决粘包问题
struct模块
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
|
import struct ? ? print ( "--------------------------1--------------------------" ) msg = "asdasdasdasdasd" print ( "原字符串的长度" ) print ( len (msg)) ? handler = struct.pack( "i" , len (msg)) print ( "创建报头的长度" ) print ( len (handler)) ? ? res = struct.unpack( "i" ,handler)[ 0 ] print ( "解报头过后的长度" ) print (res) ? ? ? ? ? print ( "--------------------------2--------------------------" ) ? msg1 = "asdasdasdasdasdasdasdasd" print ( "原字符串的长度" ) print ( len (msg1)) ? handler1 = struct.pack( "i" , len (msg1)) print ( "创建报头的长度" ) print ( len (handler1)) ? ? res1 = struct.unpack( "i" ,handler1)[ 0 ] print ( "解报头过后的长度" ) print (res1) ? """ --------------------------1-------------------------- 原字符串的长度 15 创建报头的长度 4 解报头过后的长度 15 --------------------------2-------------------------- 原字符串的长度 24 创建报头的长度 4 解报头过后的长度 24 """ |
经过观察这个模块会将自己的长度固定为4,8几个等级,我们一般选i就够用了
我们就可以将这个发过去,解报头,就可以知道原来的数据的字节大小
如果这个字节大小比我们接收的大
我们就然他一直接收,直达接收完为止
代码如下
服务端
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
|
import socket import subprocess import json import struct ? # 创建套接字 server = socket.socket() # 绑定端口号,及IP地址 server.bind(( "127.0.0.1" , 8181 )) # 进入半连接池状态 server.listen( 5 ) while True : conn, address = server.accept() # 等待客户端连接 while True : # 判断是否会出错 try : date = conn.recv( 1024 ).decode( "utf-8" ) # 接收客户端发来的数据 if len (date) = = 0 : break # 判断客户端发来的数据是否为空,为空就退出循环,关闭这个通道 # 创建一个subprocess的对象 obj = subprocess.Popen(date, shell = True , stdout = subprocess.PIPE, stderr = subprocess.PIPE) # 将对象拿到的类容做个合并 res = obj.stdout.read() + obj.stderr.read() # 创建一个字典,将拿到的数据的字符数,装入字典中 dic = { "file_size" : len (res)} # 将字典数据类型,转换为json的字符串数据类型 json_dic = json.dumps(dic) # 将json字符串数据类型,创建为报头 handler = struct.pack( "i" , len (json_dic)) ? # 发送报头 conn.send(handler) # 发送json字符串的字典 conn.send(json_dic.encode( "utf-8" )) # 发送原始数据 conn.send(res) # 捕捉异常 except ConnectionResetError: break # 关闭通道 conn.close() |
客户端
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
|
import socket import struct import json ? # 创键套接字 client = socket.socket() # 绑定端口和IP地址 client.connect(( "127.0.0.1" , 8181 )) ? while True : # 用户输入发送内容,并装换成2进制 msg = input ( "cmd>>>" ).strip().encode( "utf-8" ) # 判断用户输入是否为空,为空跳过本次循环 if len (msg) = = 0 : continue # 发送数据 client.send(msg) # 接收报头 handler = client.recv( 4 ) # 解析报头,注意必须加索引,拿到json过后的字典的字节数 json_dic_size = struct.unpack( "i" , handler)[ 0 ] # 接收json字典的字节数的json字典 json_dic = client.recv(json_dic_size) # 将json数据的字典,反序列化 dic = json.loads(json_dic) # 定义一个字节数为0 的变量 msg_size = 0 # 定义一个空的2进制字符 msg_date = b‘‘ # 判断,如果定义的字节数小于我们字典中的字节数就读取文件,如果等于和大于就退出循环,打印文件类容 while msg_size < dic.get( "file_size" ): date = client.recv( 1024 ) # 文件类容累加 msg_date + = date # 字节数累加 msg_size + = len (date) ? print (msg_date.decode( "gbk" )) |
?