029_網絡編程

楔子python

  你如今已經學會了寫python代碼,假如你寫了兩個python文件a.py和b.py,分別去運行,你就會發現,這兩個python的文件分別運行的很好。可是若是這兩個程序之間想要傳遞一個數據,你要怎麼作呢?web

  這個問題以你如今的知識就能夠解決了,咱們能夠建立一個文件,把a.py想要傳遞的內容寫到文件中,而後b.py從這個文件中讀取內容就能夠了。shell

  可是當你的a.py和b.py分別在不一樣電腦上的時候,你要怎麼辦呢?編程

  相似的機制有計算機網盤,qq等等。咱們能夠在咱們的電腦上和別人聊天,能夠在本身的電腦上向網盤中上傳、下載內容。這些都是兩個程序在通訊。windows

軟件開發的架構設計模式

  咱們瞭解的涉及到兩個程序之間通信的應用大體能夠分爲兩種:瀏覽器

  第一種是應用類:qq、微信、網盤、優酷這一類是屬於須要安裝的桌面應用緩存

  第二種是web類:好比百度、知乎、博客園等使用瀏覽器訪問就能夠直接使用的應用服務器

  這些應用的本質其實都是兩個程序之間的通信。而這兩個分類又對應了兩個軟件開發的架構~微信

1,C/S架構

  C/S即:Client與Server ,中文意思:客戶端與服務器端架構,這種架構也是從用戶層面(也能夠是物理層面)來劃分的。

  這裏的客戶端通常泛指客戶端應用程序EXE,程序須要先安裝後,才能運行在用戶的電腦上,對用戶的電腦操做系統環境依賴較大。

2,B/S架構

   B/S即:Browser與Server,中文意思:瀏覽器端與服務器端架構,這種架構是從用戶層面來劃分的。

  Browser瀏覽器,其實也是一種Client客戶端,只是這個客戶端不須要你們去安裝什麼應用程序,只需在瀏覽器上經過HTTP請求服務器端相關的資源(網頁資源),客戶端Browser瀏覽器就能進行增刪改查。

 網絡基礎

1,計算機網絡的發展及基礎網絡概念

  1.1,早期 : 聯機

  1.2,以太網 : 局域網與交換機

 

2,交換機 —— 解決 多臺機器以前的通訊問題

3,廣播

  主機之間「一對全部」的通信模式,網絡對其中每一臺主機發出的信號都進行無條件複製並轉發,全部主機均可以接收到全部信息(無論你是否須要),因爲其不用路徑選擇,因此其網絡成本能夠很低廉。有線電視網就是典型的廣播型網絡,咱們的電視機其實是接受到全部頻道的信號,但只將一個頻道的信號還原成畫面。在數據網絡中也容許廣播的存在,但其被限制在二層交換機的局域網範圍內,禁止廣播數據穿過路由器,防止廣播數據影響大面積的主機。

4,ip地址與ip協議

  規定網絡地址的協議叫ip協議,它定義的地址稱之爲ip地址,普遍採用的v4版本即ipv4,它規定網絡地址由32位2進製表示

  3.1,ipv4

    4個點分十進制(4個8位2進制數):00000000.00000000.00000000.00000000

    範圍   0.0.0.0——255.255.255.255

    保留字段 192.168.----

    127.0.0.1 本地的迴環地址

  3.2,ipv6

    6個點分十進制(6個8位2進制數):

    範圍    0.0.0.0.0.0 - 255.255.255.255.255.255

 5,mac地址

  head中包含的源和目標地址由來:ethernet規定接入internet的設備都必須具有網卡,發送端和接收端的地址即是指網卡的地址,即mac地址。

  mac地址:每塊網卡出廠時都被燒製上一個世界惟一的mac地址,長度爲48位2進制,一般由12位16進制數表示(前六位是廠商編號,後六位是流水線號)

6,arp協議 ——查詢IP地址和MAC地址的對應關係

  地址解析協議,即ARP(Address Resolution Protocol),是根據IP地址獲取物理地址的一個TCP/IP協議。
  主機發送信息時將包含目標IP地址的ARP請求廣播到網絡上的全部主機,並接收返回消息,以此肯定目標的物理地址。
  收到返回消息後將該IP地址和物理地址存入本機ARP緩存中並保留必定時間,下次請求時直接查詢ARP緩存以節約資源。
  地址解析協議是創建在網絡中各個主機互相信任的基礎上的,網絡上的主機能夠自主發送ARP應答消息,其餘主機收到應答報文時不會檢測該報文的真實性就會將其記入本機ARP緩存;由此攻擊者就能夠向某一主機發送僞ARP應答報文,使其發送的信息沒法到達預期的主機或到達錯誤的主機,這就構成了一個ARP欺騙。ARP命令可用於查詢本機ARP緩存中IP地址和MAC地址的對應關係、添加或刪除靜態對應關係等。相關協議有RARP、代理ARP。NDP用於在IPv6中代替地址解析協議。 
 
廣域網與路由器

 

1,路由器

  路由器(Router),是鏈接因特網中各局域網、廣域網的設備,它會根據信道的狀況自動選擇和設定路由,以最佳路徑,按先後順序發送信號。 路由器是互聯網絡的樞紐,"交通警察"。目前路由器已經普遍應用於各行各業,各類不一樣檔次的產品已成爲實現各類骨幹網內部鏈接、骨幹網間互聯和骨幹網與互聯網互聯互通業務的主力軍。路由和交換機之間的主要區別就是交換機發生在OSI參考模型第二層(數據鏈路層),而路由發生在第三層,即網絡層。這一區別決定了路由和交換機在移動信息的過程當中需使用不一樣的控制信息,因此說二者實現各自功能的方式是不一樣的。
  路由器(Router)又稱網關設備(Gateway)是用於鏈接多個邏輯上分開的網絡,所謂邏輯網絡是表明一個單獨的網絡或者一個子網。當數據從一個子網傳輸到另外一個子網時,可經過路由器的路由功能來完成。所以,路由器具備判斷網絡地址和選擇IP路徑的功能,它能在多網絡互聯環境中,創建靈活的鏈接,可用徹底不一樣的數據分組和介質訪問方法鏈接各類子網,路由器只接受源站或其餘路由器的信息,屬網絡層的一種互聯設備。 

2,局域網

  如上圖,一個圓圈內是一個局域網

  局域網(Local Area Network,LAN)是指在某一區域內由多臺計算機互聯成的計算機組。通常是方圓幾公里之內。局域網能夠實現文件管理、應用軟件共享、打印機共享、工做組內的日程安排、電子郵件和傳真通訊服務等功能。局域網是封閉型的,能夠由辦公室內的兩臺計算機組成,也能夠由一個公司內的上千臺計算機組成。  

3,子網掩碼 

  所謂」子網掩碼」,就是表示子網絡特徵的一個參數。它在形式上等同於IP地址,也是一個32位二進制數字,它的網絡部分所有爲1,主機部分所有爲0。好比,IP地址172.16.10.1,若是已知網絡部分是前24位,主機部分是後8位,那麼子網絡掩碼就是11111111.11111111.11111111.00000000,寫成十進制就是255.255.255.0。

  知道」子網掩碼」,咱們就能判斷,任意兩個IP地址是否處在同一個子網絡。方法是將兩個IP地址與子網掩碼分別進行AND運算(兩個數位都爲1,運算結果爲1,不然爲0),而後比較結果是否相同,若是是的話,就代表它們在同一個子網絡中,不然就不是。 

好比,已知IP地址172.16.10.1和172.16.10.2的子網掩碼都是255.255.255.0,請問它們是否在同一個子網絡?二者與子網掩碼分別進行AND運算, 172.16.10.1:10101100.00010000.00001010.000000001 255255.255.255.0:11111111.11111111.11111111.00000000 AND運算得網絡地址結果:10101100.00010000.00001010.000000001->172.16.10.0 172.16.10.2:10101100.00010000.00001010.00000010 255255.255.255.0:11111111.11111111.11111111.00000000 AND運算得網絡地址結果:10101100.00010000.00001010.000000001->172.16.10.0 結果都是172.16.10.0,所以它們在同一個子網絡。
View Code

4,網段

# 交換機 —— 多臺機器以前的通訊問題
# 網關的概念 —— 局域網中的機器想要訪問局域網外的機器,須要經過網關訪問
# 網段—— ip地址 和 子網掩碼 按位與  獲得網段地址,即ip地址前三個數。
例如:
# 255.255.255.0   子網掩碼
#192.168.13.253   ip地址的網段爲:192.168.13.0
192.168.13.0 到 192.168.13.255 爲一段

 

5,端口

  • 在計算機上 每個須要網絡通訊的程序 都會開一個端口
  • 在同一時間只會有一個程序佔用一個端口,不可能在同一時間 在同一個計算機上有兩個程序 佔用同一個端口
  • 端口的範圍 : 0-65535
  • 通常狀況下 8000 以後的端口,由於以前的要留給系統用的。

6,一個程序如何在網絡上找到另外一個程序?

  • ip -- 肯定惟一一臺機器
  • 端口 -- 肯定惟一的一個程序
  • ip+端口 找到惟一的一臺機器上的惟一的一個程序

 計算機網絡協議

  互聯網的核心就是由一堆協議組成,協議就是標準,好比全世界人通訊的標準是英語,若是把計算機比做人,互聯網協議就是計算機界的英語。全部的計算機都學會了互聯網協議,那全部的計算機都就能夠按照統一的標準去收發信息從而完成通訊了。

1,osi七層模型

  1.1,互聯網協議按照功能不一樣分爲  osi七層  或  tcp/ip五層  或  tcp/ip四層

  1.2,每層運行常見物理設備

  1.3,每層運行常見的協議

2,socket層  概念

  2.1,理解socket

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

  2.2,站在你的角度上看socket

    其實站在你的角度上看,socket就是一個模塊。咱們經過調用模塊中已經實現的方法創建兩個進程之間的鏈接和通訊。
也有人將socket說成ip+port,由於ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序。
因此咱們只要確立了ip和port就能找到一個應用程序,而且使用socket模塊來與之通訊。

  2.3,socket層

 3,套接字(socket)的發展史

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

  3.1,基於文件類型的套接字家族

套接字家族的名字:AF_UNIX

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

3.2,基於網絡類型的套接字家族

套接字家族的名字:AF_INET

(還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候我麼只使用AF_INET)

4.tcp協議和udp協議

  TCP(Transmission Control Protocol)可靠的、面向鏈接的協議(eg:打電話)、傳輸效率低、全雙工通訊(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;電子郵件、文件傳輸程序。

  UDP(User Datagram Protocol)不可靠的、無鏈接的服務,傳輸效率高(發送前時延小),一對1、一對多、多對1、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視頻流;IP語音(VoIP)。

  TCP---傳輸控制協議,提供的是面向鏈接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間創建一個TCP鏈接,以後才能傳輸數據。TCP提供超時重發,丟棄重複數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另外一端。
  UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,可是並不能保證它們能到達目的地。因爲UDP在傳輸數據報前不用在客戶和服務器之間創建一個鏈接,且沒有超時重發等機制,故而傳輸速度很快

如今Internet上流行的協議是TCP/IP協議,該協議中對低於1024的端口都有確切的定義,他們對應着Internet上一些常見的服務。這些常見的服務能夠分爲使用TCP端口(面向鏈接)和使用UDP端口(面向無鏈接)兩種。 說到TCP和UDP,首先要明白「鏈接」和「無鏈接」的含義,他們的關係能夠用一個形象地比喻來講明,就是打電話和寫信。兩我的若是要通話,首先要創建鏈接——即打電話時的撥號,等待響應後——即接聽電話後,才能相互傳遞信息,最後還要斷開鏈接——即掛電話。寫信就比較簡單了,填寫好收信人的地址後將信投入郵筒,收信人就能夠收到了。從這個分析能夠看出,創建鏈接能夠在須要痛心地雙方創建一個傳遞信息的通道,在發送方發送請求鏈接信息接收方響應後,因爲是在接受方響應後纔開始傳遞信息,並且是在一個通道中傳送,所以接受方能比較完整地收到發送方發出的信息,即信息傳遞的可靠性比較高。但也正由於須要創建鏈接,使資源開銷加大(在創建鏈接前必須等待接受方響應,傳輸信息過程當中必須確認信息是否傳到及斷開鏈接時發出相應的信號等),獨佔一個通道,在斷開鏈接錢不能創建另外一個鏈接,即兩人在通話過程當中第三方不能打入電話。而無鏈接是一開始就發送信息(嚴格說來,這是沒有開始、結束的),只是一次性的傳遞,是先不須要接受方的響應,於是在必定程度上也沒法保證信息傳遞的可靠性了,就像寫信同樣,咱們只是將信寄出去,卻不能保證收信人必定能夠收到。 TCP是面向鏈接的,有比較高的可靠性, 一些要求比較高的服務通常使用這個協議,如FTP、Telnet、SMTP、HTTP、POP3等。 而UDP是面向無鏈接的,使用這個協議的常見服務有DNS、SNMP、QQ等。對於QQ必須另外說明一下,QQ2003之前是隻使用UDP協議的,其服務器使用8000端口,偵聽是否有信息傳來,客戶端使用4000端口,向外發送信息(這也就不難理解在通常的顯IP的QQ版本中顯示好友的IP地址信息中端口常爲4000或其後續端口的緣由了),即QQ程序既接受服務又提供服務,在之後的QQ版本中也支持使用TCP協議了。
更多

 

 

5,TCP協議

  當應用程序但願經過 TCP 與另外一個應用程序通訊時,它會發送一個通訊請求。這個請求必須被送到一個確切的地址。在雙方「握手」以後,TCP 將在兩個應用程序之間創建一個全雙工 (full-duplex) 的通訊。

  這個全雙工的通訊將佔用兩個計算機之間的通訊線路,直到它被一方或雙方關閉爲止。

TCP是因特網中的傳輸層協議,使用三次握手協議創建鏈接。當主動方發出SYN鏈接請求後,等待對方回答SYN+ACK[1],並最終對對方的 SYN 執行 ACK 確認。這種創建鏈接的方法能夠防止產生錯誤的鏈接。[1] TCP三次握手的過程以下: 客戶端發送SYN(SEQ=x)報文給服務器端,進入SYN_SEND狀態。 服務器端收到SYN報文,迴應一個SYN (SEQ=y)ACK(ACK=x+1)報文,進入SYN_RECV狀態。 客戶端收到服務器端的SYN報文,迴應一個ACK(ACK=y+1)報文,進入Established狀態。 三次握手完成,TCP客戶端和服務器端成功地創建鏈接,能夠開始傳輸數據了。
tcp的三次握手
創建一個鏈接須要三次握手,而終止一個鏈接要通過四次握手,這是由TCP的半關閉(half-close)形成的。 (1) 某個應用進程首先調用close,稱該端執行「主動關閉」(active close)。該端的TCP因而發送一個FIN分節,表示數據發送完畢。 (2) 接收到這個FIN的對端執行 「被動關閉」(passive close),這個FIN由TCP確認。 注意:FIN的接收也做爲一個文件結束符(end-of-file)傳遞給接收端應用進程,放在已排隊等候該應用進程接收的任何其餘數據以後,由於,FIN的接收意味着接收端應用進程在相應鏈接上再無額外數據可接收。 (3) 一段時間後,接收到這個文件結束符的應用進程將調用close關閉它的套接字。這致使它的TCP也發送一個FIN。 (4) 接收這個最終FIN的原發送端TCP(即執行主動關閉的那一端)確認這個FIN。[1] 既然每一個方向都須要一個FIN和一個ACK,所以一般須要4個分節。 注意: (1) 「一般」是指,某些狀況下,步驟1的FIN隨數據一塊兒發送,另外,步驟2和步驟3發送的分節都出自執行被動關閉那一端,有可能被合併成一個分節。[2] (2) 在步驟2與步驟3之間,從執行被動關閉一端到執行主動關閉一端流動數據是可能的,這稱爲「半關閉」(half-close)。 (3) 當一個Unix進程不管自願地(調用exit或從main函數返回)仍是非自願地(收到一個終止本進程的信號)終止時,全部打開的描述符都被關閉,這也致使仍然打開的任何TCP鏈接上也發出一個FIN。 不管是客戶仍是服務器,任何一端均可以執行主動關閉。一般狀況是,客戶執行主動關閉,可是某些協議,例如,HTTP/1.0卻由服務器執行主動關閉。[2] 
tcp的四次揮手

6,UDP協議

  當應用程序但願經過UDP與一個應用程序通訊時,傳輸數據以前源端和終端不創建鏈接。

  當它想傳送時就簡單地去抓取來自應用程序的數據,並儘量快地把它扔到網絡上。

套接字(socket)初使用

1,基於TCP協議的socket

    tcp是基於連接的,必須先啓動服務端,而後再啓動客戶端去連接服務端

  1.1,server端

import socket
sk = socket.socket()         # 建立服務器scoket(接口)對象
sk.bind(('127.0.0.1',8898))  # 給本接口綁定地址,端口   # bind 捆綁
sk.listen()                  # 監聽是否有連接請求
conn, addr = sk.accept()     # 接受客戶端連接,地址。建立連接對象conn
# print(conn,addr)
ret = conn.recv(1024)      # recvive接收 客戶端send發送信息;參數設置接:收多少字節
print(ret)                 # 收到的消息是bytes類型的
conn.send(b'helloy')       # 向客戶端發送信息,必須是bytes類型
conn.close()               # 關閉客戶端連接
sk.close()                 # 關閉服務器接口  # 可選

# accept 接受 akˈsept  # connect 鏈接 kəˈnekt  # address 地址 əˈdres,
# receive 接收 riˈsēv

 

  1.2,client端

import socket
sk = socket.socket()         # 建立客戶端scoket(接口)對象
sk.connect(('127.0.0.1',8898)) # 經過本接口嘗試連接要連接的服務器及它的接口
sk.send(b'hello!!')          # 向服務器發送信息,必須是bytes類型
ret = sk.recv(1024)
print(ret)                  # 收到的消息是bytes類型的
sk.close()                  # 關閉客戶端接口

  

  1.3,問題:在重啓服務端時可能會遇到

    解決方法:

#加入一條socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()         # 建立服務器scoket(接口)對象
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898))  # 給本接口綁定地址,端口   # bind 捆綁
sk.listen()                  # 監聽是否有連接請求
conn, addr = sk.accept()     # 接受客戶端連接,地址。建立連接對象conn
# print(conn,addr)
ret = conn.recv(1024)      # recvive接收 客戶端send發送信息;參數設置接:收多少字節
print(ret)                 # 收到的消息是bytes類型的
conn.send(b'helloy')       # 向客戶端發送信息,必須是bytes類型
conn.close()               # 關閉客戶端連接
sk.close()                 # 關閉服務器接口  # 可選

# accept 接受 akˈsept  # connect 鏈接 kəˈnekt  # address 地址 əˈdres,
# receive 接收 riˈsēv 

 

  1.4,稍加完善

import socket sk = socket.socket()         # 建立服務器scoket(接口/套接字)對象
sk.bind(('127.0.0.1',8898))  # 給本接口綁定地址,端口 # bind 捆綁
sk.listen()                  # 監聽是否有連接請求
while True: conn, addr = sk.accept()     # 接受客戶端連接,地址。建立連接對象conn

    while True: msg = conn.recv(1024).decode('utf-8')      # recvive接收 客戶端send發送信息;參數設置接:收多少字節;將接收的信息解碼
        print(msg) if msg == 'bye':    #若是收到客戶端發來的是bye,客戶端想結束
            break info = input('>>>')     # 輸入想向客戶端發送的消息

        if info == 'bye':       # 若是服務端想結束
            conn.send(b'bye')   # 向客戶端發送bye
            break conn.send(info.encode('utf-8'))       # 向客戶端發送信息,必須是bytes類型
 conn.close() # 關閉客戶端連接
sk.close()                 # 關閉服務器接口,若是不關閉,還能繼續接收

# accept 接受 akˈsept # connect 鏈接 kəˈnekt # address 地址 əˈdres, # receive 接收 riˈsēv

# 服務端只開了一個接口,一次只能與一個客戶端創建鏈接, # # 當關閉該客戶端鏈接後,纔會與另外一個客戶端創建鏈接
sever
import socket sk = socket.socket()         # 建立客戶端scoket(接口)對象
sk.connect(('127.0.0.1',8898)) # 經過本接口嘗試連接要連接的服務器及它的接口
while True: msg = input('>>>')    # 輸入要發送的消息

    if msg == 'bye':      # 若是想結束,發送bye
        sk.send(b'bye') break sk.send(msg.encode('utf-8'))          # 向服務器發送信息,必須是bytes類型
    ret = sk.recv(1024).decode('utf-8') if ret == 'bye': break
    print(ret)                  # 收到的消息是bytes類型的
sk.close()                  # 關閉客戶端接口
client
import socket sk = socket.socket()         # 建立客戶端scoket(接口)對象
sk.connect(('127.0.0.1',8898)) # 經過本接口嘗試連接要連接的服務器及它的接口
while True: msg = input('>>>')    # 輸入要發送的消息

    if msg == 'bye':      # 若是想結束,發送bye
        sk.send(b'bye') break sk.send(('client2:' + msg).encode('utf-8'))          # 向服務器發送信息,必須是bytes類型
    ret = sk.recv(1024).decode('utf-8') if ret == 'bye': break
    print(ret)                  # 收到的消息是bytes類型的

# 運行後,若是服務端已經與客戶端創建鏈接,此處會等待, # 當服務端關閉鏈接後會與該客戶端創建鏈接,並接收到發送的消息
client2

 

2,基於UDP協議的socket

    udp是無連接的,啓動服務以後能夠直接接受消息,不須要提早創建連接

  2.1,server端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)  #DGRAM datagram
sk.bind(('127.0.0.1',8080))       #只有服務端有的

msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.sendto(b'bye',addr)

sk.close()


# udp的server 不須要進行監聽也不須要創建鏈接
# 在啓動服務以後只能被動的等待客戶端發送消息過來
# 客戶端發送消息的同時還會 自帶地址信息
# 消息回覆的時候 不只須要發送消息,還須要把對方的地址填寫上

   2.2,client端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)

sk.sendto(b'hello',ip_port)
ret,addr = sk.recvfrom(1024)
print(ret.decode('utf-8'))

sk.close()

# client端不須要connect 由於UDP協議是不須要創建鏈接的
# 直接瞭解到對方的ip和端口信息就發送數據就好了
# sendto和recvfrom的使用方法是徹底和server端一致的

 

3, qq聊天

  3.1,簡單實現 

#_*_coding:utf-8_*_
import socket ip_prot = ('127.0.0.1', 8081) sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sk.bind(ip_prot) while True: qq_msg, addr = sk.recvfrom(1024) print('%s:%s:\033[1;44m%s\033[0m'%(addr[0],addr[1],qq_msg.decode('utf-8'))) back_msg = input('me:').strip() sk.sendto(back_msg.encode('utf-8'), addr)
sever
#_*_coding:utf-8_*_
import socket BUFSIZE = 1024   # buffer 緩衝 [ˈbʌfə(r)]
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) qq_name_dic = { 'Alice':('127.0.0.1',8081), '亞絲娜':('127.0.0.1',8081), '桐人':('127.0.0.1',8081), '尤吉歐':('127.0.0.1',8081), } while True: qq_name = input('請選擇聊天對象:').strip() if not qq_name or qq_name not in qq_name_dic: print('不是您的好友!') continue
    while True: msg = input('請輸入消息,回車發送,輸入q結束和他的聊天:').strip() if msg == 'q':break sk.sendto(msg.encode('utf-8'), qq_name_dic[qq_name]) back_msg, addr = sk.recvfrom(BUFSIZE) print('%s:%s\033[1;44m%s\033[0m'%(addr[0], addr[1],back_msg.decode('utf-8'))) sk.close() # 本qq例,無論人物有幾個,每次只能與其中一我的物對話。 # 終端只建立了一個套接字對象,即終端的地址端口是一個,不是說一我的物一個端口。
client

  3.2,稍加完善

#_*_coding:utf-8_*_
import socket ip_prot = ('127.0.0.1', 8080) sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(ip_prot) while True: qq_msg, addr = sk.recvfrom(1024) print('%s:%s'%(addr,qq_msg.decode('utf-8'))) back_msg = input('SAO:').strip() back_msg = ('\033[34mSAO :%s\033[0m'%back_msg).encode('utf-8') sk.sendto(back_msg, addr) sk.close() # 可分別與各終端互動, # 但本例中的服務端,在接收到一個終端的消息後必須回覆該終端消息後才能接收新消息。 # 本服務端只能被動互動
sever

 

import socket sk = socket.socket(type=socket.SOCK_DGRAM) ip_port = ('127.0.0.1',8080) while True: info = input('亞絲娜 :') info = ('\033[34m亞絲娜 :%s\033[0m'%info).encode('utf-8') sk.sendto(info,ip_port) msg,addr = sk.recvfrom(1024) print(msg.decode('utf-8')) sk.close()
client
import socket sk = socket.socket(type=socket.SOCK_DGRAM) ip_port = ('127.0.0.1',8080) while True: info = input('桐人 :') info = ('\033[32m桐人 :%s\033[0m'%info).encode('utf-8') sk.sendto(info,ip_port) msg,addr = sk.recvfrom(1024) print(msg.decode('utf-8')) sk.close()
client2

4,時間服務器

# _*_coding:utf-8_*_
from socket import *
from time import strftime ip_port = ('127.0.0.1', 9000) bufsize = 1024 tcp_server = socket(AF_INET, SOCK_DGRAM) tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) tcp_server.bind(ip_port) while True: msg, addr = tcp_server.recvfrom(bufsize) print('===>', msg) if not msg: time_fmt = '%Y-%m-%d %X'
    else: time_fmt = msg.decode('utf-8') back_msg = strftime(time_fmt) tcp_server.sendto(back_msg.encode('utf-8'), addr) tcp_server.close()
server
#_*_coding:utf-8_*_
from socket import * ip_port=('127.0.0.1',9000) bufsize=1024 tcp_client=socket(AF_INET,SOCK_DGRAM) while True: msg=input('請輸入時間格式(例%Y %m %d)>>: ').strip() tcp_client.sendto(msg.encode('utf-8'),ip_port) data=tcp_client.recv(bufsize)
client

 5,socket參數的詳解

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

 

建立socket對象的參數說明:
family 地址系列應爲AF_INET(默認值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。
(AF_UNIX 域其實是使用本地 socket 文件來通訊)
type 套接字類型應爲SOCK_STREAM(默認值),SOCK_DGRAM,SOCK_RAW或其餘SOCK_常量之一。
SOCK_STREAM 是基於TCP的,有保障的(即能保證數據正確傳送到對方)面向鏈接的SOCKET,多用於資料傳送。
SOCK_DGRAM 是基於UDP的,無保障的面向消息的socket,多用於在網絡上發廣播信息。
proto 協議號一般爲零,能夠省略,或者在地址族爲AF_CAN的狀況下,協議應爲CAN_RAW或CAN_BCM之一。
fileno 若是指定了fileno,則其餘參數將被忽略,致使帶有指定文件描述符的套接字返回。
與socket.fromfd()不一樣,fileno將返回相同的套接字,而不是重複的。
這可能有助於使用socket.close()關閉一個獨立的插座。 

 黏包問題

題:回答

# 爲何會出現黏包現象?
    # 首先只有在TCP協議中才會出現黏包現象,
    # 是由於TCP協議是面向流的協議
    # 在發送的數據傳輸的過程當中還有緩存機制來避免數據丟失
    # 所以 在連續發送小數據的時候 以及接收大小不符的時候都容易出現黏包現象
    # 本質仍是由於咱們在接收數據的時候不知道發送的數據的長短
# 解決黏包問題
    # 在傳輸大量數據以前先告訴接收端要發送的數據大小
    # 若是想更漂亮的解決問題,能夠經過struct模塊來定製協議

 

 

讓咱們基於tcp先製做一個遠程執行命令的程序(命令ls -l ; lllllll ; pwd)

res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 的結果的編碼是以當前所在的系統爲準的,若是是windows,那麼res.stdout.read()讀出的就是GBK編碼的,在接收端須要用GBK解碼 且只能從管道里讀一次結果
注意

同時執行多條命令以後,獲得的結果極可能只有一部分,在執行其餘命令的時候又接收到以前執行的另一部分結果,這種顯現就是黏包。

1,基於tcp協議實現的黏包

#_*_coding:utf-8_*_
from socket import *
import subprocess ip_port=('127.0.0.1',8888) BUFSIZE=1024 tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) while True: conn,addr=tcp_socket_server.accept() print('客戶端',addr) while True: cmd=conn.recv(BUFSIZE) if len(cmd) == 0:break res=subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) stderr=res.stderr.read() stdout=res.stdout.read() conn.send(stderr) conn.send(stdout)
tcp - server
#_*_coding:utf-8_*_
import socket BUFSIZE=1024 ip_port=('127.0.0.1',8888) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) while True: msg=input('>>: ').strip() if len(msg) == 0:continue
    if msg == 'quit':break s.send(msg.encode('utf-8')) act_res=s.recv(BUFSIZE) print(act_res.decode('utf-8'),end='')
tcp - client

2,基於udp協議實現的黏包

#_*_coding:utf-8_*_
from socket import *
import subprocess ip_port=('127.0.0.1',9000) bufsize=1024 udp_server=socket(AF_INET,SOCK_DGRAM) udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) udp_server.bind(ip_port) while True: #收消息
    cmd,addr=udp_server.recvfrom(bufsize) print('用戶命令----->',cmd) #邏輯處理
    res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE) stderr=res.stderr.read() stdout=res.stdout.read() #發消息
 udp_server.sendto(stderr,addr) udp_server.sendto(stdout,addr) udp_server.close()
udp - server
from socket import * ip_port=('127.0.0.1',9000) bufsize=1024 udp_client=socket(AF_INET,SOCK_DGRAM) while True: msg=input('>>: ').strip() udp_client.sendto(msg.encode('utf-8'),ip_port) err,addr=udp_client.recvfrom(bufsize) out,addr=udp_client.recvfrom(bufsize) if err: print('error : %s'%err.decode('utf-8'),end='') if out: print(out.decode('utf-8'), end='')
udp - client

注意:只有TCP有粘包現象,UDP永遠不會粘包

相關文章
相關標籤/搜索