协议:两边都商量好的东西
LAN:局域网,在小范围通信没问题,但是如果计算机数量庞大会造成广播风暴,需要划分网段
WAN:广域网,大范围网络通信,通过划分网段实现,通过路由器连接。
物理层:通常指物理网卡
数据链路层:每台计算机唯一的MAC地址
网络层:ip地址,通过划分网段来通信
传输层:提供了端口的协议用来定位应用程序,tcp是连续的,可靠的数据传输,需要等待对方回应,效率低。 UDP是不连续的,不可靠的传输,传过去就不管了,效率高。
应用层:app之间的通信
套接字:也就是socket套接字连接,数据表的封装内容 【目标端口 目标ip 目标mac 数据 源mac 源ip 源端口】
一、基于TCP的socket编程
socket就是套接字,一种通信协议。
TCP连接:需要先建立起连接,发送数据后要等待对方回复
server端:先开启server端,并监听自己的ip和端口,等待客户端连接
client端:再开启client端,主动去连接server端,一次只能发一条数据
1、基于TCP的socket编程-server端
import socket
# 创建sorckt对象
sk = socket.socket()
# 绑定server端ip和端口,里面是元组
sk.bind(('127.0.0.1',6888))
# 开启监听(监听客户端的连接数,记录有哪些客户端来连接并记录相关ip信息)
sk.listen()
print('等待连接')
conn, address = sk.accept() # 建立连接,conn是C-S连接状态,address记录客户端ip端口
print('等待成功')
2、基于TCP的socket编程-client端
import socket
# 创建socket对象
sk = socket.socket()
# 连接server端
sk.connect(('127.0.0.1',6888))
3、连接成功后就可以通信了
(1) server端配置(先启动server端)
import socket
# 创建sorckt对象
sk = socket.socket()
# 绑定server端ip和端口,里面是元组
sk.bind(('127.0.0.1',6888))
# 开启监听(监听客户端的连接数)
sk.listen()
# 等待客户端连接,conn是连接状态,address记录客户端ip端口
print('等待连接')
conn, address = sk.accept()
print('连接成功')
while 1:
# 服务端先收到字节(使用conn收)
byts = conn.recv(1024)
# 如果接受的内容是"Q",就退出聊天
msg = byts.decode('utf-8') # 再把字节转换成中文,并交给变量msg,主要看一下接收到的字节是否是 'Q'
if msg == "Q":
break
# 如果不是Q,再把接收的字节转换成中文打印出来,这里打印的是来自客户端的消息
print('来自客户端:', byts.decode('utf-8'))
# 服务端回复消息并转换成字节发送出去(使用conn去法)
msg = input('>>>:')
conn.send(msg.encode('utf-8'))
conn.close() # 关闭连接状态
sk.close() # 关闭socket连接
(2) client端配置(后启动client端)
import socket
# 创建socket对象
sk = socket.socket()
# 连接server端
sk.connect(('127.0.0.1',6888))
while 1:
# 客户端发送消息,并转换成字节(使用socket对象去发)
msg = input('>>>:')
sk.send(msg.encode('utf-8'))
# 如果发送的内容是"Q",就退出聊天
if msg == "Q":
break
# 客户端先收到字节,再转换成中文(使用sorcket收)
byts = sk.recv(1024)
print('来自服务端:', byts.decode('utf-8'))
sk.close() # 关闭socket连接
一、基于UDP的socket编程
UDP连接:只负责发送,对方收没收到不管
server端:先开启server端,并绑定自己的ip和端口
client端:再开启client端,主动去连接server端,可以连续发送多条数据
(1) server端配置
import socket
# 创建socket对象,指定类型为UDP
sk = socket.socket(type=socket.SOCK_DGRAM)
# 绑定ip和端口,里面是元组
sk.bind(('127.0.0.1',6889))
while 1:
# 接受来自客户端的数据,byts是接收的字节,address存放客户端ip和端口
byts, address = sk.recvfrom( 1024)
# 如果收到的是"Q",就退出
byts_msg = byts.decode('utf-8') # 先把收到的信息保存
if byts_msg == "Q":
break
# 把收到的字节内容转换成中文
print('来自客户端:', byts.decode('utf-8'))
# 给客户端发送数据,address是客户端ip和端口
msg = input('>>>:')
sk.sendto(msg.encode('utf-8'), address)
# 关闭socket连接
sk.close()
(2) client端连接
import socket
# 创建socket对象
sk = socket.socket(type=socket.SOCK_DGRAM)
while 1:
# 给服务端发送数据,
msg = input('>>>:')
sk.sendto(msg.encode('utf-8'), ('127.0.0.1', 6889))
# 如果发送内容是"Q",就退出
if msg == "Q":
break
# 接收来自服务端的数据
byts,address = sk.recvfrom(1024)
print('来自服务端:', byts.decode('utf-8'))
sk.close()
一、粘包
粘包只存在于TCP,UDP没有粘包一说,粘包属于通信bug
大量发送数据时,由于发送数据太快,导致目标主机收到的数据包堆积到一起的,比如第一次发送了‘ABC’,第二次发送了‘123’,目标主机收到的应该是‘ABC,123‘,如果出现粘包收到的就是’ABC123‘
1、解决粘包的核心思想
server: 先把发送信息[ABCD]转换成字节[b’ABCD’]——>统计字节长度4 并统一改成4位数0004 ——> 先发送4位数[0004] ——> 再发送字节[b’ABCD’]
client: 先接收4位数[0004] 并还原成字节长度4 ——> 再接收字节[b’ABCD’] ——> 把字节还原成原始信息[ABCD]
# 用户发送
(1) 比如用户第一次发送了'ABCDEFGHIGKLMN',先转换成字节,字节长度是14个,14是2位数(千、白、十、个),通过format()统一格式化成4位数0014(千、百、十、个)
(2) 用户第二次发送了'123456',先转换成字节,字节长度是6个,6是1位数(千、白、十、个),通过format()统一格式化成4位数0006(千、百、十、个)
# 对方接收
(3) 对方第一次先接收的是一个4位数,拆开是0014,发现字节长度是14个,后收到字节内容b'ABCDEFGHIGKLMN123456',再取前14个字节并把字节转换成原始信息。
(4) 对方第二次接收的依然是4位数,拆开后是0006,发现字节长度是6个,由于第一次已经收到了字节,所以直接把后面的6个字节转换成原始信息就可以了解决粘包的问题了。
2、用函数解决粘包(很lou),主要用于理解
(1) server端(先启动)
import socket
# 创建socket对象
sk = socket.socket()
# 绑定server端地址,里面是元组
sk.bind(('127.0.0.1',6888))
# 开启监听(监听客户端的连接数)
sk.listen()
# 等待客户端连接,conn是C-S连接状态,address存放客户端地址
print('等待连接')
conn,address = sk.accept()
print('连接成功')
# 收信函数,用于解决粘包
def my_recv():
# 先接收到的是对方传来了一个4位数0009
msg_four = conn.recv(4)
# 再把这个4位数还原成实际字节长度9
msg_len_byts = int(msg_four.decode('utf-8'))
# 然后再接收转换成字节的信息
msg_byts = conn.recv(msg_len_byts)
# 最后把接收到的字节转换成文字('哈哈哈')
print('来自客户端:',msg_byts.decode('utf-8'))
my_recv()
my_recv()
my_recv()
(2) client端(后启动)
import socket
# 创建socket对象
sk = socket.socket()
# 用于server端建立连接
sk.connect(('127.0.0.1',6888))
# 发信函数,用于解决粘包
def my_send(msg):
# 先把要发送的信息转换成字节('哈哈哈'转换成字节)
msg_byts = msg.encode('utf-8')
# 再统计出字节长度('哈哈哈'的字节长度是9)
msg_byts_len = len(msg_byts)
# 然后把字节长度统一转换成4位数(9转换成千位数是 0009)
msg_four = format(msg_byts_len, '04d')
# 再然后把4位数(0009)转换成字节发送出去,告诉对方第一次发送的内容是4位数
sk.send(msg_four.encode('utf-8'))
# 最后发送转换成字节的信息
sk.send(msg_byts)
# 输入要发送的内容
my_send(input(">>>:"))
my_send(input(">>>:"))
my_send(input(">>>:"))
3、使用struct() 模块解决粘包(正常解决粘包用法)
struct(‘i’, 数字): i表示转换格式,转换成千位数(千百十个),也就是4位数
3、使用struct() 模块解决粘包(正常解决粘包用法)
struct('i', 数字): i表示转换格式,转换成千位数(千百十个),也就是4位数
<这里可能不完整需要网上自行搜索教程>
使用struct()解决粘包
(1) server端
import socket
import struct
# 创建socket对象
sk = socket.socket()
# 绑定server端ip端口,里面是元组
sk.bind(('127.0.0.1',6888))
# 开启监听(监听客户端的连接数)
sk.listen()
# 等待客户端连接,conn是CS连接状态,address记录客户端ip端口
print('等待连接')
conn,address = sk.accept()
print('连接成功')
# 使用struct解决粘包,收信函数
def my_recv():
# 先收到的是4位数的包(0003)
msg_four_len = conn.recv(4)
# 再还原成实际发信字节的长度('哈哈哈'的字节长度是9)
msg_byts_len = struct.unpack('i', msg_four_len)[0]
# 然后把发信的字节接收下来
msg_byts = conn.recv(msg_byts_len) # 注意:这里接收的是字节长度,就可以实现接收字节的效果。
# 最后把发信字节转换成中文
print(msg_byts.decode('utf-8'))
# 接受内容
my_recv()
(2) client端
import socket
import struct
# 创建socket对象
sk = socket.socket()
# 连接server端
sk.connect(('127.0.0.1',6888))
# 使用struct解决粘包,发信函数
def my_send(msg):
# 先把要发送的信息转换成字节('哈哈哈'转换成字节)
msg_byts = msg.encode('utf-8')
# 再统计字节长度并转换成4位数('哈哈哈'的长度是3,4位数是0003)
msg_four_len = struct.pack('i', len(msg_byts))
# 然后把4位数先发送出去,告诉对方第一次发送的是4位数的包
sk.send(msg_four_len)
# 最后发送这个转换成字节的数据包
sk.send(msg_byts)
# 发送消息
my_send(input(">>>:"))
4、客户端发送图片,服务端接受图片
(1) server端,收图片
import socket
import struct
# 创建socket对象
sk = socket.socket()
# 绑定server端ip端口,里面是元组
sk.bind(('127.0.0.1', 6888))
# 开启监听(监听客户端的连接数)
sk.listen()
# 等待客户端连接
print('等待连接')
conn,address = sk.accept()
print('连接成功')
# 先接收4位长度,并转换成图片实际大小(字节)
file_four_len = conn.recv(4)
file_size_len = struct.unpack('i', file_four_len)[0]
"""
接收图片实际长度(如果图片大小超过1024可能会出现接收不完整问题)
file_size = conn.recv(1024)
f = open('upload/吴亦凡.jpg', mode='wb')
f.write(file_size)
"""
# 所以需要一点一点的保存图片
f = open('upload/吴亦凡.jpg', mode='wb')
while file_size_len > 0: # 如果图片长度不为0
byts = conn.recv(1024) # 每次接收一部分字节
f.write(byts) # 并写入文件中
file_size_len -= len(byts) # 直到全部接收完毕
print('图片接收完毕')
(2) client端-发图片
import socket
import os
import struct
# 创建socket对象
sk = socket.socket()
# 连接server端
sk.connect(('127.0.0.1', 6888))
# 获取图片大小(字节),并转换成4位长度
file_size = os.path.getsize('tu.jpg')
file_four_len = struct.pack('i', file_size)
# 先把4位长度发送出去
sk.send(file_four_len)
# 再发送图片
f = open('tu.jpg',mode='rb')
for line in f:
sk.send(line) # 每次发送一部分字节
print('图片发送完毕')
4.1、连图片名一起发送过去
(1) server端 接收图片
import socket
import struct
import json
import sys
import time
# 创建socket对象
sk = socket.socket()
# 绑定server端ip端口,里面是元组
sk.bind(('127.0.0.1', 6888))
# 开启监听(监听客户端的连接数)
sk.listen()
# 等待客户端连接
print('等待连接')
conn,address = sk.accept()
print('连接成功')
# 先接收4位数,再还原成json字节的长度
json_four_len = conn.recv(4)
file_size_len = struct.unpack('i', json_four_len)[0]
# 然后接收json字符串字节,并把json字符串字节还原成字符串
json_byts = conn.recv(file_size_len)
dic_json = json_byts.decode('utf-8')
# 再把json字符串转换成字典
dic = json.loads(dic_json)
# 保存图片
f = open(f"upload/{dic['file_name']}", mode='wb') # 创建文件并获取文件名
while dic["file_size"] > 0: # 如果图片长度不为0
byts = conn.recv(1024) # 每次接收一部分字节
f.write(byts) # 并写入文件中
dic["file_size"] -= len(byts) # 直到全部接收完毕
# 显示进度条
def person(totle, now):
baifen = (now * 100 // totle) # 得到一个数字
sys.stdout.write(f"\r【{baifen * '#'}】 - {baifen}%") # 字符串拼接
now = 0
for i in range(1,12):
time.sleep(0.8)
person(500, now)
now += 50
print('\n图片接收完毕')
(2) client端发送图片
import socket
import os
import struct
import json
# 创建socket对象
sk = socket.socket()
# 连接server端
sk.connect(('127.0.0.1', 6888))
# 获取图片文件名和图片大小
name = 'tu.jpg'
file_name = os.path.basename(name)
file_size = os.path.getsize(name)
# 把图片名和图片大小保存为字典,并转换成json格式(字符串)
dic = {'file_name':file_name, 'file_size':file_size}
dic_json = json.dumps(dic)
# 把json格式的字符串转换成字节
dic_json_byts = dic_json.encode('utf-8')
# 计算json格式字节长度,并转换成4位数
json_four_len = struct.pack('i', len(dic_json_byts))
# 先把4位数发送出去,再发送转换成字节的json字符串
sk.send(json_four_len)
sk.send(dic_json_byts)
# 最后发送图片(图片默认就是字节)
f = open('tu.jpg',mode='rb')
for line in f:
sk.send(line) # 每次发送一部分字节
print('图片发送完毕')
1、进度条
import sys
import time
def person(totle, now):
baifen = (now * 100 // totle) # 得到一个数字
sys.stdout.write(f"\r【{baifen * '#'}】 - {baifen}%") # 字符串拼接
now = 0
for i in range(1,12):
time.sleep(0.8)
person(500, now)
now += 50
----------------------------------------
【###########################################################】 - 100%
1、socketserver可以通过有多个客户端连接到server端
有一个客户端连接进来,就启动一个线程
(1) server端配置
import socketserver,time
from socketserver import BaseRequestHandler
class Myserver(BaseRequestHandler):
def handle(self): # 客户端默认会连接handle()
conn = self.request # 拿到连接状态conn
while 1:
time.sleep(1)
conn.send(b'hello') # 给客户端发送字节内容
if __name__ == '__main__':
# 绑定server端ip端口,同时调用Myserver类
server = socketserver.ThreadingTCPServer(('127.0.0.1', 6888), Myserver)
server.serve_forever() #永远执行
(2) 客户端1配置
import socket
sk = socket.socket() # 建立socket对象
sk.connect(('127.0.0.1', 6888)) # 连接server端
while 1:
print(sk.recv(1024)) #接收数据
(3) 客户端2配置
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 6888))
while 1:
print(sk.recv(1024))