python网络编程

网络开发两大框架

c/s 架构 client server 

b/s 架构 BRower server

c/s架构

 

 B/S 架构

基础概念 

一台主机两个重要标识

(1)mac地址:标记一台机器的物理地址  (不可变)

(2)ip地址:标记一台机器的逻辑地址 (可变)

网段

  网段的作用,主要用来划分同一区域里的某些机器是否能够互相通信。在一个网段里可以不同过因特网,直接对话
判别的依据:如果IP地址和子网掩码相与得到的值相同就是同一网段

内网

以下地址为预留地址,永远不会被当做公网ip来分配

  192.168.0.0 - 192.168.255.255
  172.16.0.0 - 172.31.255.255
  10.0.0.0 - 10.255.255.255

外网

在任何地方都可以访问的就是外网(排除防火墙的因素)

子网掩码

区分网段和主机

  255.255.255.0 / 255.255.0.0 / 255.0.0.0
  ip1:192.168.10.12 ip2:192.168.1.16

端口

  "端口"是英文port的意译,是具体某个程序与外界通讯的出口。 取值范围:0~65535

局域网

  在同一区域内由多台计算机互联形成通讯。【具有可重复的内网ip】

广域网

  在不同区域内有多台计算机互联形成通讯。【具有唯一的公网ip】

交换机

  对同一网段的不同机器之间进行数据转发的设备     [每一台机器和交换机相连,形成通信]

路由器

  对不同网段的不同机器之间进行数据转发的设备     [每一个局域网和路由器相连,形成通信]

arp协议

  每台主机都有arp缓存表 ,主要作用通过ip找mac的一个协议规则

  【实现方式:通过交换机一次广播,一次单播找到的】

osi网络七层模型

  人们按照分工不同把互联网协议从逻辑上划分了层级: osi4层,osi5层,osi7层 三类模型

 

局域网模型

广域网模型

tcp/udp协议

TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议(比如:打电话)
  优点:可靠,稳定,传输完整稳定,不限制数据大小
  缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认
  应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景

UDP(User Datagram Protocol)一种无连接的,不可靠的传输层通信协议(比如:发短信)
  优点:速度快,可以多人同时聊天,耗费资源少,不需要建立连接
  缺点:不稳定,不能保证每次数据都能接收到
  应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景

客户端和服务端在建立连接时: 三次握手
客户端和服务端在断开连接时: 四次挥手
SYN 创建连接
ACK 确认响应
FIN 断开连接

三次握手

 四次挥手

整体缩略图

socket

socket的意义:通络通信过程中,信息拼接的工具(中文:套接字)
  开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉)
  在bind方法之前加上这句话,可以让一个端口重复使用
  sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

tcp服务端客户端实现

server端步骤

(1)创建一个socket对象,默认安装tcp协议

(2)绑定ip和端口(注册主机)

(3)开启监听

(4)三次握手

(5)收发数据逻辑

(6)四次挥手

(7)退还端口

# 服务端
import socket
# 1.创建一个socket对象 , 默认按照tcp协议创建
sk = socket.socket()
# 2.绑定ip 和 端口 (在网络上注册该主机,让其他主机找到你)
'''bind( 元组 ) 默认本地ip 127.0.0.1  (ip,端口) '''
sk.bind( ("127.0.0.1",9000) )
# 3.开启监听
sk.listen()

# listen  accept recv 都是阻塞;如果不满足条件,程序不会往下执行;
# 5.收发数据 quit

while True:
    # 4.三次握手
    '''conn 是三次握手后的连接对象,addr是对方的ip和端口号'''
    conn,addr = sk.accept()
    while True:
        # 收消息
        msg = conn.recv(1024)
        print(msg.decode())
        # 发消息
        message = input("请输入>>>")
        conn.send(message.encode("utf-8"))
        if message == "q":
            break
            
# 6.四次挥手
conn.close()

# 7.退还端口
sk.close()

client端步骤

(1)创建tcp对象

(2)与服务器主机建立连接

(3)收发逻辑

(4)关闭连接

# 客户端
import socket
# 创建tcp对象
sk = socket.socket()
# 直接与服务器主机建立连接
'''connect( 元组 )  (ip,端口号)'''
sk.connect( ("127.0.0.1" , 9000) )

while True:
    # 发消息
    message = input(">>>:")
    sk.send(message.encode("utf-8"))
    # 收消息
    res = sk.recv(1024)
    if res == b'q':
        break
    print(res.decode("utf-8"))

# 关闭连接
sk.close()

 粘包

tcp协议在发送数据时会出现粘包现象

(1)数据粘包是因为在客户端/服务端都会有一个数据缓冲区,用来临时保存数据,保证完整的接受数据,缓冲区设置的都比较大

(2)在收发数据频繁时,tcp传输消息无边界,不清楚截取多少长度,导致客户端/服务端可能把多条数据当成一条,造成粘包

粘包出现的情况

(1)在发送端,由于两个数据短,发送的时间间隔短,在发送端形成粘包

(2)在接收端,两个数据几乎同时被发送到对方的缓存中,在接收端形成了粘包

总结:发送端,包之间时间间隔短或者接收端接受不及时,就会粘包。核心是因为tcp对数据无边界截取,不会按照发送的顺序判断

粘包示例:

# 服务端
import socket 
sk = socket.socket()
sk.bind( ("127.0.0.1",9000) )
sk.listen()
conn,addr = sk.accept()
conn.send("hello,".encode("utf-8"))
conn.send("world".encode("utf-8"))
conn.close()
sk.close()

#客户端
import socket
import time
sk = socket.socket()
sk.connect( ("127.0.0.1",9000) )

time.sleep(0.1)
print(sk.recv(10))
print(sk.recv(10))
sk.close()

# 执行结果
b'hello,worl'
b'd'

解决粘包

# 服务端
import socket
import struct 
sk = socket.socket()
sk.bind( ("127.0.0.1" ,9000) )
sk.listen()

conn,addr = sk.accept()

# 收发数据逻辑
inp = input("请输入msg>>>:")
msg = inp.encode("utf-8")

# 把这个长度的数字转化成二进制字节流,然后发送给对面,按照这么大的长度进行截取
res = struct.pack("i",len(msg))
conn.send(res)
conn.send(msg)
conn.send("world".encode("utf-8"))

# 四次挥手
conn.close()
# 退还端口
sk.close()

# 客户端
import socket
import struct
import time
sk = socket.socket()
sk.connect( ("127.0.0.1" ,9000) )

time.sleep(0.1)
# 先接收要截取的长度是多少
n = sk.recv(4)
n = struct.unpack("i",n)[0]

# print(n)
# 再去接收真实的数据,防止黏包
print(sk.recv(n))
print(sk.recv(10))

sk.close()

struct模块

(1)struct.pack 把任意长度的数字转化成具有固定长度的4个字节的值,组成字节流

(2)struct.unpack 把 4个字节的值恢复成原有的数据,返回的是元组

>>> import struct
>>> res = struct.pack('i',10000)
>>> res
b"\x10'\x00\x00"
>>> print(len(res))
4
>>> res = struct.unpack('i',res)[0]
>>> res
10000
>>> res = struct.pack('i',10000)
>>> res = struct.unpack('i',res)
>>> res
(10000,)

粘包对比

tcp协议

优点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包 

缺点:不限制数据包的大小,稳定传输不丢包

udp协议

优点:接收时候数据之间有边界,传输速度快,不黏包

缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

tcp和udp协议

(1)对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送

(2)tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止

(3)udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包

粘包场景

  解决:应用场景在实时通讯时,需要阅读此次发的消息是什么

  不解决:下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓

hashlib模块

  hashlib模块是一堆加密算法的集合体,哈希算法的加密方式不止一种

md5在线解密

  https://www.cmd5.com/

应用场景-校验

(1)用户密码 --> 加密,解密

(2)相关校验 --> 加密,解密

哈希算法

  哈希算法也叫摘要算法,相同的数据始终得到相同的输出,不同的数据得到不同的输出

  (1)哈希将不可变的任意长度的数据,变成具有固定长度的唯一值

  (2)字典的键值对映射关系是通过哈希计算的,哈希存储的数据是散列(无序)

示例

基本用法

import hashlib
import random
hs = hashlib.md5() # 创建一个md5算法对象
hs.update("abc123".encode("utf-8")) # 把要加密的字符串update更新到hs对象中处理
res = hs.hexdigest() # 返回32位16进制的字符串
print(res,len(res))

# result
"""
e99a18c428cb38d5f260853678922e03 32
"""

加盐

  key 只有自己知道的关键字,目的是增加密码的复杂度

import hashlib
import random
hs = hashlib.md5("x".encode("utf-8"))
hs.update("abc123".encode("utf-8"))
res = hs.hexdigest()
print(res)

# result 
"""
a191e01243ce9c58f4ac3ebe8622505d
"""

动态加盐

import hashlib
import random
res = str(random.randrange(1000,10000))
hs = hashlib.md5(res.encode("utf-8"))
hs.update("abc123".encode("utf-8"))
res = hs.hexdigest()
print(res)

# result
"""
51114e76682b6b696a565a248a519b5b
"""

sha算法系列

import hashlib
import random
hs = hashlib.sha1()
hs.update("abc123".encode())
res = hs.hexdigest()
print(res,len(res))

# result
"""
6367c48dd193d56ea7b0baad25b19455e529f5ee 40
"""

hmac

import hashlib
import random
import hmac
key = b"x"
msg = b"abc123"
hm = hmac.new(key,msg)
res = hm.hexdigest()
print(res,len(res))

# 随机返回长度为32位的二进制字节流
import os 
key = os.urandom(32)
print(key,len(key))
hm = hmac.new(key,msg)
res = hm.hexdigest()
print(res)

# result 
"""
b1a7fbbe3502e9c1047989731380d228 32
b'abhw\x1e\xb4\xeeN\xb1\xde\x10\xa1\xbbG.\x9e\xdbx\xc1\xb9C\x8b\xa7\xba\x8d?\xca\xb4\xa6=,\x8f' 32
153fb21b4d8439e373bb886dd76d6e5e
"""

socketserver模块

  网络协议最底层就是socket,基于原有socket模块又封装了一层,就是socketserver

  socketserver为了实现tcp协议,server端的并发

# server
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print(self.request.recv(1024).decode('utf-8'))  # 相当于conn
        while True:
            msg = self.request.recv(1024).decode('utf-8')
            if msg == 'q':
                break
            print(msg)
            info = input('>>>')
            self.request.send(info.encode('utf-8'))

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer)
    server.serve_forever()

#clinet 
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)如果当前模块是作为主文件调用,__name__返回的是__main__ --> 主进程

(2)如果当前模块是通过import导入,__name__返回当前模块名 --> 子进程

(3)if __name__ == "__main__" 主要用来做测试,节省空间和效率

print(__name__)
attr = "属性"
def method():
    print("方法")

if __name__ == "__main__":
    method()

# result
"""
__main__
方法
"""

  用import引入上面内容后执行为上面文件名称即模块名

 

posted @ 2019-08-17 15:34  wangzihong  阅读(331)  评论(0编辑  收藏  举报