python第八週學習內容
1.socket網路程式設計
1.1概念:
網路套接字是跨計算機網路的連線的端點。今天,計算機之間的大多數通訊都基於網際網路協議;因此大多數網路套接字都是Internet套接字。更準確地說,套接字是一個控制代碼(抽象引用),本地程式可以將其傳遞給網路應用程式程式設計介面(API)以使用該連線,例如“在此套接字上傳送此資料”。
例如,傳送“Hello,world!”通過TCP到地址為1.2.3.4的主機的埠80,可以獲得一個套接字,將其連線到遠端主機,傳送字串,然後關閉套接字。
實現一個socket至少要分以下幾步,(虛擬碼):
1.Socket socket
=
getSocket(
type
=
"TCP"
)
#設定好協議型別
2.connect(socket, address
=
"1.2.3.4"
, port
=
"80"
)
#連線遠端機器
3.send(socket,
"Hello, world!"
)
#傳送訊息
4.close(socket)
#關閉連線
套接字API是一種應用程式程式設計介面(API),通常由作業系統提供,允許應用程式控制和使用網路套接字。 Internet套接字API通常基於Berkeley套接字標準。在Berkeley套接字標準中,套接字是檔案描述符(檔案控制代碼)的一種形式,由於Unix哲學“一切都是檔案”,以及套接字和檔案之間的類比:你可以讀,寫,開啟和關閉。
套接字地址是IP地址和埠號的組合,很像電話連線的一端是電話號碼和特定分機的組合。 套接字不需要有地址(例如僅用於傳送資料),但如果程式將套接字繫結到地址,則套接字可用於接收發送到該地址的資料。 基於此地址,Internet套接字將傳入的資料包傳遞到適當的應用程式程序或執行緒
Socket Families(地址簇)
socket.
AF_UNIX unix本機程序間通訊
socket.
AF_INET IPV4
socket.
AF_INET6 IPV6
這些常量表示用於socket()的第一個引數的地址(和協議)系列。 如果未定義AF_UNIX常量,則不支援此協議。 根據系統的不同,可能會有更多常量可用。
Socket Types
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通常僅限於高階使用者或管理員執行的程式使用。
socket.
SOCK_SEQPACKET #廢棄了
這些常量表示套接字型別,用於socket()的第二個引數。 根據系統的不同,可能會有更多常量可用。 (只有SOCK_STREAM和SOCK_DGRAM似乎通常很有用。)
1.2 Socket 引數
(1)socket.socket
(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) 必會
使用給定的地址系列,套接字型別和協議號建立一個新套接字。 地址族應為AF_INET(預設值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 套接字型別應該是SOCK_STREAM(預設值),SOCK_DGRAM,SOCK_RAW或者其他SOCK_常量之一。 協議號通常為零並且可以省略,或者在地址族是AF_CAN的情況下,協議應該是CAN_RAW或CAN_BCM之一。 如果指定了fileno,則忽略其他引數,從而返回具有指定檔案描述符的套接字。 與socket.fromfd()不同,fileno將返回相同的套接字而不是重複。 這可能有助於使用socket.close()關閉分離的套接字。
(2)socket.
socketpair
([family[, type[, proto]]])
使用給定的地址系列,套接字型別和協議編號構建一對連線的套接字物件。 地址族,套接字型別和協議號與上面的socket()函式相同。 如果在平臺上定義,則預設系列為AF_UNIX; 否則,預設為AF_INET。
(3)socket.
create_connection
(address[, timeout[, source_address]])
連線到偵聽Internet地址(2元組(主機,埠))的TCP服務,並返回套接字物件。 這是一個比socket.connect()更高階的函式:如果host是非數字主機名,它將嘗試為AF_INET和AF_INET6解析它,然後嘗試依次連線到所有可能的地址,直到連線成功。 這樣可以輕鬆編寫與IPv4和IPv6相容的客戶端。
傳遞可選的timeout引數將在嘗試連線之前設定套接字例項上的超時。 如果未提供超時,則使用getdefaulttimeout()返回的全域性預設超時設定。
如果提供,則source_address必須是要連線的套接字的2元組(主機,埠)作為其源地址才能連線。 如果主機或埠分別為'或0,則將使用OS預設行為。
(4) socket.
getaddrinfo
(host, port, family=0, type=0, proto=0, flags=0) #獲取要連線的對端主機地址
(5) sk.bind(address) 必會
sk.bind(address) 將套接字繫結到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
(6) sk.listen(backlog) 必會
開始監聽傳入連線。backlog指定在拒絕連線之前,可以掛起的最大連線數量。backlog等於5,表示核心已經接到了連線請求,但伺服器還沒有呼叫accept進行處理的連線個數最大為5。這個值不能無限大,因為要在核心中維護連線佇列
(7)sk.setblocking(bool) 必會
是否阻塞(預設True),如果設定False,那麼accept和recv時一旦無資料,則報錯。
(8) sk.accept() 必會
接受連線並返回(conn,address),其中conn是新的套接字物件,可以用來接收和傳送資料。address是連線客戶端的地址。接收TCP 客戶的連線(阻塞式)等待連線的到來
(9)sk.connect(address) 必會
連線到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
(10)sk.connect_ex(address)
同上,只不過會有返回值,連線成功時返回 0 ,連線失敗時候返回編碼,例如:10061
(11)sk.close() 必會
關閉套接字
(12)sk.recv(bufsize[,flag]) 必會
接受套接字的資料。資料以字串形式返回,bufsize指定最多可以接收的數量。flag提供有關訊息的其他資訊,通常可以忽略。
(13)sk.recvfrom(bufsize[.flag])
與recv()類似,但返回值是(data,address)。其中data是包含接收資料的字串,address是傳送資料的套接字地址。
(14)sk.send(string[,flag]) 必會
將string中的資料傳送到連線的套接字。返回值是要傳送的位元組數量,該數量可能小於string的位元組大小。即:可能未將指定內容全部發送。
(15)sk.sendall(string[,flag]) 必會
將string中的資料傳送到連線的套接字,但在返回之前會嘗試傳送所有資料。成功返回None,失敗則丟擲異常。內部通過遞迴呼叫send,將所有內容傳送出去。
(16)sk.sendto(string[,flag],address)
將資料傳送到套接字,address是形式為(ipaddr,port)的元組,指定遠端地址。返回值是傳送的位元組數。該函式主要用於UDP協議。
(17)sk.settimeout(timeout) 必會
設定套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛建立套接字時設定,因為它們可能用於連線的操作(如 client 連線最多等待5s )
(18)sk.getpeername() 必會
返回連線套接字的遠端地址。返回值通常是元組(ipaddr,port)。
(19)sk.getsockname()
返回套接字自己的地址。通常是一個元組(ipaddr,port)
(20)sk.fileno()
套接字的檔案描述符
(21)socket.
sendfile
(file, offset=0, count=None)
傳送檔案 ,但目前多數情況下並無什麼卵用。
1.3 通過socket實現簡單的ssh
程式流程如下:
程式程式碼:
socket_server端:
import socket,os server = socket.socket() server.bind(("localhost",9999)) server.listen() while True: conn,addr = server.accept() #阻塞 while True: print("wait for new cmd:") cmd = conn.recv(1024).decode() if not cmd: print("客戶端已斷開") break cmd_result = os.popen(cmd).read() if len(cmd_result) == 0: cmd_result = "這條命令錯誤" length = len(cmd_result.encode("utf-8")) length = len(cmd_result.encode("utf-8")) conn.send(str(length).encode("utf-8")) conn.recv(1024) conn.send(cmd_result.encode("utf-8")) server.close()
socket_client端:
import socket client = socket.socket() client.connect(("localhost",9999)) while True: cmd = input("輸入指令:") if len(cmd) == 0: continue client.send(cmd.encode("utf-8")) length = client.recv(1024).decode() print("命令結果大小:",length) receive_size = 0 receive_data = b"" client.send(b"OK") while receive_size < int(length): data = client.recv(1024) receive_size += len(data) receive_data += data print("命令結果實際大小:",receive_size) print(receive_data.decode()) client.close()
程式執行結果:
#server端: wait for new cmd: ipconfig wait for new cmd: #client端: 輸入指令:ipconfig 命令結果大小: 1680 命令結果實際大小: 1680 Windows IP 配置 乙太網介面卡 乙太網: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開連線 連線特定的 DNS 字尾 . . . . . . . : 無線區域網介面卡 本地連線* 3: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開連線 連線特定的 DNS 字尾 . . . . . . . : 無線區域網介面卡 本地連線* 12: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開連線 連線特定的 DNS 字尾 . . . . . . . : 乙太網介面卡 VMware Network Adapter VMnet1: 連線特定的 DNS 字尾 . . . . . . . : 本地連結 IPv6 地址. . . . . . . . : fe80::7ddd:a3e4:9673:512e%7 IPv4 地址 . . . . . . . . . . . . : 192.168.74.1 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 預設閘道器. . . . . . . . . . . . . : 乙太網介面卡 VMware Network Adapter VMnet8: 連線特定的 DNS 字尾 . . . . . . . : 本地連結 IPv6 地址. . . . . . . . : fe80::4cc1:5dc2:37f:7e7b%17 IPv4 地址 . . . . . . . . . . . . : 192.168.43.1 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 預設閘道器. . . . . . . . . . . . . : 無線區域網介面卡 WLAN: 連線特定的 DNS 字尾 . . . . . . . : IPv6 地址 . . . . . . . . . . . . : 2001:da8:215:8f01:8d1d:db29:3fd2:c6d6 臨時 IPv6 地址. . . . . . . . . . : 2001:da8:215:8f01:dc75:865e:ccf6:e781 本地連結 IPv6 地址. . . . . . . . : fe80::8d1d:db29:3fd2:c6d6%10 IPv4 地址 . . . . . . . . . . . . : 10.122.252.64 子網掩碼 . . . . . . . . . . . . : 255.255.192.0 預設閘道器. . . . . . . . . . . . . : fe80::274:9cff:fe7d:fadb%10 10.122.192.1 輸入指令:
1.4 通過socket實現簡單的ftp server
1.讀取檔名
2.檢測檔案是否存在
3.開啟檔案
4.檢測檔案大小
5.傳送檔案大小和md5給客戶端
6.等客戶端確認
7.開始邊讀邊發資料
8.md5
9.關閉檔案
程式程式碼:
客戶端:
# -*- coding:utf-8 -*- #!/user/bin/env.python #Author:Mr Wu '''FTP Server''' import socket,os,hashlib server = socket.socket() server.bind(("localhost",1999)) server.listen() while True: conn,addr = server.accept() #阻塞 while True: data = conn.recv(1024).decode() if not data: print("客戶端已斷開連線!") break file_cmd,file_name = data.split() if os.path.isfile(file_name): file_size = os.stat(file_name).st_size conn.send(str(file_size).encode("utf-8")) conn.recv(1024) #避免粘包 m = hashlib.md5() f = open(file_name,"rb") print("開始傳送檔案.....") for line in f: m.update(line) conn.send(line) f.close() print("傳送md5.......") conn.send(m.hexdigest().encode("utf-8")) server.close()
服務端:
# -*- coding:utf-8 -*- #!/user/bin/env.python #Author:Mr Wu import socket,os,hashlib client = socket.socket() client.connect(("localhost",1999)) while True: cmd = input("輸入檔名[格式:get 檔名]>>>:").strip() if len(cmd) == 0: continue if cmd.startswith("get"): file_name = cmd.split()[1] client.send(cmd.encode("utf-8")) data = client.recv(1024) file_total_size = int(data.decode()) client.send(b"OK") file_size = 0 f = open(file_name,"wb") m = hashlib.md5() while file_size < file_total_size: last_size = file_total_size - file_size if last_size < 1024: size = last_size else: size = 1024 '''避免粘包''' data = client.recv(size) m.update(data) f.write(data) file_size =+ len(data) f.close() md5 = m.hexdigest() received_md5 = client.recv(1024).decode() if md5 == received_md5: print("檔案md5一致,傳輸成功!") else: print("檔案傳輸錯誤!") else: print("輸入格式錯誤!") continue
執行結果:
server端: 開始傳送檔案..... 傳送md5....... client端: 輸入檔名[格式:get 檔名]>>>:get test.py 檔案md5一致,傳輸成功!
2.socketServer的使用
注:由於其涉及執行緒的相關知識(還未學習),在學習完執行緒之後,再來寫socketServer的相關概念
2.1 使用方法:
(1)你必須自己建立一個處理類,並且這個類要繼承BaseRequestHandler,並且還要重寫父類裡的handle()
(2)你必須例項化TCPServer,並且傳遞server ip和你上面建立的請求處理類給這個TCPServer
(3)server.handle_request() #只處理一個請求
server.serve_forever() #處理多個連線請求,永遠執行
2.2 SocketServer程式示例:
# -*- coding:utf-8 -*- #!/user/bin/env.python #Author:Mr Wu import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): '''每一個請求過來都會例項化MyTCPHandler''' def handle(self): '''與客戶端所有的互動都是在handle裡完成的''' while True: try: self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) #if not self.data: # print("客戶端斷開連線") # break #just send the same data,but upper-cased self.request.send(self.data.upper()) except ConnectionResetError as e: print(e) break if __name__ == "__main__": HOST,PORT = "localhost",9999 #create the server,binding to localhost on port 9999 server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler) ''' activate the server:this will keep running until you interrupt the program with Ctrl-C ''' server.serve_forever() #處理多個請求,即可以連線多個客戶端
未完待續。。。。。。。。