Python中TCP協(xié)議的理解

Num01-->TCP通信模型

這里寫圖片描述

Test01-->TCP客戶端案例

#! /usr/bin/env python3
# -*- coding:utf-8 -*- 

from socket import *

def main():

    # 1.創(chuàng)建socket
    client_socket = socket(AF_INET, SOCK_STREAM)

    # 2.指定服務(wù)器的地址和端口號
    server_addr = ('192.168.105.125',8080)
    client_socket.connect(server_addr)

    print('connect %s success' % str(server_addr))

    while True:
        # 3.給用戶提示,讓用戶輸入要檢索的資料
        send_data = input('>>')
        # 退出
        if send_data == 'quit':
            break
        # 向服務(wù)器請求數(shù)據(jù)
        client_socket.send(send_data.encode())

    client_socket.close()

if __name__ == "__main__":
    main()

Test02-->TCP服務(wù)器端案例

TCP服務(wù)器端創(chuàng)建流程如下:

1,socket創(chuàng)建一個套接字
2,bind綁定ip和port
3,listen使套接字變?yōu)榭梢员粍渔溄?br> 4,accept等待客戶端的鏈接
5,recv/send接收/發(fā)送數(shù)據(jù)

#! /usr/bin/env python3
# -*- coding:utf-8 -*- 

from socket import *
import time

def main():

    # 1.創(chuàng)建socket,stream流式套接字,對應(yīng)tcp
    listen_socket = socket(AF_INET, SOCK_STREAM) 
    
    # 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
    #  設(shè)置socket層屬性    復(fù)用地址    允許
    listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

    # 2.綁定端口號
    my_addr = ('192.168.105.125', 8080)
    #shift + insert
    listen_socket.bind(my_addr)

    # 3.接聽狀態(tài)
    #listen中的black表示已經(jīng)建立鏈接和半鏈接的總數(shù)
    #如果當(dāng)前已建立鏈接數(shù)和半鏈接數(shù)以達(dá)到設(shè)定值,那么新客戶端就不會connect成功,而是等待服務(wù)器。直到有鏈接退出。
    listen_socket.listen(4)
    print('listening...')

    # 4.等待客戶端來請求服務(wù)器

    while True:

        # 接受連接請求,創(chuàng)建新的連接套接字,用于客戶端連通信
        connect_socket, client_addr = listen_socket.accept() 
        # accept默認(rèn)會引起阻塞 
        # 新創(chuàng)建連接用的socket, 客戶端的地址
        # print(connect_socket)
        # print(client_addr)

        while True:
            # tcp recv() 只會返回接收到的數(shù)據(jù)
            # 1024表示接受的數(shù)據(jù)長度
            recv_data = connect_socket.recv(1024)
            
            if len(recv_data) == 0:
                #發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回''
                print('client %s close' % str(client_addr))

                time.sleep(5)
                break

            print('recv: %s' % recv_data.decode('gbk'))
        
        # 用完之后,關(guān)閉新創(chuàng)建的那個connect_socket
        connect_socket.close()

if __name__ == "__main__":
    main()

Num02-->TCP協(xié)議三次握手

這里寫圖片描述

Num03-->TCP協(xié)議四次揮手

這里寫圖片描述

Num04-->TCP協(xié)議十種狀態(tài)

這里寫圖片描述

當(dāng)一端收到一個FIN,內(nèi)核讓read返回0來通知應(yīng)用層另一端已經(jīng)終止了向本端的數(shù)據(jù)傳送

發(fā)送FIN通常是應(yīng)用層對socket進(jìn)行關(guān)閉的結(jié)果

Num05-->TCP協(xié)議的2MSL問題

這里寫圖片描述

加以說明:
1,2MSL即兩倍的MSL,TCP的TIME_WAIT狀態(tài)也稱為2MSL等待狀態(tài)。

2,當(dāng)TCP的一端發(fā)起主動關(guān)閉,在發(fā)出最后一個ACK包后,

3,即第3次握 手完成后發(fā)送了第四次握手的ACK包后就進(jìn)入了TIME_WAIT狀態(tài),

4,必須在此狀態(tài)上停留兩倍的MSL時間,

5,等待2MSL時間主要目的是怕最后一個 ACK包對方?jīng)]收到,

6,那么對方在超時后將重發(fā)第三次握手的FIN包,

7,主動關(guān)閉端接到重發(fā)的FIN包后可以再發(fā)一個ACK應(yīng)答包。

8,在TIME_WAIT狀態(tài) 時兩端的端口不能使用,要等到2MSL時間結(jié)束才可繼續(xù)使用。

9,當(dāng)連接處于2MSL等待階段時任何遲到的報(bào)文段都將被丟棄。

10,不過在實(shí)際應(yīng)用中可以通過設(shè)置 SO_REUSEADDR選項(xiàng)達(dá)到不必等待2MSL時間結(jié)束再使用此端口。

Num06-->TCP協(xié)議長鏈接和短鏈接

TCP在真正的讀寫操作之前,server與client之間必須建立一個連接,
當(dāng)讀寫操作完成后,雙方不再需要這個連接時它們可以釋放這個連接,
連接的建立通過三次握手,釋放則需要四次握手,
所以說每個連接的建立都是需要資源消耗和時間消耗的。

Test01-->長鏈接

1, client 向 server 發(fā)起連接
2,server 接到請求,雙方建立連接
3,client 向 server 發(fā)送消息
4,server 回應(yīng) client
5,一次讀寫完成,連接不關(guān)閉
6,后續(xù)讀寫操作...
7,長時間操作之后client發(fā)起關(guān)閉請求

Test02-->短鏈接

1,client 向 server 發(fā)起連接請求
2,server 接到請求,雙方建立連接
3,client 向 server 發(fā)送消息
4,server 回應(yīng) client
5,一次讀寫完成,此時雙方任何一個都可以發(fā)起 close 操作

Test03-->長鏈接和短鏈接的區(qū)別

長鏈接可以省去較多的TCP建立和關(guān)閉的操作,減少浪費(fèi),節(jié)約時間。
對于頻繁請求資源的客戶來說,較適用長連接。

client與server之間的連接如果一直不關(guān)閉的話,會存在一個問題,
隨著客戶端連接越來越多,server早晚有扛不住的時候,這時候server端需要采取一些策略,
如關(guān)閉一些長時間沒有讀寫事件發(fā)生的連接,這樣可以避免一些惡意連接導(dǎo)致server端服務(wù)受損;

如果條件再允許就可以以客戶端機(jī)器為顆粒度,限制每個客戶端的最大長連接數(shù),這樣可以完全避免某個蛋疼的客戶端連累后端服務(wù)。

短鏈接對于服務(wù)器來說管理較為簡單,存在的連接都是有用的連接,不需要額外的控制手段。但如果客戶請求頻繁,將在TCP的建立和關(guān)閉操作上浪費(fèi)時間和帶寬。

Test04-->TCP長/短鏈接的應(yīng)用場景

長鏈接多用于操作頻繁,點(diǎn)對點(diǎn)的通訊,而且連接數(shù)不能太多情況。
每個TCP連接都需要三次握手,這需要時間,如果每個操作都是先連接,
再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,
再次處理時直接發(fā)送數(shù)據(jù)包就OK了,不用建立TCP連接。

例如:數(shù)據(jù)庫的連接用長連接,如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創(chuàng)建也是對資源的浪費(fèi)。

像WEB網(wǎng)站的HTTP服務(wù)一般都用短鏈接,因?yàn)殚L連接對于服務(wù)端來說會耗費(fèi)一定的資源。

像WEB網(wǎng)站這么頻繁的成千上萬甚至上億客戶端的連接,用短連接會更省一些資源;如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都占用一個連接的話,那可想而知吧。雖然并發(fā)量大,但每個用戶無需頻繁操作情況下需用短連好。

Num07-->TCP并發(fā)服務(wù)器--多進(jìn)程實(shí)現(xiàn)

通過為每個客戶端創(chuàng)建一個進(jìn)程的方式,能夠同時為多個客戶端進(jìn)行服務(wù)。當(dāng)客戶端不是特別多的時候,這種方式還行,如果有幾百上千個,就不可取了,因?yàn)槊看蝿?chuàng)建進(jìn)程等過程需要好較大的資源。

#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author  : xiaoke

from multiprocessing import Process
from socket import *


# 需要為客戶端提供服務(wù)
def do_service(connect_socket):
    while True:
        recv_data = connect_socket.recv(1024)
        if len(recv_data) == 0:
            # 發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回''
            # print('client %s close' % str(client_addr))     
            # s.getpeername()   s.getsockname()
            print('client %s close' % str(connect_socket.getpeername()))
            break
        print('recv: %s' % recv_data.decode('gbk'))


def main():
    # 1.創(chuàng)建socket
    listen_socket = socket(AF_INET, SOCK_STREAM)
    # stream流式套接字,對應(yīng)tcp

    # 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
    # 設(shè)置socket層屬性    復(fù)用地址,不用等2msl,    允許
    listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

    # 2.綁定端口
    my_addr = ('192.168.105.125', 8080)
    listen_socket.bind(my_addr)

    # 3,接聽狀態(tài)
    listen_socket.listen(4)  # 設(shè)置套接字成監(jiān)聽,4表示一個己連接隊(duì)列長度
    print('listening...')

    # 4.等待客戶端來請求

    # 父進(jìn)程只專注接受連接請求
    while True:
        # 接受連接請求,創(chuàng)建連接套接字,用于客戶端間通信
        connect_socket, client_addr = listen_socket.accept()  # accept默認(rèn)會引起阻塞
        # 新創(chuàng)建連接用的socket, 客戶端的地址
        # print(connect_socket)
        print(client_addr)

        # 每當(dāng)來新的客戶端連接,創(chuàng)建子進(jìn)程,由子進(jìn)程和客戶端通信
        process_do_service = Process(target=do_service, args=(connect_socket,))
        process_do_service.start()

        # 父進(jìn)程,關(guān)閉connect_socket
        connect_socket.close()


if __name__ == "__main__":
    main()

Num08-->TCP并發(fā)服務(wù)器--多線程實(shí)現(xiàn)

#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author  : xiaoke
from socket import *
from threading import Thread


# 需要為客戶端提供服務(wù)
def do_service(connect_socket):
    while True:
        recv_data = connect_socket.recv(1024)
        if len(recv_data) == 0:
            # 發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回''
            # print('client %s close' % str(client_addr))
            # s.getpeername()   s.getsockname()
            print('client %s close' % str(connect_socket.getpeername()))
            break
        print('recv: %s' % recv_data.decode('gbk'))


def main():
    # 1.創(chuàng)建socket
    listen_socket = socket(AF_INET, SOCK_STREAM)
    # 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
    # 設(shè)置socket層屬性    復(fù)用地址,不用等2msl    允許
    listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

    # 2.綁定端口
    my_addr = ('192.168.105.125', 8080)
    # shift + insert
    listen_socket.bind(my_addr)

    # 3.接聽狀態(tài)
    listen_socket.listen(4)  # 設(shè)置套接字成監(jiān)聽,4表示一個己連接隊(duì)列長度
    print('listening...')

    # 4.等待來電話

    # 主線程只專注接受連接請求
    while True:
        # 接受連接請求,創(chuàng)建連接套接字,用于客戶端連通信
        connect_socket, client_addr = listen_socket.accept()  # accept默認(rèn)會引起阻塞
        # 新創(chuàng)建連接用的socket, 客戶端的地址
        # print(connect_socket)
        print(client_addr)

        # 每當(dāng)來新的客戶端連接,創(chuàng)建子線程,由子線程和客戶端通信
        thread_do_service = Thread(target=do_service, args=(connect_socket,))
        thread_do_service.start()

        # 主線程,不能關(guān)閉connect_socket,多個線程共享打開的文件
        # connect_socket.close()


if __name__ == "__main__":
    main()

Num09-->TCP單進(jìn)程阻塞服務(wù)器實(shí)現(xiàn)

#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author  : xiaoke
import time
from socket import *


def main():
    # 1.創(chuàng)建socket
    listen_socket = socket(AF_INET, SOCK_STREAM)

    # 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
    #  設(shè)置socket層屬性    復(fù)用地址    允許
    listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

    # 2.綁定端口
    my_addr = ('192.168.105.125', 8080)
    listen_socket.bind(my_addr)

    # 3.接聽狀態(tài)
    listen_socket.listen(4)
    print('listening...')

    # 4.等待客戶端發(fā)起請求
    while True:

        # 接受連接請求,創(chuàng)建連接套接字,用于客戶端間通信
        connect_socket, client_addr = listen_socket.accept()  # accept默認(rèn)會引起阻塞
        # 新創(chuàng)建連接用的socket, 客戶端的地址
        # print(connect_socket)
        print(client_addr)

        while True:
            # tcp recv() 只會返回接收到的數(shù)據(jù)
            recv_data = connect_socket.recv(1024)
            if len(recv_data) == 0:
                # 發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回''
                print('client %s close' % str(client_addr))
                time.sleep(5)
                break
            print('recv: %s' % recv_data.decode('gbk'))
        # 用完之后,關(guān)閉connect_socket
        connect_socket.close()


if __name__ == "__main__":
    main()

Num10-->TCP單進(jìn)程非阻塞服務(wù)器實(shí)現(xiàn)

#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author  : xiaoke
import time
from socket import *


def main():
    # 1.創(chuàng)建socket
    listen_socket = socket(AF_INET, SOCK_STREAM)
    # 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
    # 設(shè)置socket層屬性    復(fù)用地址    允許
    listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    # 設(shè)置listen_socket為非阻塞方式
    listen_socket.setblocking(False)  # 設(shè)置非阻塞

    # 2.綁定端口
    my_addr = ('192.168.105.125', 8080)
    listen_socket.bind(my_addr)

    # 3.接聽狀態(tài)
    listen_socket.listen(4)
    print('listening...')

    # 4.等待客戶端發(fā)起請求
    # 創(chuàng)建一個列表,保存已連接socket
    connect_socket_list = []
    while True:
        print(connect_socket_list)
        try:
            # 接受連接請求,創(chuàng)建連接套接字,用于客戶端連通信
            connect_socket, client_addr = listen_socket.accept()  # accept默認(rèn)會引起阻塞 
        except Exception as e:  # 還沒有客戶端連接
            # print(e)
            # time.sleep(1)
            pass
        else:  # 此時有連接請求
            # 新創(chuàng)建連接用的socket, 客戶端的地址
            print('有新的客戶端連接 %s' % str(client_addr))
            # 將新socket 設(shè)成非阻塞
            connect_socket.setblocking(False)
            # 將新的socket添加到列表中,以便后續(xù)循環(huán)讀數(shù)據(jù)
            connect_socket_list.append(connect_socket)
        # 保存刪除socket列表
        need_delete_socket_list = []

        # 遍歷已連接的socket分別讀數(shù)據(jù)
        for new_socket in connect_socket_list:
            try:
                recv_data = new_socket.recv(1024)
            except:
                pass
            else:
                # 如果對方關(guān)閉
                if len(recv_data) == 0:
                    print('%s close' % (str(new_socket.getpeername())))
                    new_socket.close()
                    # 從connect_socket_list列表中刪除,單獨(dú)使用列表保存要刪除socket
                    need_delete_socket_list.append(new_socket)
                    continue
                print('from %s : %s' %
                      (str(new_socket.getpeername()), recv_data.decode('gbk')))
        # 從connect_socket_list刪除已關(guān)閉soccket
        for s in need_delete_socket_list:
            connect_socket_list.remove(s)
        time.sleep(1)


if __name__ == "__main__":
    main()

Num11-->select版--TCP服務(wù)器實(shí)現(xiàn)

Test01-->select 原理

在多路復(fù)用的模型中,比較常用的有select模型和epoll模型。這兩個都是系統(tǒng)接口,由操作系統(tǒng)提供。當(dāng)然,Python的select模塊進(jìn)行了更高級的封裝。

網(wǎng)絡(luò)通信被Unix系統(tǒng)抽象為文件的讀寫,通常是一個設(shè)備,由設(shè)備驅(qū)動程序提供,驅(qū)動可以知道自身的數(shù)據(jù)是否可用。支持阻塞操作的設(shè)備驅(qū)動通常會實(shí)現(xiàn)一組自身的等待隊(duì)列,如讀/寫等待隊(duì)列用于支持上層(用戶層)所需的block或non-block操作。設(shè)備的文件的資源如果可用(可讀或者可寫)則會通知進(jìn)程,反之則會讓進(jìn)程睡眠,等到數(shù)據(jù)到來可用的時候,再喚醒進(jìn)程。

這些設(shè)備的文件描述符被放在一個數(shù)組中,然后select調(diào)用的時候遍歷這個數(shù)組,如果對于文件描述符可讀則會返回該文件描述符。當(dāng)遍歷結(jié)束之后,如果仍然沒有一個可用設(shè)備文件描述符,select則讓用戶進(jìn)程睡眠,直到等待資源可用的時候再喚醒,喚醒之后遍歷之前那個監(jiān)視的數(shù)組。每次遍歷都是依次進(jìn)行判斷的。

Test02-->select的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點(diǎn)。
缺點(diǎn):select的一個缺點(diǎn)在于單個進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會造成效率的降低。

一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位機(jī)默認(rèn)是1024個。64位機(jī)默認(rèn)是2048.

對socket進(jìn)行掃描時是依次掃描的,即采用輪詢的方法,效率較低。

當(dāng)套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費(fèi)很多CPU時間。

Test03-->案例的實(shí)現(xiàn)代碼

#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author  : xiaoke
import select
import sys  # sys.stdin 代表鍵盤設(shè)備的文件對象
from socket import *
def main():
    # 1.創(chuàng)建socket
    listen_socket = socket(AF_INET, SOCK_STREAM)
    # 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
    # 設(shè)置socket層屬性    復(fù)用地址    允許
    listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

    # 2.綁定端口
    my_addr = ('192.168.105.125', 8080)
    listen_socket.bind(my_addr)

    # 3.接聽狀態(tài)
    listen_socket.listen(4)
    print('listening...')

    # 指定select關(guān)心的哪些路(socket,或文件)數(shù)據(jù)
    rlist = [listen_socket, sys.stdin]  # 要讀的文件對象列表,包括listen socket
    wlist = []  # 要寫的文件對象列表
    xlist = []  # 出現(xiàn)異常的文件對象列表

    while True:
        print(rlist)
        # select 會阻塞等待三個列表中文件對象就緒,如果沒就緒,select一直阻塞;只有任意文件就緒,select返回
        # 就緒的文件列表
        read_ready_list, wready, excplist = select.select(rlist, wlist, xlist)
        # 指定seclet關(guān)注讀,寫,異常文件列表

        # 如果select返回,一定有客戶端連接服務(wù)器
        # 循環(huán)判斷是哪個關(guān)注的文件,讀就緒了
        for fobj in read_ready_list:
            # 如果fobj是listen_socket對象,一定有客戶端連接服務(wù)器
            if fobj == listen_socket:
                new_socket, peer_addr = fobj.accept()  # 此時accept調(diào)用一定不會阻塞
                print(peer_addr)

                # 將新的socket添加至rlist,也要進(jìn)行關(guān)注
                rlist.append(new_socket)

            elif fobj == sys.stdin:  # 鍵盤有數(shù)據(jù)輸入

                data = sys.stdin.readline()  # input()

                print('input %s' % data)

                if data == 'quit\n':
                    exit()

            else:  # 已連接socket有數(shù)據(jù)可讀
                recv_data = fobj.recv(1024)
                if len(recv_data) > 0:
                    print('from %s : %s' % (str(fobj.getpeername()), recv_data.decode('gbk')))
                else:  # 客戶端關(guān)閉socket
                    print('%s close' % str(fobj.getpeername()))
                    fobj.close()  # 將關(guān)閉socket從rlist列表中刪除,表示不再關(guān)注這個socket
                    rlist.remove(fobj)


if __name__ == "__main__":
    main()

Num12-->epoll版--TCP服務(wù)器實(shí)現(xiàn)

Test01-->epoll的優(yōu)點(diǎn):

沒有最大并發(fā)連接的限制,能打開的FD(指的是文件描述符,通俗的理解就是套接字對應(yīng)的數(shù)字編號)的上限遠(yuǎn)大于1024
效率提升,不是輪詢的方式,不會隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會調(diào)用callback函數(shù);即epoll最大的優(yōu)點(diǎn)就在于它只管你“活躍”的連接,而跟連接總數(shù)無關(guān),因此在實(shí)際的網(wǎng)絡(luò)環(huán)境中,epoll的效率就會遠(yuǎn)遠(yuǎn)高于select和poll。

Test02-->一些術(shù)語

EPOLLIN (可讀)
EPOLLOUT (可寫)
EPOLLET (ET模式)
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。

LT模式是默認(rèn)模式,LT模式與ET模式的區(qū)別如下:

LT模式:當(dāng)epoll檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序可以不立即處理該事件。下次調(diào)用epoll時,會再次響應(yīng)應(yīng)用程序并通知此事件。

ET模式:當(dāng)epoll檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件。如果不處理,下次調(diào)用epoll時,不會再次響應(yīng)應(yīng)用程序并通知此事件。

Test03-->案例的實(shí)現(xiàn)代碼

#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author  : xiaoke

import socket
import select

# 創(chuàng)建套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 設(shè)置可以重復(fù)使用綁定的信息
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 綁定本機(jī)信息
s.bind(("", 8080))

# 變?yōu)楸粍?s.listen(10)

# 創(chuàng)建一個epoll對象
epoll = select.epoll()

# 測試,用來打印套接字對應(yīng)的文件描述符
# print s.fileno()
# print select.EPOLLIN|select.EPOLLET

# 注冊事件到epoll中
# epoll.register(fd[, eventmask])
# 注意,如果fd已經(jīng)注冊過,則會發(fā)生異常
# 將創(chuàng)建的套接字添加到epoll的事件監(jiān)聽中
epoll.register(s.fileno(), select.EPOLLIN | select.EPOLLET)

connections = {}
addresses = {}

# 循環(huán)等待客戶端的到來或者對方發(fā)送數(shù)據(jù)
while True:

    # epoll 進(jìn)行 fd 掃描的地方 -- 未指定超時時間則為阻塞等待
    epoll_list = epoll.poll()

    # 對事件進(jìn)行判斷
    for fd, events in epoll_list:

        # print fd
        # print events

        # 如果是socket創(chuàng)建的套接字被激活
        if fd == s.fileno():
            conn, addr = s.accept()

            print('有新的客戶端到來%s' % str(addr))

            # 將 conn 和 addr 信息分別保存起來
            connections[conn.fileno()] = conn
            addresses[conn.fileno()] = addr

            # 向 epoll 中注冊 連接 socket 的 可讀 事件
            epoll.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)


        elif events == select.EPOLLIN:
            # 從激活 fd 上接收
            recvData = connections[fd].recv(1024)

            if len(recvData) > 0:
                print('recv:%s' % recvData)
            else:
                # 從 epoll 中移除該 連接 fd
                epoll.unregister(fd)

                # server 側(cè)主動關(guān)閉該 連接 fd
                connections[fd].close()

                print("%s---offline---" % str(addresses[fd]))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容