【網絡接口API】Python Socket與Linux Socket

Python Socket與Linux Socket

[TOC]python

socket: Python的底層網絡接口,通常狀況程序員不須要接觸到這個模塊。有更多的高級模塊,好比requests能夠直接使用。本文章試圖從Python的socket模塊和linux socket api的角度來對Python實現網絡通信方式進行分析,提升對TCP,UDP通信方式的理解。最後用Python實現一個hello/hi的簡單的網絡聊天程序。linux

1. socket

在Python中若是想要使用一個本身的網絡應用層協議,或者說想使用純原生TCP,UDP來實現通信,就須要使用Python的socket模塊。git

import socket

socket模塊提供了訪問BSD套接字的接口。在全部現代Unix系統、Windows、macOS和其餘一些平臺上可用。程序員

1.1 socket()方法

# 使用socket()方法返回一個socket對象
s = socket.socket([family[, type, proto, fileno]])

重要參數:github

  • family: 套接字家族,如ipv4,ipv6,unix系統進程間通訊
  • type: 套接字類型,如tcp,upd
參數 描述
family
socket.AF_INET(默認) IPv4
socket.AF_INET6 IPv6
socket.AF_UNIX Unix系統進程間通訊
type
socket.SOCK_STREAM 流式套接字,TCP
socket.SOCK_DGRAM 數據報套接字,UDP
socket.SOCK_RAW 原始套接字

socket方法與Linux Socket的socket函數對應編程

// socket(協議域,套接字類型,協議)
int socket(int domain, int type, int protocol);

經過s = socket.socket()方法,獲得了一個socket對象。api

Python中的socket對象的成員方法,是對套接字系統調用的高級實現,每每比C語言更高級。服務器


2. TCP

2.1 bind()方法

一般若是是服務器,須要綁定一個總所周知的地址用於提供服務,因此須要綁定一個(IP:PORT),客戶端能夠經過鏈接這個地址來得到服務。而客戶端則直接經過鏈接,由系統隨機分配一個端口號。網絡

python中bind()方法傳入一個地址和端口的元組dom

s.bind((host: str, port: int))

linux socket將套接字做爲對象,傳入一個套接字和地址結構體

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

2.2 listen()方法

開始監聽,backlog指定在拒絕鏈接以前,操做系統能夠掛起的最大鏈接數,默認爲1

s.listen(backlog: int)

linux socket一樣須要額外傳入套接字參數

int listen(int sockfd, int backlog);

2.3 connect()方法

connect方法是客戶端用發起某個鏈接的,接受一個目標主機名和端口號的元組參數

# address -> (hostname, port)
s.connect(address)
# connect_ex是connect的擴展方法,不一樣在於返回錯誤代碼,而不是拋出錯誤
s.connect_ex(address)

linux socket中,參數分別爲客戶端套接字socket描述符,服務器socket地址,socket地址長度

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

2.4 accpet()方法

服務器依次調用socket(), bind(), listen()後就會監聽指定地址,客戶端經過connect()向服務器發起鏈接請求。服務器監聽到請求後會調用accept()函數接受請求。這樣端與端的鏈接就創建好了

python中,accept()方法阻塞進程,等待鏈接,返回一個新的套接字對象和鏈接請求者地址信息。

# accept() -> (socket object, address info)
s.accept()

linux socket中,第一個參數是服務器套接字描述符,第二個爲一個地址指針,用於返回客戶端協議地址,第三個參數是協議地址長度。若是鏈接成功,函數返回值爲內核自動生成的一個全新描述符

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

服務器的監聽套接字通常只建立一個,而accept函數會返回一個鏈接套接字,服務器與客戶端之間的通訊是在鏈接套接字上進行。每來一個服務請求新建一個鏈接套接字與請求者通訊,而監聽套接字只有一個,完成服務後對應的鏈接套接字就會被關閉。(能夠理解成,監聽套接字是專門的接線員,只負責將電話轉接給別的部門)

2.5 recv()與send()

send(data[, flags]) ->count

發送數據到socket,發送前要將數據轉換爲utf-8的二進制格式。返回發送數據長度,由於網絡可能繁忙,致使數據沒有所有發送完畢,因此要再次對剩下的數據進行發送。

python還有一個sendall()

sendall(data[, flags])

做用是不停調用send()函數,直到全部數據發送完畢

linux socket中的send()

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • send()函數先檢查協議是否正在發送緩衝區數據,等待協議發送完畢或則緩衝區已沒有數據,那麼send比較sockfd緩衝區剩餘空間大小和發送數據的len。
  • 若是len大於剩餘空間大小,則等待協議發送緩衝中數據
  • 若len小於剩餘空間,則將buf中數據拷貝到剩餘空間
  • 若發送數據長度大於套接字發送緩衝區長度,則返回-1

python中,從已鏈接套接字讀取數據的函數爲recv()

s.recv(bufsize: int)

從套接字接受數據,若是沒有數據到達套接字,將會阻塞直到來數據或則遠程鏈接關閉。

若是遠程鏈接關閉且數據已所有讀取,則拋出一個錯誤。

linux socket也有讀取數據函數recv()

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • recv()等待s發送緩衝區發送完畢
  • 檢查套接字s的接受緩衝區,若協議正在接收數據,則等待接受完畢。
  • 將接收緩衝區的數據拷到buf中,接受數據可能大於buf長度,因此須要屢次調用recv()

3. UDP

在無鏈接的狀況下,端到端須要使用另外的數據發送和接受方式

3.1 sendto()

python中發送UDP數據,將數據data發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。

s.sendto(data,address)

linux socket: 因爲本地socket並無與遠端機器創建鏈接,因此在發送數據時應指明目的地址

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

該函數比send()函數多了兩個參數,dest_addr表示目地機的IP地址和端口號信息,而addrlen是地址長度。

3.2 recvfrom()

s.recvfrom() -> (data, address)

接收UDP數據,與recv()相似,但返回值是(data,address)。其中data是包含接收的數據,address是發送數據的套接字地址。

linux socket: recvfrom()的狀況與sendto()相似,須要指針來存放發送數據的套接字地址

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

4. close()

python和linux socket都須要對套接字關閉

python:

s.close()

linux socket:

int close(int socketfd)

5. Python實現hello/hi的簡單的網絡聊天程序

5.1 server.py

#! /usr/bin/env python3

import socket
from threading import Thread
import traceback

HOST = "127.0.0.1"
PORT = 65432

def recv_from_client(conn):
    try:
        content = conn.recv(1024)
        return content
    except Exception:
        return None

class ServiceThread(Thread):
    def __init__(self, conn, addr):
        super().__init__()
        self.conn = conn
        self.addr = addr

    def run(self):
        try:
            while True:
                content = recv_from_client(self.conn)
                if not content:
                    break
                print(f"{self.addr}: {content.decode('utf-8')}")
                self.conn.sendall(content)
            self.conn.close()
            print(f"{self.addr[0]}:{self.addr[1]} leave.")
        except Exception:
            traceback.print_exc()

if __name__ == "__main__":
    s = None
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind((HOST, PORT))
        s.listen()
        print("Repeater server started successfully.")
        while True:
            conn, addr = s.accept()
            print(f"Connected from {addr}")
            service_thread = ServiceThread(conn, addr)
            service_thread.daemon = True
            service_thread.start()
    except Exception:
        traceback.print_exc()
    s.close()

5.2 client.py

#! /usr/bin/env python3

import socket
from threading import Thread

HOST = "127.0.0.1"
PORT = 65432

class ReadFromConnThread(Thread):
    def __init__(self, conn):
        super().__init__()
        self.conn = conn

    def run(self):
        try:
            while True:
                content = self.conn.recv(1024)
                print(f"\n({HOST}:{PORT}): {content.decode('utf-8')}\nYOUR:", end="")
        except Exception:
            pass

if __name__ == "__main__":
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
    read_thread = ReadFromConnThread(s)
    read_thread.daemon = True
    read_thread.start()
    while True:
        content = input("YOUR:")
        if content == "quit":
            break
        s.sendall(content.encode("utf-8"))
    s.close()

5.3 運行截圖

  • 服務器

server

  • 客戶端

client

做者:SA19225176,萬有引力丶

參考資料來源:USTC Socket網絡編程

相關文章
相關標籤/搜索