python select網絡編程詳細介紹


剛看了反應堆模式的原理,特意復習了socket編程,本文主要介紹python的基本socket使用和select使用,主要用於了解socket通信過程

一、socket模塊

socket — Low-level networking interface

This module provides access to the BSD socket interface. It is available on all modern Unix systems, Windows, MacOS, and probably additional platforms.

更多詳細信息請看官方文檔 https://docs.python.org/3/library/socket.html

1、Socket類型

socket 常量  

描述

socket.AF_UNIX

只能夠用於單一的Unix系統進程間通信

socket.AF_INET

服務器之間網絡通信

socket.AF_INET6

IPv6

socket.SOCK_STREAM

流式socket , for TCP

socket.SOCK_DGRAM

數據報式socket , for UDP

socket.SOCK_RAW

原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。

socket.SOCK_SEQPACKET

可靠的連續數據包服務

創建TCP Socket:

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

創建UDP Socket:

s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2、socket函數

服務器端 Socket 函數

Socket 函數 描述
s.bind(address) 將套接字綁定到地址,在AF_INET下,以tuple(host, port)的方式傳入,如s.bind((host, port))
s.listen(backlog) 開始監聽TCP傳入連接,backlog指定在拒絕鏈接前,操作系統可以掛起的最大連接數,該值最少為1,大部分應用程序設為5就夠用了
s.accpet() 接受TCP鏈接並返回(conn, address),其中conn是新的套接字對象,可以用來接收和發送數據,address是鏈接客戶端的地址。

客戶端 Socket 函數

Socket 函數 描述
s.connect(address) 鏈接到address處的套接字,一般address的格式為tuple(host, port),如果鏈接出錯,則返回socket.error錯誤
s.connect_ex(address) 功能與s.connect(address)相同,但成功返回0,失敗返回errno的值

公共 Socket 函數

Socket 函數 描述
s.recv(bufsize[, flag]) 接受TCP套接字的數據,數據以字符串形式返回,buffsize指定要接受的最大數據量,flag提供有關消息的其他信息,通常可以忽略
s.send(string[, flag]) 發送TCP數據,將字符串中的數據發送到鏈接的套接字,返回值是要發送的字節數量,該數量可能小於string的字節大小
s.sendall(string[, flag]) 完整發送TCP數據,將字符串中的數據發送到鏈接的套接字,但在返回之前嘗試發送所有數據。成功返回None,失敗則拋出異常
s.recvfrom(bufsize[, flag]) 接受UDP套接字的數據u,與recv()類似,但返回值是tuple(data, address)。其中data是包含接受數據的字符串,address是發送數據的套接字地址
s.sendto(string[, flag], address) 發送UDP數據,將數據發送到套接字,address形式為tuple(ipaddr, port),指定遠程地址發送,返回值是發送的字節數
s.close() 關閉套接字
s.getpeername() 返回套接字的遠程地址,返回值通常是一個tuple(ipaddr, port)
s.getsockname() 返回套接字自己的地址,返回值通常是一個tuple(ipaddr, port)
s.setsockopt(level, optname, value) 設置給定套接字選項的值
s.getsockopt(level, optname[, buflen]) 返回套接字選項的值
s.settimeout(timeout) 設置套接字操作的超時時間,timeout是一個浮點數,單位是秒,值為None則表示永遠不會超時。一般超時期應在剛創建套接字時設置,因為他們可能用於連接的操作,如s.connect()
s.gettimeout() 返回當前超時值,單位是秒,如果沒有設置超時則返回None
s.fileno() 返回套接字的文件描述
s.setblocking(flag) 如果flag為0,則將套接字設置為非阻塞模式,否則將套接字設置為阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那么將引起socket.error異常。
s.makefile() 創建一個與該套接字相關的文件

 

3、socket異常

Exception   解釋
socket.error

由Socket相關錯誤引發

 socket.herror  由地址相關錯誤引發
 socket.gaierror  由地址相關錯誤,如getaddrinfo()或getnameinfo()引發
 socket.timeout  當socket出現超時時引發。超時時間由settimeout()提前設定

 

二、socket編程

1、基於TCP(面向連接)的Socket編程(C++)

服務器端順序:
(1. 加載套接字庫
(2. 創建套接字(serversocket)
(3. 將套接字綁定到一個本地地址和端口上(bind)
(4. 將套接字設為監聽模式,准備接收客戶請求(listen)
(5. 等待客戶請求的到來;當請求帶來后,接受連接請求,返回一個新的對應於此次連接的套接字(accept)
(6. 用返回的套接字和客戶端進行通信(send/recv)調用socket類的getOutputStream()和getInputStream()獲取輸出流和輸入流
(7. 返回,等待另一個客戶請求
(8. 關閉套接字(closesocket)
 
客戶端程序:
(1. 加載套接字庫
(2. 創建套接字(socket)
(3. 向服務器發送連接請求(connect)
(4. 和服務器端進行通信(send/receive) 調用socket類的getOutputStream()和getInputStream()獲取輸出流和輸入流
(5. 關閉套接字(closesocket)
 
2、基於UDP(面向無連接)的socket編程(C++)
服務器端(接收端)程序:
(1. 加載套接字庫
(2. 創建套接字(socket)
(3. 將套接字綁定到一個本地地址和端口上(bind)
(4. 等待接收數據(recvfrom)
(5. 關閉套接字(closesocket)
 
客戶端(發送端)程序
(1. 加載套接字庫
(2. 創建套接字(socket)
(3. 向服務器發送數據(sendto)
(4. 關閉套接字(closesocket)

3、socket tcp 編程實例,c/s程序

#!/bin/env python                                                         
# -*- coding:utf8 -*-  
"""
server.py
"""                                                   
                                                                          
import socket                                                                              
                                                                          
host = ('10.1.32.80', 33333)                                              
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # 網絡通信, TCP流 
s.bind(host)                                                              
s.listen(5)  # listen 5 client                                            
print "i'm waiting for connection..."                                     
                                                                          
while True:                                                               
    conn, addr = s.accept()   # connection  and ip address                
    print 'connected by', addr                                            
    while True:                                                           
        data = conn.recv(1024)                                            
        print "receive from %s:%s" % (addr, data)                         
        conn.sendall("server receive your messages, good bye.")           
        conn.close()                                                      
        break                                                             
# s.close()
#!/bin/env python                                                         
# -*- coding:utf8 -*-                                                     
                                                                          
import socket                                                             
"""                                                                       
client.py                                                                 
"""                                                                       
                                                                          
host = ('10.1.32.80', 33333)                                              
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # 網絡通信, TCP流 
s.connect(host)                                                           
                                                                          
while True:                                                               
    msg = raw_input("Please input message: ")                             
    try:                                                                  
        s.sendall(msg)                                                    
    except socket.error:                                                  
        print "i'm die, bye bye~"                                         
        break                                                             
    data = s.recv(1024)                                                   
    print data                                                            
    if "good bye" in data:                                                
        break                                                             
s.close()                                                                 

 

三、基於select的網絡編程

1、select介紹

在python中,select函數是一個對底層操作系統的直接訪問的接口。它用來監控sockets、files和pipes,等待IO完成(Waiting for I/O completion)。當有可讀、可寫或是異常事件產生時,select可以很容易的監控到。
select.select(rlist, wlist, xlist[, timeout]) 傳遞三個參數,一個為輸入而觀察的文件對象列表,一個為輸出而觀察的文件對象列表和一個觀察錯誤異常的文件列表。第四個是一個可選參數,表示超時秒數。其返回3個tuple,每個tuple都是一個准備好的對象列表,它和前邊的參數是一樣的順序。

2、使用select編程,聊天室程序如下。運行多個client,則可互相聊天,輸入"exit"即可退出

服務器代碼

#!/bin/env python
#-*- coding:utf8 -*-

"""
server select
"""


import sys
import time
import socket
import select
import logging
import Queue


g_select_timeout = 10

class Server(object):
    def __init__(self, host='10.1.32.80', port=33333, timeout=2, client_nums=10):
        self.__host = host
        self.__port = port
        self.__timeout = timeout
        self.__client_nums = client_nums
        self.__buffer_size = 1024

        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.setblocking(False)
        self.server.settimeout(self.__timeout)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) #keepalive
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口復用
        server_host = (self.__host, self.__port)
        try:
            self.server.bind(server_host)
            self.server.listen(self.__client_nums)
        except:
            raise

        self.inputs = [self.server] #select 接收文件描述符列表  
        self.outputs = [] #輸出文件描述符列表
        self.message_queues = {}#消息隊列
        self.client_info = {}

    def run(self):
        while True:
            readable , writable , exceptional = select.select(self.inputs, self.outputs, self.inputs, g_select_timeout)
            if not (readable or writable or exceptional) :
                continue

            for s in readable :
                if s is self.server:#是客戶端連接
                    connection, client_address = s.accept()
                    #print "connection", connection
                    print "%s connect." % str(client_address)
                    connection.setblocking(0) #非阻塞
                    self.inputs.append(connection) #客戶端添加到inputs
                    self.client_info[connection] = str(client_address)
                    self.message_queues[connection] = Queue.Queue()  #每個客戶端一個消息隊列

                else:#是client, 數據發送過來
                    try:
                        data = s.recv(self.__buffer_size)
                    except:
                        err_msg = "Client Error!"
                        logging.error(err_msg)
                    if data :
                        #print data
                        data = "%s %s say: %s" % (time.strftime("%Y-%m-%d %H:%M:%S"), self.client_info[s], data)
                        self.message_queues[s].put(data) #隊列添加消息 
                        
                        if s not in self.outputs: #要回復消息
                            self.outputs.append(s)
                    else: #客戶端斷開
                        #Interpret empty result as closed connection
                        print "Client:%s Close." % str(self.client_info[s])
                        if s in self.outputs :
                            self.outputs.remove(s)
                        self.inputs.remove(s)
                        s.close()
                        del self.message_queues[s]
                        del self.client_info[s]

            for s in writable: #outputs 有消息就要發出去了
                try:
                    next_msg = self.message_queues[s].get_nowait()  #非阻塞獲取
                except Queue.Empty:
                    err_msg = "Output Queue is Empty!"
                    #g_logFd.writeFormatMsg(g_logFd.LEVEL_INFO, err_msg)
                    self.outputs.remove(s)
                except Exception, e:  #發送的時候客戶端關閉了則會出現writable和readable同時有數據,會出現message_queues的keyerror
                    err_msg = "Send Data Error! ErrMsg:%s" % str(e)
                    logging.error(err_msg)
                    if s in self.outputs:
                        self.outputs.remove(s)
                else:
                    for cli in self.client_info: #發送給其他客戶端
                        if cli is not s:
                            try:
                                cli.sendall(next_msg)
                            except Exception, e: #發送失敗就關掉
                                err_msg = "Send Data to %s  Error! ErrMsg:%s" % (str(self.client_info[cli]), str(e))
                                logging.error(err_msg)
                                print "Client: %s Close Error." % str(self.client_info[cli])
                                if cli in self.inputs:
                                    self.inputs.remove(cli)
                                    cli.close()
                                if cli in self.outputs:
                                    self.outputs.remove(s)
                                if cli in self.message_queues:
                                    del self.message_queues[s]
                                del self.client_info[cli]

            for s in exceptional:
                logging.error("Client:%s Close Error." % str(self.client_info[cli]))
                if s in self.inputs:
                    self.inputs.remove(s)
                    s.close()
                if s in self.outputs:
                    self.outputs.remove(s)
                if s in self.message_queues:
                    del self.message_queues[s]
                del self.client_info[s]
        

if "__main__" == __name__:
    Server().run()

 

客戶端代碼

#!/usr/local/bin/python
# *-* coding:utf-8 -*-

"""
client.py
"""


import sys
import time
import socket
import threading


class Client(object):
    def __init__(self, host, port=33333, timeout=1, reconnect=2):
        self.__host = host
        self.__port = port
        self.__timeout = timeout
        self.__buffer_size = 1024
        self.__flag = 1
        self.client = None
        self.__lock = threading.Lock() 
    
    @property
    def flag(self):
        return self.__flag

    @flag.setter
    def flag(self, new_num):
        self.__flag = new_num

    def __connect(self):
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        #client.bind(('0.0.0.0', 12345,))
        client.setblocking(True)
        client.settimeout(self.__timeout)
        client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口復用
        server_host = (self.__host, self.__port)
        try:
            client.connect(server_host)
        except:
            raise
        return client

    def send_msg(self):
        if not self.client:
            return
        while True:
            time.sleep(0.1)
            #data = raw_input()
            data = sys.stdin.readline().strip()
            if "exit" == data.lower():
                with self.__lock:
                    self.flag = 0
                break
            self.client.sendall(data)
        return

    def recv_msg(self):
        if not self.client:
            return
        while True:
            data = None
            with self.__lock:
                if not self.flag:
                    print 'ByeBye~~'
                    break
            try:
                data = self.client.recv(self.__buffer_size)
            except socket.timeout:
                continue
            except:
                raise
            if data:
                print "%s\n" % data
            time.sleep(0.1)
        return

    def run(self):
        self.client = self.__connect()
        send_proc = threading.Thread(target=self.send_msg)
        recv_proc = threading.Thread(target=self.recv_msg)
        recv_proc.start()
        send_proc.start()
        recv_proc.join()
        send_proc.join()
        self.client.close()


if "__main__" == __name__:
    Client('10.1.32.80').run()

 

四、多路IO復用介紹和區別

sellect、poll、epoll三者的區別( 多路IO 復用都是同步的
select
select最早於1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組,當select()返回后,該數組中就緒的文件描述符便會被內核修改標志位,使得進程可以獲得這些文件描述符從而進行后續的讀寫操作。
select目前幾乎在所有的平台上支持,其良好跨平台支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。
select的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一 般為1024,不過可以通過修 改宏定義甚至重新編譯內核的方式提升這一限制。
另外, select()所維護的存儲大量文件描述符的數據結構隨着文件描述符數量的增大,其復制的開銷也線性增長。同時,由於網絡響應時間的延遲使得大量TCP連接處於非活躍狀態,但調用 select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。
 
poll
poll在1986年誕生於System V Release 3,它和select在本質上沒有多大差別,但是 poll沒有最大文件描述符數量的限制
poll和select同樣 存在一個缺點就是,包含大量文件描述符的數組被整體復制於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增加而線性增大。
另外,select()和poll()將就緒的文件描述符告訴進程后,如果 進程沒有對其進行IO操作,那么下次調用select()和poll()的時候將再次報告這些文件描述符,所以它們一般不會丟失就緒的消息,這種方式稱為水平觸發(Level Triggered)。
 
epoll
直到Linux2.6才出現了由內核直接支持的實現方法,那就是epoll,它幾乎具備了之前所說的一切優點,被公認為Linux2.6下性能最好的多路I/O就緒通知方法。
epoll可以同 時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有采取行動,那么它將不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當復雜。
 
epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這里也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時復制的開銷。
 
另一個本質的改進在於epoll采用基於事件的就緒通知方式。 在select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符當進程調用epoll_wait()時便得到通知。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM