网络编程
part1:
网络编程基础
-
不变的:mac地址能够唯一标识你这台机器的
-
变化的:ip地址能够更好的更方便的找到你的机器
-
局域网:
- 网段交换机不能理解ip地址,只能理解mac地址
-
局域网和局域网之间通信了:
- 网关路由器可以理解ip地址
-
ip地址:
- *ipv4:四位点分十进制
- *公网地址:需要我们自己申请购买的地址
- *内网地址:保留字段(不在这个范围内的都是公网ip)
- *特殊的ip地址
- 127.0.0.1本地回环地址(表示我自己)测试的时候用的(过网卡不过交换机)
-
*查看自己的ip地址ipconfig(window10)/ifconfig(mac/linux)
-
*子网掩码也是一个ip地址用来判断两台机器在不在一个局域网内
-
IPV6(一般小公司ipv4大公司ipv6)
-
ip/mac确认机器的
-
端口确认机器上的具体应用程序的
-
概念的整理
- 局域网的概念
- 交换机
- 在同一个局域网内的机器由交换机负责通信
- 交换机只认识mac地址
- 可以完成广播 组播 单播
- 单播--mac地址(在网卡上)
- 交换机
- 局域网之间通信
- 路由器
- 提供网关ip,同一个局域网的所有机器共享一个网关
- 我们不能访问除了本局域网之外的其他内网的IP地址
- 子网掩码
- 用来判断两台机器是不是在一个网段内
- 路由器
- ip地址:ipv4协议ipv6协议
- mac地址:arp协议(通过ip找mac)
- 局域网的概念
-
端口port:用来确认一台机器上的具体应用
osi五(七)层协议
osi五层协议
- 应用层 python代码
- 传输层 port tcp udp 四层路由器 四层交换机
- 网络层 ipv4 ipv6 路由器三层交换机
- 数据链路层 mac arp协议 网卡 二层交换机(单播,广播,组播arp协议用到前两种)
- 物理层
tcp和udp
- tcp(语音聊天/视频聊天)-线下缓存高强电影qq远程控制发邮件
-
需要先建立连接然后才能通信的
-
占用连接可靠(消息不会丢失)实时性高慢
-
建立连接-三次握手(握手有客户段发起请求)
-
断开连接-四次挥手
-
什么是三次握手?什么是四次挥手?为什么握手是三次挥手是四次?这个过程都传递了哪些信号
-
因为可能我有些话可能没跟他说完/他有些话可能没跟我说完
-
tcp协议有
# 三次握手
# 客户端向服务器端发送syn请求,
# 服务端向客户端回复ack并发送syn请求,
# 客户端接收到请求之后再回复ack表示建立连接
# 由客户端的connect + 服务端的accept
# 四次挥手
# 客户端向服务端发送fin请求,
# 服务端回复ack确认
# 服务端向客户端发送fin请求,
# 客户端回复ack确认
# 有客户端的close和服务端的close
- udp(发消息)-在线播放视频qq发消息微信消息
- 不需要建立连接就可以通信的
- 不占用连接不可靠(消息因为网络不稳定丢失)快
最简单的网络通信(基于tcp协议)
- 客户端(client):
import socket
sk=socket.socket()
sk.connect(('127.0.0.1',9001))
msg=sk.recv(1024)#最多接受1024个
print(msg)
sk.send(b'bebebe')
sk.close()
- 服务端(serve):
import socket
sk=socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
conn,addr=sk.accept()
conn.send(b'hell')
msg=conn.recv(1024)#最多接受1024个字节
print(msg)
conn.close() #断开连接
sk.close() #关闭整个serve服务
prat2:
tcp协议
- tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
- 服务器端(server):(即可服务器先发送也可客户端先发送)
import socket
sk=socket.socket()
sk.bind(('127.0.0.1',9001)) #申请操作系统的资源,绑定一个ip 和端口(元组)
sk.listen() # 代表socket服务已经开启
print('sk:',sk)
conn,addr=sk.accept() #conn里存储的是一个客户端和我serve端的连接信息,等,到有客户端来访问和客户端建立联系
print('coon:',conn)
conn.send(b'hello') # send直接通过连接发送消息,不需要写地址
msg=conn.recv(1024)
print(msg)
conn.close() #四次挥手,断开连接
sk.close() #关闭服务,归还申请的操作系统资源
- 客户端(client):
import socket
sk=socket.socket()
sk.connect(('127.0.0.1',9001)) # 客户端/tcp协议的方法,和serve端建立连接
msg=sk.recv(1024) # 只接受消息
print(msg)
sk.send(b'bebebe')
sk.close()
利用tcp协议和多个客户端进行通信
- 服务器端(server):
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=conn.recv(1024).decode('utf-8')
if msg.upper()=='Q':break
print(msg)
conn.close()#挥手,断开连接
sk.close()#关闭服务,归还申请的操作系统资源
- 客户端(client):
import socket
sk=socket.socket()
sk.connect(('127.0.0.1',9001))
while True:#和服务器端使劲聊
msg=sk.recv(1024).decode('utf-8')
if msg.upper()=='Q':break#关闭和该服务器通信
print(msg)
send_msg=input('>>>')
sk.send(send_msg.encode('utf-8'))
if send_msg.upper()=='Q':break
sk.close()
struct模块
import struct
#2的23次方都可以转换成4个字节
num1=129469649
num2=123
num3=8
ret1=struct.pack('i',num1) # 转化为四字节
print(ret1)
ret2=struct.pack('i',num2)
print(ret2)
ret3=struct.pack('i',num3)
print(ret3)#返回的是4位字节
print(struct.unpack('i',ret1)) # 解开为原来长度
print(struct.unpack('i',ret2))
print(struct.unpack('i',ret3))
黏包现象只发生在tcp协议中:
- 从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
- 发生在发送端:发送数据小,间隔短。由于优化机制就合并在一起发送了
- 发生在接受端:接受不及时,所以数据就在接受端的缓存端粘在一起了
- 粘包现象发生的本质:tcp协议的传输时流式传输,数据之间没有边界
- 实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
解决tcp协议中的粘包现象
- 服务器端(server):
import struct
sk=socket.socket()
sk.bind(('127.0.0.1',9001))#申请操作系统的资源
sk.listen()
conn,addr=sk.accept() #conn里存储的是一个客户端和我serve端的连接信息
msg1=input('>>>').encode()
msg2=input('>>>').encode()
#num=str(len(msg1))#'6'
#ret=num.zfill(4)#'0006'
#conn.send(ret.encode('utf-8'))
blen=struct.pack('i',len(msg1))
print(blen)
conn.send(blen)
conn.send(msg1)
conn.send(msg2)
conn.close()#四次挥手,断开连接
sk.close()#关闭服务,归还申请的操作系统资源
- 客户端(client):
import time
import socket
import struct
sk=socket.socket()
sk.connect(('127.0.0.1',9001))
length=sk.recv(4) # 因为socket发送的是4个字节
length=struct.unpack('i',length)[0] #因为其返回的是个元组
msg1=sk.recv(length)
print(msg1.decode('utf-8'))
msg2=sk.recv(1024)
print(msg2.decode('utf-8'))
sk.close()
解决粘包的本质:设置边界(发送多少个接受多少个)
- 粘包现象:本来分开的数据粘合到一起去了
- 只出现在tcp协议中,因为tcp协议多条消息之间没有边界,并且还有一大推优化算法
- 发送端:两条消息都很短,发送的间隔时间也非常短
- 接收端:多条消息由于没有及时接受,而在接受方的缓存短,堆在一起导致的粘包
- tcp协议数据之间没有边界,可以传输大的数据
udp协议
- udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接(不会粘包)
- 服务器端(server):(不需要listen监听)
import socket
#type=socket.SOCK_DGRAM:udp协议 默认是tcp协议
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'))
mmsg=input('>>>')
sk.sendto(mmsg.encode('utf-8'),addr) # 需要写一个对方的地址
sk.close() # 关闭服务器套接字 #关闭服务,归还申请的操作系统资源
- 客户端(client):(由客户端发起对话)
import socket
sk=socket.socket(type=socket.SOCK_DGRAM)
serve=('127.0.0.1',9001)
while True:
mmmsg=input('>><')
if mmmsg.upper()=='Q':break
sk.sendto(mmmsg.encode('utf-8'),serve)
msg=sk.recv(1024).decode('utf-8')
if msg.upper()=='Q':break
print(msg)
part3:
验证客户端的合法性
- serve端:
import socket
import os
import hashlib
secret_key=b'hahaha'
sk=socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
conn,addr=sk.accept()
#创建一个随机的字符串
rand=os.urandom(32)
#发送随机字符串
conn.send(rand)
#根据发送的字符串+secretekey进行摘要
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()
#如果不一致,这关闭连接
- client端:
import socket
import hashlib
secret_key=b'hahaha'
sk=socket.socket()
sk.connect(('127.0.0.1',9001))
#接收客户端发送的随机字符串
rand=sk.recv(32)
#根据发送的字符串+secretkey进行摘要
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)
sk.close()
socketserver模块--针对并发编程
- socket底层模块
- socketserver基于socket完成的
- tcp协议的server端处理并发的客户端请求
- 例子:网盘:文件的上传和下载
- server端:
import socketserver
class Myserver(socketserver.BaseRequestHandler):
def handle(self):
conn=self.request
while True:
try:
content=conn.recv(1024).decode('utf-8')
print(content)
conn.send(content.upper().encode('utf-8'))
except ConnectionResetError:
break
server=socketserver.ThreadingTCPServer(('127.0.0.1',9001),Myserver)
server.serve_forever()
- client端:与原来的相同
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)