1. 程式人生 > >Python之TCP詳解和 OSI七層模型

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)