Python之TCP详解和 OSI七层模型
1.OSI七层模型和TCP/IP四层
基本模型:
OSI七层模型
先有模型,后有协议,先有标准,后有实践,TCP/IP反之
ARP协议,获取主机的mac地址,全世界唯一
应用程序:QQ、微信,我们开发都是在传输层
七层模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
TCP/IP四层
四层:应用层、传输层、网络层、数据链路层
TCP可靠传输:三次握手,四次挥手
建立连接,三次握手
数据传输
断开连接,四次挥手
2.套接字
套接字的基本概念
三种套接字(监听套接字、客户端套接字、对等连接套接字)
创建套接字
服务器端:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/10/13 7:38
# @Author : DoubleChina
# @Site :
# @File : SocketTest.py
# @Software: PyCharm
import socket
# 创建套接字
sock = socket.socket()
# 把监听套接字,绑定到本地的9999端口上面
sock.bind(('', 9999))
# 开始监听,等待套接字连接,
# 默认写5就好,在没有accept之前,能挂起最大连接数
# python3.6版本后才起效果
sock.listen(5)
# socket.socket fd=3, 文件描述符(唯一标识了一个socket)
# family=AddressFamily.AF_INET,(#AF_INET表示IPV4)
# type=SocketKind.SOCK_STREAM,(#SOCK_STREAM表示TCP)
# proto=0, #通常都是0
# laddr=('0.0.0.0', 0)
print(sock)
while True:
# 线程阻塞
print('连接已经建立,等待接受数据')
conn, addr = sock.accept()
while True:
# recv也会进行阻塞
data = conn.recv(1024)
if data:
print('接受客户端的消息:', data)
conn.send(data)
else:
break
# sock.close()
客户端
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/10/13 7:38
# @Author : DoubleChina
# @Site :
# @File : SocketTest.py
# @Software: PyCharm
import socket
# 创建套接字
s = socket.socket()
# 连接套接字,ip和端口必须是服务器上的
s.connect(('127.0.0.1', 9999))
while True:
data = input("输入发送消息:")
if 'q' == data:
s.close()
break
s.send(data.encode())
print('接受服务器返回的消息:', s.recv(1024))
# s.close()
普通套接字实现的服务端的缺陷
一次只能服务一个客户端accept
阻塞
在没有新的套接字来之前,不能处理已经建立连接的套接字的请求。recv
阻塞
在没有接受到客户端请求数据之前,不能与其他客户端建立连接!
普通服务器的IO模型
###非阻塞套接字
非阻塞套接字服务器端
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/10/13 7:38
# @Author : DoubleChina
# @Site :
# @File : SocketTest.py
# @Software: PyCharm
import socket
# 创建套接字
sock = socket.socket()
# 把套接字设置未非阻塞,这必须要在其他操作之前
sock.setblocking(False)
# 把监听套接字,绑定到本地的9999端口上面
sock.bind(('', 9999))
# 开始监听,等待套接字连接,
# 默认写5就好,在没有accept之前,能挂起最大连接数
# python3.6版本后才起效果
sock.listen(5)
# socket.socket fd=3, 文件描述符(唯一标识了一个socket)
# family=AddressFamily.AF_INET,(#AF_INET表示IPV4)
# type=SocketKind.SOCK_STREAM,(#SOCK_STREAM表示TCP)
# proto=0, #通常都是0
# laddr=('0.0.0.0', 0)
print(sock)
print('开始监听')
client_list = []
while True: # 通过while循环不断的检测,有没有资源到达
try:
# print('连接已经建立,等待接受数据')
# 线程阻塞
conn, addr = sock.accept()
except BlockingIOError as e:
pass
else:
#1.sock.setblocking(False)无法生效,非阻塞不起效果?
#这里也要设置False
conn.setblocking(False)
print('客户端{},连接成功'.format(addr))
# 客户端连接添加进来
client_list.append(conn)
# 遍历所有的socket进行遍历接受数据
for client_socket in client_list:
# data = client_socket.recv(1024)
try:
data = client_socket.recv(1024)
except BlockingIOError as e:
pass
else:
if len(data) > 0:
print(data)
else:
client_socket.close()
# sock.close()
非阻塞套接字客户端
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/10/13 7:38
# @Author : DoubleChina
# @Site :
# @File : SocketTest.py
# @Software: PyCharm
import socket
# 创建套接字
s = socket.socket()
# 连接套接字,ip和端口必须是服务器上的
s.connect(('127.0.0.1', 9999))
while True:
data = input("输入发送消息:")
if 'q' == data:
s.close()
break
s.send(data.encode())
# print('接受服务器返回的消息:', s.recv(1024))
# s.close()
socket connect
操作一定会引发BlockingIOError
异常,需要进行try except
如果连接没有建立,那么send操作引发OSError异常
非阻塞IO模型
非阻塞套接字服务器优化之IO多路复用epoll
IO多路复用epoll
,其实就是把socket交给操作系统去监控
IO多路复用epoll
实现逻辑就是事件循环加回调epoll
是惰性事件回调,惰性事件回调是由用户进程自己调用的,操作系统只起到通知的作用。epoll
是目前Linux上效率最高的IO多路复用 技术 !
epoll
注册事件使用
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/10/13 14:41
# @Author : DoubleChina
# @Site :
# @File : EpollServer.py
# @Software: PyCharm
import socket
import selectors
import time
# 选择epoll
# window使用
# sel = selectors.DefaultSelector()
# linux使用
# 实例化一个epoll选择器
sel = selectors.EpollSelector()
servers = socket.socket()
servers.bind(('', 9999))
servers.listen(5)
print('开始监听')
# 注册事件
# 当客户端连接了,怎么办?
# 当客户端发送消息,应该怎么办?
def readable(conn):
data = conn.recv(1024)
if data:
print(data)
else:
print('close', conn)
# 取消注册事件
sel.unregister(conn)
conn.close()
def acc(server):
conn, addr = server.accept()
print('客户端{},连接成功'.format(addr))
sel.register(conn, selectors.EVENT_READ, readable)
# 注册事件(套接字,事件,回调函数)
sel.register(servers, selectors.EVENT_READ, acc)
while True:
# 返回有变化的套接字,是一个二元组的列表
events = sel.select()
# events事件信息
# SelectorKey(
# fileobj=<socket.socket fd=4, 生成了个打包对象,fileobj是对应的套接字
# family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0,
# laddr=('0.0.0.0', 9999)>,
# fd=4,
# events=1, 事件(1表示可读事件EVENT_READ)
# data=<function acc at 0xb72475cc>) 对应的回调函数
for key, mask in events:
# print(key, mask)
# 注册的回调函数
callback = key.data
# 惰性:此处还是需要我们自己去调用回调函数
callback(key.fileobj)
time.sleep(5)