1. 程式人生 > >python全棧開發基礎【第十八篇】網絡編程(socket)

python全棧開發基礎【第十八篇】網絡編程(socket)

回復 pro 解決 gettime connect 問題: 發送 lose post

一、網絡協議

客戶端/服務器架構

1.硬件C/S架構(打印機)

2.軟件C/S架構(互聯網中處處是C/S架構):B/S架構也是C/S架構的一種,B/S是瀏覽器/服務器

C/S架構與socket的關系:我們用socket就是為了完成C/S架構的開發

osi七層

引子:

須知一個完整的計算機系統是由硬件、操作系統、應用軟件三者組成,具備了這三個條件,一臺計算機系統就可以自己跟自己玩了(打個單機遊戲,玩個掃雷啥的)

如果你要跟別人一起玩,那你就需要上網了,什麽是互聯網?

互聯網的核心就是由一堆協議組成,協議就是標準,比如全世界人通信的標準是英語

如果把計算機比作人,互聯網協議就是計算機界的英語。所有的計算機都學會了互聯網協議,那所有的計算機都就可以按照統一的標準去收發信息從而完成通信了。

人們按照分工不同把互聯網協議從邏輯上劃分了層級,

詳見網絡通信原理:http://www.cnblogs.com/linhaifeng/articles/5937962.html

為何學習socket一定要先學習互聯網協議?

  首先C/S架構是基於網絡通信的

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

技術分享圖片

socke層

技術分享圖片

二、socket是什麽?

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

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

三、基於TCP協議的socket

套接字的分類:

  基於文件類型的套接字家族:AF_UNIX(在Unix系統上,一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程同時運行在同一機器,可以通過訪問同一個文件系統間接完成通信)

  基於網絡類型的套接字家族:AF_INET (python支持很多種地址家族,但是由於我們只關心網絡編程,所以大部分時候我們只使用AF_INET)

套接字的工作流程:

下面我們舉個打電話的小例子來說明一下

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

(如果你去一家餐館吃飯,假設哪裏的老板就是服務端,而你自己就是客戶端,當你去吃飯的時候,你肯定的知道那個餐館,也就是服務端的地址吧,但是對於你自己來說,餐館的老板不需要知道你的地址吧)

技術分享圖片

最簡單的套接字函數!!!

#1.服務端端套接字函數
import socket
2 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
3 # 1.服務端套接字函數
4 phone.bind(‘主機ip地址‘,端口號)  #綁定到(主機,端口號)套接字
5 phone.listen() #開始TCP監聽
6 phone.accept() #被動接受TCP客戶的連接,等待連接的到來

#2.客戶端套接字函數
2 import socket
3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機
4 phone.connect()  #主動連接服務端的ip和端口
5 phone.connect_ex()  #connect()函數的擴展版本,出錯的時候返回錯碼,而不是拋出異常
#3.服務端和客戶端的公共用途的嵌套字函數
phone.recv() #接受TCP數據
phone.send() #發送TCP數據
phone.recvfrom() #接受UDP數據
phone.sendto() #發送UDP數據
phone.getpeername() #接收到當前套接字遠端的地址
phone.getsockname() #返回指定套接字的參數
phone.setsockopt() #設置指定套接字的參數
phone.close() #關閉套接字
#面向鎖的套接字方法
phone.setblocking()  #設置套接字的阻塞與非阻塞模式
phone.settimeout()  #設置阻塞套接字操作的超時時間
phone.gettimeout()  #得到阻塞套接字操作的超時時間

#面向文件的套接字函數
phone.fileno()  # 套接字的文件描述符
phone.makefile() #創建一個與該套接字相關的文件

TCP是基於鏈接的,必須先啟動服務器,然後再啟動客戶端去鏈接服務端(三次握手建立連接後,才發送數據)

服務端:

import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #可以多次啟動
#執行多次的時候會報錯,那麽怎麽辦呢、?就在綁卡前面加上上面那句setsockopt方法就ok了
phone.bind((‘192.168.20.44‘,8080))#綁定手機卡(ip,端口)
# 端口號在1024以前的是系統用的,1024以後的都是你自己寫的程序去定義的端口

print(‘starting run......‘)
phone.listen(5) #開機   5代表的是最多掛起5個,也可以好多個
while True: #鏈接循環
    coon,client_addr=phone.accept()#等待接電話,(coon是建立的鏈接,客戶端的ip和端口號組成的元組)
    print(coon,client_addr)

    #收發消息
    while True:  #通信循環
        try:  #如果不加try...except ,就會報錯,因為它不知道你什麽時候斷開鏈接的,服務器還以為你在運行
            data = coon.recv(1024) #收了1024個字節的消息
            print(‘client data 收到消息:%s‘%data.decode(‘utf-8‘))
            coon.send(data.upper())  #發消息
        except Exception:  #因為你不知道客戶端什麽時候斷開鏈接,
            break
    coon.close() #掛電話
phone.close() #關機


# 處理邏輯錯誤的兩種方式:
    # if 判斷
    # try...except 異常處理
# 異常處理
# 當你知道直接錯誤的條件時就用if判斷了
# 當程序錯誤一定發生,但是你又預知不了它出錯的條件是什麽的時候,就用try...except

客戶端:  

import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機
phone.connect((‘192.168.20.44‘,8080))  #直接連接服務端的ip和端口

# 發收消息
while True:
    msg = input(‘>>:‘).strip()  #用戶輸入
    if not msg:continue  #如果為空就繼續輸
    phone.send(msg.encode(‘utf-8‘))  #  發送你輸入的消息
    # phone.send(‘hello‘.encode(‘utf-8‘))
    data = phone.recv(1024)  #在接收一下
    print(‘server back res服務端返回結果:>>%s‘%data.decode(‘utf-8‘))

phone.close()

註意:

如果你在重啟服務端的時候可能遇到這樣的問題:

技術分享圖片

這個是由於你的服務端仍然存在四次揮手的time_wait狀態在占用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務器高並發情況下會有大量的time_wait狀態的優化方法)。那麽怎麽解決呢?

#加入一條socket配置,重用ip和端口
 
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind((‘127.0.0.1‘,8080))

  

四、基於TCP協議模擬ssh遠程執行命令  

#服務端
import socket import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機 phone.bind((‘192.168.20.44‘,8081))#綁定手機卡 phone.listen(5)#阻塞的最大個數 print(‘starting....‘) while True: conn,addr=phone.accept()#等待連接 print(addr,conn) while True: cmd=conn.recv(10240)#接收的最大值 # if not cmd :break print(‘接收的是:%s‘%cmd.decode(‘utf-8‘)) #處理過程 res=subprocess.Popen(cmd.decode(‘utf-8‘),shell=True, #Popen是執行命令的方法 stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout=res.stdout.read() stuerr=res.stderr.read() conn.send(stdout+stuerr) conn.close() phone.close()
#客戶端
import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect((‘192.168.20.44‘,8081))#綁定端口
while True:
    cmd=input(‘>>請輸入‘).strip()
    if  not cmd: continue
    phone.send(cmd.encode(‘utf-8‘))
    data=phone.recv(10240)
    print(‘返回的是%s‘%data.decode(‘gbk‘))
phone.close()

  

六、基於UDP協議的socket

#服務端
from socket import *
udp_server = socket(AF_INET,SOCK_DGRAM)
udp_server.bind((‘127.0.0.1‘,8080)) #綁定
while True:#通訊循環
    msg,client_addr= udp_server.recvfrom(1024)
    print(‘收到的消息是:%s‘%msg.decode(‘utf-8‘))
    udp_server.sendto(msg.upper(),client_addr)
udp_server.close()

# 客戶端
# udp 無鏈接,所以發送數據錢不需要先建立連接
from socket import *
udp_client = socket(AF_INET,SOCK_DGRAM)

while True:
    msg = input(‘>>:‘).strip()
    udp_client.sendto(msg.encode(‘utf-8‘),(‘127.0.0.1‘,8080))
    res,sever_addr = udp_client.recvfrom(1024)
    print(‘返回的結果是:%s‘%res.decode(‘utf-8‘))
udp_client.close()

基於UDP協議的socket的應用(模擬QQ聊天)

# 服務端
from socket import *
udp_server= socket(AF_INET,SOCK_DGRAM)
udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
udp_server.bind((‘127.0.0.1‘,8080))
print(‘start running...‘)

while True:
    qq_msg,addr = udp_server.recvfrom(1024)
    print(‘來自[%s:%s]的一條消息:\033[44m%s\033[0m‘%(addr[0],addr[1],qq_msg.decode(‘utf-8‘)))
    back_msg = input(‘回復消息:>>‘).strip()
    udp_server.sendto(back_msg.encode(‘utf-8‘),addr)
udp_server.close()

#客戶端
from socket import *
udp_client = socket(AF_INET,SOCK_DGRAM)
qq_name_dic = {
    ‘房得成‘:(‘127.0.0.1‘,8080),
    ‘陳鳳琴‘:(‘127.0.0.1‘,8080),
    ‘王雅玲‘:(‘127.0.0.1‘,8080),
    ‘喜洋洋‘:(‘127.0.0.1‘,8080)
}
while True:
    qq_name = input(‘請輸入聊天對象:>>‘).strip()
    if qq_name not in qq_name_dic: continue
    while True:
        msg = input(‘請輸入消息,回車發送:‘).strip()
        if msg==‘quit‘:break
        if not msg or not qq_name or qq_name not in qq_name_dic:continue
        udp_client.sendto(msg.encode(‘utf-8‘),qq_name_dic[qq_name])
        back_msg,addr = udp_client.recvfrom(1024)
        print(‘來自[%s:%s]的一條消息:\033[41m%s\033[0m‘%(addr[0],addr[1],back_msg.decode(‘utf-8‘)))
udp_client.close()

運行結果截圖

技術分享圖片

技術分享圖片

五、subprocess子進程模塊

import subprocess
#Popen方法是用來執行系統命令的,直接把結果打印到終端了
res =subprocess.Popen(r‘dir‘,shell=True,
                       #r‘dsfsdfr‘,shell=True,
                      # stdin= #標準輸入(不常用)
                       stdout=subprocess.PIPE,#stdout 標準輸出
                       stderr=subprocess.PIPE) #stderr 標準錯誤
# 拿到的是‘gbk’編碼的結果,
# 這個命令可能有正確結果,也可能有錯誤結果
print(res.stdout.read().decode(‘gbk‘))
print(‘========‘)
print(res.stdout.read().decode(‘gbk‘))  #說明只能讀一次
print(res.stderr.read().decode(‘gbk‘))  #如果是錯誤的就會提示

六、struct模塊

#該模塊可以把一個類型,如數字,轉成固定長度的bytes類型
import struct
# res = struct.pack(‘i‘,12345)
# print(res,len(res),type(res))  #長度是4

res2 = struct.pack(‘i‘,12345111)
print(res2,len(res2),type(res2))  #長度也是4

unpack_res =struct.unpack(‘i‘,res2)
print(unpack_res)  #(12345111,)
# print(unpack_res[0]) #12345111  

python全棧開發基礎【第十八篇】網絡編程(socket)