1. 程式人生 > >python ==》 網絡編程

python ==》 網絡編程

per gif 標準 r+ pri int 幫我 啟用 ron

一、服務端和客戶端

BS架構 (騰訊通軟件:server+client)

CS架構 (web網站)

C/S架構與socket的關系:

我們學習socket就是為了完成C/S架構的開發

二、OSI七層模型

互聯網協議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層

技術分享

學習socket一定要先學習互聯網協議:

1.首先:本節課程的目標就是教會你如何基於socket編程,來開發一款自己的C/S架構軟件

2.其次:C/S架構的軟件(軟件屬於應用層)是基於網絡進行通信的

3.然後:網絡的核心即一堆協議,協議即標準,你想開發一款基於網絡通信的軟件,就必須遵循這些標準。

4.最後:就讓我們從這些標準開始研究,開啟我們的socket編程之旅

技術分享

socket:就是位於 應用層和傳輸層 之間。socket幫我們封裝了一系列協議,統一標準。

技術分享

三、socket是什麽?

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

所以,我們無需深入理解tcp/udp協議,socket已經為我們封裝好了,我們只需要遵循socket的規定去編程,寫出的程序自然就是遵循tcp/udp標準的。

四、套接字發展史及分類

套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通訊。這也被稱進程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基於文件型的和基於網絡型的。

1、基於文件類型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信

2、基於網絡類型的套接字家族

套接字家族的名字:AF_INET

(還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要麽是只用於某個平臺,要麽就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由於我們只關心網絡編程,所以大部分時候我麽只使用AF_INET)

五、套接字工作流程

生活中的場景,你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲後提起電話,這時你和你的朋友就建立起了連接,就可以講話了。等交流結束,掛斷電話結束此次交談。

生活中的場景就解釋了這工作原理,也許TCP/IP協議族就是誕生於生活中,這也不一定。

技術分享

socket例子:

1.服務端與客戶端的正常通信。

技術分享
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind((127.0.0.1,8080))
phone.listen(5)
print(really ==== go!!)

conn,client_addr=phone.accept()
print(conn,client_addr)

data=conn.recv(1024)
conn.send(data.upper())
print(client data:<%s>%data)

conn.close()
phone.close()
服務端 技術分享
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect  ((127.0.0.1,8080))

phone.send(hello.encode(utf-8))
data1 = phone.recv(1024)
print(server back res:<%s>%data1)

phone.close()
客戶端
首先:
服務端 先開始運行,等待接收,
之後,客戶端運行,向服務端發送信息。

結果如下:
服務端:
really ==== go!!
<socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53789)> (‘127.0.0.1‘, 53789)
client data:<b‘hello‘>

客戶端:
server back res:<b‘HELLO‘>

客戶端發了 hello  給服務端, 服務端收到信息,做了 大寫化處理,返回給客戶端。

2.服務端與客戶端的正常通信。socket通信循環

技術分享
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind((127.0.0.1,8080))
phone.listen(5)
print(really ==== go!!)

conn,client_addr=phone.accept()
print(conn,client_addr)

while True:  #通信循環
    data=conn.recv(1024)
    # print(‘server has recv‘)
    conn.send(data.upper())
    print(client data:<%s>%data)

conn.close()
phone.close()
服務端 技術分享
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect  ((127.0.0.1,8080))

while True:
    cmd = input(>>:).strip()
    if not cmd:continue     #如果cmd為空,繼續發
    phone.send(cmd.encode(utf-8))
    print(====> has send)
    data = phone.recv(1024)
    print(server back res:<%s>%data)

phone.close()
客戶端
首先:
這裏比上一個例子,優化了,這裏設置了input,可以自己輸入。
添加了個循環,當客戶端輸入為空,不在報錯,而是需要繼續輸入。

結果如下:
服務端:
really ==== go!!
<socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53865)> (‘127.0.0.1‘, 53865)
client data:<b‘1‘>
client data:<b‘2‘>
client data:<b‘3‘>
client data:<b‘a‘>
client data:<b‘b‘>
client data:<b‘c‘>

客戶端:
>>:1
====> has send
server back res:<b‘1‘>
>>:2
====> has send
server back res:<b‘2‘>
>>:3
====> has send
server back res:<b‘3‘>
>>:a
====> has send
server back res:<b‘A‘>
>>:b
====> has send
server back res:<b‘B‘>
>>:c
====> has send
server back res:<b‘C‘>

註意:
這裏服務端在接到客戶端的額信息是,只做了加大化吃力,所以把abc處理後為ABC返回給客戶端。

3.服務端與客戶端的正常通信。socket鏈接循環。

技術分享
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind((127.0.0.1,8080))
phone.listen(5)
print(really ==== go!!)
while True:  #鏈接循環
    conn,client_addr=phone.accept()
    print(conn,client_addr)

    while True:  #通信循環
        try:
            data=conn.recv(1024)
            # print(‘server has recv‘)
            conn.send(data.upper())
            print(client data:<%s>%data)
        except Exception:
            break
    conn.close()
phone.close()
服務端 技術分享
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect  ((127.0.0.1,8080))

while True:
    cmd = input(>>:).strip()
    if not cmd:continue     #如果cmd為空,繼續發
    phone.send(cmd.encode(utf-8))
    data = phone.recv(1024)
    print(server back res:<%s>%data)
phone.close()
客戶端1 技術分享
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect  ((127.0.0.1,8080))

while True:
    cmd = input(>>:).strip()
    if not cmd:continue     #如果cmd為空,繼續發
    phone.send(cmd.encode(utf-8))
    print(====> has send)
    data = phone.recv(1024)
    print(server back res:<%s>%data)

phone.close()
客戶端2
首先,這裏加的鏈接循環是為了防止,當我們有多個客戶端時,
要關閉其中一個,而不導致整個程序出錯。
在沒做鏈接循環前,當我們關閉了其中一個客戶端,服務端那裏是不能在運行的。

輸出結果:
服務端:
really ==== go!!
<socket.socket fd=384, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53898)> (‘127.0.0.1‘, 53898)
client data:<b‘1‘>
client data:<b‘2‘>
client data:<b‘a‘>
client data:<b‘b‘>
client data:<b‘c‘>
<socket.socket fd=384, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53911)> (‘127.0.0.1‘, 53911)
<socket.socket fd=384, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53911)> (‘127.0.0.1‘, 53911)
client data:<b‘a‘>
client data:<b‘s‘>
client data:<b‘d‘>

客戶端1:
>>:1
server back res:<b‘1‘>
>>:2
server back res:<b‘2‘>
>>:3
server back res:<b‘3‘>
>>:a
server back res:<b‘A‘>
>>:b
server back res:<b‘B‘>
>>:c
server back res:<b‘C‘>


客戶端2:
>>:a
====> has send
server back res:<b‘A‘>
>>:s
====> has send
server back res:<b‘S‘>
>>:d
====> has send
server back res:<b‘D‘>

4.socket模擬ssh遠程執行。

技術分享
import subprocess
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind((127.0.0.1,8081))
phone.listen(5)
print(really ==== go!!)
while True:  #鏈接循環
    conn,client_addr=phone.accept()
    print(conn,client_addr)

    while True:  #通信循環
        try:
            cmd=conn.recv(1080)
            if not cmd: break  #針對linux
            #執行cmd命令,拿到cmd的結果,結果應該是bytes類型
            #。。。
            #發送命令結果
            res = subprocess.Popen(cmd.decode(utf-8), shell=True,
                                   stdout=subprocess.PIPE,  # 正確
                                   stderr=subprocess.PIPE  # 錯誤
                                   )
            stdout = res.stdout.read()
            stderr = res.stderr.read()
            conn.send(stdout+stderr)
        except Exception:
            break
    conn.close()
phone.close()
服務端 技術分享
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect  ((127.0.0.1,8081))

while True:
    cmd = input(>>:).strip()
    if not cmd:continue     #如果cmd為空,繼續發
    phone.send(cmd.encode(utf-8))
    cmd_res = phone.recv(1080)
    print(cmd_res.decode(gbk))
phone.close()
客戶端 技術分享
說明:
1.客戶端遠程執行服務端。
2.登錄的是windows系統,用的是‘gbk’ 編碼。


服務端:
really ==== go!!
<socket.socket fd=400, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(127.0.0.1, 8081), raddr=(127.0.0.1, 53955)> (127.0.0.1, 53955)


客戶端:
>>:dir
 驅動器 E 中的卷沒有標簽。
 卷的序列號是 0001-0682

 E:\zbk\work_\work_8.21 socket模擬ssh遠程執行 的目錄

2017/08/21  19:25    <DIR>          .
2017/08/21  19:25    <DIR>          ..
2017/08/21  19:25               333 客戶端1.py
2017/08/21  16:29               367 客戶端2.py
2017/08/21  19:25               966 服務端2.py
2017/08/21  19:05               413 模塊subprocess.py
               4 個文件          2,079 字節
               2 個目錄 266,249,355,264 可用字節

>>:ipconfig /all

Windows IP 配置

   主機名  . . . . . . . . . . . . . : DESKTOP-0QR7V9H
   主 DNS 後綴 . . . . . . . . . . . : 
   節點類型  . . . . . . . . . . . . : 混合
   IP 路由已啟用 . . . . . . . . . . : 否
   WINS 代理已啟用 . . . . . . . . . : 否

以太網適配器 以太網:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開連接
   連接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
   物理地址. . . . . . . . . . . . . : 80-FA-5B-3C-8F-54
   DHCP 已啟用 . . . . . . . . . . . : 是
   自動配置已啟用. . . . . . . . . . : 是

無線局域網適配器 本地連接* 1:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開連接
   連接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft Wi-Fi Direct Virtual Adapter
   物理地址. . . . . . . . . . . . . : 70-1C-E7-32-BC-D5
   DHCP 已啟用 . . . . . . . . . . . : 是
   自動配置已啟用. . . . . . . . . . : 是

無線局域網適配器 WLAN:

   連接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Intel(R) Dual Band Wireless-
>>:
輸出結果

5.socket 解決粘包問題。

技術分享
import struct
import subprocess
from socket import *

phone = socket(AF_INET,SOCK_STREAM)
phone.bind((127.0.0.1,8080))
phone.listen(5)
print(ready go !!)

while True:
    conn,client.addr=phone.accept()
    print(conn,client_addr)
    while True:
        try:
        cmd = conn.recv(1024)
        if not cmd : break
        res = subprocess.Popen(cmd.decode(utf-8),shell = True,
                                            stdout = stdout.subprocess.PIPE,
                                            stderr = stderr.subprocess.PIPE,)
        stdout  = res.stdout.read()
        stderr = res.stderr.read()
        header = struct.pack(i,len(stdout)+len(stderr))
        conn.send(header)
        conn.send(stdout)
        conn.send(stderr)
        except Exception:
            break
    conn.close()
phone.close()
服務端 技術分享
import struct
from socket import *

phone = socket(AF_INET,SOCK_STREAM)
phone.connect((127.0.0.1,8080))

while True:
    cmd = input(>>:).strip()
    if not cmd : continue
    phone.sent(cmd.encode(utf-8))
    header_struct = phone.recv(4)
    unpack_res = struct.unpack(i,header_struct)
    total_size = unpack_res[0]
    total_data = b‘‘
    recv_size = 0
    while recv_size < total_size:
        recv_data = phone.recv(1024)
        recv_size += len(recv_data)
        total_data += recv_data
    print(total_data.decode(gbk))
phone.close()
客戶端

python ==》 網絡編程