Python之TCP詳解和 OSI七層模型
阿新 • • 發佈:2018-12-15
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)