02_套接字编程(socket抽象层)
1.套接字概述
1.套接概述: 套接是进行网络通信的一种手段(socket)
2.套接字分类:
流式套接字(SOCK_STREAM): 传输层基于tcp协议进行通信
数据报套接字(SOCK_DEGAM): 传输层基于udp协议进行通信
原始套接字(SOCK_RAW): 访问底层协议的套接字
3.TCP与UDP通讯模型流程图: https://www.processon.com/view/link/5ef43bfd1e0853263742690b
4.套接字属性和方法
import sockets
= socket.socket() # 默认会创建流式套接字# 功能: 获取套接字的描述符
# 描述符: 每一个IO操作系统都会分配一个不同的整数与之对应,该整数极为此IO操作的描述符
s.fileno() # 12
print(s.fileon()) # 12
# 获取套接字类型
s.type # <SocketKind.SOCK_STREAM: 1>
print(s.type) # SocketKind.SOCK_STREAM
# 获取套接字绑定的地址
s.getsockname() # ("0.0.0.0", 0)
s.bind(("127.0.0.1", 7890))
print(s.getsockname()) # ("127.0.0.1", 7890)
# 使用accept生成的套接字调用,获取该套接字对应的客户端的地址,在一个服务器有多个客户端连接时常会用到这个方法
s.listen(128)
conn, addr = s.accept() # 阻塞时需要找一个用户连接
conn.getpeername() # ("127.0.0.1", 53519)
print(conn.getpeername()) # ("127.0.0.1", 53519)
# s.setsockopt(level, optname, value) 设置套接字选项
# 参数 level: 定义的选项类型,常用选项(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET)
# 参数 optname: 根据level选项确定的子选项
# 参数 value: 根据子选项设置的值
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 设置端口重用
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # 设置套接字允许接收广播
# s.getsockopt(level, optname) 获取套接字选项
# 参数 level: 定义的选项类型,常用选项(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET)
# 参数 optname: 根据level选项确定的子选项
# 返回值: 返回根据子选项设置的值
s.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) # 1
# 面向锁的套接字方法
s.setblocking() # 设置套接字阻塞与非阻塞模式,参数默认为True表示阻塞
s.settimeout() # 设置阻塞套接字操作的超时时间
s.gettimeout() # 获取阻塞套接字操作的超时时间
# 面向文件的套接字函数
s.fileno() # 套接字的文件描述符
s.makefile() # 创建一个与该套接字相关的文件
2.TCP流式套接字
1.TCP服务端流程
1.创建套接字
sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STERAM, proto=0) # 创建套接字并返回套接字对象
参数:
socket_family: 选择地址族种类AF_INET(UNIX)
socket_type: 套接字类型 流式(SOCK_STREAM),数据报(SOCK_DGRAM)
proto: 子协议类型,tcp和udp都没有用到自协议因此默认为0
2.绑定IP和端口号
sockfd.bind(("", 7890)) # 绑定IP和端口号
参数: 类型为元组("127.0.0.1", 7890) # 第一项是字符串形式的IP,第二项是端口号
3.让套接字具有监听功能
sockfd.listen(n) # 使套接字变为监听套接字,同时创建监听队列
参数: n监听队列大小,一般设置为128
4.等待客户端连接
new_socket, client_addr = socket.accept() # 阻塞等待客户端连接
返回值: 类型为元祖(new_socket, client_addr)
第一项: 返回一个新的套接字用来和客户端通信
第二项: 返回连接的客户端的地址
5.消息的收发
接收: new_socket.recv(buffer) # 接收消息
参数: 一次接收消息的大小, 即一次收多少字节
返回值: 接收到的内容, 类型为字节
发送: new_socket.send(data) # 发送消息,当没有接收端的时候send操作会导致管道破裂(broken pipe)
参数: 发送的内容(bytes), 类型为字节
返回值: 发送了多少个字节
6.关闭套接字
new_socket.close() # 关闭为客户端服务的套接字
sockfd.close() # 关闭监听套接字
2.TCP客户端流程
1.创建流式套接字: sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STREAM, proto=0)
2.发起连接请求
sockfd.connect(("127.0.0.1", 7890)) # 发起连接请求
参数(元组): 第一项是服务器的IP,第二项是服务器的PORT
3.收发消息: sockfd.recv() sockfd.send()
4.关闭套接字: sockfd.close()
3.TCP服务端-通讯示例
import socketdef main():# 创建数据流套接字tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128)
# 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户端" %s "已经到来" % str(client_addr))
# 循环目的: 为同一个客服端服务多次
while True:
# 接收客户端发送过来的请求
recv_data = new_client_socket.recv(1024)
# recv解堵塞的两种方式: 1.客户端发送过来数据,2.客户端调用了close
if recv_data:
print("客户端" %s "发送过来的请求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
# 回送数据给客户端表示响应客户端的请求
send_data = "----ok----Request accepted"
new_client_socket.send(send_data.encode("utf-8"))
else:
break
# 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close()
if__name__ == "__main__":
main()
4.TCP客户端-通讯示例
import socketdef main():# 创建数据流套接字tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入要连接的服务器的ip:")
serve_port = int(input("请输入要连接的服务器的port:"))
server_addr = (server_ip, serve_port)
tcp_client_socket.connect(server_addr) # 连接失败会报错
# s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果连接失败不会报错,会返回错误的编码
# 发送数据
send_data = input("请输入要发生的数据:")
tcp_client_socket.send(send_data.encode("utf-8"))
# 接收服务器发送过来的数据
recv_data = tcp_client_socket.recv(1024)
print("接收到的数据为:%s" % recv_data.decode("utf-8"))
# 关闭套接字
tcp_client_socket.close()
if__name__ == "__main__":
main()
5.TCP服务端-文件下载示例
import socketdef send_file_client(nwe_client_socket, client_addr):# 1.接收客户端发送过来的需要下载的文件名file_name = nwe_client_socket.recv(1024).decode("utf-8")
print("客户端 %s 要下载的文件是:%s" % (str(client_addr), file_name))
# 2.打开文件读取数据
file_content = None
try:
f = open(file_name, "rb")
file_content = f.read()
f.close()
except Exception as ret:
print("没有要下载的文件%s" % file_name)
# 3.发送文件的数据给客户端
if file_content:
nwe_client_socket.send(file_content)
def main():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定本地信息
local_addr = ("", 7890)
tcp_server_socket.bind(local_addr)
# 开启监听
tcp_server_socket.listen(128)
while True:
# 等待用户连接
nwe_client_socket, client_addr = tcp_server_socket.accept()
# 调用发送文件函数,完成为客户端服务
send_file_client(nwe_client_socket, client_addr)
# 关闭套接字
nwe_client_socket.close()
# 关闭监听套接字
tcp_server_socket.close()
if__name__ == "__main__":
main()
6.TCP客户端-文件下载示例
import socketdef main():# 创建套接字tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
dest_ip = input("请输入下载服务器的ip:")
dest_port = int(input("请输入下载服务器的port:"))
dest_addr = (dest_ip, dest_port)
tcp_client_socket.connect(dest_addr)
# 获取要下载的文件名
download_file_name = input("请输入要下载的文件名:")
# 发送要下载的文件名
tcp_client_socket.send(download_file_name.encode("utf-8"))
# 接收文件数据
recv_data = tcp_client_socket.recv(1024) # 一次接收1k数据
# 打开文件写入数据
if recv_data:
with open("[new]" + download_file_name, "wb") as f:
f.write(recv_data)
# 关闭套接字
tcp_client_socket.close()
if__name__ == "__main__":
main()
7.TCP粘包现象
1.发送接收缓冲区
发送和接收消息均放到缓存区再进行处理
当recv接收消息一次接收不完的时候下次会继续接收,当recv阻塞时,如果客户端断开则recv立即返回空字符串
2.TCP粘包概述:
1.TCP中数据以数据流的方式发送接收,每次发送的数据间没有边界,在接收时可能造成数据的粘连即为粘包
2.合包机制造成数据混乱: nagle算法将多次连续发送且间隔较小的数据进行打包成一个数据传输
3.拆包机制造成数据混乱: 在发送端因为受到数据链路层网卡的MTU限制,会将大的超过MTU限制的数据进行拆分成多个小的数据包进行传输
当传输到目标主机的操作系统层时,会将多个小的数据包合并成原本的数据包
3.粘包如何处理方案:
1.每次发送消息和结束位置加标志,每次收到消息按结束标准切割
2.发送的消息添加结构描述,例如加一个包头,包头里记录 name:大小, password: 大小
3.当连续发送的时每次发送有一个短暂延迟sleep(0.1)
4.tcp粘包参考链接: https://www.cnblogs.com/LY-C/p/9120992.html
8.TCP服务端-粘包现象验证
import socketdef main():# 创建数据流套接字tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128)
# 等待客户端连接
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户%s端已连接" % str(client_addr))
# 循环目的: 为同一个客服端服务多次
while True:
# 接收客户端发送过来的请求
recv_data = new_client_socket.recv(1024)
# recv解堵塞的两种方式: 1.客户端发送过来数据,2.客户端调用了close
ifnot recv_data:
break
# 客户端是先遍历["Coco", "1355656****", "Coco@xxx.com", "xx省xx市xx区xxx"]列表,再分了4次发送用户信息的数据
# 但是此时服务端收到的数据却是连续的: Coco1355656****Coco@xxx.comxx省xx市xx区xxx
print("客户端" %s "发送过来的请求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
# 回送数据给客户端表示响应客户端的请求
send_data = "----ok----Request accepted"
new_client_socket.send(send_data.encode("utf-8"))
# 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close()
if__name__ == "__main__":
main()
9.TCP客户端-粘包现象验证
import socketimport timedef main():# 创建数据流套接字tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_addr = ("", 7890)
tcp_client_socket.connect(server_addr)
# 发送数据
user_info = ["Coco", "1355656****", "Coco@xxx.com", "xx省xx市xx区xxx"]
for i in user_info:
tcp_client_socket.send(str(i).encode("utf-8"))
# 发送时添加延时可以解决粘包现象
# time.sleep(0.1)
# 接收服务器发送过来的数据
recv_data = tcp_client_socket.recv(1024)
print("接收到的数据为:%s" % recv_data.decode("utf-8"))
# 关闭套接字
tcp_client_socket.close()
if__name__ == "__main__":
main()
10.TCP服务端-大文件上传
import socketimport jsonimport structdef main():# 创建数据流套接字tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128)
# 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户端" %s "已经到来" % str(client_addr))
# 接收客户端发送过来的请求
b_len_dic = new_client_socket.recv(4)
print(b_len_dic)
len_dic = struct.unpack("i", b_len_dic)[0] # unpack得到一个元祖,取下标0的位置获取到int类型的字典长度
recv_data = new_client_socket.recv(len_dic).decode("utf-8")
# new_client_socket.send(b"OK") # 向客户端发送文件准备就绪标识,同时也避免接收数据过快产生粘包
recv_dic = json.loads(recv_data)
if recv_dic["opt"] == "upload":
filename = "副本" + recv_dic["filename"]
with open(filename, "ab") as f:
while recv_dic["filesize"]:
content = new_client_socket.recv(1024)
f.write(content)
recv_dic["filesize"] -= len(content)
elif recv_dic["opt"] == "download":
pass
# 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close()
if__name__ == "__main__":
main()
11.TCP客户端-大文件上传
import socketimport osimport jsonimport structdef main():# 创建数据流套接字tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入要连接的服务器的ip:")
serve_port = int(input("请输入要连接的服务器的port:"))
server_addr = (server_ip, serve_port)
tcp_client_socket.connect(server_addr) # 连接失败会报错
# s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果连接失败不会报错,会返回错误的编码
menu = {"1": "upload", "2": "download"}
for i in menu:
print(i, menu[i])
num = input("请输入功能选项: ")
if num == "1":
send_dic = {"opt": menu.get(num), "filename": None, "filesize": None}
file_path = input("请输入一个绝对路径: ") # 要上传文件的绝对路径
filename = os.path.basename(file_path) # 获取要上传文件的文件名
filesize = os.path.getsize(file_path) # 获取文件大小
send_dic["filename"] = filename
send_dic["filesize"] = filesize
send_data = json.dumps(send_dic)
len_dic = len(send_data) # 获取字典长度,是一个int数据类型,可能是30,也可能是120
b_len_dic = struct.pack("i", len_dic) # 加字典长度打包成一个4位的bytes类型
# 将bytes类型的字典长度 + bytes类型的字典内容,一起发送给服务器
tcp_client_socket.send(b_len_dic + send_data.encode("utf-8"))
# tcp_client_socket.recv(1024) # 1.向服务器确认是否可以上传文件;2.避免与下列代码的send过快造成粘包
with open(file_path, "rb") as f:
while filesize:
content = f.read(1024)
tcp_client_socket.send(content)
filesize -= len(content)
# 发送数据
tcp_client_socket.send(send_data.encode("utf-8"))
elif num == "2":
pass
# 关闭套接字
tcp_client_socket.close()
if__name__ == "__main__":
main()
12.TCP服务端-身份加密验证
import socketimport osimport hmacdef main():# 创建数据流套接字tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128)
# 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户端" %s "已经到来" % str(client_addr))
sor = b"hmy"# 服务器加盐
r_str = os.urandom(16) # 随机一个16位长度的bytes类型数据
new_client_socket.send(r_str)
# md5加密
md5_obj = hmac.new(sor, r_str)
result = md5_obj.digest()
# 接收客户端发送过来的密文与服务器的密文对比
recv_msg = new_client_socket.recv(1024)
if recv_msg == result:
new_client_socket.send(b"success")
else:
new_client_socket.send(b"failed")
# 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close()
if__name__ == "__main__":
main()
13.TCP客户端-身份加密验证
import socketimport hmacdef main():# 创建数据流套接字tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入要连接的服务器的ip:")
serve_port = int(input("请输入要连接的服务器的port:"))
server_addr = (server_ip, serve_port)
tcp_client_socket.connect(server_addr) # 连接失败会报错
# s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果连接失败不会报错,会返回错误的编码
sor = b"hmy"# 客户端加盐
r_str = tcp_client_socket.recv(1024)
# md5加密
md5_obj = hmac.new(sor, r_str)
result = md5_obj.digest()
# 向服务器发送验证密文
tcp_client_socket.send(result)
# 接收验证结果
recv_msg = tcp_client_socket.recv(1024)
print(recv_msg)
# 关闭套接字
tcp_client_socket.close()
if__name__ == "__main__":
main()
14.TCP服务端-切换目录
import socketimport osdef send_data(new_client_socket, path):"""你给我一个目录,我把目录发给client"""lis_dir
= os.listdir(path)str_dir
= "--".join(lis_dir)new_client_socket.send(str_dir.encode(
"utf-8"))def main():# 创建数据流套接字tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128)
# 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户端" %s "已经到来" % str(client_addr))
abs_path = new_client_socket.recv(1024).decode("utf-8") # 获取用户输入的绝对路径
current_dir = abs_path + "/"# 以下再处理,都要根据当前路径去处理,无论是返回上一层,还是进入下一层
send_data(new_client_socket, current_dir) # 把用户输入的路径下的所有文件及文件夹返回给客户端
while True:
# 测试输入: /Users/tangxuecheng
cmd = new_client_socket.recv(1024).decode("utf-8")
if cmd == "..":
current_dir = current_dir.split("/")[:-2]
current_dir = "/".join(current_dir) + "/"
# if 如果当前是根目录:
# 就返回给客户端告诉说没有上一层了
send_data(new_client_socket, current_dir)
else:
filename = cmd.split("")[1] # 获取用户输入的文件名字
current_dir += filename + "/"# 将文件名字添加到当前路径下,组成一个完整的新路径
if os.path.isdir(current_dir): # 如果客户输入的文件名字是一个文件夹
send_data(new_client_socket, current_dir)
else: # 如果不是一个文件夹
new_client_socket.send(b"not a folder")
# 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close()
if__name__ == "__main__":
main()
15.TCP客户端-切换目录
import socketdef main():# 创建数据流套接字tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入要连接的服务器的ip:")
serve_port = int(input("请输入要连接的服务器的port:"))
server_addr = (server_ip, serve_port)
tcp_client_socket.connect(server_addr) # 连接失败会报错
# s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果连接失败不会报错,会返回错误的编码
# 测试输入: /Users/tangxuecheng
abs_path = input("请输入您的根目录:")
tcp_client_socket.send(abs_path.encode("utf-8"))
current_dir = tcp_client_socket.recv(1024).decode("utf-8")
print(current_dir.split("--"))
while True:
cmd = input("请输入>>>")
# cd + 文件夹 ..
if cmd == "..":
tcp_client_socket.send(cmd.encode("utf-8"))
current_dir = tcp_client_socket.recv(1024).decode("utf-8")
print(current_dir.split("--"))
if cmd == "cd":
filename = input("请输入一个文件夹名:")
tcp_client_socket.send((cmd + "" + filename).encode("utf-8"))
current_dir = tcp_client_socket.recv(1024).decode("utf-8")
print(current_dir.split("--"))
# 关闭套接字
tcp_client_socket.close()
if__name__ == "__main__":
main()
16.TCP应用-web静态服务器
# html文件夹网盘链接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg 密码: hp9o#
下载完命令行解压后放到程序的同级路径下,解压命令: tar -zxvf 06_html.tar.gz -C ./import socket
def client_handle(client_socket):
# 接收客户端发送过来的请求数据
request = client_socket.recv
以上是 02_套接字编程(socket抽象层) 的全部内容, 来源链接: utcz.com/z/530608.html