socket.
socket
(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
socket.
AF_UNIX unix本機進程間通訊
python
socket.
AF_INET IPV4
程序員
socket.
AF_INET6 IPV6
編程
socket.
SOCK_STREAM #for tcp
服務器
socket.
SOCK_DGRAM #for udp
網絡
socket.
SOCK_RAW #原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠;其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,能夠經過IP_HDRINCL套接字選項由用戶構造IP頭。
併發
socket.
SOCK_RDM #是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在須要執行某些特殊操做時使用,如發送ICMP報文。SOCK_RAM一般僅限於高級用戶或管理員運行的程序使用。
ssh
socket.
SOCK_SEQPACKET #廢棄了
socket
剩下的參數proto,fileno通常咱們不用管。tcp
最簡單的socket實例ide
import socket client = socket.socket() client.connect(('localhost',9999)) client.send(b'hello world') data = client.recv(1024) print('recv',data) client.close()
import socket server = socket.socket() server.bind(('localhost',9999)) server.listen() conn,addr = server.accept() data = conn.recv(1024) print('recv:',data) conn.send(data.upper()) server.close()
上面的代碼的有一個問題, 就是SocketServer.py運行起來後, 接收了一次客戶端的data就退出了。。。, 但實際場景中,一個鏈接創建起來後,可能要進行屢次往返的通訊。
import socket client = socket.socket() client.connect(('localhost',8888)) while True: data = input('>>請輸入要發送的內容:') if len(data) == 0: continue client.send(data.encode('utf-8')) res = client.recv(1024) print(res.decode()) client.close()
import socket server = socket.socket() server.bind(('localhost',8888)) server.listen(5) print('我要開始等電話了') conn,addr = server.accept() print('電話來了') while True: data = conn.recv(1024) print(data) if not data: print('client has lost') break conn.send(data.upper()) conn.close()
可是上面這種寫法仍是有缺陷的,雖然這種寫法能夠實現客戶端和服務器端屢次交互了,可是隻要客戶端斷開了,服務器端也會斷開,由於只有一個while循環,客戶端一斷開,服務器端收不到數據,就會直接break跳出循環,這樣程序就結束了。這還不是咱們想要的結果,咱們想要的是,客戶端斷開了,服務器端能夠接收下一個連進來的鏈接,而後和下一個連進來的客戶端再次的進行屢次的交互。
import socket client = socket.socket() client.connect(('localhost',8888)) while True: data = input('>>請輸入要發送的內容:') if len(data) == 0: continue client.send(data.encode('utf-8')) res_size = client.recv(1024) print(res_size) res_data = b'' recv_size = 0 while recv_size < int(res_size.decode()): data = client.recv(1024) recv_size += len(data) res_data += data else: print(recv_size) print(res_data.decode()) print(res_data) client.close()
import socket import os server = socket.socket() server.bind(('localhost',8888)) server.listen(5) print('我要開始等電話了') while True: conn,addr = server.accept() print('電話來了') while True: data = conn.recv(1024) if not data: print('client has lost') break res = os.popen(data.decode()).read() conn.send(str(len(res.encode())).encode()) conn.send(res.encode()) print('send done') conn.close()
輸出結果:
看程序執行報錯了, 我在客戶端本想只接服務器端命令的執行結果大小,但實際上卻連命令結果也跟着接收了一部分。
這裏就引入了一個重要的概念,「粘包」, 即服務器端你調用時send 2次,但你send調用時,數據其實並無馬上被髮送給客戶端,而是放到了系統的socket發送緩衝區裏,等緩衝區滿了、或者數據等待超時了,數據纔會被send到客戶端,這樣就把好幾回的小數據拼成一個大數據,統一發送到客戶端了,這麼作的目地是爲了提升io利用效率,一次性發送總比連發好幾回效率高。 但也帶來一個問題,就是「粘包」,即2次或屢次的數據粘在了一塊兒統一發送了。就是咱們上面看到的狀況 。
咱們在這裏必需要想辦法把粘包分開, 由於不分開,你就沒辦法取出來服務器端返回的命令執行結果的大小。首先你是沒辦法讓緩衝區強制刷新把數據發給客戶端的。 你能作的,只有一個。就是讓緩衝區超時,超時了,系統就不會等緩衝區滿了,會直接把數據發走,那麼如何讓緩衝區超時呢?
答案就是:
import socket client = socket.socket() client.connect(('localhost',8888)) while True: data = input('>>請輸入要發送的內容:') if len(data) == 0: continue client.send(data.encode('utf-8')) res_size = client.recv(1024) client.send(b'ok') print(res_size) res_data = b'' recv_size = 0 while recv_size < int(res_size.decode()): data = client.recv(1024) recv_size += len(data) res_data += data else: print(recv_size) print(res_data.decode()) client.close()
import socket import os server = socket.socket() server.bind(('localhost',8888)) server.listen(5) print('我要開始等電話了') while True: conn,addr = server.accept() print('電話來了') while True: data = conn.recv(1024) if not data: print('client has lost') break res = os.popen(data.decode()).read() conn.send(str(len(res.encode())).encode()) conn.recv(1024) conn.sendall(res.encode()) print('send done') conn.close()
SocketServer
socketserver一共有這麼幾種類型
1
|
class
socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate
=
True
)
|
This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server.
1
|
class
socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate
=
True
)
|
This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as for TCPServer
1
2
|
class
socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate
=
True
)
class
socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate
=
True
)
|
There are five classes in an inheritance diagram, four of which represent synchronous servers of four types:
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
建立一個socketserver 至少分如下幾步:
最基本的socketserver代碼實現
import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): def handle(self): while True: try: self.data = self.request.recv(1024).strip() print(self.data) self.request.send(self.data.upper()) except Exception as e: print('連接斷開',e) break if __name__ == "__main__": HOST, PORT = "localhost", 9999 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) server.serve_forever()
讓你的socketserver併發起來, 必須選擇使用如下一個多併發的類
class socketserver.
ForkingTCPServer
class socketserver.
ForkingUDPServer
class socketserver.
ThreadingTCPServer
class socketserver.
ThreadingUDPServer
so 只須要把下面這句
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
換成下面這個,就能夠多併發了,這樣,客戶端每連進一個來,服務器端就會分配一個新的線程來處理這個客戶端的請求
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
固然,知道了socket的實現原理以後,咱們還可使用socket來完成不少事情,好比說用socket來完成一個FTP文件上傳下載的功能也均可以。。。學無止境,慢慢加油吧。