Python Day8
Socket
Socket是網絡編程的一個抽象概念。通常我們用一個Socket表示“打開了一個網絡鏈接”,而打開一個Socket需要知道目標計算機的IP地址和端口號,再指定協議類型即可。
Socket 參數
socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) #獲取要連接的對端主機地址
sk.bind(address)
s.bind(address) 將套接字綁定到地址。address地址的格式取決于地址族。在AF_INET下,以元組(host,port)的形式表示地址。
sk.listen(backlog)
開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。
backlog等于5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的連接個數最大為5,這個值不能無限大,因為要在內核中維護連接隊列
sk.setblocking(bool)
是否阻塞(默認True),如果設置False,那么accept和recv時一旦無數據,則報錯。
sk.accept()
接受連接并返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。
接收TCP 客戶的連接(阻塞式)等待連接的到來
sk.connect(address)
連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
sk.connect_ex(address)
同上,只不過會有返回值,連接成功時返回 0 ,連接失敗時候返回編碼,例如:10061
sk.recv(bufsize[,flag])
接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略。
sk.send(byte[,flag])
將byte中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小于string的字節大小。即:可能未將指定內容全部發送。
sk.sendall(byte[,flag])
將byte中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。
內部通過遞歸調用send,將所有內容發送出去。
sk.settimeout(timeout)
設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用于連接的操作(如 client 連接最多等待5s )
sk.getpeername()
返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。
sk.close()
關閉套接字
基本Socket實例
# socket server端import socketserver = socket.socket() #獲得socket實例server.bind(("localhost",9998)) #綁定ip port server.listen() #開始監聽 print("等待客戶端的連接...") conn,addr = server.accept() #接受并建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來... print("新連接:",addr )data = conn.recv(1024) print("收到消息:",data)server.close() # socket client端import socketclient = socket.socket()client.connect(("localhost",9998)) #連接服務器client.send(b"hey") #向服務器發送內容 必須是 byte類型client.close()上面的代碼的有一個問題,就是SocketServer.py運行起來后,接收了一次客戶端的data就退出了。。。, 但實際場景中,一個連接建立起來后,可能要進行多次往返的通信。
# socket server端實現多次交互import socketserver = socket.socket() #獲得socket實例server.bind(("localhost",9998)) #綁定ip port server.listen() #開始監聽 print("等待客戶端的連接...") conn,addr = server.accept() #接受并建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來... print("新連接:",addr ) while True:data = conn.recv(1024)if not data:print("客戶端斷開了...")breakprint("收到消息:",data)conn.send(data.upper())server.close()Socket實現多連接處理
上面的代碼雖然實現了服務端與客戶端的多次交互,但是你會發現,如果客戶端斷開了, 服務器端也會跟著立刻斷開,因為服務器只有一個while 循環,客戶端一斷開,服務端收不到數據 ,就會直接break跳出循環,然后程序就退出了,這顯然不是我們想要的結果 ,我們想要的是,客戶端如果斷開了,我們這個服務端還可以為下一個客戶端服務。
conn,addr = server.accept() #接受并建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...我們知道上面這句話負責等待并接收新連接,對于上面那個程序,其實在while break之后,只要讓程序再次回到上面這句代碼這,就可以讓服務端繼續接下一個客戶啦。
import socketserver = socket.socket() #獲得socket實例server.bind(("localhost",9998)) #綁定ip port server.listen() #開始監聽while True: #第一層loopprint("等待客戶端的連接...")conn,addr = server.accept() #接受并建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...print("新連接:",addr )while True:data = conn.recv(1024)if not data:print("客戶端斷開了...")break #這里斷開就會再次回到第一次外層的loopprint("收到消息:",data)conn.send(data.upper())server.close()注意了, 此時服務器端依然只能同時為一個客戶服務,其客戶來了,得排隊(連接掛起)
通過socket實現簡單的ssh
# socket ssh server端import socket,osserver = socket.socket() #獲得socket實例 #server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)server.bind(("localhost",9998)) #綁定ip port server.listen() #開始監聽while True: #第一層loopprint("等待客戶端的連接...")conn,addr = server.accept() #接受并建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...print("新連接:",addr )while True:data = conn.recv(1024)if not data:print("客戶端斷開了...")break #這里斷開就會再次回到第一次外層的loopprint("收到命令:",data)res = os.popen(data.decode()).read() #py3 里socket發送的只有bytes,os.popen又只能接受str,所以要decode一下print(len(res))conn.send(res.encode("utf-8"))server.close() # socket ssh client端 import socketclient = socket.socket()client.connect(("localhost",9998))while True:msg = input(">>:").strip()if len(msg) == 0:continueclient.send( msg.encode("utf-8") )data = client.recv(1024)print(data.decode()) #命令執行結果client.close()這樣我們就做了一個簡單的ssh , 但多試幾條命令你就會發現,上面的程序有以下3個問題。
1.不能執行top等類似的 會持續輸出的命令,這是因為,服務器端在收到客戶端指令后,會一次性通過os.popen執行,并得到結果后返回給客戶,但top這樣的命令用os.popen執行你會發現永遠都不會結束,所以客戶端也永遠拿不到返回。(真正的ssh是通過select 異步等模塊實現的,我們以后會涉及)
2.不能執行像cd這種沒有返回的指令, 因為客戶端每發送一條指令,就會通過client.recv(1024)等待接收服務器端的返回結果,但是cd命令沒有結果 ,服務器端調用conn.send(data)時是不會發送數據給客戶端的。 所以客戶端就會一直等著,等到天荒地老,結果就卡死了。解決的辦法是,在服務器端判斷命令的執行返回結果的長度,如果結果為空,就自己加個結果返回給客戶端,如寫上"cmd exec success, has no output."
3.如果執行的命令返回結果的數據量比較大,會發現,結果返回不全,在客戶端上再執行一條命令,結果返回的還是上一條命令的后半段的執行結果,這是為什么呢?這是因為,我們的客戶寫client.recv(1024), 即客戶端一次最多只接收1024個字節,如果服務器端返回的數據是2000字節,那有至少9百多字節是客戶端第一次接收不了的,那怎么辦呢,服務器端此時不能把數據直接扔了呀,so它會暫時存在服務器的io發送緩沖區里,等客戶端下次再接收數據的時候再發送給客戶端。 這就是為什么客戶端執行第2條命令時,卻接收到了第一條命令的結果的原因。 這時有同學說了, 那我直接在客戶端把client.recv(1024)改大一點不就好了么, 改成一次接收個100mb,哈哈,這是不行的,因為socket每次接收和發送都有最大數據量限制的,畢竟網絡帶寬也是有限的呀,不能一次發太多,發送的數據最大量的限制 就是緩沖區能緩存的數據的最大量,這個緩沖區的最大值在不同的系統上是不一樣的, 我實在查不到一個具體的數字,但測試的結果是,在linux上最大一次可接收10mb左右的數據,不過官方的建議是不超過8k,也就是8192,并且數據要可以被2整除,不要問為什么 。anyway , 如果一次只能接收最多不超過8192的數據 ,那服務端返回的數據超過了這個數字怎么辦呢?比如讓服務器端打開一個5mb的文件并返回,客戶端怎么才能完整的接受到呢?那就只能循環收取啦。
# 接收大數據 server端 #_*_coding:utf-8_*_import socket import os,subprocessserver = socket.socket() #獲得socket實例 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)server.bind(("localhost",9999)) #綁定ip port server.listen() #開始監聽while True: #第一層loopprint("等待客戶端的連接...")conn,addr = server.accept() #接受并建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...print("新連接:",addr )while True:data = conn.recv(1024)if not data:print("客戶端斷開了...")break #這里斷開就會再次回到第一次外層的loopprint("收到命令:",data)#res = os.popen(data.decode()).read() #py3 里socket發送的只有bytes,os.popen又只能接受str,所以要decode一下res = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE).stdout.read() #跟上面那條命令的效果是一樣的if len(res) == 0:res = "cmd exec success,has not output!".encode("utf-8")conn.send(str(len(res)).encode("utf-8")) #發送數據之前,先告訴客戶端要發多少數據給它print("等待客戶ack應答...")client_final_ack = conn.recv(1024) #等待客戶端響應print("客戶應答:",client_final_ack.decode())print(type(res))conn.sendall(res) #發送端也有最大數據量限制,所以這里用sendall,相當于重復循環調用conn.send,直至數據發送完畢server.close() # 接收大數據客戶端 #_*_coding:utf-8_*_import socket import sysclient = socket.socket()client.connect(("localhost",9999))while True:msg = input(">>:").strip()if len(msg) == 0:continueclient.send( msg.encode("utf-8") )res_return_size = client.recv(1024) #接收這條命令執行結果的大小print("getting cmd result , ", res_return_size)total_rece_size = int(res_return_size)print("total size:",res_return_size)client.send("準備好接收了,發吧loser".encode("utf-8"))received_size = 0 #已接收到的數據cmd_res = b''f = open("test_copy.html","wb")#把接收到的結果存下來,一會看看收到的數據 對不對while received_size != total_rece_size: #代表還沒收完data = client.recv(1024)received_size += len(data) #為什么不是直接1024,還判斷len干嘛,注意,實際收到的data有可能比1024少cmd_res += dataelse:print("數據收完了",received_size)#print(cmd_res.decode())f.write(cmd_res) #把接收到的結果存下來,一會看看收到的數據 對不對#print(data.decode()) #命令執行結果client.close()SocketServer
socketserver一共有這么幾種類型
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.
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.
class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True) class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)These more infrequently used classes are similar to the TCP and UDP classes, but use Unix domain sockets; they’re not available on non-Unix platforms. The parameters are the same as for TCPServer.
創建一個socketserver 至少分以下幾步:
1.First, you must create a request handler class by subclassing the BaseRequestHandlerclass and overriding its handle() method; this method will process incoming requests.
你必須自己創建一個請求處理類,并且這個類要繼承BaseRequestHandler,并且還有重寫父親類里的handle()
2.Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
你必須實例化TCPServer ,并且傳遞server ip 和 你上面創建的請求處理類 給這個TCPServer
3.Then call the handle_request() orserve_forever() method of the server object to process one or many requests.
server.handle_request() #只處理一個請求(基本不用)
server.serve_forever() #處理多個請求,永遠執行
4.Finally, call server_close() to close the socket.
基本socketserver代碼
import socketserverclass MyTCPHandler(socketserver.BaseRequestHandler):def handle(self):# self.request is the TCP socket connected to the clientself.data = self.request.recv(1024).strip()print("{} wrote:".format(self.client_address[0]))print(self.data)self.request.send(self.data.upper())if __name__ == "__main__":HOST, PORT = "localhost", 9999server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)server.serve_forever()讓你的socketserver并發起來, 必須選擇使用以下一個多并發的類
class socketserver.ForkingTCPServer
class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer
轉載于:https://www.cnblogs.com/shaolin2016/p/5871178.html
總結
以上是生活随笔為你收集整理的Python Day8的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ElasticSearch logo
- 下一篇: 第一次团队作业——团队展示