python 网络传输

网络开发架构


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

  client 客户端 我们用的 需要安装的
  server 服务端


B/S架构 : 百度 博客园 谷歌 码云

  browser 浏览器
  server 服务端

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

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

C/S架构的好处

  可以离线使用/功能更完善/安全性更高


B/S架构的好处

  不用安装就可以使用

  统一PC端用户的入口

 

osi五层协议

应用层       python
传输层       port udp tcp 四层路由 四层交换
网络层       ipv4 ipv6 路由器 三层交换机
数据链路层    mac arp协议 网卡 二层交换
物理层
osi五层协议
    应用层
    传输层
        tcp协议 : 效率低 面向连接\可靠\全双工的通信
            三次握手
                客户端向服务器端发送syn请求,
                服务端向客户端回复ack并发送syn请求,
                客户端接收到请求之后再回复ack表示建立连接
                由客户端的connect + 服务端的accept
            四次挥手
                客户端向服务端发送fin请求,
                服务端回复ack确认
                服务端向客户端发送fin请求,
                客户端回复ack确认
                有客户端的close和服务端的close
        udp协议 : 效率高 无连接的\不可靠
        四层交换机 四层路由器
    网络层
        ip协议(ipv4 ipv6)
        路由器\三层交换机
    数据链路层
        arp协议 地址解析协议 通过ip找到mac地址
        交换机\网卡 : 单播 广播 组播
    物理层

 

七层协议

应用层
表示层
会话层
传输层
网络层
数据链路层
物理层

 

tcp协议与 udp协议 区别

tcp协议 : 效率低 面向连接\可靠\全双工的通信

udp协议 : 效率高 无连接的\不可靠

 

三次握手

客户端向服务器端发送syn请求,
服务端向客户端回复ack并发送syn请求,
客户端接收到请求之后再回复ack表示建立

四次挥手

客户端向服务端发送fin请求,
服务端回复ack确认
服务端向客户端发送fin请求,
客户端回复ack确认

socket套接字

socket套接字充当的就是内置模块的角色

socket 套接字,它存在于传输层与应用层之间的抽象层

 

tcp协议

import socket
sk=socket.socket()          #创建一个server端口
sk.bind(("127.0.0.1",9001)) #申请操作系统的资源
sk.listen()                 #开始监听(可以接收)客户端给我的连接
conn,addr=sk.accept()       #创建连接 conn是连接信息 addr是端口号
conn.send(b"hello")         #发送内容
msg=conn.recv(1024)         #收到字节
print(msg)
conn.close()                #挥手 断开连接
sk.close ()                 #申请归还的操作系统资源

 

普通版

server

import socket
sk=socket.socket()          #创建一个server端口
sk.bind(("127.0.0.1",9001)) #申请操作系统的资源
sk.listen()                 #开始监听(可以接收)客户端给我的连接
conn,addr=sk.accept()       #创建连接 conn是连接信息 addr是端口号
conn.send(b"hello")         #发送内容
msg=conn.recv(1024)         #收到字节
print(msg)
conn.close()                #挥手 断开连接
sk.close ()                   #申请归还的操作系统资源
View Code

client

import socket
sk=socket.socket()
sk.connect(("127.0.0.1",9001))
msg=sk.recv(1024)
print(msg)
sk.send(b"beybey")
sk.close()
View Code

 

持续发送

server

import socket
sk=socket.socket()
sk.bind(("127.0.0.1",9001))# 申请操作系统资源
sk.listen()
while True:                #为了和多个客户端进行握手
    conn,addr=sk.accept() 
    while True:
        send_msg=input(">>>")
        conn.send(send_msg.encode("utf-8"))
        if send_msg.upper()=="Q":
            break
        msg=sk.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
View Code

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()
View Code

 

 

udp协议

注意:UDP协议不用sk.close()

server两发两收

import socket

sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9001))
while True:
    # print(sk.recv(1024))              # b'Your message'
    # print(sk.recvfrom(1024))          # (b'Your message', ('127.0.0.1', 60182)) 注意这个本身是个大元组
    recv_msg,addr = sk.recvfrom(1024)   # 如果这里用recv,能接收信息,但是无法获得对方IP地址和端口号。所以这里要用recvfrom同时获得消息和地址
    print(recv_msg.decode('utf-8'))
    send_msg = input('>>>')
    sk.sendto(send_msg.encode('utf-8'),addr)
improt 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)
View Code

client:三发三收,都先if

import socket

sk = socket.socket(type=socket.SOCK_DGRAM)
server = ('127.0.0.1',9001)
while True:
    send_msg = input('>>>')
    if send_msg.upper() == 'Q': break
    sk.sendto(send_msg.encode('utf-8'),server)
    recv_msg = sk.recv(1024).decode('utf-8')     #此时已经有对面的IP地址和端口了,无须再recvfrom
    if recv_msg.upper() == 'Q': break
    print(recv_msg)
View Code

 

上述程序中导致程序阻塞的语句

input():等待,直到用户输入enter键
sk.accept():阻塞,有客户端来和我建立完连接之后
sk.connect:阻塞,直到server端结束了对一个client的服务,开始和当前client建立连接的时候
recv():阻塞,直到收到对方发过来的消息之后
recvfrom():阻塞,直到收到对方发过来的消息之后

  

粘包

粘包:两条或更多条分开发送的数据连在一起的现象

   粘包只出现在tcp协议中,因为tcp协议,多条消息之间没有边界,并且还有一大堆优化算法。

粘包导致的原因:

  1. 发送端 : 两条消息都很短,发送的间隔时间也非常短,由于优化机制就合并在一起发送了

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

自定义协议

第一条数据精确地规定成4字节,由于第一条数据是第二条数据的长度,因此第二条和第三条数据也不粘了

server

import socket

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

conn,addr = sk.accept()
msg1 = input('>>>').encode('utf-8')        #注意要在len前先encode
msg2 = input('>>>').encode('utf-8')
num = str(len(msg1))
ret = num.zfill(4)
conn.send(ret.encode('utf-8'))
conn.send(msg1)
conn.send(msg2)
conn.close()
sk.close()
View Code

client

import socket

sk = socket.socket()
sk.connect(('127.0.0.1',9001))
length = int(sk.recv(4).decode('utf-8'))
msg1 = sk.recv(length)
msg2 = sk.recv(1024)
print(msg1.decode('utf-8'))
print(msg2.decode('utf-8'))

sk.close()
View Code

 

 

struct模块

精髓在于把第一条数据精确地规定成4字节,由于第一条数据是第二条数据的长度,因此第二条和第三条数据也不粘了。但是,当第二条数据长度很长,即第一条数据用4字节表示不下时,此方法失效,由此引出第二种方法——struct模块

 

struct.pack():把-2**23到2**23范围内的整数转化成4字节bytes

struct.unpack():把4字节bytes转化成一个元组,元组的第一项取int()就是想要的长度

 

例子

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))
print(struct.unpack('i', ret2))
print(struct.unpack('i', ret3))
View Code

 

server

import struct
import socket

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

conn,addr = sk.accept()
msg1 = input('>>>').encode('utf-8')        #注意要在len前先encode
msg2 = input('>>>').encode('utf-8')

#使用struct.pack()把len(msg1)转化成固定长度为4的bytes
blen = struct.pack('i',len(msg1))

conn.send(blen)
conn.send(msg1)
conn.send(msg2)
conn.close()
sk.close()

# 粘包现象
# 只出现在tcp协议中,因为tcp协议 多条消息之间没有边界,并且还有一大堆优化算法
# 发送端 : 两条消息都很短,发送的间隔时间也非常短
# 接收端 : 多条消息由于没有及时接收,而在接收方的缓存短堆在一起导致的粘包

# 解决粘包问题的本质 :设置边界
View Code

client

import struct
import socket

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

#使用struct.unpack()把接收到的4字节bytes转化成一个元组,元组的第一项就是即将接收的msg1的长度,进而保证了msg1和msg2不会粘在一起
length = struct.unpack('i',sk.recv(4))[0]

msg1 = sk.recv(length)
msg2 = sk.recv(1024)
print(msg1.decode('utf-8'))
print(msg2.decode('utf-8'))

sk.close()
View Code

 

基于tcp协议的文件传输

server

port 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()
View Code

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()
View Code

 

验证客户端的合法性(hashlib)

第一种方法

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()
    # 如果不一致,应立即关闭连
View Code

client

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

第二种方法

server

import socket
import os
import hmac
sk=socket.socket()
sk.bind(("127.0.0.1",218))
sk.listen(5)
conn,addr = sk.accept()
rand = os.urandom(32)
conn.send(rand)
h=hmac.new(b"alex_sb",rand)
ret=h.digest()
a=conn.recv(1024)
if a==ret:
    print('是合法的客户端')
    # 如果一致,就显示是合法的客户端
    # 并可以继续操作
    conn.send(b'hello')
else:
    conn.close()
View Code

client

import socket
import os
import hmac
sk=socket.socket()
sk.connect(("127.0.0.1",218))
a=sk.recv(1024)
h=hmac.new(b"alex_sb",a)
ret=h.digest()
sk.send(ret)
msg=sk.recv(1024)
print(msg)
View Code

 

socketserver(tcp多用户)

tcp支持多客户端同时下载

server

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()
View Code

clinet

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

 

 

文件的上传下载(重点)

server

import os
import sys
import json
import struct
import socket
import hashlib

# 登录成功 100 101
# 注册成功 102 103
# 上传成功 104 105
# 下载成功 106 107

def my_send(conn,dic):
    str_dic = json.dumps(dic)
    b_dic = str_dic.encode('utf-8')
    mlen = struct.pack('i', len(b_dic))
    conn.send(mlen)  # 4个字节 表示字典转成字节之后的长度
    conn.send(b_dic)  # 具体的字典数据

def download():
    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}
    my_send(conn,dic)

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

def my_recv(conn):
    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)
    return msg

def get_md5(username,password):
    md5 = hashlib.md5(username.encode('utf-8'))
    md5.update(password.encode('utf-8'))
    return md5.hexdigest()

def login(conn):
    flag = True
    while flag:
        # 登录
        msg = my_recv(conn)
        with open('userinfo') as f:
            for line in f:
                name, pwd = line.strip().split('|')
                if name == msg['username'] and pwd == get_md5(name, msg['password']):
                    res, flag = True, False
                    break
            else:
                res = False
            dic = {'operate': 'login', 'result': res}
            my_send(conn, dic)

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

conn,_ =sk.accept()
# 有了一个客户端来连接你
login(conn)
# 接收消息,根据用户的选择进行上传/下载操作
opt_dic = my_recv(conn)
if hasattr(sys.modules[__name__],opt_dic['operate']):
    getattr(sys.modules[__name__],opt_dic['operate'])()




conn.close()
sk.close()
View Code

client

import os
import sys
import json
import struct
import socket
def download(sk):  # 下载
    opt_dic = {'operate':'download'}
    my_send(sk,opt_dic)
    msg = my_recv(sk)
    with open(msg['filename'], 'wb') as f:
        while msg['filesize'] > 0:
            content = sk.recv(1024)
            msg['filesize'] -= len(content)
            f.write(content)

def login(sk):
    while True:
        usr = input('用户名:').strip()
        pwd = input('密 码 :').strip()
        dic = {'username': usr, 'password': pwd}
        my_send(sk, dic)
        ret = my_recv(sk)
        if ret['operate'] == 'login' and ret['result']:
            print('登录成功')
            break
        else:
            print('登录失败')
def my_recv(sk):     # 接收
    msg_len = sk.recv(4)
    dic_len = struct.unpack('i', msg_len)[0]
    msg = sk.recv(dic_len).decode('utf-8')
    msg = json.loads(msg)
    return msg


def my_send(sk,dic):   # 发送
    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)  # 具体的字典数据



sk = socket.socket()
# sk.connect(('192.168.14.109',9012))
sk.connect(('127.0.0.1',9001))

login(sk)   # 登录
# 上传\下载
opt_lst = ['upload','download']
for index,opt in enumerate(opt_lst,1):
    print(index,opt)
num = int(input('请选择您要操作的序号 :'))
getattr(sys.modules[__name__],opt_lst[num-1])(sk)

sk.close()
View Code

 

基础版

server

import json
import struct
import socket
# 接收
sk = socket.socket()
sk.bind(('127.0.0.1',931))
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()
View Code

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()
View Code

 

posted @ 2019-08-13 16:56  驰念  阅读(545)  评论(0编辑  收藏  举报