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

1、網絡協議html

客戶端/服務器架構python

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

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層

 

2、socket是什麼?

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

因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。

 

3、基於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))

  

4、基於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()

  

6、基於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()

運行結果截圖

 

5、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'))  #若是是錯誤的就會提示

 

6、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
相關文章
相關標籤/搜索