網絡編程之socket

1 簡單的網絡交互實現

1.1 TCP服務器/客戶端

#server_tcp.py
import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 9999,))
sk.listen(5)

# 接受客戶端的請求
while True:
    print('等待鏈接...')
    conn, address = sk.accept()  # 會阻塞,接受客戶端的connect
    conn.sendall(bytes('歡迎致電XXX', encoding='utf-8'))

    while True:
        ret_bytes = conn.recv(1024)
        ret_str = str(ret_bytes, encoding='utf-8')

        if ret_str == 'q':
            break

        conn.sendall(bytes(ret_str + '好', encoding='utf-8'))

    print(conn)
    
------------------------------------------------

#client_tcp.py
import socket

obj = socket.socket()

obj.connect(('127.0.0.1', 9999,))  # 鏈接
print("等待接收服務器數據中...")

ret_bytes = obj.recv(1024)  # 阻塞,若是接收不到消息就等着
print("接收到數據!")
ret_str = str(ret_bytes, encoding='utf-8')
print(ret_str)
while True:
    inp = input('請輸入要發送的內容(q退出):')
    if inp == 'q':
        obj.sendall(bytes(inp, encoding='utf-8'))
        break
    else:
        obj.sendall(bytes(inp, encoding='utf-8'))
        ret_str = str(obj.recv(1024), encoding='utf-8')
        print(ret_str)

obj.close()  # 關閉

1.2 UDP服務器/客戶端

#server_udp.py
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data = sk.recv(1024)
    print data



#client_udp.py
import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = raw_input('數據:').strip()
    if inp == 'exit':
        break
    sk.sendto(inp,ip_port)

sk.close()

2 socket的方法

2.1 socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)服務器

參數一:地址簇網絡

  • socket.AF_INE:IPv4(默認)多線程

  • socket.AF_INET6:IPv6  併發

  • socket.AF_UNIX:只可以用於單一的Unix系統進程間通訊socket

參數二:類型Dtcp

  • 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_RDM:是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RDM用來提供對原始協議的低級訪問,在須要執行某些特殊操做時使用,如發送ICMP報文。SOCK_RDM一般僅限於高級用戶或管理員運行的程序使用。code

  • socket.SOCK_SEQPACKET:可靠的連續數據包服務

參數三:協議

  0  (默認)與特定的地址家族相關的協議,若是是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議

2.2 其餘方法

sk.bind(address)
將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。

sk.listen(backlog)
開始監聽傳入鏈接。backlog指定在拒絕鏈接以前,能夠掛起的最大鏈接數量。
若是backlog等於5,表示內核已經接到了鏈接請求,但服務器尚未調用accept進行處理的鏈接個數最大爲5。這個值不能無限大,由於要在內核中維護鏈接隊列。

sk.setblocking(bool)
是否阻塞(默認True)。若是設置False,那麼accept和recv時一旦無數據,則報錯。IO多路複用時會用到。

sk.accept()
接受鏈接並返回(conn,address),其中conn是新的套接字對象,能夠用來接收和發送數據。address是鏈接客戶端的地址。
接收TCP 客戶的鏈接(阻塞式)等待鏈接的到來

sk.connect(address)
客戶端鏈接到address處的套接字。通常,address的格式爲元組(hostname,port),若是鏈接出錯,返回socket.error錯誤。

sk.connect_ex(address)
同上,只不過會有返回值,鏈接成功時返回0,鏈接失敗時候返回編碼,例如:10061

sk.close()
關閉套接字

sk.recv(bufsize[,flag])
接受套接字的數據。數據以字符串形式返回,bufsize指定最多能夠接收的數量。flag提供有關消息的其餘信息,一般能夠忽略。

sk.recvfrom(bufsize[.flag])
與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。

sk.send(string[,flag])
將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容所有發送。

sk.sendall(string[,flag])
將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。成功返回None,失敗則拋出異常。
內部經過遞歸調用send,將全部內容發送出去。

sk.sendto(string[,flag],address)
將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。

sk.settimeout(timeout)
設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如 client 鏈接最多等待5s )

sk.getpeername()
返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。

sk.getsockname()
返回套接字本身的地址。一般是一個元組(ipaddr,port)

sk.fileno()
套接字的文件描述符

3 文件上傳的簡單實現

#server_file.py

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 9999,))
sk.listen(5)

# 接受客戶端的請求
while True:
    print('等待鏈接...')
    conn, address = sk.accept()  # 會阻塞,接受客戶端的connect
    conn.sendall(bytes('歡迎登陸FTP',encoding='utf-8'))

    # 先接收文件大小
    file_size = str(conn.recv(1024), encoding='utf-8')

    # 與client的接受配合解決粘包問題
    conn.sendall(bytes('ack', encoding='utf-8'))

    total_size = int(file_size)
    has_recv = 0

    f = open('new.png', 'wb')

    # 接收文件內容
    while True:
        if total_size == has_recv:
            break
        data = conn.recv(1024)
        f.write(data)
        has_recv += len(data)
    f.close()
#client_file.py
import socket
import os

obj = socket.socket()

obj.connect(('127.0.0.1', 9999,))  # 鏈接
print("等待接收服務器數據中...")

ret_bytes = obj.recv(1024)  # 阻塞,若是接收不到消息就等着
print("接收到數據!")
ret_str = str(ret_bytes, encoding='utf-8')
print(ret_str)

# 發送當前文件大小
size = os.stat('1.png').st_size
size_bytes = bytes(str(size), encoding='utf-8')
obj.sendall(size_bytes)

# 發送完文件大小以後,執行接受(阻塞),當再次接受到服務器的數據時,就表示收到確認,而後纔開始發送文件內容,此法能夠用於解決粘包問題
obj.recv(1024)

# 發送文件
with open('1.png', 'rb') as f:
    for line in f:
        obj.sendall(line)

obj.close()  # 關閉

TCP協議是面向流的協議,以流爲單位提取數據,這也是容易出現粘包問題的緣由
UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據

4 socketserver模塊

socketserver實現多併發=I/O多路複用+多線程

針對網絡交互實現的併發支持,server端代碼修改以下:

import socketserver

class MyServer(socketserver.BaseRequestHandler):  # 定義本身的類,必須繼承BaseRequestHandler類
    def handle(self):   # 必須使用方法名爲handle的方法
        print("等待客戶端的鏈接..")
        conn = self.request  # 客戶端的鏈接
        conn.sendall(bytes('歡迎致電XXX', encoding='utf-8'))
        
        while True:
            ret_bytes = conn.recv(1024)
            ret_str = str(ret_bytes, encoding='utf-8')

            if ret_str == 'q':
                break

            conn.sendall(bytes(ret_str + '好', encoding='utf-8'))


if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyServer)
    server.serve_forever()  #其內部是一個while循環,不斷等待用戶鏈接
相關文章
相關標籤/搜索