一、socket(單鏈接) 1、socket:應用層與TCP/IP協議族通信的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面;也有人將socket說成ip+port,ip是用來標識互聯網中的一臺主機的位置,而por ...
一、socket(單鏈接)
1、socket:應用層與TCP/IP協議族通信的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面;也有人將socket說成ip+port,ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這台機器上的一個應用程式,ip地址是配置到網卡上的,而port是應用程式開啟的,ip與port的綁定就標識了互聯網中獨一無二的一個應用程式;而程式的pid是同一臺機器上不同進程或者線程的標識。
2、套接字:用於在同一臺主機上多個應用程式之間的通訊。套接字有兩種(或者稱為有兩個種族),分別是基於文件型(AF_UNIX)和基於網路型(AF_INET)。
3、基於TCP的套接字(類型一)
工作原理:先從伺服器端說起。伺服器端先初始化Socket,然後與埠綁定(bind),對埠進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後連接伺服器(connect),如果連接成功,這時客戶端與伺服器端的連接就建立了。客戶端發送數據請求,伺服器端接收請求並處理請求,然後把回應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 import socket #導入socket模塊 5 ip_port = ("127.0.0.1",9999) #設置伺服器ip和埠 6 server = socket.socket() #創建server實例 //聲明socket類型同時生成socket對象 7 server.bind(ip_port) #套接字綁定ip與埠 8 server.listen(5) #監聽連接//允許5個客戶端排隊 9 conn,addr = server.accept() #等待客戶端連接 // 客戶端連接後,返回新的套接字與IP地址 10 client_data = conn.recv(1024) #接收數據//把接收的數據實例化 11 #client_data = b'hello' 12 conn.sendall(client_data.upper()) #把數據發送到客戶端 //upper() 字母變成大寫 13 conn.close() #關閉連接#TCP_server
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 import socket #導入socket模塊 5 ip_port = ("127.0.0.1",9999) #設置伺服器ip和埠 6 client = socket.socket() #創建client實例 7 client.connect(ip_port) #設置要連接的ip和埠 8 info = "hello world" #要發送的數據 9 client.sendall(info.encode("utf-8")) #發送數據// 把str轉換為bytes類型 10 server_data = client.recv(1024) #接收數據 11 client.close() #關閉連接#TCP_client
1 ① server = socket.socket() 2 套接字格式:socket(family,type[,protocal]) 使用給定的地址族、套接字類型、協議編號(預設為0)來創建套接字。 3 參數一:地址簇 4 socket.AF_INET IPv4(預設) 5 socket.AF_INET6 IPv6 6 socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信 7 參數二:類型 8 socket.SOCK_STREAM 流式socket , for TCP (預設) 9 socket.SOCK_DGRAM 數據報式socket , for UDP 10 socket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網路報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。 11 socket.SOCK_RDM 是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在需要執行某些特殊操作時使用,如發送ICMP報文。SOCK_RAM通常僅限於高級用戶或管理員運行的程式使用。 12 socket.SOCK_SEQPACKET 可靠的連續數據包服務 13 參數三:協議 14 0 (預設)與特定的地址家族相關的協議,如果是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議 15 #創建TCP Socket:server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 16 #創建UDP Socket:server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 17 註意點: 18 1)TCP發送數據時,已建立好TCP連接,所以不需要指定地址。UDP是面向無連接的,每次發送要指定是發給誰。 19 2)服務端與客戶端不能直接發送列表,元組,字典。需要字元串化repr(data) 20 ② server.bind(address) 21 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址 22 ③ server.listen(backlog) 23 開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。該值至少為1,大部分應用程式設為5就可以了。backlog等於5,表示內核已經接到了連接請求,但伺服器還沒有調用accept進行處理的連接個數最大為5,這個值不能無限大,因為要在內核中維護連接隊列 24 ④ server.setblocking(bool) 25 是否阻塞(預設True),如果設置False,那麼accept和recv時一旦無數據,則報錯 26 ⑤ conn,addr = server.accept() 27 接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。接收TCP 客戶的連接(阻塞式)等待連接的到來 28 ⑥ client.connect(address) 29 連接到address處的套接字。一般address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。 30 ⑦ client.connect_ex(address) 31 同上,只不過會有返回值,連接成功時返回 0 ,連接失敗時候返回編碼,例如:10061 32 ⑧ client.close() 33 關閉套接字 34 ⑨ client.recv(bufsize[,flag]) 35 接受套接字的數據。數據以字元串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略 36 ⑩ client.recvfrom(bufsize[.flag]) 37 與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字元串,address是發送數據的套接字地址 38 ⑪ server.send(string[,flag]) 39 發送TCP數據;將string中的數據發送到連接的套接字。返回值是要發送的位元組數量,該數量可能小於string的位元組大小。即:可能未將指定內容全部發送 40 ⑫ server.sendall(string[,flag]) 41 完整發送TCP數據;將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常;內部通過遞歸調用send,將所有內容發送出去 42 ⑬ server.sendto(string[,flag],address) 43 將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的位元組數。該函數主要用於UDP協議 44 ⑭ sk.settimeout(timeout) 45 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作(如 client 連接最多等待5s ) 46 ⑮ sk.getpeername() 47 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port) 48 ⑯ sk.getsockname() 49 返回套接字自己的地址。通常是一個元組(ipaddr,port) 50 ⑰ sk.fileno() 51 套接字的文件描述符 52 53 54 ###服務端套接字函數### 55 s.bind() #綁定(主機,埠號)到套接字 56 s.listen() #開始TCP監聽 57 s.accept() #被動接受TCP客戶的連接,(阻塞式)等待連接的到來 58 59 ###客戶端套接字函數### 60 s.connect() #主動初始化TCP伺服器連接 61 s.connect_ex() #connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常 62 63 ###公共用途的套接字函數### 64 s.recv() #接收TCP數據 65 s.send() #發送TCP數據(send在待發送數據量大於己端緩存區剩餘空間時,數據丟失,不會發完) 66 s.sendall() #發送完整的TCP數據(本質就是迴圈調用send,sendall在待發送數據量大於己端緩存區剩餘空間時,數據不丟失,迴圈調用send直到發完) 67 s.recvfrom() #接收UDP數據 68 s.sendto() #發送UDP數據 69 s.getpeername() #連接到當前套接字的遠端的地址 70 s.getsockname() #當前套接字的地址 71 s.getsockopt() #返回指定套接字的參數 72 s.setsockopt() #設置指定套接字的參數 73 s.close() #關閉套接字 74 75 ###面向鎖的套接字方法### 76 s.setblocking() #設置套接字的阻塞與非阻塞模式 77 s.settimeout() #設置阻塞套接字操作的超時時間 78 s.gettimeout() #得到阻塞套接字操作的超時時間 79 80 ###面向文件的套接字的函數### 81 s.fileno() #套接字的文件描述符 82 s.makefile() #創建一個與該套接字相關的文件#參數功能解釋
4、基於UDP的套接字(類型二)
udp是無鏈接的,先啟動哪一端都不會報錯且可以同時多個客戶端去跟服務端通信
1 #UDP server 2 ss = socket() #創建一個伺服器的套接字 3 ss.bind() #綁定伺服器套接字 4 inf_loop: #伺服器無限迴圈 5 cs = ss.recvfrom()/ss.sendto() # 對話(接收與發送) 6 ss.close() # 關閉伺服器套接字 7 8 9 #UDP client 10 cs = socket() # 創建客戶套接字 11 comm_loop: # 通訊迴圈 12 cs.sendto()/cs.recvfrom() # 對話(發送/接收) 13 cs.close() # 關閉客戶套接字
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 import socket 5 ip_port=('127.0.0.1',9000) 6 BUFSIZE=1024 7 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 8 udp_server_client.bind(ip_port) 9 10 while True: 11 msg,addr=udp_server_client.recvfrom(BUFSIZE) 12 print(msg,addr) 13 udp_server_client.sendto(msg.upper(),addr)#UDP_server
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 import socket 5 ip_port=('127.0.0.1',9000) 6 BUFSIZE=1024 7 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 8 9 while True: 10 msg=input('>>: ').strip() 11 if not msg:continue 12 udp_server_client.sendto(msg.encode('utf-8'),ip_port) 13 back_msg,addr=udp_server_client.recvfrom(BUFSIZE) 14 print(back_msg.decode('utf-8'),addr) 15 udp_client_socket.close()#UDP_client
##qq聊天(由於udp無連接,所以可以同時多個客戶端去跟服務端通信)##
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 import socket 5 ip_port=('127.0.0.1',8081) 6 udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #買手機 7 udp_server_sock.bind(ip_port) 8 9 while True: 10 qq_msg,addr=udp_server_sock.recvfrom(1024) 11 print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8'))) 12 back_msg=input('回覆消息: ').strip() 13 14 udp_server_sock.sendto(back_msg.encode('utf-8'),addr)#UDP_server
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 import socket 5 BUFSIZE=1024 6 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 7 8 qq_name_dic={ 9 '狗哥alex':('127.0.0.1',8081), 10 '瞎驢':('127.0.0.1',8081), 11 '一棵樹':('127.0.0.1',8081), 12 '武大郎':('127.0.0.1',8081), 13 } 14 15 16 while True: 17 qq_name=input('請選擇聊天對象: ').strip() 18 while True: 19 msg=input('請輸入消息,回車發送: ').strip() 20 if msg == 'quit':break 21 if not msg or not qq_name or qq_name not in qq_name_dic:continue 22 udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) 23 24 back_msg,addr=udp_client_socket.recvfrom(BUFSIZE) 25 print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8'))) 26 27 udp_client_socket.close()#UDP_client1
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 import socket 5 BUFSIZE=1024 6 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 7 8 qq_name_dic={ 9 '狗哥alex':('127.0.0.1',8081), 10 '瞎驢':('127.0.0.1',8081), 11 '一棵樹':('127.0.0.1',8081), 12 '武大郎':('127.0.0.1',8081), 13 } 14 15 16 while True: 17 qq_name=input('請選擇聊天對象: ').strip() 18 while True: 19 msg=input('請輸入消息,回車發送: ').strip() 20 if msg == 'quit':break 21 if not msg or not qq_name or qq_name not in qq_name_dic:continue 22 udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) 23 24 back_msg,addr=udp_client_socket.recvfrom(BUFSIZE) 25 print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8'))) 26 27 udp_client_socket.close()#UDP_client2
5、粘包現象:①發送端需要等緩衝區滿才發送出去,造成粘包(發送數據時間間隔很短,數據了很小,TCP有一個Nagle演算法會把數據合到一起,產生粘包)
②接收方不及時接收緩衝區的包,造成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候還是從緩衝區拿上次遺留的數據,產生粘包)
原因:接收方不知道消息之間的界限,不知道一次性提取多少位元組的數據。
TCP有粘包現象,UDP永遠不會粘包:tcp是基於數據流的,收發兩端都要有一一成對的socket,TCP採用Nagle優化演算法消息進行消息處理機制,面向流的通信是無消息保護邊界的。而udp是基於數據報的,支持的是一對多的模式,套接字緩衝區採用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中添加了消息頭(消息來源地址,埠等信息),面向消息的通信是有消息保護邊界的。
tcp是可靠傳輸,udp是不可靠傳輸:tcp在數據傳輸時,發送端先把數據發送到自己的緩存中,然後協議控制將緩存中的數據發往對應端,對應端返回一個ack=1,發送端則清理緩存中的數據,對端返回ack=0,則重新發送數據,所以tcp是可靠的;而udp發送數據,對端是不會返回確認信息的,因此不可靠。
解決粘包:問題的根源在於,接收端不知道發送端將要傳送的位元組流的長度,所以解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把自己將要發送的位元組流總大小讓接收端知曉,然後接收端來一個死迴圈接收完所有數據。
第一種解決方案:(low)
1 #server端 2 3 #!/usr/bin/env python 4 # -*- coding:utf-8 -*- 5 # _author_soloLi 6 from socket import * #由於 socket 模塊中有太多的屬性。我們在這裡破例使用了'from module import *'語句。使用 'from socket import *',我們就把 socket模塊里的所有屬性都帶到我們的命名空間里了,這樣能 大幅減短我們的代碼。 7 import subprocess 8 ip_port=('127.0.0.1',8080) 9 back_log=5 10 buffer_size=1024 11 12 server=socket(AF_INET,SOCK_STREAM) 13 server.bind(ip_port) 14 server.listen(back_log) 15 16 while True: #鏈接迴圈 17 conn,addr=server.accept() 18 print('新的客戶端鏈接',addr) 19 while True: #通信迴圈 20 21 ##//收數據//## 22 try: 23 cmd=conn.recv(buffer_size) 24 if not cmd:break 25 print('收到客戶端的命令',cmd) 26 27 #執行命令,得到命令的運行結果cmd_res 28 29 #subprocess模塊提供了一種一致的方法來創建和處理附加進程,與標準庫中的其它模塊相比,提供了一個更高級的介面。用於替換如下模塊:os.system() , os.spawnv() , os和popen2模塊中的popen()函數,以及 commands(). 30 res=subprocess.Popen(cmd.decode('utf-8'),shell=True, #解碼(bytes->str) 31 stderr=subprocess.PIPE, 32 stdout=subprocess.PIPE, 33 stdin=subprocess.PIPE) 34 35 err=res.stderr.read() 36 if err: 37 cmd_res=err 38 else: 39 cmd_res=res.stdout.read() ##編碼是以當前所在的系統為準的,如果是windows,那麼res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼且只能從管道里讀一次結果 40 41 ##//發數據//## 42 if not cmd_res: 43 cmd_res='執行成功'.encode('gbk') #轉換編碼(gbk->unicode)(轉換顯示中文的字元串) 44 45 length=len(cmd_res) 46 conn.send(str(length).encode('utf-8')) #編碼(str->bytes) 47 client_ready=conn.recv(buffer_size) 48 if client_ready == b'ready': 49 conn.send(cmd_res) 50 except Exception as e: 51 print(e) 52 breakTCP_serve
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 from socket import * 5 ip_port=('127.0.0.1',8080) 6 back_log=5 7 buffer_size=1024 8 9 client=socket(AF_INET,SOCK_STREAM) 10 client.connect(ip_port) 11 12 while True: 13 cmd=input('>>: ').strip() 14 if not cmd:continue 15 if cmd == 'quit':break 16 17 client.send(cmd.encode('utf-8')) #編碼(str->bytes) 18 19 #解決粘包 20 length=client.recv(buffer_size) 21 client.send(b'ready') 22 23 length=int(length.decode('utf-8')) #解碼(bytes->str) 24 25 recv_size=0 26 recv_msg=b'' 27 while recv_size < length: 28 recv_msg += tcp_client.recv(buffer_size) 29 recv_size=len(recv_msg) #1024 30 31 print('命令的執行結果是 ',recv_msg.decode('gbk')) #轉換編碼(gbk->unicode)(轉換顯示中文的字元串) 32 client.close()TCP_client
low的原因:程式的運行速度遠快於網路傳輸速度,所以在發送一段位元組前,先用send去發送該位元組流長度,這種方式會放大網路延遲帶來的性能損耗
第二種解決方案:(NB)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # _author_soloLi 4 from socket import * #由於 socket 模塊中有太多的屬性。我們在這裡破例使用了'from module import *'語句。使用 'from socket import *',我們就把 socket模塊里的所有屬性都帶到我們的命名空間里了,這樣能 大幅減短我們的代碼。 5 import subprocess 6 ip_port=('127.0.0.1',8080) 7 back_log=5 8 buffer_size=1024 9 10 server=socket(AF_INET,SOCK_STREAM) 11 server.bind(ip_port) 12 server.listen(back_log) 13 14 while True: #鏈接迴圈 15 conn,addr=server.accept() 16 print('新的客戶端鏈接',addr) 17 while True: #通信迴圈 18 19 ##//收數據//## 20 try: 21 cmd=conn.recv(buffer_size) 22 if not cmd:break 23 print('收到客戶端的命令',cmd) 24 25 #執行命令,得到命令的運行結果cmd_res 26 27 #subprocess模塊提供了一種一致的方法來創建和處理附加進程,與標準庫中的其它模塊相比,提供了一個更高級的介面。用於替換如下模塊:os.system() , os.spawnv() , os和popen2模塊中的popen()函數,以及 commands(). 28 res=subprocess.Popen(cmd.decode('utf-8'),shell=True, #解碼(bytes->str) 29 stderr=subprocess.PIPE, 30 stdout=subprocess.PIPE, 31 stdin=subprocess.PIPE) 32 33 err=res.stderr.read() 34 if err: 35 cmd_res=err 36 else: 37 cmd_res=res.stdout.read() ##編碼是以當前所在的系統為準的,如果是windows,那麼res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼且只能從管道里讀一次結果 38 39 ##//發數據//## 40 if not cmd_res: 41 cmd_res='執行成功'.encode('gbk') #轉換編碼(gbk->unicode)(轉換顯示中文的字元串) 42 43 length=len(cmd_res) 44 conn.send(str(length).encode('utf-8')) #編碼(str->bytes) 45 client_ready=conn.recv(buffer_size) 46 if client_ready == b'ready': 47 conn.send(cmd_res) 48 except Exception as e: 49 print(e) 50 breakTCP_server