首页 文章详情

<em><!--red_beg-->Python<!--red_end--></em>之网络编程(2)

聪少 | 4 2023-08-23 00:42 0 0 0
UniSMS (合一短信)

. . . . . .

 


1. 网络开发架构

  • C/S架构:需要安装才能使用

    • client 客户端  我们用的 需要安装的

    • server 服务端

    
      # server 服务端
    
    

import socket


sk = socket.socket()    # 创建一个server端的对象
sk.bind(('127.0.0.1', 801))   # 给server端绑定一个地址 以元组的形式,('ip地址',端口)
sk.listen()  # 开始监听(可以接收)客户端给我的连接了

conn,addr = sk.accept()   # 建立连接 conn是连接
while 1:
   mes = input('>>>')
   conn.send(mes.encode('utf-8'))
   msg = conn.recv(1024).decode('utf-8')
   print(msg)

conn.close()   # 关闭连接

sk.close()
    
      # client 客户端
    
    

import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 801))

while 1:
   mse = input('>>>')
   sk.send(mse.encode('utf-8'))
   msg = sk.recv(1024).decode('utf-8')
   print(msg)


sk.close()
  • socket参数的详解

    socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
            
              
                
                  
                    创建socket对象的参数说明:
    family 地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。
    (AF_UNIX 域实际上是使用本地 socket 文件来通信)
    type 套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。
    SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。
    SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。
    proto 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。
    fileno 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。
    与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。
    这可能有助于使用socket.close()关闭一个独立的插座。
  • B/S架构 : 百度 博客园 谷歌 码云

    • browser 浏览器

    • server 服务端


  • b/s和c/s什么关系?

    • B/S架构也是C/S架构中的一种


    
      C
      /
      S架构的好处
    
    
   可以离线使用/功能更完善/安全性更高
B / S架构
   不用安装就可以使用
   统一PC端用户的入口

2. osi5层协议

    应用层     python   【包括:表示层  会话层】       # 5 层
    
传输层     port  udp  tcp   四层路由器 四层交换机  # 4
网络层     ip协议(ipv4 ipv6)     路由器 三层交换机        # 3
数据链路层  mac arp协议   网卡  二层交换机          # 2
物理层                                         # 1  

人们按照分工不同把互联网协议从逻辑上划分了层级:

e8f7db3ea9a4ddac4d2803550ec9a624.webp

socket层

a989e906f6b7a1a98eb4e316abd4eb79.webp

3. tcp和udp

  • tcp(语音聊天/视频聊天) - 线下缓存高清电影\qq远程控制\发邮件

    • 需要先建立连接 然后才能通信的

    • 占用连接\可靠(消息不会丢失)\实时性高\速度慢

    
      # 建立连接 - 三次握手
    
    
客户端向服务器端发送syn请求,
服务端向客户端回复ack并发送syn请求,
客户端接收到请求之后再回复ack表示建立连接
由客户端的connect + 服务端的accept

# 断开连接 - 四次挥手
客户端向服务端发送fin请求,
服务端回复ack确认
服务端向客户端发送fin请求,
客户端回复ack确认
由客户端的close和服务端的close


如果说这些你们不懂,没关系,直接上图。

579bef7e0ce90a4da044b20a5677b6b9.webp

  • 最简单的tcp网络通信

    import socket
    
sk = socket.socket()
sk.bind(('127.0.0.1',9001))   # 申请操作系统的资源
sk.listen()
print('sk : ',sk)
conn,addr = sk.accept() # conn里存储的是一个客户端和server端的连接信息
print('conn : ',conn)
conn.send(b'hello')
msg = conn.recv(1024)
print(msg)
conn.close()    # 挥手 断开连接

sk.close()      # 归还申请的操作系统的资源
    import socket
    
sk = socket.socket()
sk.connect(('127.0.0.1',9001))



msg = sk.recv(1024)
print(msg)
sk.send(b'byebye')

sk.close()

tcp协议

  1. 在连接内多和客户说几句-代码

  2. 加whlie True 为了和多个客户端进行握手

  3. sk代表申请的系统的资源

  4. conn里存储的是一个客户端和server端的连接信息

    
      tcp协议的多人多次通信
    
    
   # 和一个人通信说多句话
   # 和一个人聊完再和其他人聊
   # socket() tcp协议的server
   # bind 绑定一个ip和端口
   # listen 监听,代表socket服务的开启
   # accept 等,到有客户端来访问和客户端建立连接
   # send 直接通过连接发送消息,不需要写地址
   # recv 只接收消息
   # connect 客户端/tcp协议的方法,和server端建立连接
   # close 关闭服务/连接
    **server**(服务端)
    

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9001))   # 申请操作系统的资源
sk.listen()  # 监听 代表socket的开启

while True:  # 为了和多个客户端进行握手
   conn,addr = sk.accept() # 能够和多个客户端进行握手了
   print('conn : ',conn)
   while True:   # 能够接受多个客户的请求
       send_msg = input('>>>')
       conn.send(send_msg.encode('utf-8'))
       if send_msg.upper() == 'Q':   # 服务端(server)断开和客户端(client)的连接
           break
       msg = conn.recv(1024).decode('utf-8')
       if msg.upper() == 'Q': break  # 客户端断开和服务端的连接
       print(msg)
   conn.close()    # 挥手 断开连接

sk.close()      # 归还申请的操作系统的资源

# str -encode('utf-8')-> bytes
# str -encode('gbk')-> bytes
# '你' 字符
# utf-8 b'181921'     b'181921' -decode('utf-8')-> 你
# gbk   b'17210'       b'17210' -decode('gbk') -> 你

# 基于tcp协议的文件传输
    **client**(客户端)
    

import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))


while True:
   msg = sk.recv(1024)
   msg2 = msg.decode('utf-8')
   if msg2.upper() == 'Q':break
   print(msg,msg2)
   send_msg = input('>>>')
   sk.send(send_msg.encode('utf-8'))
   if send_msg.upper() == 'Q':
           break
sk.close()

tcp协议的粘包现象

什么是粘包?

两条或更多条分开发送的信息连在一起就是粘包现象

粘包现象 只出现在tcp协议中,因为tcp协议 多条消息之间没有边界,并且还有一大堆优化算法 发送端 : 两条消息都很短,发送的间隔时间也非常短

发生在发送端 : 发送间隔短,数据小,由于优化机制就合并在一起发送了


接收端 : 多条消息由于没有及时接收,而在接收方的缓存短堆在一起导致的粘包

发生在接收端 : 接收不及时,所以数据就在接收方的缓存端黏在一起了


粘包发生的本质 : tcp协议的传输是流式传输 数据与数据之间没有边界

解决粘包问题的本质 :设置边界,可以借助struct模块

    
      # 先发送四字节的数据长度       # 先接受4字节 知道数据的长度
    
    
# 再按照长度发送数据           # 再按照长度接收数据
    tcp协议的自定义协议解决粘包问题 主要掌握逻辑和代码
    
   # 1 recv(1024)不代表一定收到1024个字节,而是最多只能收这么多
   # 2 两条连续发送的数据一定要避免粘包问题
   # 3 先发送数据的长度 再发送数据
   #   发送的数据相关的内容组成json:先发json的长度,再发json,json中存了接下来要发送的数据长度,再发数据
    
      # 基于tcp上传(大)文件
    
    
# server端

import json
import struct
import socket
# 接收
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

conn,_ =sk.accept()
msg_len = conn.recv(4)
dic_len = struct.unpack('i',msg_len)[0]
msg = conn.recv(dic_len).decode('utf-8')
msg = json.loads(msg)

with open(msg['filename'],'wb') as f:
   while msg['filesize'] > 0:
       content = conn.recv(1024)
       msg['filesize'] -= len(content)
       f.write(content)
conn.close()
sk.close()
    
      # client端
    
    

import os
import json
import struct
import socket
# 发送
sk = socket.socket()
# sk.connect(('192.168.14.109',9012))
sk.connect(('127.0.0.1',9001))

# 文件名\文件大小
abs_path = r'D:\python22期\day28 课上视频\3.网络基础概念.mp4'
filename = os.path.basename(abs_path)
filesize = os.path.getsize(abs_path)
dic = {'filename':filename,'filesize':filesize}
str_dic = json.dumps(dic)
b_dic = str_dic.encode('utf-8')
mlen = struct.pack('i',len(b_dic))
sk.send(mlen)   # 4个字节 表示字典转成字节之后的长度
sk.send(b_dic)  # 具体的字典数据

with open(abs_path,mode = 'rb') as f:
   while filesize>0:
       content = f.read(1024)
       filesize -= len(content)
       sk.send(content)
sk.close()


struct模块 :该模块可以把一个类型,如数字,转成固定长度的bytes【socket 底层模块】

    >>> struct.pack('i',1111111111111)
    

struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围


   
   
import struct

num1 = 129469649
num2 = 123
num3 = 8

ret1 = struct.pack('i',num1)
print(len(ret1))
ret2 = struct.pack('i',num2)
print(len(ret2))
ret3 = struct.pack('i',num3)
print(len(ret3))

print(struct.unpack('i',ret1)[0])
print(struct.unpack('i', ret2))
print(struct.unpack('i', ret3))
借助sturct可以完成自定义协议
    import struct
    
import socket

sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

conn,addr = sk.accept()
msg1 = input('>>>').encode()
msg2 = input('>>>').encode()
# num = str(len(msg1)) # '10001'
# ret = num.zfill(4)   # '0006' # 不借助struct模块也可以实现自定义协议
# conn.send(ret.encode('utf-8'))
blen = struct.pack('i',len(msg1))
conn.send(blen)
conn.send(msg1)
conn.send(msg2)
conn.close()
sk.close()



import time
import struct
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',9001))
# length = int(sk.recv(4).decode('utf-8'))
length = sk.recv(4)
length = struct.unpack('i',length)[0]
msg1 = sk.recv(length)
msg2 = sk.recv(1024)
print(msg1.decode('utf-8'))
print(msg2.decode('utf-8'))

sk.close()
    
      # 每一句话什么意思?执行到哪儿程序会阻塞?为什么阻塞?什么时候结束阻塞?
    
    
   # input() # 等待,直到用户输入enter键
   # accept 阻塞,有客户端来和我建立完连接之后
   # recv   阻塞,直到收到对方发过来的消息之后
   # recvfrom 阻塞,直到收到对方发过来的消息之后
   # connect 阻塞,直到server端结束了对一个client的服务,开始和当前client建立连接的时候

验证客户合法性 【主要防止恶意攻击,提高安全性】

    
      # 什么场景?
    
    
# 是在公司内部 无用户的情况下(没人能看得见我们的client端代码的时候),有人的情况下 就直接做登录即可。

# 密文登录
   # server userinfo密文
   # client input明文密码
   # 至少要在server端进行一次摘要
    import os
    
import socket
import hashlib

secret_key = b'alex_sb'
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

conn,addr = sk.accept()
# 创建一个随机的字符串
rand = os.urandom(32)
# 发送随机字符串
conn.send(rand)

# 根据发送的字符串 + secrete key 进行摘要
sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()

# 等待接收客户端的摘要结果
res_client = conn.recv(1024).decode('utf-8')
# 做比对
if res_client == res:
   print('是合法的客户端')
   # 如果一致,就显示是合法的客户端
   # 并可以继续操作
   conn.send(b'hello')
else:
   conn.close()
   # 如果不一致,应立即关闭连接
    import socket
    
import hashlib

secret_key = b'alex_sb979'
sk = socket.socket()
sk.connect(('127.0.0.1',9001))

# 接收客户端发送的随机字符串
rand = sk.recv(32)
# 根据发送的字符串 + secret key 进行摘要
sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()
# 摘要结果发送回server端
sk.send(res.encode('utf-8'))
# 继续和server端进行通信
msg = sk.recv(1024)
print(msg)

socketserver模块 【socketserver 基于socket完成的,可以模仿并发可以多用户同时访问】

    import time
    
import socketserver

class Myserver(socketserver.BaseRequestHandler):
   def handle(self):
       conn = self.request
       while True:
           try:
               content = conn.recv(1024).decode('utf-8')
               conn.send(content.upper().encode('utf-8'))
               time.sleep(0.5)
           except ConnectionResetError:
               break
server = socketserver.ThreadingTCPServer(('127.0.0.1',9001),Myserver)
server.serve_forever()

# import socket
#
# sk = socket.socket()
# sk.bind(('127.0.0.1',9001))
# sk.listen()
# while True:
#     conn,_ = sk.accept()
#     while True:
#         try:
#             content = conn.recv(1024).decode('utf-8')
#             conn.send(content.upper().encode('utf-8'))
#             time.sleep(0.5)
#         except ConnectionResetError:
#             break


# class BaseRequestHandler:
#     def __init__(self):
#         self.handle()
#     def handle(self):
#         pass
#
# class Myserver(BaseRequestHandler):
#     def handle(self):
#         pass
# my = Myserver()

    import socket
    

sk = socket.socket()
sk.connect(('127.0.0.1',9001))

while True:
   sk.send(b'hello')
   content = sk.recv(1024).decode('utf-8')
   print(content)

udp协议

  • udp(发消息) - 在线播放视频\qq发消息\微信消息


    • 不需要建立连接 就可以通信的

    • 不占用连接\不可靠(消息因为网络不稳定丢失)\速度快

    • udp不会发生粘包

    
      udp协议的多人通信
    
    
   # socket(type=socket.SOCK_DGRAM)
   # sendto 需要写一个对方的地址
   # recvfrom 接收消息和地址
   # close 关闭服务/连接
    import socket
    

sk = socket.socket(type = socket.SOCK_DGRAM) # 套接字
sk.bind(('127.0.0.1',9001))
while True:
   msg,addr= sk.recvfrom(1024)
   print(msg.decode('utf-8'))
   msg = input('>>>')
   sk.sendto(msg.encode('utf-8'),addr)
   


import socket

sk = socket.socket(type=socket.SOCK_DGRAM)
server = ('127.0.0.1',9001)
while True:
   msg = input('>>>')
   if msg.upper() == 'Q':break
   sk.sendto(msg.encode('utf-8'),server)
   msg = sk.recv(1024).decode('utf-8')
   if msg.upper() == 'Q':break
   print(msg)

好了小伙伴们,这就差不多python中网络编程要掌握的知识,如果有兴趣的话,可以自己去看看,然后一起交流。


- END -


82b494a42a2a580af3badcc4108a66d5.webp


good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter