python Socket網絡編程 基礎
內容概要
- Socket介紹
- Socket參數介紹
- 基本Socket實例
- Socket實現多連接處理
- 通過Socket實現簡單SSH
- 通過Socket實現文件傳送
1. Socket介紹
Socket Families(地址簇)
socket.
AF_UNIX unix本機進程間通信
socket.
AF_INET IPV4
socket.
AF_INET6 IPV6
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 #廢棄了
2. Socket 參數介紹
socket.
socket
(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) #實例化一個socket 必會
socket.
getaddrinfo
(host, port, family=0, type=0
返回值:[(family, socktype, proto, canonname, sockaddr)]有元組組成的列表,元組裏面包含5個元素,其中sockaddr是(host,port)
- family: 表示socket使用的協議簇。常用的協議簇包括AF_UNIX(本機通信)/AF_INET(TCP/IP協議簇中的IPv4協議)/AF_INET6(TCP/IP協議簇中的IPv6協議)。在python的socket包中,用1表示AF_UNIX,2表示AF_INET,10表示AF_INET6。
- sockettype:表示socket的類型。常見的socket類型包括SOCK_STREAM(TCP流)/SOCK_DGRAM(UDP數據報)/SOCK_RAW(原始套接字)。其中,SOCK_STREAM=1,SOCK_DGRAM=2,SOCK_RAW=3
- proto:顧名思義,就是指定協議。套接口所用的協議。如調用者不想指定,可用0。常用的協議有,IPPROTO_TCP(=6)和IPPTOTO_UDP(=17),它們分別對應TCP傳輸協議、UDP傳輸協議。
>>> import socket >>> socket.getaddrinfo(‘www.baodu.com‘,80) [(<AddressFamily.AF_INET: 2>, 0, 0, ‘‘, (‘115.29.223.128‘, 80))]
s = socket.socket() # 默認就使用 family=AF_INET, type=SOCK_STREAM
s.bind(address) 必會
s.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
s.listen(backlog) 必會
開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。
backlog等於5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的連接個數最大為5
這個值不能無限大,因為要在內核中維護連接隊列
s.setblocking(bool) 必會
是否阻塞(默認True),如果設置False,那麽accept和recv時一旦無數據,則報錯。
s.accept() 必會
接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。
接收TCP 客戶的連接(阻塞式)等待連接的到來
s.connect(address) 必會
連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
s.close() 必會
關閉套接字
s.recv(bufsize[,flag]) 必會
接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略。
s.send(string[,flag]) 必會
將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。
s.sendall(string[,flag]) 必會
將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。
內部通過遞歸調用send,將所有內容發送出去。
s.settimeout(timeout) 必會
設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作(如 client 連接最多等待5s )
s.getpeername()
返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。
s.getsockname()
返回套接字自己的地址。通常是一個元組(ipaddr,port)
s.fileno()
套接字的文件描述符
3. 基本Socket實例
1 import socket 2 3 target_host = ‘localhost‘ 4 target_port = 9999 5 6 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 8 client.connect((target_host, target_port)) 9 10 while True: 11 msg = input(‘>>>‘).strip() 12 if len(msg) == 0:continue 13 if msg == ‘q‘:break 14 client.send(msg.encode(‘utf-8‘)) 15 response = client.recv(4096) 16 print(response) 17 18 client.close()
以上是socket客戶端支持多交互
1 import socket 2 3 HOST = ‘localhost‘ 4 PORT = 9999 5 # 建立一個socket對象 6 server = socket.socket() 7 # 綁定host,port 8 server.bind((HOST, PORT)) 9 # 開始監聽 10 server.listen(5) 11 # 等待客戶端連接 12 conn, addr = server.accept() 13 # 接受數據 14 data = conn.recv(1024) 15 # 打印數據 16 print(data) 17 # 關閉server 18 server.close()
上面是一個最基本的server端,只能建立一個鏈接,再接受一次客戶端的data後就結束了. 需要多次交互只需加個while循環
1 import socket 2 3 HOST = ‘localhost‘ 4 PORT = 9999 5 # 建立一個socket對象 6 server = socket.socket() 7 # 綁定host,port 8 server.bind((HOST, PORT)) 9 # 開始監聽 10 server.listen(5) 11 # 等待客戶端連接 12 conn, addr = server.accept() 13 # 接受數據 14 15 while True: 16 data = conn.recv(1024) 17 if not data: 18 print(‘客戶端斷開了‘) 19 break 20 print(data) 21 conn.send(data.upper()) 22 23 # 關閉server 24 server.close()
4.Socket實現多連接處理
上面的代碼雖然實現了服務端與客戶端的多次交互,但是你會發現,如果客戶端斷開了, 服務器端也會跟著立刻斷開,因為服務器只有一個while 循環,客戶端一斷開,服務端收不到數據 ,就會直接break跳出循環,然後程序就退出了,這顯然不是我們想要的結果 ,我們想要的是,客戶端如果斷開了,我們這個服務端還可以為下一個客戶端服務,它不能斷,她接完一個客,擦完嘴角的遺留物,就要接下來勇敢的去接待下一個客人。 在這裏如何實現呢?
1 conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...
我們知道上面這句話負責等待並接收新連接,對於上面那個程序,其實在while break之後,只要讓程序再次回到上面這句代碼這,就可以讓服務端繼續接下一個客戶啦。
1 import socket 2 3 HOST = ‘localhost‘ 4 PORT = 9999 5 6 server = socket.socket() 7 8 server.bind((HOST, PORT)) 9 10 server.listen(5) 11 12 while True: 13 14 conn, addr = server.accept() 15 16 while True: 17 data = conn.recv(1024) 18 if not data: 19 print(‘客戶端斷開了‘) 20 break 21 print(data) 22 conn.send(data.upper()) 23 24 # 關閉server 25 server.close()
註意了, 此時服務器端依然只能同時為一個客戶服務,其客戶來了,得排隊(連接掛起)
5.通過socket實現簡單的ssh
光只是簡單的發消息、收消息沒意思,幹點正事,可以做一個極簡版的ssh,就是客戶端連接上服務器後,讓服務器執行命令,並返回結果給客戶端。
1 import socket 2 import os 3 4 bind_ip = ‘0.0.0.0‘ 5 bind_port = 9999 6 7 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 8 server.bind((bind_ip, bind_port)) 9 10 server.listen() 11 12 while True: 13 print(‘[*] Listening on %s:%s‘ % (bind_ip, bind_port)) 14 conn, addr = server.accept() 15 print(‘[*] conn from %s:%s‘ % addr) 16 while True: 17 request = conn.recv(1024) 18 if not request: 19 print(‘[*] Client is closed‘) 20 break 21 print(‘[*] Execute the cmd: %s‘ % request) 22 cmd_res = os.popen(request.decode()).read() 23 if len(cmd_res) == 0: 24 cmd_res = ‘[*] CMD is not output...‘ 25 # 先發送大小,bytes狀態大小, 26 conn.send(str(len(cmd_res.encode())).encode()) 27 # 2次send數據中間等待客戶端確認,解決粘包問題 28 client_ack = conn.recv(1024) 29 conn.send(cmd_res.encode()) 30 print(‘[*] Send down‘)
上面 socket ssh server
1 import socket 2 3 target_host = ‘localhost‘ 4 target_port = 9999 5 6 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 client.connect((target_host, target_port)) 8 9 while True: 10 cmd = input(‘>>:‘).strip() 11 if len(cmd) == 0:continue 12 client.send(cmd.encode()) 13 # 接受數據總大小 14 res_size = client.recv(1024) 15 # 響應服務端,解決粘包問題 16 client.send(‘start send‘.encode()) 17 print(‘[*] Res_size:‘, res_size) 18 # 文件大小都是bytes狀態大小,和服務器發過來的大小在同一狀態 19 received_size = 0 20 while received_size < int(res_size): 21 data = client.recv(1024) 22 # 每次收到的有可能小於1024 23 received_size += len(data) 24 print(data.decode()) 25 else: 26 print(‘[*] Receive done...%s‘ % received_size)
上面 socket ssh client
very cool , 這樣我們就做了一個簡單的ssh , 但多試幾條命令你就會發現,上面的程序有以下2個問題。
- 不能執行top等類似的 會持續輸出的命令,這是因為,服務器端在收到客戶端指令後,會一次性通過os.popen執行,並得到結果後返回給客戶,但top這樣的命令用os.popen執行你會發現永遠都不會結束,所以客戶端也永遠拿不到返回。(真正的ssh是通過select 異步等模塊實現的,我們以後會涉及)
- 不能執行像cd這種沒有返回的指令, 因為客戶端每發送一條指令,就會通過client.recv(1024)等待接收服務器端的返回結果,但是cd命令沒有結果 ,服務器端調用conn.send(data)時是不會發送數據給客戶端的。 所以客戶端就會一直等著,等到天荒地老,結果就卡死了。解決的辦法是,在服務器端判斷命令的執行返回結果的長度,如果結果為空,就自己加個結果返回給客戶端,如寫上"cmd exec success, has no output."
python Socket網絡編程 基礎