02_套接字编程(socket抽象层)

python

1.套接字概述

    1.套接概述: 套接是进行网络通信的一种手段(socket)

    2.套接字分类:
        流式套接字(SOCK_STREAM): 传输层基于tcp协议进行通信


        数据报套接字(SOCK_DEGAM): 传输层基于udp协议进行通信


        原始套接字(SOCK_RAW): 访问底层协议的套接字

    3.TCP与UDP通讯模型流程图: https://www.processon.com/view/link/5ef43bfd1e0853263742690b

    4.套接字属性和方法

import socket

s = 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 socket

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))

# 循环目的: 为同一个客服端服务多次

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 socket

def 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 socket

def 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 socket

def 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 socket

def 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 socket

import time

def 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 socket

import json

import struct

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))

# 接收客户端发送过来的请求

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 socket

import os

import json

import struct

def 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 socket

import os

import hmac

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))

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 socket

import hmac

def 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 socket

import os

def 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 socket

def 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

回到顶部