第五十三節,socket模塊介紹,socket單線程通信

socket單線程通信,只能單線程通信,不能併發python

 

socket是基於(TCP、UDP、IP)的通信、也叫作套接字瀏覽器

通信過程由服務端的socket處理信息發送,由客戶端的socket處理信息接收。服務器

socket一般也稱做"套接字",用於描述IP地址和端口,是一個通訊鏈的句柄,應用程序一般經過"套接字"向網絡發出請求或者應答網絡請求。網絡

socket起源於Unix,而Unix/Linux基本哲學之一就是「一切皆文件」,對於文件用【打開】【讀寫】【關閉】模式來操做。socket就是該模式的一個實現,socket便是一種特殊的文件,一些socket函數就是對其進行的操做(讀/寫IO、打開、關閉)併發

socket和file的區別:socket

file模塊是針對某個指定文件進行【打開】【讀寫】【關閉】函數

socket模塊是針對 服務器端 和 客戶端Socket 進行【打開】【讀寫】【關閉】編碼

 

socket通信過程由服務端的socket處理信息發送,由客戶端的socket處理信息接收。spa

舉例:線程

模擬一個服務端的socket  WEB服務應用

#!/usr/bin/env python
# -*- coding:utf8 -*-
import socket

def handle_request(client):
    buf = client.recv(1024)
    client.sendall(bytes("HTTP/1.1 200 OK\r\n\r\n", encoding='utf-8'))
    client.sendall(bytes("Hello, World", encoding='utf-8'))

def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost',8080))
    sock.listen(5)

    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()

if __name__ == '__main__':
  main()

啓動服務端的socket後,咱們利用瀏覽器的客戶端socket來訪問這個 WEB服務應用 (固然客戶端的socket也能夠本身寫的

打開瀏覽器輸入 http://127.0.0.1:8080/ 就能夠接收到服務端socket輸出的 Hello, World

 

socket通信有基於TCP和UDP兩個通信的,TCP是須要客戶端和服務端相互鏈接後進行通信,UDP是不須要相互鏈接直接由單方面發起的,使用最多的仍是TCP

 

 

socket通信過程
socket通信由服務端和客戶端雙方完成通信,服務端啓動着等待客戶端來鏈接,客戶端從服務端的IP和指定端口進行鏈接通信

 

建立服務端

socket.socket()建立socket對象,有三個可選參數

socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

參數一:地址簇

  socket.AF_INET IPv4(默認)
  socket.AF_INET6 IPv6

  socket.AF_UNIX 只可以用於單一的Unix系統進程間通訊

參數二:類型

  socket.SOCK_STREAM  流式socket , for TCP (默認)
  socket.SOCK_DGRAM   數據報式socket , for UDP

  socket.SOCK_RAW 原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠;其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,能夠經過IP_HDRINCL套接字選項由用戶構造IP頭。
  socket.SOCK_RDM 是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在須要執行某些特殊操做時使用,如發送ICMP報文。SOCK_RAM一般僅限於高級用戶或管理員運行的程序使用。
  socket.SOCK_SEQPACKET 可靠的連續數據包服務

參數三:協議

  0  (默認)與特定的地址家族相關的協議,若是是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議

使用方法:對象變量 = socket.socket()

格式:a = socket.socket()

 

bind()在服務端設置服務端ip和端口

使用方法:對象變量.bind(元祖類型的服務端IP和端口)

格式:a.bind(('127.0.0.1',9999))

 

listen()監聽IP和端口,設置一個參數,表示最多鏈接排隊數量

使用方法:對象變量.listen(排隊數)

格式:a.listen(5)

 

accept()等待接收客戶端的請求,一旦有客戶端請求鏈接,就會返回兩個值,一個是鏈接對象,一個是客戶端的地址信息,因此須要兩個變量來接收

注意:accept()是阻塞的,意思就是程序執行帶這裏就是等待狀態,在沒有客戶端請求鏈接的狀況下,下面的代碼將不會執行,只有客戶端請求鏈接時下面的代碼纔會被執行

accept()被客戶端鏈接一次後就會被中斷,能夠寫個while循環讓它永遠等待客戶端鏈接

使用方法:定義鏈接變量,定義客戶端地址信息變量 = 對象變量.accept()

格式:b, c = a.accept()

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立服務端"""
import socket #導入模塊
a = socket.socket() #建立socket對象
a.bind(('127.0.0.1', 9999,)) #綁定服務端ip和端口
a.listen(5) #監聽IP和端口,設置一個參數,表示最多鏈接排隊數量
while True: #accept()被客戶端鏈接一次後就會被中斷,寫個while循環讓它永遠等待客戶端鏈接
    b, c = a.accept() #等待接收客戶端的請求,一旦有客戶端請求鏈接,就會返回兩個值,一個是鏈接,一個是客戶端的地址信息,因此須要兩個變量來接收
    print(b, c) #打印出客戶端鏈接的,鏈接,和客服端地址信息

 根據以上就建立了一個等待客戶端鏈接的socket服務端

 

建立客戶端

connect()鏈接服務端,在客戶端綁定服務端IP和端口

使用方法:對象變量.socket(元祖類型的服務端IP和端口)

格式:z.connect(('127.0.0.1', 9999,))

 

close()在客戶端關閉鏈接

使用方法:對象變量.close()

格式:z.close()

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立客戶端"""
import socket #導入模塊
z = socket.socket() #建立socket對象
z.connect(('127.0.0.1', 9999,))#鏈接服務端,在客戶端綁定服務端IP和端口
z.close() #在客戶端關閉鏈接

 

客戶端每次鏈接服務端後,服務端的accept()就能獲取到來自客戶端的一個鏈接對象,和客戶端地址信息,兩個元素

 客戶端執行代碼

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立客戶端"""
import socket #導入模塊
z = socket.socket() #建立socket對象
z.connect(('127.0.0.1', 9999,))#鏈接服務端,在客戶端綁定服務端IP和端口
z.close() #在客戶端關閉鏈接

等待接收客戶端鏈接的服務端代碼

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立服務端"""
import socket #導入模塊
a = socket.socket() #建立socket對象
a.bind(('127.0.0.1', 9999,)) #綁定服務端ip和端口
a.listen(5) #監聽IP和端口,設置一個參數,表示最多鏈接排隊數量
while True: #accept()被客戶端鏈接一次後就會被中斷,寫個while循環讓它永遠等待客戶端鏈接
    b, c = a.accept() #等待接收客戶端的請求,一旦有客戶端請求鏈接,就會返回兩個值,一個是鏈接,一個是客戶端的地址信息,因此須要兩個變量來接收
    print(b, c) #打印出客戶端鏈接的,鏈接,和客服端地址信息

#顯示客戶端的鏈接信息
# <socket.socket fd=264, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 58272)> ('127.0.0.1', 58272)

 客戶端訪問服務端流程圖

 

sendall()【推薦】服務端向客戶端發信息,或者客戶端向服務端發信息

服務端:經過accept()接收到的客戶端對象信息變量,客戶端對象信息變量.sendall(字節方式要發送的字符串)

客戶端:經過客戶端的socket對象.sendall(字節方式要發送的字符串)

格式:b.sendall(bytes("歡迎你",encoding='utf-8'))

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立服務端"""
import socket #導入模塊
a = socket.socket() #建立socket對象
a.bind(('127.0.0.1', 9999,)) #綁定服務端ip和端口
a.listen(5) #監聽IP和端口,設置一個參數,表示最多鏈接排隊數量
while True: #accept()被客戶端鏈接一次後就會被中斷,寫個while循環讓它永遠等待客戶端鏈接
    b, c = a.accept() #等待接收客戶端的請求,一旦有客戶端請求鏈接,就會返回兩個值,一個是鏈接,一個是客戶端的地址信息,因此須要兩個變量來接收
    b.sendall(bytes("你好歡迎你",encoding='utf-8')) #根據accept()接收到客戶端鏈接對象信息,向客戶端發送信息

 

recv()【推薦】服務端接收客戶端發來的信息,或者客戶端接收服務端發來的信息

注意:recv()也是阻塞的,也就是說等待接收信息,若是recv()沒接收到信息,下面的代碼就不會執行,只有接收到信息後下面的代碼纔會被執行

服務端:經過accept()接收到的客戶端對象信息變量,客戶端對象信息變量.recv()

客戶端:經過客戶端的socket對象.recv()

格式:f = z.recv(1024)

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立客戶端"""
import socket #導入模塊
z = socket.socket() #建立socket對象
z.connect(('127.0.0.1', 9999,))#鏈接服務端,在客戶端綁定服務端IP和端口
f = z.recv(1024) #客戶端接收服務端sendall()發來的信息,1024表示最大接收1024字節
f2 = str(f, encoding='utf-8') #將接收到的服務端字節信息轉換成字符串
print(f2) #打印出服務端發來的信息
z.close() #在客戶端關閉鏈接
# 輸出
# 你好歡迎你

 客戶端鏈接服務端,服務端向客戶端發送一條信息原理圖

 

 

客戶端與服務端進行交互信息

服務端代碼

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立服務端"""
import socket #導入模塊
a = socket.socket() #建立socket對象
a.bind(('127.0.0.1', 9999,)) #綁定服務端ip和端口
a.listen(5) #監聽IP和端口,設置一個參數,表示最多鏈接排隊數量
while True: #accept()被客戶端鏈接一次後就會被中斷,寫個while循環讓它永遠等待客戶端鏈接
    b, c = a.accept() #等待接收客戶端的請求,一旦有客戶端請求鏈接,就會返回兩個值,一個是鏈接,一個是客戶端的地址信息,因此須要兩個變量來接收
    b.sendall(bytes("你好歡迎你",encoding='utf-8')) #根據accept()接收到客戶端鏈接對象信息,向客戶端發送信息
    while True: #當客戶端鏈接成功後,進入循環,保持與客戶端的通信
        j = b.recv(1024)#接收客戶端發來的信息
        j2 = str(j, encoding='utf-8') #將接收到的客戶端信息轉換成字符串
        print(j2)
        if j2 == "q": #判斷客戶端輸入q,表示再也不與服務端通信,跳出循環,不在保持客戶端的通信
            break
        b.sendall(bytes(j2+"",encoding='utf-8')) #將接收到客戶端的信息加上一個好字,在發送給客戶端

客戶端代碼

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立客戶端"""
import socket #導入模塊
z = socket.socket() #建立socket對象
z.connect(('127.0.0.1', 9999,))#鏈接服務端,在客戶端綁定服務端IP和端口
f = z.recv(1024) #客戶端接收服務端sendall()發來的信息,1024表示最大接收1024字節
f2 = str(f, encoding='utf-8') #將接收到的服務端字節信息轉換成字符串
print(f2) #打印出服務端發來的信息
while True: #當鏈接服務端成功後進入循環,保持與服務端的通信
    a = input("請輸入信息")
    z.sendall(bytes(a,encoding='utf-8')) #向服務端發送信息
    if a == "q": #判斷客戶端輸入 q表示,再也不以服務端通信跳出循環
        break
    js = z.recv(1024) #接收服務端發來的信息
    js2 = str(js, encoding='utf-8') #將服務端發來的信息轉換成字符串
    print(js2)
z.close() #在客戶端關閉鏈接

 客戶端與服務端進行交互信息原理圖

 

UDP通信方式

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""UDP通信"""
#服務端
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)
while True:
    data = sk.recv(1024)
    print(data)
#客戶端
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = input('數據:').strip()
    if inp == 'exit':
        break
    sk.sendto(inp,ip_port)
sk.close()

 

setblocking()設置accept()和recv()是否阻塞,參數爲1表示阻塞【默認】,參數爲0表示不阻塞,那麼accept和recv時一旦無數據,則報錯

使用方法:對象變量.setblocking(0)

格式:a.setblocking(0)

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立服務端"""
import socket #導入模塊
a = socket.socket() #建立socket對象
a.bind(('127.0.0.1', 9999,)) #綁定服務端ip和端口
a.listen(5) #監聽IP和端口,設置一個參數,表示最多鏈接排隊數量
a.setblocking(0) #設置爲不阻塞
b, c = a.accept() #等待接收客戶端的請求,一旦有客戶端請求鏈接,就會返回兩個值,一個是鏈接,一個是客戶端的地址信息,因此須要兩個變量來接收

 

connect_ex()客戶端鏈接服務端,與connect()相同,只不過會有返回值,鏈接成功時返回 0 ,鏈接失敗時候返回編碼,例如:10061

使用方法:對象變量.connect_ex(元祖類型IP和端口)

格式:b = z.connect_ex(('127.0.0.1', 9999,))

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立客戶端"""
import socket #導入模塊
z = socket.socket() #建立socket對象
b = z.connect_ex(('127.0.0.1', 9999,))#鏈接服務端,在客戶端綁定服務端IP和端口
print(b)

 

recvfrom()接收數據信息,與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立客戶端"""
import socket #導入模塊
z = socket.socket() #建立socket對象
b = z.connect(('127.0.0.1', 9999,))#鏈接服務端,在客戶端綁定服務端IP和端口
f,s = z.recvfrom(1024) #客戶端接收服務端sendall()發來的信息,1024表示最大接收1024字節
f = str(f, encoding='utf-8') #將接收到的服務端字節信息轉換成字符串
print(f,s) #打印出服務端發來的信息

 

send()發數據,sendall底層也是調用的它,將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容所有發送。

 

sendto(string[,flag],address) 發送數據,將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。

# 服務端
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data,(host,port) = sk.recvfrom(1024)
    print(data,host,port)
    sk.sendto(bytes('ok', encoding='utf-8'), (host,port))


#客戶端
import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = input('數據:').strip()
    if inp == 'exit':
        break
    sk.sendto(bytes(inp, encoding='utf-8'),ip_port)
    data = sk.recvfrom(1024)
    print(data)

sk.close()

 

settimeout(timeout)設置鏈接超時時間,設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如 client 鏈接最多等待5s )

 

getpeername()客戶端獲取服務端信息或者服務端獲取客戶端信息如IP,返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)

 

getsockname()客戶端獲取服務端信息或者服務端獲取客戶端信息如IP,返回套接字本身的地址。一般是一個元組(ipaddr,port)

 

fileno()通信信號檢測,套接字的文件描述符

 

UDP通信舉例

# 服務端
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data,(host,port) = sk.recvfrom(1024)
    print(data,host,port)
    sk.sendto(bytes('ok', encoding='utf-8'), (host,port))


#客戶端
import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = input('數據:').strip()
    if inp == 'exit':
        break
    sk.sendto(bytes(inp, encoding='utf-8'),ip_port)
    data = sk.recvfrom(1024)
    print(data)

sk.close()

 

基於socket實現文件上傳

列如:客戶端向服務端上傳文件

1.客戶端鏈接上服務端

2.客戶端檢測要傳給服務端的文件大小,而且通信告訴服務端,

3.服務端接收客戶端發來的文件大小信息,接收文件大小信息後通信客戶端告訴客戶端文件大小信息收到(解決粘包問題)

4.客戶端接收服務端是否收到文件大小的信息,沒收到服務端信息阻塞,收到繼續下面執行

5.客戶端已字節方式打開要傳輸的文件,for循環打開的文件,每循環一行向服務端傳輸一行的數據

6.服務端設置一個文件接收了多少的判斷變量默認等於0

7.服務端打開文件等待寫入接收到的客戶端文件數據

8.服務端while 循環接收客戶端發的文件數據而且寫入文件,每循環寫入一次檢查一下服務端,當次接收到客戶端多少字節文件數據,將當次接收到的文件大小字節累計加到,文件接收判斷變量裏

9.if判斷,當文件接收判斷變量等於文件總大小時,說明文件已經接收完成,跳出循環,關閉打開的文件

服務端代碼

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立服務端"""
import socket #導入模塊
a = socket.socket() #建立socket對象
a.bind(('127.0.0.1', 9999,)) #綁定服務端ip和端口
a.listen(5) #監聽IP和端口,設置一個參數,表示最多鏈接排隊數量
while True: #accept()被客戶端鏈接一次後就會被中斷,寫個while循環讓它永遠等待客戶端鏈接
    b, c = a.accept() #等待接收客戶端的請求,一旦有客戶端請求鏈接,就會返回兩個值,一個是鏈接,一個是客戶端的地址信息,因此須要兩個變量來接收
    # 接收客戶端發來的文件大小

    dx = b.recv(1024) #接受客戶端發來的文件大小
    dx2 = str(dx, encoding="utf-8") #將文件大小轉換成字符串
    dx3 = int(dx2) #將文件大小字符串轉換成數字

    b.sendall(bytes("文件大小接收完成", encoding="utf-8")) #接收到文件大小後,告訴客戶端
    #設置接收判斷

    pd = 0 #設置一個接受了文件的判斷大小
    #服務端打開文件接收

    f = open("456.png", "wb")
    while True: #循環寫入接收到客戶端發的文件內容
        if pd == dx3: #判斷,判斷大小等於總大小的時候跳出循環
            break
        j = b.recv(1024) #接收到的文件內容
        f.write(j) #將接收到的內容寫入文件
        pd += len(j) #每循環寫入一次就將寫入的大小加給判斷
    f.close()#關閉打開的文件

客戶端代碼

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""建立客戶端"""
import os
import socket #導入模塊
z = socket.socket() #建立socket對象
b = z.connect(('127.0.0.1', 9999,))#鏈接服務端,在客戶端綁定服務端IP和端口
#檢查發送文件大小

dx = os.stat("32.png").st_size #檢測要發送文件的大小
z.sendall(bytes(str(dx), encoding="utf-8")) #將文件大小發送給服務端

bao = z.recv(1024) #接收服務端文件大小,接收成功後的返回信息
bao2 = str(bao, encoding="utf-8")
print(bao2)
#打開文件循環發送文件

with open("32.png", "rb") as f:
    for i in f: #循環文件
        z.sendall(i) #將每次循環到文件內容發送給服務端
z.close() #關閉通信鏈接

重點:注意傳輸數據時的粘包問題

粘包就是一端發了兩次信息或數據,第一次發的有可能計算機緩衝區還沒發出去,第二次的信息就發到了緩衝區,這樣兩次的信息粘在一塊兒發出去了,爲了不粘包問題,在第一次發送後接收一下另一端是否接收到第一次發的信息,若是接收到才發第二次,若是沒接收到就不發用recv()阻塞,注意上面的列子

注意:socket模塊不能處理併發,也就是不能實現多線路通信,只能一條線路,第二個進來第一個佔線着,要第一個退出後第二個才能進來

相關文章
相關標籤/搜索