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模塊不能處理併發,也就是不能實現多線路通信,只能一條線路,第二個進來第一個佔線着,要第一個退出後第二個才能進來