1. 程式人生 > >python Socket網絡編程 基礎

python Socket網絡編程 基礎

2個 listen info 普通 locking client 套接字選項 傳輸 utf-8

內容概要

  1. Socket介紹
  2. Socket參數介紹
  3. 基本Socket實例
  4. Socket實現多連接處理
  5. 通過Socket實現簡單SSH
  6. 通過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

, proto=0, flags=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個問題。

  1. 不能執行top等類似的 會持續輸出的命令,這是因為,服務器端在收到客戶端指令後,會一次性通過os.popen執行,並得到結果後返回給客戶,但top這樣的命令用os.popen執行你會發現永遠都不會結束,所以客戶端也永遠拿不到返回。(真正的ssh是通過select 異步等模塊實現的,我們以後會涉及)
  2. 不能執行像cd這種沒有返回的指令, 因為客戶端每發送一條指令,就會通過client.recv(1024)等待接收服務器端的返回結果,但是cd命令沒有結果 ,服務器端調用conn.send(data)時是不會發送數據給客戶端的。 所以客戶端就會一直等著,等到天荒地老,結果就卡死了。解決的辦法是,在服務器端判斷命令的執行返回結果的長度,如果結果為空,就自己加個結果返回給客戶端,如寫上"cmd exec success, has no output."

python Socket網絡編程 基礎