1. 程式人生 > >day8--socket網絡編程進階

day8--socket網絡編程進階

連接 緩存 創建過程 sta accep ive line connect 進行

socket:socket就是實現服務器和客戶端數據的交換,服務器端接收並發送數據,客戶端發送並接收數據,並且需要註意的是,在python3中,socket值接收字節。因為客戶端在發送連接給服務器的時候,要轉換為字節碼;服務器端在返回給客戶端的時候,也要轉換為字節碼。

如下所示:

服務器端:

import socket,os
server = socket.socket()
server.bind(("localhost",9999))
server.listen()

while True:
    conn,addr = server.accept()
    print(
"new conn:",addr) while True: data = conn.recv(1024) if not data: print("客戶端已斷開!") break print("執行指令:",data) cmd_res = os.popen(data.decode()).read() print("before send",len(cmd_res)) if len(cmd_res) == 0: cmd_res
= "cmd has no output....." conn.send(cmd_res.encode(utf-8)) print("send done") server.close()

上面是服務器端,使用os.popen()實現數據的處理,不過只能處理字符串,因此需要decode()成字符串格式,然後發送的時候要轉換為字節碼,encode()。

客戶端:

import socket
client = socket.socket()
client.connect(("localhost",9999))

while True:
    cmd 
= input(">>:").strip() if len(cmd) == 0: continue client.send(cmd.encode(utf-8)) ‘‘‘服務器端發送為空,客戶端是卡主的‘‘‘ cmd_res = client.recv(1024) print(cmd_res.decode()) client.close()

首先啟動服務器端,然後啟動客戶端,如下所示:

服務器端發送數據:

>>:1
cmd has no output.....
>>:dir
build_server.py  get_ip.py       socket_server.py  客戶端創建過程.py
class_method.py  lib           s_server.py         類的方法.py
class的方法.py     property屬性.py   static_method.py  人類.py
error_handle.py  s_client.py       動態導入.py         上節內容
get_attr.py     socket_client.py  反射.py

>>:ls
build_server.py
class_method.py
class的方法.py
error_handle.py
get_attr.py
get_ip.py
lib
property屬性.py
s_client.py
socket_client.py
socket_server.py
s_server.py
static_method.py
動態導入.py
反射.py
客戶端創建過程.py
類的方法.py
人類.py
上節內容

客戶端接收數據:

new conn: (127.0.0.1, 55324)
執行指令: b1
before send 0
send done
/bin/sh: 1: 1: not found
執行指令: bdir
before send 249
send done
執行指令: bls
before send 219
send done

當客戶端卡頓的時候,說明服務器端是沒有數據發送過來的,因為客戶端不能接收空的數據服務器端也不能接收空的數據。這樣就會造成卡頓的情況。

在接收的時候,會有一個緩存,緩存不滿的時候,是不會發送數據的,客戶端就接收不到數據。

由於緩存的存在,客戶端接收數據有時候接收不完整,如何避免呢?要求服務器告訴客戶端數據的大小,客戶端根據數據的大小來接收數據。

收發一樣大,告訴客戶端接收數據的大小,如下:

服務器端:

import socket,os,time
server = socket.socket()
server.bind(("localhost",9998))
server.listen()

while True:
    conn,addr = server.accept()
    print("new conn:",addr)
    while True:
        data = conn.recv(1024)
        if not data:
            print("客戶端已斷開!")
            break
        print("執行指令:",data)
        cmd_res = os.popen(data.decode()).read()
        print("before send",len(cmd_res))
        if len(cmd_res) == 0:
            cmd_res = "cmd has no output....."
        conn.send(str(len(cmd_res)).encode(utf-8))     #先發送數據的大小給客戶端 第一個send()發送數據
        time.sleep(1)    #連續發送數據會造成粘包現象,因此要有區分,不然容易粘包,這裏讓程序休眠一秒,先另外一個接收執行
        conn.send(cmd_res.encode(utf-8))     #第二個send()發送數據
        print("send done")

server.close()

在服務器端上,我們計算了發送給客戶端的數據大小,先把數據的大小告知客戶端,接收多大的數據,然後在發送真正的數據給客戶端。在數據發送的時候,要防止粘包,因為send()兩次同時發送,造成粘包的情況,因此讓程序休眠一秒,time.sleep(1),讓客戶端先接收數據,過了一秒重新接收數據,這樣就不會粘包。

粘包情況如下;

>>:1
命令結果大小: b22cmd has no output.....
Traceback (most recent call last):
  File "/home/zhuzhu/第七天/s_client.py", line 15, in <module>
    while received_size < int(cmd_res_size.decode()):
ValueError: invalid literal for int() with base 10: 22cmd has no output.....

客戶端發送數據的時候,服務器返回的時候,由於兩個send()同時發送數據造成粘包的情況,會出現錯誤。兩條數據發送的時候連接到一起,造成粘包。兩次數據發送鏈接到一起,為什麽造成粘包,是因為程序執行的太快,客戶端接收數據很快,速度趕在下一次發送之前。

客戶端:

import socket
client = socket.socket()
client.connect(("localhost",9998))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode(utf-8))
    ‘‘‘服務器端發送為空,客戶端是卡主的‘‘‘
    cmd_res_size = client.recv(1024)     #接收命令結果的長度(服務器發送的)
    print("命令結果大小:",cmd_res_size)

    received_size = 0
    while received_size < int(cmd_res_size.decode()):
        data = client.recv(1024)
        received_size += len(data)    #每次收到的有可能小於1024,所以必須用len()判斷
        print(data.decode())
        print(received_size)
    else:
        print("cmd res receive done......",received_size)

    # cmd_res = client.recv(1024)
    #
    # print(cmd_res.decode())

client.close()

在客戶端中,我們先接收服務器發來的數據的大小,然後開始接收數據,當數據長度小於接收長度時,繼續接收,一直等到沒有數據接收為止。

在客戶端中,接收的數據不一定等於規定的長度。並且要統一格式,我們知道,漢字和英文的長度是不一致的,漢字是由3個字節組成,而因為是由兩個字節組成的。

首先啟動客戶端,然後啟動服務器端,如下:

客戶端發送數據:
>>:1
命令結果大小: b22
cmd has no output.....
22
cmd res receive done...... 22
>>:ls
命令結果大小: b275
build_server.py
class_method.py
class的方法.py
error_handle.py
get_attr.py
get_ip.py
lib
property屬性.py
s_client.py
socket_client.py
socket_server.py
s_server.py
static_method.py
動態導入.py
反射.py
客戶端創建過程.py
類的方法.py
人類.py
上節內容

275
cmd res receive done...... 275
>>:dir
命令結果大小: b305
build_server.py  get_ip.py       socket_server.py  客戶端創建過程.py
class_method.py  lib           s_server.py         類的方法.py
class的方法.py     property屬性.py   static_method.py  人類.py
error_handle.py  s_client.py       動態導入.py         上節內容
get_attr.py     socket_client.py  反射.py

305
cmd res receive done...... 305

從接收數據可以看出,如果有中文的話,接收數據的長度是不一致的,就是由於中文的字節是3個,因此都要轉換為統一格式,現在調整服務器端,讓服務器發送數據的長度的時候也是按照字節的形式發送長度,如下:

import socket,os,time
server = socket.socket()
server.bind(("localhost",9998))
server.listen()

while True:
    conn,addr = server.accept()
    print("new conn:",addr)
    while True:
        data = conn.recv(1024)
        if not data:
            print("客戶端已斷開!")
            break
        print("執行指令:",data)
        cmd_res = os.popen(data.decode()).read()
        print("before send",len(cmd_res))
        if len(cmd_res) == 0:
            cmd_res = "cmd has no output....."
        conn.send(str(len(cmd_res.encode(utf-8))).encode(utf-8))     #先發送數據的大小給客戶端
        time.sleep(1)    #連續發送數據會造成粘包現象,因此要有區分,不然容易粘包,這裏讓程序休眠一秒,先另外一個接收執行
        conn.send(cmd_res.encode(utf-8))
        print("send done")

server.close()

重新啟動服務器,啟動客戶端發送數據,如下:

客戶端輸入指令:
>>:1
命令結果大小: b22
cmd has no output.....
22
cmd res receive done...... 22
>>:ls
命令結果大小: b275
build_server.py
class_method.py
class的方法.py
error_handle.py
get_attr.py
get_ip.py
lib
property屬性.py
s_client.py
socket_client.py
socket_server.py
s_server.py
static_method.py
動態導入.py
反射.py
客戶端創建過程.py
類的方法.py
人類.py
上節內容

275
cmd res receive done...... 275
>>:dir
命令結果大小: b305
build_server.py  get_ip.py       socket_server.py  客戶端創建過程.py
class_method.py  lib           s_server.py         類的方法.py
class的方法.py     property屬性.py   static_method.py  人類.py
error_handle.py  s_client.py       動態導入.py         上節內容
get_attr.py     socket_client.py  反射.py

305
cmd res receive done...... 305

從上面可以看出,當調整服務器端發送長度的方式之後,接收數據的長度和服務器告知客戶端的長度是一致的。因此,在接收和發送數據的時候要以字節碼方式統一計算長度,格式統一很重要,在socket中,計算長度統一格式為:字節碼,發送和接收數據都是以字節碼的形式操作。

socket網絡編程,其實就是收發數據,只能收發字節的形式,要統一字節格式,要進行及時傳喚,要告知客戶端數據的大小。然後接收的時候,按照大小來接收。接收完成之後在繼續進行執行程序。

粘包:兩次發送數據粘到一起。

如何解決粘包:

  (1)sleep(),停留一秒,time.sleep(0.5);

服務器端:

import socket,os,time
server = socket.socket()
server.bind(("localhost",9999))
server.listen()

while True:
    conn,addr = server.accept()
    print("new conn:",addr)
    while True:
        data = conn.recv(1024)
        if not data:
            print("客戶端已斷開!")
            break
        print("執行指令:",data)
        cmd_res = os.popen(data.decode()).read()
        print("before send",len(cmd_res))
        if len(cmd_res) == 0:
            cmd_res = "cmd has no output....."
        conn.send(str(len(cmd_res.encode(utf-8))).encode(utf-8))     #先發送數據的大小給客戶端
        time.sleep(1)    #連續發送數據會造成粘包現象,因此要有區分,不然容易粘包,這裏讓程序休眠一秒,先另外一個接收執行
        conn.send(cmd_res.encode(utf-8))
        print("send done")

server.close()

服務器兩次發送數據的時間有間隔,這樣就能避免數據發送粘包的情況。不過使用sleep()太low了。

(2)第一次發送之後,接收數據,接收客戶端發來的第一次數據發送接收成功的消息,進行第二次發送,這樣就能隔斷數據的發送

服務器端:

import socket,os,time
server = socket.socket()
server.bind(("localhost",9998))
server.listen()

while True:
    conn,addr = server.accept()
    print("new conn:",addr)
    while True:
        data = conn.recv(1024)
        if not data:
            print("客戶端已斷開!")
            break
        print("執行指令:",data)
        cmd_res = os.popen(data.decode()).read()
        print("before send",len(cmd_res))
        if len(cmd_res) == 0:
            cmd_res = "cmd has no output....."
        conn.send(str(len(cmd_res.encode(utf-8))).encode(utf-8))     #先發送數據的大小給客戶端
        # time.sleep(1)    #連續發送數據會造成粘包現象,因此要有區分,不然容易粘包,這裏讓程序休眠一秒,先另外一個接收執行
        clicent_ack = conn.recv(1024)     #等待接收數據,讓下面的發送send()暫時不執行,要收到客戶端的數據大小進行響應。wait to confirm
        print("ack from client:",clicent_ack)
        conn.send(cmd_res.encode(utf-8))
        print("send done")

server.close()

服務器端,要想防止粘包,則在兩次發送數據直接增加一個第一次發送數據成功的確認,即接收成功發送數據的指令,如上面所示,conn.recv()其實也沒有什麽功能,就是隔斷第二次發送的執行時間,讓第二次發送數據在第一次執行完成之後再進行執行;

客戶端:

import socket
client = socket.socket()
client.connect(("localhost",9998))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode(utf-8))
    ‘‘‘服務器端發送為空,客戶端是卡主的‘‘‘
    cmd_res_size = client.recv(1024)     #接收命令結果的長度(服務器發送的)
    print("命令結果大小:",cmd_res_size)
    client.send("準備好接收了,loser,可以發了".encode(utf-8))

    received_size = 0
    while received_size < int(cmd_res_size.decode()):
        data = client.recv(1024)
        received_size += len(data)    #每次收到的有可能小於1024,所以必須用len()判斷
        print(data.decode())
        print(received_size)
    else:
        print("cmd res receive done......",received_size)

    # cmd_res = client.recv(1024)
    #
    # print(cmd_res.decode())

client.close()

客戶端上,數據接收完成之後,發送一條指令,即讓服務器接著發送第二條指令。第一條指令是發送數據的大小,第二條指令是發送數據;

上面就解決了粘包的情況。

其實,socket在發送和就是數據的時候都是同步的,要想隔斷,只能進行成功驗證,先隔斷不讓後面send()執行,只有第一次發送成功之後,並且通過驗證,再來進行第二次執行。

day8--socket網絡編程進階