s9python网络编程

python之路——网络编程
计算机网络
ftp作业

网络编程

mac地址

arp协议:通过ip地址找mac地址

ip地址是什么——一台机器在网络上的位置

公网ip 局域网ip

端口号

网络相关的程序才需要开一个端口,为的是能找到某台计算机上唯一的一个程序
在同一台机器上,同一时间同一端口只能被一个程序占用
一般情况下使用8000之后的端口

TCP协议和UDP协议

TCP:可靠的(不会丢包),面向链接的,相对耗时长
三次握手,四次挥手
UDP:不可靠,无链接,效率高

常见问题

ip协议属于网络osi7层协议中的哪一层:网络层
tcp协议和udp协议属于传输层
arp协议属于数据链路层
应用层的协议:http(https)网页 ftp文件传输 smtp邮件相关的协议

socket

tcp协议(server端要先运行)

  1. 简单例子
#服务端代码
import socket
sk = socket.socket() #买手机 创建一个socket对象
sk.bind(('127.0.0.1',8080))  #给server端绑定一个ip和端口
sk.listen()      #监听链接

conn,addr = sk.accept()  #获取一个客户端的链接,已经完成三次握手,建立的链接就是conn
# 阻塞(等待一个客户端的链接)
msg = conn.recv(1024)      #阻塞,直到收到一个客户端发的消息
print(msg)
conn.send(b'hello')      #发消息

conn.close()        #关闭链接
sk.close()          #关闭socket对象,如果不关闭,还能继续接收
#现在有一个服务端和两个客户端
#server与client1建立长链接,此时client1与server可通信
#client2可链接,占线状态,发送的消息会被缓存,直到client1与server断开长链接
#客户端代码
import  socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
sk.send(b'hi')
ret = sk.recv(1024)
print(ret)
sk.close()
  1. 尬聊
#server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8888))
sk.listen()
conn,addr = sk.accept()

while True:
    msg = conn.recv(1024)
    msg = msg.decode('utf-8')
    if msg == 'bye':
        break
    print(msg)
    info = input('请输入一句话:')
    if info == 'bye':
        info = info.encode('utf-8')
        conn.send(info)
        break
    info = info.encode('utf-8')
    conn.send(info)

conn.close()
sk.close()
#client端
import socket
sk = socket.socket()
sk.connect((('127.0.0.1',8888)))
while True:
    msg = input('请输入一句话:')
    if msg == 'bye':
        msg = msg.encode('utf-8')
        sk.send(msg)
        break
    msg = msg.encode('utf-8')
    sk.send(msg)
    ret = sk.recv(1024)
    ret = ret.decode('utf-8')
    if ret == 'bye':
        break
    print(ret)

sk.close()

udp协议(都可以先运行)

  1. 简单例子
#server端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM) #DGRAM datagram
sk.bind((‘127.0.0.1’,8080))

msg, addr = sk.recvfrom(1024)
msg = msg.decode(‘utf-8’)
print(msg)
sk.sendto(b’bye’,addr)

sk.close()

#udp的server不需要进行监听也不需要建立链接
#在启动服务之后只能被动的等待客户端发送消息过来,这点与tcp不同(tcp两边都可以发送)
#客户端发送消息的同时还会自带地址信息
#消息回复的时候不仅需要发送消息,还需要把对方的地址写在后面
#client端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
ip_port = (‘127.0.0.1’, 8080)

sk.sendto(b’hello’,ip_port)
ret, addr = sk.recvfrom(1024)
ret = ret.decode(‘utf-8’)
print(ret)

sk.close() 
  1. 简单QQ
#server端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = (‘127.0.0.1’, 8080)
sk.bind(ip_port)
while True:
    msg, addr = sk.recvfrom(1024)
    msg = msg.decode(‘utf-8’)
    print(msg)
    info = input(‘>>>’).encode(‘utf-8’)
    sk.sendto(info, addr)

sk.close()
#client端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = (‘127.0.0.1’,8080)
while True:
    info = input(‘A请输入一句话:’)
    info = (‘\033[32m来自A的消息:%s\033[0m’ %info).encode(‘utf-8’)
    sk.sendto(info,ip_port)
    msg, addr = sk.recvfrom(1024)
    msg = msg.decode(‘utf-8’)
    print(msg)

sk.close()
#其他客户端复制粘贴即可,只需要改为某某请输入一句话
  1. 作业
    提供报时服务,接受一个时间格式,然后将时间转化为接收到的格式发给客户端。(用udp写)

例子(黏包现象)

  1. tcp
    所有的客户端执行server端下发的cmd指令,将结果反馈回来。
    需要用到以下工具
import subprocess
res = subprocess.Popen(‘dir’,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
print(‘strout:’,res.stdout.read().decode(‘gbk’))
print(‘stderr:’,res.stderr.read().decode(‘gbk’))
#因为windows默认为gbk编码
#stdout指的是正常输出
#stderr指的是错误输出
#基于tcp实现的远程执行命令
#在server下发命令
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn, addr =sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'q':
        conn.send(b'q')
        break
    cmd = cmd.encode('gbk')
    conn.send(cmd)
    res = conn.recv(1024).decode('gbk')
    print(res)
conn.close()
sk.close()
#在client端接收命令并执行
import socket
import subprocess
sk = socket.socket()
ip_port =('127.0.0.1',8080)
sk.connect(ip_port)
while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    res = subprocess.Popen(cmd,shell= True,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE)
    sk.send(res.stdout.read())
    sk.send(res.stderr.read())
sk.close()
#这里两个发对应一个收,会产生黏包现象
#不丢包
  1. udp
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = (‘127.0.0.1’,8090)
sk.bind(ip_port)
msg, addr = sk.recvfrom(1024)
while True:
    cmd = input(‘>>>’)
    if cmd == ‘q’:
        break
    sk.sendto(cmd.encode(‘utf-8’),addr)
    msg,addr = sk.recvfrom(1024)
    print(msg.decode(‘utf-8’)

sk.close()
import socket
import subprocess
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = (‘127.0.0.1’,8090)

sk.sendto(b’hi’,ip_port)
while True:
    cmd,addr = sk.recvfrom(1024)
    cmd = cmd.decode(‘utf-8’)
    ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    std_out = ‘stdout:’+(ret.stdout.read()).decode(‘gbk’)
    std_err = ‘stderr:’+(ret.stderr.read()).decode(‘gbk’)
    print(std_out)
    print(std_err)
    sk.sendto(std_out.encode(‘utf-8’),addr)
    sk.sendto(std_err.encode(‘utf-8’),addr)
sk.close()
#不会黏包,会丢包
  1. 作业
    网盘 文件的上传下载
    登陆:客户端登陆,将用户名和密码发给服务器,服务器确认信息后上传下载
    选择上传/下载
    上传:选择要上传的文件路径,在server创建一个同名的空文件夹
    下载:选择要下载的文件路径,在client端创建一个同名的空文件
    只需要实现上述的功能,真正的传输可以用几句话代替(打印几句话之类的)
  2. 黏包问题原因


    黏包生成原因.png

    图片与程序相反,图片是服务器给客户端发文件,程序是服务端接收文件

两个recv,第一个recv特别小

#server
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8090))
sk.listen()

conn, addr = sk.accept()
ret = conn.recv(2)
ret2 = conn.recv(10)
print(ret)
print(ret2)

conn.close()
sk.close()
#client
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
sk.send(b'hello,egg')
sk.close()

黏包问题本质的问题是不知道发送数据的长度

连续send两个小数据

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8090))
sk.listen()

conn, addr = sk.accept()
ret = conn.recv(12)
print(ret)
conn.close()
sk.close()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
sk.send(b'hello')
sk.send(b'egg')
sk.close()
#tcp协议内部的优化算法,连续的小数据包会被合并
  1. 黏包问题解决方法
    首先,发送数据长度
    按照数据的长度接收数据
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn, addr =sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'q':
        conn.send(b'q')
        break
    cmd = cmd.encode('gbk')
    conn.send(cmd) #引入struct模块这一步可删除
    num = conn.recv(1024).decode('utf-8')
    conn.send(b'ok')
    res = conn.recv(int(num)).decode('gbk')
    print(res)
conn.close()
sk.close()
import socket
import subprocess
sk = socket.socket()
ip_port =('127.0.0.1',8080)
sk.connect(ip_port)
while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    res = subprocess.Popen(cmd,shell= True,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE)
    std_out = res.stdout.read()
    std_err = res.stderr.read()
    sk.send(str(len(std_out)+len(std_err)).encode('utf-8'))
    #发送的长度是一个小数据,可能会发生粘包,所以要有一个接收与真正要发送的消息区别开
    sk.recv(1024)#
    sk.send(std_out)
    sk.send(std_err)
sk.close()

#好处:确定了我到底要接收多大的数据
    #要在文件中设置一个配置项:就是每一次recv的大小,一般情况下程序里不会超过4096个字节
    #当我们要发送大数据的时候,要明确的告诉接收方数据的大小,
    # 以便接收方能准确的接收所有数据
    #多用于文件传输
        #大文件的传输 一定是按照字节读 每一次读固定的字节
        #传输的过程中 一边读一遍传 接收端 一边收一边写
#不好的地方:多了一次交互
#send和sendto在超过一定范围的时候都会报错
#要设计好程序的内存管理
  1. struct模块,进一步解决黏包问题
    该模块可以把一个类型,如数字,转成固定长度的bytes
import struct
ret = struct.pack('i',80000) #'i'代表int,就是即将要把一个数字转化成固定长度(数字对应4)的bytes类型
print(ret)
print(len(ret))
num = struct.unpack('i',ret) #num是一个元组
print(num[0])
#若已知是固定长度,这样接收就有了确定长度,可在上一次的基础上继续改进程序
import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn, addr =sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'q':
        conn.send(b'q')
        break
    cmd = cmd.encode('gbk')
    conn.send(cmd)
    num = conn.recv(4)
    num = struct.unpack('i',num)[0]
    res = conn.recv(int(num)).decode('gbk')
    print(res)
conn.close()
sk.close()
import socket
import struct
import subprocess
sk = socket.socket()
ip_port =('127.0.0.1',8080)
sk.connect(ip_port)
while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    res = subprocess.Popen(cmd,shell= True,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE)
    std_out = res.stdout.read()
    std_err = res.stderr.read()
    len_num = len(std_out)+len(std_err)
    num = struct.pack('i',len_num) #已经是bytes形式,不用decode
    sk.send(num)
    sk.send(std_out)
    sk.send(std_err)
sk.close()
上面程序的思路.jpg
  1. struct定制报头


    定制报头.jpg

    文件传输代码

import json
import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8090))
sk.listen()
buffer =  1024
conn, addr =sk.accept()       
head_len = conn.recv(4)       #接收bytes长度为4(报头的长度)
head_len = struct.unpack('i',head_len)[0]     #转化为int类型
json_head = conn.recv(head_len).decode('utf-8')     #根据报头的长度接收报头的内容,将bytes转化为字符串
head = json.loads(json_head)    #得到报头的信息
filesize = head['filesize']
with open(head['filename'],'wb') as f:
    while filesize:
        if filesize >= buffer:
            content = conn.recv(buffer)    #接收文件并写入
            f.write(content)
            filesize -= buffer
        else:
            content = conn.recv(filesize)
            f.write(content)
            break
conn.close()
sk.close()
#发送端
import os
import json
import struct
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
buffer = 1024
#发送文件
head = {'filepath':r'D:\Python全栈9期(第一部分):基础+模块+面向对象+网络编程\day18'
        ,'filename':r'04 python fullstack s9day18 re模块.mp4'
        ,'filesize':None}
file_path = os.path.join(head['filepath'],head['filename'])
filesize = os.path.getsize(file_path)
head['filesize'] = filesize
json_head = json.dumps(head)  #字典转化为字符串
bytes_head = json_head.encode('utf-8') #字符串转bytes
#计算head的长度
head_len = len(bytes_head)  #报头的长度
pack_len = struct.pack('i',head_len)
sk.send(pack_len) #先发报头的长度
sk.send(bytes_head)   #再发bytes类型的报头
with open(file_path,'rb') as f:
    while filesize:
        if filesize >= buffer:
            content = f.read(buffer)  #每次读出来的内容
            sk.send(content)  #将文件拆分为小块后发送
            filesize -= buffer
        else:
            content = f.read(filesize)
            sk.send(content)
            break
代码逻辑.jpg

8.总结


总结.jpg

hmac的检验客户端合法性

检测一下客户端是否合法,不依靠登陆认证,也可以用hashilib来实现

import hmac
h = hmac.new() #通过密钥和你想进行加密的bytes创建一个对象
密文 = h.digest() #得到一个密文
hmac.compare_digest() #对比密文和另一个密文,返回一个True或者Flase
#服务端发送一个bytes给客户端,
#正常的客户端都有和服务端相同的key,
#所以正常的客户端得到的密文和服务端密文一致
#通过compare_digest进行对比,就可以判断客户端是否正常

import os
import hmac
import socket
secret_key = b'egg1'
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
def check_conn(conn):
    msg = os.urandom(32)
    conn.send(msg)
    h = hmac.new(secret_key,msg)
    digest = h.digest()
    client_digest = conn.recv(1024)
    return hmac.compare_digest(digest, client_digest)
conn,addr = sk.accept()
res = check_conn(conn)
if res:
    print('合法的客户端')
    conn.close()
else:
    print('不合法的客户端')
    conn.close()
sk.close()
import hmac
import socket
secret_key = b'egg'
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
msg = sk.recv(1024)
h = hmac.new(secret_key,msg)
digest = h.digest()
sk.send(digest)

sk.close()

socketserver

简单实现

#socketserver可以和多个客户端通信
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):     #self.request就相当于一个conn
        print(self.request.recv(1024).decode('utf-8'))
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer)
    #thread 线程
    server.serve_forever()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
sk.send('饿了么'.encode('utf-8'))
sk.recv(1024)
sk.close()

与多个客户端通信,这段代码存在问题
疑点:#sk.send(b'q')和 self.request.close()
当sk.send(b'q')在美团客户端内不被注释掉的时候,可以正常关闭,搞清楚self.request.close()的机制

#socketserver可以和多个客户端通信
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):     #self.request就相当于一个conn
        while True:
            msg = self.request.recv(1024).decode('utf-8')
            if msg == 'q':
                self.request.close()
                break
            print(msg)
            info = input('>>>')
            self.request.send(info.encode('utf-8'))
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer)
    #thread 线程
    server.serve_forever()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    msg = input('>>>')
    if msg == 'q':
        #sk.send(b'q')
        break
    sk.send(('饿了么:'+msg).encode('utf-8'))
    ret = sk.recv(1024).decode('utf-8')
    print(ret)
sk.close()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    msg = input('>>>')
    if msg == 'q':
        sk.send(b'q')
        break
    sk.send(('美团:'+msg).encode('utf-8'))
    ret = sk.recv(1024).decode('utf-8')
    print(ret)
sk.close()

看源码方法

  1. 多个类之间的继承关系要先整理
  2. 每个类的方法要大致列出来
  3. 要了解self是谁的对象
  4. 所有的方法要退回最子类的类中开始寻找,逐级向上
    socketserver源码视频讲解较难,有时间自己去看

ftp作业

大作业,暂时不做

你可能感兴趣的:(s9python网络编程)