文章目录
1.客户端与服务端
客户端:(client)响应服务器向客户提供本地服务的程序。
服务端: (server)对客户端机器提供数据服务程序。
服务器:运行服务端的计算机被称为服务器。
1.1常用的两种架构
1. cs架构 client server 客户端-服务器模式。
2. bs架构 browser server 浏览器-服务器模式。
cs 架构要求
1.用户操作系统安装客户端,产商操作系统部署服务端。
2.每个用户需要独立安装软件,服务端升级也要每个用户升级客户端。
2.socket抽象层
socket:应用层与TCP/IP协议通信的中间抽象出来的一组接口,这个接口称为socket抽象层。
socket = ip + port。
ip 标识互联网中主机位置。
port 标识主机上的一个应用程序。
ip地址是配置到网卡上的,而port书应用程序开启的,ip与port的绑定标识了互联网中独一无二的一个应用程序。
3.套接字的工作流程
3.1 套接字方法
导入scoket模块,Python自带模块.
.scoket() 创建scoket对象
.bind() 绑定(主机,端口号)到套接字。
.listen() 开启TCP监听,定义半链接池内的可以容纳请求服务的数量
.accept() 被动接受TCP客户端的连接,为阻塞态,等待链接的到来。返回链接对象和链接的地址。
客服端套接字方法
.connect() 主动初始化TCP服务器连接
通用方法
.recv() 接收TCP数据,在括号内写上接收字符的个数。
.send() 发送TCP数据,在括号内填入需要传输的数据。
* 基于网络传输发送的都是二进制数据。在Python中可以把tybe类型看成是二进制类型。
.close() 关闭套接字。
4.基于TCP协议通信
TCP是基于链接的,必须先启动服务端,然后再启动客户端去链接服务器。
4.1服务端
# A1.调用socket() 实例化一个服务端对象。
import socket
server = socket.socket()
# A2.TCP服务端对象调用bind()。
# 方法绑定监听的端口,给socket赋值一个ip地址个端口(以元组类型赋值)。
server.bind(('127.0.0.1', 8080))
# A3.当服务器有了ip和端口号,调用listen(),方法进行监听,设置半链接池。
server.listen(5)
# A4.服务器对象调用accept()阻塞,等待客户端连接,如果成功则会返回两个参数。
# 1. sock 当前连接对象 (双向通道) 2. addr 客服端的地址 ip + 端口。
sock, addr = server.accept()
# A5.服务端接收TCP协议信息,设置接收数据的最大字节数1024。byte类型。
date = sock.recv(1024)
print(date)
# A6.服务端对信息进行处理,并将处理的结果调用send()发送TCP协议信息,给客户端。
sock.send(date.upper()) # 小写转大写(只含有英文字符的byte类型可以直接进行转换)
# A7. 关闭与客户端通信的套接字。
sock.close()
# A8.关闭服务端套接字。
server.close()
4.2客户端
# B1.此时客户端调用socket()实例化一个对象。
import socket
client = socket.socket()
# B2.客户端对象调用connect()方法绑定TCP服务端对象监听的位置,连接成功。
client.connect(('127.0.0.1', 8080))
# B3.客户端对象调用send()方法发送数据请求, byte类型。
client.send(b'hello')
# B4.客户端接收信息,设置接收数据的最大字节数1024。byte类型。
date = client.recv(1024)
print(date)
# B5.客户端套接字关闭,结束。
client.close()
5.循环通信
5.1服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
# 成功建立了连接
sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))
# 循环通信
while True:
# 接收消息
data = sock.recv(1024)
if len(data) == 0: # 当客服端关闭的时候会收到 空的type数据.
break # 输入空格的话,空格被转为二进制,data的长度不会为0.
print('收到消息客户端端信息: %s' % data.decode('utf8'))
# 发送消息
in_data = input('发送的消息>>>:').strip()
sock.send(in_data.encode('utf8'))
sock.close()
server.close()
5.2客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
# 循环通信
while True:
# 发送信息, 当用户输入q的时候,关闭服务端
in_data = input('发送的消息("按下q关闭通信")>>>:').strip()
if in_data == 'q':
break
client.send(in_data.encode('utf8'))
# 接收信息
data = client.recv(1024)
print(data.decode('utf8'))
client.close()
5.3测试
-----------与顾客的聊天记录-----------
收到消息客户端端信息: 1
发送的消息>>>:2
收到消息客户端端信息: 3
发送的消息>>>:4
Process finished with exit code 0
-----------与客服的聊天记录-----------
发送的消息("按下q关闭通信")>>>:1
收到消息服务端端信息: 2
发送的消息("按下q关闭通信")>>>:3
收到消息服务端端信息: 4
发送的消息("按下q关闭通信")>>>:q
Process finished with exit code 0
服务端在与客服端正常通信中,如果客服端正常断开,服务端会一直接到空数据包,byte类型数据 b'',
加上一个判断语句,如果收到空的数据包就表示客户端断开了,则使用break退出通信循环。
5.4直接回车
send()的data包不可以空.
在测试代码的时候直接输入回车,data为空.
send()发数据,data为空,数据就不会被发送出去,这行程序登入执行完了,到下一句去了.
服务端接收不到数据一直等待客服端发信息,另一边客户端等待服务端发信息.
就像两个人打电话,都等对方开口.噶在这里了.
# 客户端代码
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
print('与客服的聊天记录'.center(30, '-'))
# 循环通信
while True:
# 发送信息
in_data = input('发送的消息("按下q关闭通信")>>>:').strip()
if len(in_data) == 0: # 防止客户端输入空
print('输入不能为空')
continue
if in_data == 'q':
break
client.send(in_data.encode('utf8'))
# 接收信息
data = client.recv(1024)
print('收到消息服务端端信息: %s' % data.decode('utf8'))
client.close()
-----------与客服的聊天记录-----------
发送的消息("按下q关闭通信")>>>:(直接回车)
输入不能为空
发送的消息("按下q关闭通信")>>>:q
5.5客户端非正常断开连接
对报错的代码行进行针对性的异常捕获,做出处理.避免服务器直接宕机.
# 服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
# 成功建立了连接
sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))
# 循环通信
while True:
# 接收消息
try:
data = sock.recv(1024)
except ConnectionResetError as f:
print(f)
break
if len(data) == 0:
break
print('收到消息客户端端信息: %s' % data.decode('utf8'))
# 发送消息
in_data = input('发送的消息>>>:').strip()
sock.send(in_data.encode('utf8'))
sock.close()
server.close()
6.循环连接
服务器正常情况下是24 * 365天运行的.
客服端断开,就重新进入监听的状态,去服务其它的客户端.
# 服务端重启频繁报端口占用错误&添加兼容性代码(mac linux)
# 在bind前加
from socket import SOL_SOCKET, SO_REUSEADDR
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(2)
# 成功建立了连接
while True: # 循环连接
sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))
# 循环通信
while True:
# 接收消息
try:
data = sock.recv(1024)
except ConnectionResetError as f:
print(f)
break
if len(data) == 0:
break
print('收到消息客户端端信息: %s' % data.decode('utf8'))
# 发送消息
in_data = input('发送的消息>>>:').strip()
sock.send(in_data.encode('utf8'))
sock.close()
server.close()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJLKfSgj-1642005029164)(https://s2.loli.net/2022/01/13/apS1VDWELzb7tlN.png)]
当客户端断开后,重新监听,等待下一个客户端连接.
# 服务端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
print('与客服的聊天记录'.center(30, '-'))
# 循环通信
while True:
# 发送信息
in_data = input('发送的消息("按下q关闭通信")>>>:').strip()
if len(in_data) == 0:
print('输入不能为空')
continue
if in_data == 'q':
break
client.send(in_data.encode('utf8'))
# 接收信息
data = client.recv(1024)
print('收到消息服务端端信息: %s' % data.decode('utf8'))
client.close()
7.半连接池
半连接池内是等待服务的用户请求(可以排队的最大连接个数),目前只能同时服务一个用户.
半连接池内满了.其他的用户就无法进行访问了.
第一个客服端结束访问后,开始服务第二个客户端,这是时候半连接池才腾出一个可以排队的位置.
8.UDP通信流程
8.1参数方法
udp协议是无连接的,先启动谁都没问题。
参数 (type=socket.SOCK_DGRAM),设置通信UDP协议。
设置客户务端的socket 参数 (socket.AF_INET, socket.SOCK_DGRAM),设置通信UDP协议。
.recvfrom() 接收UDP数据。
.sendto() 发送UDP数据。
8.2 UDP服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8090))
date, client_addr = server.recvfrom(1024)
print(date)
print(client_addr)
server.sendto(date.upper(), client_addr)
server.clase()
8.3 UDP客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
msg =input('输入:').strip()
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8090))
date, sever_addr = client.recvfrom(1024)
print(date)
client.close()
8.4 循环通信
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8090))
while True:
date, client_addr = server.recvfrom(1024)
print(date)
print(client_addr)
server.sendto(date.upper(), client_addr)
server.clase()
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', 8090))
date, sever_addr = client.recvfrom(1024)
print(date)
client.close()
clase()
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', 8090))
date, sever_addr = client.recvfrom(1024)
print(date)
client.close()
9.黏包
TCP协议有两个特性:
1.TCP是基于流式传输的,数据管道的数据没有被完全取出,会先去上次没有取完的值.
2.当数据量比较小且时间间隔比较短的多次数据,
那么TCP会自动打包成一个数据包发送.
在建立通行后.recv()控制每次接收数据的字节个数.
数据不会丢失,但是取值的范围不对导致顺序错乱.
9.1特性1
# 服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(2)
sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))
# 循环通信
while True:
data = sock.recv(5)
print('收到消息客户端端信息: %s' % data.decode('utf8'))
# 发送消息
in_data = input('发送的消息>>>:').strip()
sock.send(in_data.encode('utf8'))
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
print('与客服的聊天记录'.center(30, '-'))
# 循环通信
while True:
# 发送信息
in_data = input('发送的消息>>>:').strip()
client.send(in_data.encode('utf8'))
# 接收信息
data = client.recv(5)
print('收到消息服务端端信息: %s' % data.decode('utf8'))
数据值比recv()设置的值过小的时候,下次取值,从上次没取完的开始取.
9.2特性2
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(2)
sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))
data = sock.recv(11)
print('收到消息客户端端信息: %s' % data.decode('utf8'))
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
client.send(b'he')
client.send(b'llo')
client.send(b'word')
-----------与顾客的聊天记录-----------
收到消息客户端端信息: hello word
数据值比recv()设置的值过大的时候,短时间发送多个小数据会打包成一个数据进行传输.
这个组合包最大值为recv()设置的值.
9.3报头
报头的作用:能够标识即将到来的数据具体信息.
在报头中携带字节的具体数值,准确的设置recv()的值.
避免 recv()的值 过大/过小带来的黏包现象.
9.4struct模块
# 报头的长度必须是固定的
常用的模式整数类型b、h、i、q分别
表示1个字节、2个字节、4个字节、8个字节,对应的大写字母表示无符号整数.
import struct
# 1111111111 是字节数
res = struct.pack('i', 1111111111)
# i 模式能制作的 四个字节的包头 struct.error: argument out of range
print(len(res)) # 4
res1 = struct.unpack('i', res)[0] # 解析出字节
print(res1) # 1111111111
9.5简易报头
# 服务端
import socket
import struct
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(2)
sock, addr = server.accept()
res = sock.recv(4)
len_data = struct.unpack('i', res)[0] # 解析出真实的数据大小
data = sock.recv(len_data) # 真实的数据是多大的字节这个len_data就是多少
print(data)
# 客户端
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1', 8080))
# 发送的数据
data = b'123456789'
# 制作报头 固定 4个字节
res = struct.pack('i', len(data))
client.send(res)
# 发送真正的数据
client.send(data)
9.6字典报头
import struct
res = struct.pack('i', 2147483648)
# i 模式能制作的 四个字节的包头 最大表示范围为 2147483647个字节 == 2G
"""
Traceback (most recent call last):
File "F:/synchro/Project/套接字/1.py", line 3, in <module>
res = struct.pack('i', 2147483648)
struct.error: argument out of range
"""
import struct
import json
# 组织成字典格式
d = {
'file_name': '好好学习.mv',
'file_size': 1099511627776, # 一个TB 防止数据过大 i 模式无法的四个字节无法存储
'file_desc': '真的很好看!!!'
}
d = json.dumps(d) # 序列化字典可以统计字符个数,传输后能反序列化使用这个字典.
res = struct.pack('i', len(d)) # i 模式能制作的 四个字节的报头
print(len(res))
res1 = struct.unpack('i', res)[0] # 解析出变量d 的数据大小为193个字节
print(res1)
# 最后在发真实的数据 1099511627776
# 另一端通过反序列化拿到字典的值.设置rece.
实例
import socket
import struct
import subprocess
import json
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(2)
sock, addr = server.accept()
while True:
cmd = sock.recv(1024) # cmd 命令
# cmd操作模块 执行cmd命令
sub = subprocess.Popen(cmd.decode('utf8'), shell=True,
stdout=subprocess.PIPE, # 收集命令执行后正确的结果
stderr=subprocess.PIPE # 收集命令执行后错误的结果
)
# 读出来的数据就是二进制,中国的操作系统默认是使用gkb编码格式
data = sub.stdout.read() + sub.stderr.read()
# 1.组织字典
dic = {
'k1': 'v1',
'size': len(data),
# ...
}
# 2.转为json格式字符串
dic_json = json.dumps(dic)
# 3.制作接收报头 (字典的数据) 4
len_dic = struct.pack('i', len(dic_json))
# 4.发送字典的大小
sock.send(len_dic)
# 5.发送字典(字典内包含真实数据的大小)
sock.send(dic_json.encode('utf8'))
# 6.发送真实的数据
sock.send(data)
import json
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
# 1.在cmd输入命令
cmd = input('输入执行的命令>>>:')
client.send(cmd.encode('utf8'))
# 2.接收报头
res = client.recv(4)
# 3.接收字典大小
len_dic = struct.unpack('i', res)[0]
# 3.接收字典
dic_json = client.recv(len_dic)
# 4.loads会自动解码在反序列化
dic = json.loads(dic_json)
# 可以获取字典的任何数据 ...
# 5.接收真实的数据
data = client.recv(dic.get('size'))
print(data.decode('gbk'))
输入执行的命令>>>:ipconfig
Windows IP 配置
....