Python基礎:網絡編程socket基本篇

socket也叫套接字,是對各種協議的封裝,實現收發數據。


Python裏socket工作過程:(圖片來自網絡)

d000baa1cd11728b45647b06cafcc3cec3fd2c4c.jpg


socket在Python中實際上是一個模塊,實現發送和接收數據的功能。


因爲socket是一個類,所以只導入模塊需要使用socket.socket()創建一個socket對象。


  • 創建一個socket格式:

socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

參數名選項名稱作用
familyAF_UNIX
unix系統進程間傳輸數據
AF_INETIPv4網絡傳輸數據
AF_INET6IPv6網絡傳輸數據
typeSOCK_STREAM
流式數據,TCP

SOCK_DGRAM數據報式數據,UDP

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

SOCK_RDM
是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在需要執行某些特殊操作時使用,如發送ICMP報文。SOCK_RAM通常僅限於高級用戶或管理員運行的程序使用。

SOCK_SEQPACKET
連續的數據包傳輸(已廢棄)
proto
0默認是0,根據地址簇和套接類別自動選擇合適的協議
fileno默認是None
If fileno is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike socket.fromfd(), fileno will return the same socket and not a duplicate. This may help close a detached socket using socket.close().



  • socket對象的方法:

    1、socket分爲服務端和客戶端。

    2、TCP傳輸不需要IP,UDP傳輸需要IP地址。

    3、socket傳輸字符串需要變成byte型。

    4、列表、字典等數據也需要成變byte型。json處理過的數據是字符型的,decode後可以進行send。

    5、傳輸大數據,使用長度時,要注意len的對象是原數據,還是encode後的數據,接收方也得計算相應的數據。否則會造成文件長度不匹配


方法名對象作用
bind(地址)
服務端綁定服務端地址,IPv4下,是元組的形式(地址,端口)
listen(backlog)服務端設定客戶端連接數量,數字
accept()服務端

完整的接收信息:

(<socket.socket fd=316, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6666), raddr=('127.0.0.1', 58775)>, ('127.0.0.1', 58775))

<>部分是套接字信息

後面元組是客端地址。

connect(地址)客戶端綁定服務端地址,IPv4下,是元組的形式(地址,端口)
connect_ex()客戶端功能與connect相同,但是成功返回0,失敗返回errno的值。
s.recv(bufsize[,flag])服務和客戶端

接受TCP套接字的數據。數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其他信息,通常可以忽略。

bufsize官方建議8192,不同系統最大數值不同,一般一次可以收10M左右。

s.send(string[,flag])發送TCP數據。將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。
s.sendall(string[,flag])

完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。

s.recvfrom(bufsize[.flag])接受UDP套接字的數據。與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
s.sendto(string[,flag],address)發送UDP數據。將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。
s.close()關閉套接字。
s.getpeername()返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。
s.getsockname()返回套接字自己的地址。通常是一個元組(ipaddr,port)
s.setsockopt(level,optname,value)設置給定套接字選項的值。
s.getsockopt(level,optname[.buflen])返回套接字選項的值。
s.settimeout(timeout)設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因爲它們可能用於連接的操作(如 client 連接最多等待5s )
sk.fileno()套接字的文件描述符


  • 服務端連接實例:

import socket

server = socket.socket()
server.bind("localhost",6666)   # localhost是本地主機,也可以寫172.0.0.1
server.listen(6)                # 同時允許5個客戶端

while True:                     # 此位置的while是爲了客戶端結束後,再等待其它客戶端進入。
    conn,addr = server.accept() # 接收一套接字信息,和地址。對應的是客戶端的connect
    
    while True:
        conn.recv(1024)         # 服務端先接收數據,可以改變每次接收的數值,但是不要小於客戶端發送的值。
        conn.send(b'00000')              # 字符前面加r,變成字節數據,纔可以傳輸
        # 這裏就是互相通信的主體,可以有多個recv和send,需要注意的是,一收一發,要和客戶端對應
        # 服務端和客戶端不能同時收或同時發。
        
                break                            # 結束此客戶端,繼續listen其它客戶端


  • 客戶端連接實例:

import socket

client = socket.socket()                        # 創建套接字對象
client.connect(('localhost',6666))              # 連接的主機名和端 口,也可以是字符串的ip地址  "127.0.0.1"
while True:
    client.send(b'11111')
    client.recv(1024)
    # 這裏就是互相通信的主體,可以有多個recv和send,需要注意的是,一收一發,要和客戶端對應
    break

client.close()                                  # 客戶端關閉連接。


  • 簡單FTP製作的問題點

json.decoder.JSONDecodeError: Extra data: 

因爲傳輸的過程中有二進制數據,所以json無法decode。


傳輸文件完成時怎麼返回?

客戶端都一收一發。並且在傳輸個列表,第一項是標誌,第二項是True,當兩項不匹配時,提示錯誤,並返回選項列表。



有時候服務器運行程序時間長,沒有到接收語句,而客戶端發送數據太快,導致出錯

在客戶端加個sleep...



logging使用filehandler中文亂碼

創建filehandler時,寫入encode參數

file_handler = logging.FileHandler(log_path,encoding='utf-8')


調用logging模塊,重複輸出

1、使用removeHandler()把這個logger裏的handler移除掉

2、在log方法裏做判斷,如果這個logger已有handler,則不再添加handler。


字典、列表無法傳輸

使用json序列化後傳輸。json序列化後原來是字節的!


“粘包”:A給B連續發送兩個send,B接收到的兩個數據都混在一起,分不清第一次還是第二次接收的

原因是,A發送時存在緩存區,大約0.5S後緩存消失

如果想要分開兩次的數據,A使用send後立即執行recv,接收B的send,然後再發送第二句。

            '''更好的解決粘包'''
            # recv_file_size 已經接收的大小
            # file_size      文件總大小
            # base_recv      每次接收的大小
            base_recv = 1024
            recv_file_size = 0
            while recv_file_size<file_size:

                data = conn.recv(base_recv).decode()
                if file_size-recv_file_size < base_recv:
                    base_recv = file_size-recv_file_size
                recv_file_size =+ len(data)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章