1. 程式人生 > >網路程式設計<<初識socket>>

網路程式設計<<初識socket>>

楔子


你現在已經學會了寫python程式碼,假如你寫了兩個python檔案a.py和b.py,分別去執行,你就會發現,這兩個python的檔案分別執行的很好。但是如果這兩個程式之間想要傳遞一個數據,你要怎麼做呢?


這個問題以你現在的知識就可以解決了,我們可以建立一個檔案,把a.py想要傳遞的內容寫到檔案中,然後b.py從這個檔案中讀取內容就可以了

網路程式設計<<初識socket>>


但是當你的a.py和b.py分別在不同電腦上的時候,你要怎麼辦呢?

**
類似的機制有計算機網盤,qq等等。我們可以在我們的電腦上和別人聊天,可以在自己的電腦上向網盤中上傳、下載內容。這些都是兩個程式在通訊。

**

開發架構


我們瞭解的涉及到兩個程式之間通訊的應用大致可以分為兩種:


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


第二種是web類:比如百度、知乎、部落格園等使用瀏覽器訪問就可以直接使用的應用


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


  1. C/S架構

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

    這裡的客戶端一般泛指客戶端應用程式EXE,程式需要先安裝 後,才能執行在使用者的電腦上,對使用者的電腦作業系統環境依賴較大。

網路程式設計<<初識socket>>
2.B/S架構

B/S即:Browser與Server,中文意思:瀏覽器端與伺服器端架構,這種架構是從使用者層面來劃分的。
Browser瀏覽器,其實也是一種Client客戶端,只是這個客戶端不需要大家去安裝什麼應用程式,只需在瀏覽器上通過HTTP請求伺服器端相關的資源(網頁資源),客戶端Browser瀏覽器就能進行增刪改查。

網路程式設計<<初識socket>>

網路基礎

計算計網路基礎點選連結:http://blog.51cto.com/14068986/2318949

1.socket概念
網路程式設計<<初識socket>>

理解socket

Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議

其實站在你的角度上看,socket就是一個模組。我們通過呼叫模組中已經實現的方法建立兩個程序之間的連線和通訊。
也有人將socket說成ip+port,因為ip是用來標識網際網路中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程式。
所以我們只要確立了ip和port就能找到一個應用程式,並且使用socket模組來與之通訊。

2.套接字(socket)的發展史

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

基於檔案型別的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆檔案,基於檔案的套接字呼叫的就是底層的檔案系統來取資料,兩個套接字程序執行在同一機器,可以通過訪問同一個檔案系統間接完成通訊

基於網路型別的套接字家族

套接字家族的名字:AF_INET

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

3.tcp協議和udp協議

TCP:
(Transmission Control Protocol)可靠的、面向連線的協議(eg:打電話)、傳輸效率低全雙工通訊(傳送快取&接收快取)、面向位元組流。使用TCP的應用:Web瀏覽器;電子郵件、檔案傳輸程式。

.

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

我知道說這些你們也不懂,直接上圖。

網路程式設計<<初識socket>>

套接字(socket)初使用

基於TCP協議的socket
tcp是基於連結的,必須先啟動服務端,然後再啟動客戶端去連結服務端

server端

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))  #把地址繫結到套接字
sk.listen()          #監聽連結
conn,addr = sk.accept() #接受客戶端連結
ret = conn.recv(1024)  #接收客戶端資訊
print(ret)       #列印客戶端資訊
conn.send(b'hi')        #向客戶端傳送資訊
conn.close()       #關閉客戶端套接字
sk.close()        #關閉伺服器套接字(可選)

client端

import socket
sk = socket.socket()           # 建立客戶套接字
sk.connect(('127.0.0.1',8898))    # 嘗試連線伺服器
sk.send(b'hello!')
ret = sk.recv(1024)         # 對話(傳送/接收)
print(ret)
sk.close()            # 關閉客戶套接字

問題:有的同學在重啟服務端時可能會遇到
網路程式設計<<初識socket>>

解決方法:

#加入一條socket配置,重用ip和埠
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898))  #把地址繫結到套接字
sk.listen()          #監聽連結
conn,addr = sk.accept() #接受客戶端連結
ret = conn.recv(1024)   #接收客戶端資訊
print(ret)              #列印客戶端資訊
conn.send(b'hi')        #向客戶端傳送資訊
conn.close()       #關閉客戶端套接字
sk.close()        #關閉伺服器套接字(可選)

1.基於UDP協議的socket

udp是無連結的,啟動服務之後可以直接接受訊息,不需要提前建立連結
簡單使用
server端

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #建立一個伺服器的套接字
udp_sk.bind(('127.0.0.1',9000))        #繫結伺服器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 對話(接收與傳送)
udp_sk.close()                         # 關閉伺服器套接字

client端

import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

<<qq聊天>>

server

#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_sock.bind(ip_port)

while True:
    qq_msg,addr=udp_server_sock.recvfrom(1024)
    print('來自[%s:%s]的一條訊息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
    back_msg=input('回覆訊息: ').strip()

    udp_server_sock.sendto(back_msg.encode('utf-8'),addr)

client

#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

qq_name_dic={
    '金老闆':('127.0.0.1',8081),
    '哪吒':('127.0.0.1',8081),
    'egg':('127.0.0.1',8081),
    'yuan':('127.0.0.1',8081),
}

while True:
    qq_name=input('請選擇聊天物件: ').strip()
    while True:
        msg=input('請輸入訊息,回車傳送,輸入q結束和他的聊天: ').strip()
        if msg == 'q':break
        if not msg or not qq_name or qq_name not in qq_name_dic:continue
        udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])

        back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
        print('來自[%s:%s]的一條訊息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))

udp_client_socket.close()

<<時間伺服器>>

server

# _*_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()

client

#_*_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)

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()關閉一個獨立的插座。

下一篇是關於黏包現象,老鐵們點個關注。