Python-Socket網絡編程

jopen 9年前發布 | 53K 次閱讀 Python Python開發

python的網絡變成比c語言簡單許多, 封裝許多底層的實現細節, 方便程序員使用的同時, 也使程序員比較難了解一些底層的東西, 我覺得學網絡編程還是用c語言更好一點.

寫這篇博文, 也希望回顧并整理一下以前學過的c語言和linux下一些東西, 會將一些Linux網絡編程的函數和Python網絡變成函數做一個簡單的對照, 方便記憶

</blockquote>

1. Socket套接字的概念


Socket(翻譯為套接字, 我覺得很挫),是操作系統內核中的一個數據結構,它是網絡中的節點進行相互通信的門戶。它是網絡進程的ID。網絡通信,歸根到底還是進程間的通信(不同計算機上的進程間通信, 又稱進程間通信, IP協議進行的主要是端到端通信)。在網絡中,每一個節點(計算機或路由)都有一個網絡地址,也就是IP地址。兩個進程通信時,首先要確定各自所在的網絡節點的網絡地址。但是,網絡地址只能確定進程所在的計算機,而一臺計算機上很可能同時運行著多個進程,所以僅憑網絡地址還不能確定到底是和網絡中的哪一個進程進行通信,因此套接口中還需要包括其他的信息,也就是端口號(PORT)。在一臺計算機中,一個端口號一次只能分配給一個進程,也就是說,在一臺計算機中,端口號和進程之間是一一對應關系。
所以,使用端口號和網絡地址的組合可以唯一的確定整個網絡中的一個網絡進程.

端口號的范圍從0~65535,一類是由互聯網指派名字和號碼公司ICANN負責分配給一些常用的應用程序固定使用的“周知的端口”,其值一般為0~1023, 用戶自定義端口號一般大于等于1024, 我比較喜歡用8888

</blockquote>

每一個socket都用一個半相關描述{協議、本地地址、本地端口}來表示;一個完整的套接字則用一個相關描述{協議、本地地址、本地端口、遠程地址、遠程端口}來表示。socket也有一個類似于打開文件的函數調用,該函數返回一個整型的socket描述符,隨后的連接建立、數據傳輸等操作都是通過socket來實現的

1.1. Socket類型

socket類型在Liunx和Python是一樣的, 只是Python中的類型都定義在socket模塊中, 調用方式socket.SOCK_XXXX

</blockquote>

  • 流式socket(SOCK_STREAM) 用于TCP通信
  • </ul>

    流式套接字提供可靠的、面向連接的通信流;它使用TCP協議,從而保證了數據傳輸的正確性和順序性

    </blockquote>

    • 數據報socket(SOCK_DGRAM) 用于UDP通信
    • </ul>

      數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,并且不保證是可靠、無差錯的。它使用數據報協議UDP

      </blockquote>

      • 原始socket(SOCK_RAW) 用于新的網絡協議實現的測試等
      • </ul>

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

        </blockquote>

        2. Socket編程


        2.1. TCP通信

        TCP通信的基本步驟如下:
        服務端:socket---bind---listen---while(True){---accept---recv---send----}---close
        客戶端:socket----------------------------------connect---send---recv-------close

        TCP

        TCP
        </div>

        socket函數
        使用給定的地址族、套接字類型、協議編號(默認為0)來創建套接字

        #Linux
        int socket(int domain, int type, int protocol);
        domain:AF_INET:Ipv4網絡協議 AF_INET6:IPv6網絡協議
        type : tcp:SOCK_STREAM   udp:SOCK_DGRAM
        protocol : 指定socket所使用的傳輸協議編號。通常為0.
        返回值:成功則返回套接口描述符,失敗返回-1。

        python

        socket.socket([family[, type[, proto]]]) family : AF_INET (默認ipv4), AF_INET6(ipv6) or AF_UNIX(Unix系統進程間通信). type : SOCK_STREAM (TCP), SOCK_DGRAM(UDP) . protocol : 一般為0或者默認

        如果socket創建失敗會拋出一個socket.error異常</code></pre>

        2.1.1. 服務器端函數

        bind函數
        將套接字綁定到地址, python下,以元組(host,port)的形式表示地址, Linux下使用sockaddr_in結構體指針

        #Linux
        int bind(int sockfd, struct sockaddr * my_addr, int addrlen);
        sockfd : 前面socket()的返回值
        my_addr : 結構體指針變量

        #

        struct sockaddr_in //常用的結構體 { unsigned short int sin_family; //即為sa_family AF_INET uint16_t sin_port; //為使用的port編號 struct in_addr sin_addr; //為IP地址 unsigned char sin_zero[8]; //未使用 }; struct in_addr { uint32_t s_addr; };

        #

        addrlen : sockaddr的結構體長度。通常是計算sizeof(struct sockaddr); 返回值:成功則返回0,失敗返回-1

        python

        s.bind(address) s為socket.socket()返回的套接字對象 address為元組(host,port) host: ip地址, 為一個字符串 post: 自定義主機號, 為整型</code></pre>

        listen函數
        使服務器的這個端口和IP處于監聽狀態,等待網絡中某一客戶機的連接請求。如果客戶端有連接請求,端口就會接受這個連接

        #Linux
        int listen(int sockfd,int backlog);
        sockfd : 為前面socket的返回值.
        backlog : 指定同時能處理的最大連接要求,通常為10或者5。最大值可設至128
        返回值:成功則返回0,失敗返回-1

        python

        s.listen(backlog) s為socket.socket()返回的套接字對象 backlog : 操作系統可以掛起的最大連接數量。該值至少為1,大部分應用程序設為5就可以了</code></pre>

        accept函數
        接受遠程計算機的連接請求,建立起與客戶機之間的通信連接。服務器處于監聽狀態時,如果某時刻獲得客戶機的連接請求,此時并不是立即處理這個請求,而是將這個請求放在等待隊列中,當系統空閑時再處理客戶機的連接請求。

        #Linux
        int accept(int s,struct sockaddr  addr,int  addrlen);
        sockfd : 為前面socket的返回值.
        addr : 為結構體指針變量,和bind的結構體是同種類型的,系統會把遠程主機的信息(遠程主機的地址和端口號信息)保存到這個指針所指的結構體中。
        addrlen : 表示結構體的長度,為整型指針 
        返回值:成功則返回新的socket處理代碼new_fd,失敗返回-1

        python

        s.accept() s為socket.socket()返回的套接字對象 返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址</code></pre>

        2.1.2. 客戶端函數

        connect函數
        用來請求連接遠程服務器

        #Linux
        int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
        sockfd : 為前面socket的返回值.
        serv_addr : 為結構體指針變量,存儲著遠程服務器的IP與端口號信息
        addrlen : 表示結構體變量的長度
        返回值:成功則返回0,失敗返回-1

        python

        s.connect(address) s為socket.socket()返回的套接字對象 address : 格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤</code></pre>

        2.1.3. 通用函數

        接收遠端主機傳來的數據

        recv函數

        #Linux
        int recv(int sockfd,void *buf,int len,unsigned int flags);
        sockfd : 為前面accept的返回值.也就是新的套接字。
        buf : 表示緩沖區
        len : 表示緩沖區的長度
        flags : 通常為0
        返回值:成功則返回實際接收到的字符數,可能會少于你所指定的接收長度。失敗返回-1

        python

        s.recv(bufsize[,flag]) s為socket.socket()返回的套接字對象 bufsize : 指定要接收的數據大小 flag : 提供有關消息的其他信息,通常可以忽略 返回值為數據以字符串形式</code></pre>

        send函數
        發送數據給指定的遠端主機

        #Linux
        int send(int s,const void * msg,int len,unsigned int flags);
        sockfd : 為前面socket的返回值.
        msg : 一般為常量字符串
        len : 表示長度
        flags : 通常為0
        返回值:成功則返回實際傳送出去的字符數,可能會少于你所指定的發送長度。失敗返回-1

        python

        s.send(string[,flag]) s為socket.socket()返回的套接字對象 string : 要發送的字符串數據 flag : 提供有關消息的其他信息,通常可以忽略 返回值是要發送的字節數量,該數量可能小于string的字節大小。 s.sendall(string[,flag])

        完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。

        返回值 : 成功返回None,失敗則拋出異常。</code></pre>

        close函數
        關閉套接字

        #Linux
        int close(int fd);
        fd : 為前面的sockfd
        返回值:若文件順利關閉則返回0,發生錯誤時返回-1

        python

        s.close() s為socket.socket()返回的套接字對象</code></pre>

        2.2. 簡單的客戶端服務器TCP連接

        一個簡單的回顯服務器和客戶端模型, 客戶端發出的數據, 服務器會回顯到客戶端的終端上(只是一個簡單的模型, 沒考慮錯誤處理等問題)

        </blockquote>

        #服務器端

        !/usr/bin/env python

        -- coding:utf-8 --

        import socket #socket模塊 import commands #執行系統命令模塊

        BUF_SIZE = 1024 #設置緩沖區大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一個新的socket對象 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #設置地址復用 server.bind(server_addr) #綁定地址 server.listen(5) #監聽, 最大監聽數為5 while True: client, client_addr = server.accept() #接收TCP連接, 并返回新的套接字和地址 print 'Connected by', client_addr while True : data = client.recv(BUF_SIZE) #從客戶端接收數據 print data client.sendall(data) #發送數據到客戶端 server.close()</code></pre>

        #客戶端

        !/usr/bin/env python

        -- coding:utf-8 --

        import socket

        BUF_SIZE = 1024 #設置緩沖區的大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket對象 client.connect(server_addr) #要連接的服務器地址 while True: data = raw_input("Please input some string > ")
        client.sendall(data) #發送數據到服務器 data = client.recv(BUF_SIZE) #從服務器端接收數據 print data client.close()</code></pre>

        2.2.1. 帶錯誤處理的客戶端服務器TCP連接

        在進行網絡編程時, 最好使用大量的錯誤處理, 能夠盡量的發現錯誤, 也能夠使代碼顯得更加嚴謹

        </blockquote>

        #服務器端

        !/usr/bin/env python

        -- coding:utf-8 --

        import sys import socket #socket模塊

        BUF_SIZE = 1024 #設置緩沖區大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 try : server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一個新的socket對象 except socket.error, msg : print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() print "Socket Created!" server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #設置地址復用 try : server.bind(server_addr) #綁定地址 except socket.error, msg : print "Binding Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() print "Socket Bind!" server.listen(5) #監聽, 最大監聽數為5 print "Socket listening" while True: client, client_addr = server.accept() #接收TCP連接, 并返回新的套接字和地址, 阻塞函數 print 'Connected by', client_addr while True : data = client.recv(BUF_SIZE) #從客戶端接收數據 print data client.sendall(data) #發送數據到客戶端 server.close()</code></pre>

        #客戶端

        !/usr/bin/env python

        -- coding:utf-8 --

        import sys import socket

        BUF_SIZE = 1024 #設置緩沖區的大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 try : client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket對象 except socket.error, msg : print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() client.connect(server_addr) #要連接的服務器地址 while True: data = raw_input("Please input some string > ")
        if not data : print "input can't empty, Please input again.." continue client.sendall(data) #發送數據到服務器 data = client.recv(BUF_SIZE) #從服務器端接收數據 print data client.close()</code></pre>

        2.3. UDP通信

        UDP通信流程圖如下:
        服務端:socket---bind---recvfrom---sendto---close
        客戶端:socket----------sendto---recvfrom---close

        UDP

        UDP
        </div>

        sendto()函數
        發送UDP數據, 將數據發送到套接字

        #Linux
        int sendto(int sockfd, const void msg,int len,unsigned int flags,const struct sockaddr to, int tolen);
        sockfd : 為前面socket的返回值.
        msg : 一般為常量字符串
        len : 表示長度
        flags : 通常為0
        to : 表示目地機的IP地址和端口號信息, 表示地址的結構體
        tolen : 常常被賦值為sizeof (struct sockaddr)
        返回值 : 返回實際發送的數據字節長度或在出現發送錯誤時返回-1。

        Python

        s.sendto(string[,flag],address) s為socket.socket()返回的套接字對象 address : 指定遠程地址, 形式為(ipaddr,port)的元組 flag : 提供有關消息的其他信息,通常可以忽略 返回值 : 發送的字節數。</code></pre>

        recvfrom()函數
        接受UDP套接字的數據, 與recv()類似

        #Linux
        int recvfrom(int sockfd,void buf,int len,unsigned int flags,struct sockaddr from,int *fromlen);
        sockfd : 為前面socket的返回值.
        msg : 一般為常量字符串
        len : 表示長度
        flags : 通常為0
        from :是一個struct sockaddr類型的變量,該變量保存連接機的IP地址及端口號
        fromlen : 常置為sizeof (struct sockaddr)。
        返回值 : 返回接收到的字節數或當出現錯誤時返回-1,并置相應的errno。

        Python

        s.recvfrom(bufsize[.flag]) 返回值 : (data,address)元組, 其中data是包含接收數據的字符串,address是發送數據的套接字地址 bufsize : 指定要接收的數據大小 flag : 提供有關消息的其他信息,通常可以忽略</code></pre>

        2.4. 簡單的客戶端服務器UDP連接

        #服務器端

        !/usr/bin/env python

        -- coding:utf-8 --

        import socket

        BUF_SIZE = 1024 #設置緩沖區大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字對象 server.bind(server_addr) #套接字綁定IP和端口 while True : print "waitting for data" data, client_addr = server.recvfrom(BUF_SIZE) #從客戶端接收數據 print 'Connected by', client_addr, ' Receive Data : ', data server.sendto(data, client_addr) #發送數據給客戶端 server.close()</code></pre>

        #客戶端

        !/usr/bin/env python

        -- coding:utf-8 --

        import socket import struct

        BUF_SIZE = 1024 #設置緩沖區 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字對象

        while True : data = raw_input('Please Input data > ') client.sendto(data, server_addr) #向服務器發送數據 data, addr = client.recvfrom(BUF_SIZE) #從服務器接收數據 print "Data : ", data client.close()</code></pre>

        2.5. 其他

        s.getpeername()

        返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。

        s.getsockname()

        返回套接字自己的地址。通常是一個元組(ipaddr,port)

        s.setsockopt(level,optname,value)

        設置給定套接字選項的值。

        s.getsockopt(level,optname[.buflen])

        返回套接字選項的值。

        s.settimeout(timeout)

        設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用于連接的操作(如connect())

        s.gettimeout()

        返回當前超時期的值,單位是秒,如果沒有設置超時期,則返回None。

        s.fileno()

        返回套接字的文件描述符。

        s.setblocking(flag)

        如果flag為0,則將套接字設為非阻塞模式,否則將套接字設為阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那么將引起socket.error異常。

        s.makefile()

        創建一個與該套接字相關連的文件</code></pre>

        3. 參考鏈接


        python-socket官方文檔

        來自:http://www.jianshu.com/p/e062b3dd110c

         本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
         轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
         本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!