socket套接字


在學習網絡編程之前還有許多的知識需要普及。socket就是很重要的一環。今天來看一看套接字。

1.服務器端與客戶端

BS架構 (騰訊通軟件:server+client)

CS架構 (web網站)

 

C/S架構與socket的關系:

我們學習socket就是為了完成C/S架構的開發

2.OSI七層模型

互聯網協議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層

 

每層運行常見物理設備

 

詳細參考:

http://www.cnblogs.com/linhaifeng/articles/5937962.html#_label4

 

學習socket一定要先學習互聯網協議:

1.首先:本節課程的目標就是教會你如何基於socket編程,來開發一款自己的C/S架構軟件

2.其次:C/S架構的軟件(軟件屬於應用層)是基於網絡進行通信的

3.然后:網絡的核心即一堆協議,協議即標准,你想開發一款基於網絡通信的軟件,就必須遵循這些標准。

4.最后:就讓我們從這些標准開始研究,開啟我們的socket編程之旅

TCP/IP協議族包括運輸層、網絡層、鏈路層。

 3.socket層

Socket是介於應用層和傳輸層之間。

4.socket是什么

 Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

所以,我們無需深入理解tcp/udp協議,socket已經為我們封裝好了,我們只需要遵循socket的規定去編程,寫出的程序自然就是遵循tcp/udp標准的。

 掃盲篇:

1 將socket說成ip+port,ip是用來標識互聯網中的一台主機的位置,而port是用來標識這台機器上的一個應用程序,ip地址是配置到網卡上的,而port是應用程序開啟的,ip與port的綁定就標識了互聯網中獨一無二的一個應用程序
2 
3 而程序的pid是同一台機器上不同進程或者線程的標識(Google Chrome會有多個PID)

 5.套接字的發展歷程

套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設計用在同 一台主機上多個應用程序之間的通訊。這也被稱進程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基於文件型的和基於網絡型的。 

 

1、基於文件類型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信

 

2、基於網絡類型的套接字家族

套接字家族的名字:AF_INET

(還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要么是只用於某個平台,要么就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由於我們只關心網絡編程,所以大部分時候我么只使用AF_INET)

 6.套接字的工作流程

      生活中的場景,你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲后提起電話,這時你和你的朋友就建立起了連接,就可以講話了。等交流結束,掛斷電話結束此次交談。    

生活中的場景就解釋了這工作原理,也許TCP/IP協議族就是誕生於生活中,這也不一定。

 

  先從服務器端說起。服務器端先初始化Socket,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束。

 一socket模塊發送和接收消息

示例:模擬發送消息和接收消息的過程

tcp服務端(server)

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-      
 3  
 4 import socket
 5                              
 6 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #買手機
 7 phone.bind(('127.0.0.1',8000))  #綁定手機卡   #改成服務端網卡IP地址和端口
 8 phone.listen(5)  #開機  5的作用是最大掛起連接數   #backlog連接池(也叫半鏈接)
 9 print('------------->')
10 conn,addr=phone.accept()  #等電話
11 
12 msg=conn.recv(1024)  #收消息
13 print('客戶端發來的消息是:',msg)
14 conn.send(msg.upper())  #發消息
15 
16 conn.close()
17 phone.close()

執行結果:

1 ------------->

tcp客戶端(client)

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-      
 3  
 4 import socket
 5 
 6 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 7 
 8 phone.connect(('127.0.0.1',8000)) #拔通電話   #改成服務端網卡IP地址和端口
 9 
10 phone.send('hello'.encode('utf-8'))  #發消息
11 data=phone.recv(1024)
12 print('收到服務端的發來的消息: ',data)
13 
14 phone.close()

執行結果:

1 收到服務端的發來的消息:  b'HELLO'

 二、功能介紹

 server = socket.socket()

 1 參數一:地址簇
 2 
 3   socket.AF_INET IPv4(默認)
 4   socket.AF_INET6 IPv6
 5 
 6   socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信
 7 
 8 參數二:類型
 9 
10   socket.SOCK_STREAM  流式socket , for TCP (默認)
11   socket.SOCK_DGRAM   數據報式socket , for UDP
12 
13   socket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。
14   socket.SOCK_RDM 是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在需要執行某些特殊操作時使用,如發送ICMP報文。SOCK_RAM通常僅限於高級用戶或管理員運行的程序使用。
15   socket.SOCK_SEQPACKET 可靠的連續數據包服務
16 
17 參數三:協議
18  (默認)與特定的地址家族相關的協議,如果是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議
19 
20 詳情
詳情
 1 # 服務端
 2 import socket
 3 ip_port = ('127.0.0.1',9999)
 4 sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
 5 sk.bind(ip_port)
 6 
 7 while True:
 8     data,(host,port) = sk.recvfrom(1024)
 9     print(data,host,port)
10     sk.sendto(bytes('ok', encoding='utf-8'), (host,port))
11 
12 
13 #客戶端
14 import socket
15 ip_port = ('127.0.0.1',9999)
16 
17 sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
18 while True:
19     inp = input('數據:').strip()
20     if inp == 'exit':
21         break
22     sk.sendto(bytes(inp, encoding='utf-8'),ip_port)
23     data = sk.recvfrom(1024)
24     print(data)
25 
26 sk.close()
27 
28 UDP Demo
UDP demo

② server.bind(address)

  server.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址

③ server.listen(backlog)

  開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。backlog等於5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的連接個數最大為5,這個值不能無限大,因為要在內核中維護連接隊列

④ server.setblocking(bool)

  是否阻塞(默認True),如果設置False,那么accept和recv時一旦無數據,則報錯

⑤ conn,addr = server.accept() 

  接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。接收TCP 客戶的連接(阻塞式)等待連接的到來

 client.connect(address)

  連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。

⑦ client.connect_ex(address)

  同上,只不過會有返回值,連接成功時返回 0 ,連接失敗時候返回編碼,例如:10061

⑧ client.close()

  關閉套接字

⑨ client.recv(bufsize[,flag])

  接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略

⑩ client.recvfrom(bufsize[.flag])

  與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址

⑪ server.send(string[,flag])

  將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送

⑫ server.sendall(string[,flag])  

  將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常;

      內部通過遞歸調用send,將所有內容發送出去

⑬ server.sendto(string[,flag],address)

  將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議

⑭ sk.settimeout(timeout)

  設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作(如 client 連接最多等待5s )

⑮ sk.getpeername()

  返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)

⑯ sk.getsockname()

  返回套接字自己的地址。通常是一個元組(ipaddr,port)

⑰ sk.fileno()

  套接字的文件描述符

 三、ssh程序

整合下上面的代碼,做個ssh連接的客戶端,實現基本xshell功能

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 #-Author-Lian
 4 
 5 #ssh server
 6 
 7 import socket,os
 8 
 9 ip_port = ("127.0.0.1",9999)
10 server = socket.socket()
11 server.bind(ip_port)
12 server.listen(5)
13 
14 while True:
15     conn,add = server.accept()
16     while True:
17         client_data = conn.recv(1024)
18         recv_data = client_data.decode("utf-8")
19         if recv_data == "exit":
20             break
21         send_data = os.popen(recv_data).read()
22         if not send_data:
23             conn.sendall(client_data+"命令不存在".encode("utf-8"))
24         else:
25             conn.sendall(send_data.encode("utf-8"))
26     conn.close()
27 
28 ssh 服務端
ssh 服務器端
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 #-Author-Lian
 4 
 5 #ssh client
 6 
 7 import socket
 8 
 9 ip_port = ("127.0.0.1",9999)
10 client = socket.socket()
11 client.connect(ip_port)
12 
13 while True:
14     info = input("->>").strip()
15     if not info:
16         continue
17     client.sendall(info.encode("utf-8"))
18     if info == "exit":
19         break
20     server_data = client.recv(1024)
21     print(server_data.decode("utf-8"))
22 
23 client.close()
24 
25 ssh 客戶端
ssh 客戶端

7.粘包

須知:只有TCP有粘包現象,UDP永遠不會粘包。(原因詳見第3點)

1、socket收發消息的原理

                                                       socket發送原理圖

 

2、為什么會出現所謂的粘包

原因:接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的。

  此外,發送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的數據后才發送一個TCP段。若連續幾次需要send的數據都很少,通常TCP會根據優化算法把這些數據合成一個TCP段后一次發送出去,這樣接收方就收到了粘包數據。

  1. TCP(transport control protocol,傳輸控制協議)是面向連接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,因此,發送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將多次間隔較小且數據量小的數據,合並成一個大的數據塊,然后進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通信是無消息保護邊界的。
  2. UDP(user datagram protocol,用戶數據報協議)是無連接的,面向消息的,提供高效率服務。不會使用塊的合並優化算法,, 由於UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區)采用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來說,就容易進行區分處理了。 即面向消息的通信是有消息保護邊界的。
  3. tcp是基於數據流的,於是收發的消息不能為空,這就需要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即便是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭。

 

3、tcp會發生粘包的兩種情況如下:

1、發送端多次send間隔較短,並且數據量較小,tcp會通過Nagls算法,封裝成一個包,發送到接收端,接收端不知道這個包由幾部分組成,所以就會產生粘包。

2、數據量發送的大,接收端接收的小,再接一次,還會出現上次沒有接收完成的數據。就會出現粘包。

 

示例1: 發送端多次send間隔較短,並且數據量較小,tcp會通過Nagls算法,封裝成一個包,發送到接收端,接收端不知道這個包由幾部分組成,所以就會產生粘包。

server服務端:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-      
 3 #Author: nulige
 4  
 5 from socket import *
 6 ip_port=('127.0.0.1',8082)
 7 back_log=5
 8 buffer_size=1024
 9 
10 tcp_server=socket(AF_INET,SOCK_STREAM)
11 tcp_server.bind(ip_port)
12 tcp_server.listen(back_log)
13 
14 conn,addr=tcp_server.accept()
15 
16 data1=conn.recv(buffer_size)  #指定buffer_size ,得到的結果就是通過Nagle算法,隨機接收次數。
17 print('第1次數據',data1)
18 
19 data2=conn.recv(buffer_size)
20 print('第2次數據',data2)
21 
22 data3=conn.recv(buffer_size)
23 print('第3次數據',data3)

client客戶端

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-      
 3 #Author: nulige
 4  
 5 from socket import *
 6 import time
 7 
 8 ip_port=('127.0.0.1',8082)
 9 back_log=5
10 buffer_size=1024
11 
12 tcp_client=socket(AF_INET,SOCK_STREAM)
13 tcp_client.connect(ip_port)
14 
15 tcp_client.send('hello'.encode('utf-8'))
16 tcp_client.send('world'.encode('utf-8'))
17 tcp_client.send('egon'.encode('utf-8'))
18 
19 
20 time.sleep(1000)

執行結果:

1 第1次數據 b'helloworldegon'  #不確定接收次數。

 

示例2:指定接收字節數,相當於服務端知道接收長度,就不會出現粘包現象

粘包服務端

 1 from socket import *
 2 ip_port=('127.0.0.1',8080)
 3 back_log=5
 4 buffer_size=1024
 5 
 6 tcp_server=socket(AF_INET,SOCK_STREAM)
 7 tcp_server.bind(ip_port)
 8 tcp_server.listen(back_log)
 9 
10 conn,addr=tcp_server.accept()
11 
12 data1=conn.recv(5)  #指定每次接收字節數,就不會出現粘包現象
13 print('第一次數據',data1)
14 
15 data2=conn.recv(5)
16 print('第2次數據',data2)
17 
18 data3=conn.recv(5)
19 print('第3次數據',data3)

粘包客戶端

 1 from socket import *
 2 import time
 3 ip_port=('127.0.0.1',8080)
 4 back_log=5
 5 buffer_size=1024
 6 
 7 tcp_client=socket(AF_INET,SOCK_STREAM)
 8 tcp_client.connect(ip_port)
 9 
10 tcp_client.send('hello'.encode('utf-8'))
11 tcp_client.send('world'.encode('utf-8'))
12 tcp_client.send('egon'.encode('utf-8'))
13 
14 
15 time.sleep(1000)

執行結果:

1 第1次數據 b'hello'   #不會出現粘包現象,發送三次,就接收三次
2 第2次數據 b'world'
3 第3次數據 b'egon'

 

示例3:數據量發送的大,接收端接收的小,再接一次,還會出現上次沒有接收完成的數據。就會出現粘包。

粘包服務端

 1 from socket import *
 2 ip_port=('127.0.0.1',8080)
 3 back_log=5
 4 buffer_size=1024
 5 
 6 tcp_server=socket(AF_INET,SOCK_STREAM)
 7 tcp_server.bind(ip_port)
 8 tcp_server.listen(back_log)
 9 
10 conn,addr=tcp_server.accept()
11 
12 data1=conn.recv(1)
13 print('第1次數據',data1)
14 
15 # data2=conn.recv(5)
16 # print('第2次數據',data2)
17 #
18 # data3=conn.recv(1)
19 # print('第3次數據',data3)

粘包客戶端

 1 from socket import *
 2 import time
 3 ip_port=('127.0.0.1',8080)
 4 back_log=5
 5 buffer_size=1024  #接收的數據只有1024
 6 
 7 tcp_client=socket(AF_INET,SOCK_STREAM)
 8 tcp_client.connect(ip_port)
 9 
10 tcp_client.send('helloworldegon'.encode('utf-8'))
11 
12 time.sleep(1000)

執行結果: 

1 第1次數據 b'h'
2 第2次數據 b'ellow'  #發送的數據過大,接收的數據設置的較小,就會出現導致粘包 
3 第3次數據 b'o'

 

補充知識:

1、tcp是可靠傳輸

  tcp在數據傳輸時,發送端先把數據發送到自己的緩存中,然后協議控制將緩存中的數據發往對端,對端返回一個ack=1,發送端則清理緩存中的數據,對端返回ack=0,則重新發送數據,所以tcp是可靠的。

 

2、udp是不可靠傳輸

   udp發送數據,對端是不會返回確認信息的,因此不可靠。

3.解決粘包

法一:比較(LOW)版本

 示例:

low_socket_server服務端

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-      
 3 #Author: nulige
 4  
 5 #low版解決粘包版本服務端
 6 from socket import *
 7 import subprocess
 8 ip_port=('127.0.0.1',8080)
 9 back_log=5
10 buffer_size=1024
11 
12 tcp_server=socket(AF_INET,SOCK_STREAM)
13 tcp_server.bind(ip_port)
14 tcp_server.listen(back_log)
15 
16 while True:
17     conn,addr=tcp_server.accept()
18     print('新的客戶端鏈接',addr)
19     while True:
20         #收消息
21         try:
22             cmd=conn.recv(buffer_size)
23             if not cmd:break
24             print('收到客戶端的命令',cmd)
25 
26             #執行命令,得到命令的運行結果cmd_res
27             res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
28                                  stderr=subprocess.PIPE,
29                                  stdout=subprocess.PIPE,
30                                  stdin=subprocess.PIPE)
31             err=res.stderr.read()
32             if err:
33                 cmd_res=err
34             else:
35                 cmd_res=res.stdout.read()
36 
37             #發送消息
38             if not cmd_res:
39                 cmd_res='執行成功'.encode('gbk')
40 
41             length=len(cmd_res)  #計算長度
42             conn.send(str(length).encode('utf-8')) #把長度發給客戶端
43             client_ready=conn.recv(buffer_size)    #卡着一個recv
44             if client_ready == b'ready':  #如果收到客戶端的ready消息,就說明准備好了。
45                 conn.send(cmd_res)        #就可以send給客戶端發送消息啦!
46         except Exception as e:
47             print(e)
48             break

low_socket_client客戶端

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-      
 3 #Author: nulige
 4  
 5 #low版解決粘包版客戶端
 6 from socket import *
 7 ip_port=('127.0.0.1',8080)
 8 back_log=5
 9 buffer_size=1024
10 
11 tcp_client=socket(AF_INET,SOCK_STREAM)
12 tcp_client.connect(ip_port)
13 
14 while True:
15     cmd=input('>>: ').strip()
16     if not cmd:continue
17     if cmd == 'quit':break
18 
19     tcp_client.send(cmd.encode('utf-8'))
20 
21 
22     #解決粘包
23     length=tcp_client.recv(buffer_size)  #接收發送過來的長度(1024*8=8192,2**8192=可以接收的長度)
24     tcp_client.send(b'ready')   #客戶端再send給服務端,告訴服務端我准備好啦!
25 
26     length=int(length.decode('utf-8'))  #先解碼,轉成字符串的長度
27     #解決思路:就是提前發一個頭過去,告訴客戶端需要接收的長度(分兩步:1、發送發度 2、再次發送數據)
28     recv_size=0   #接收的尺寸
29     recv_msg=b''  #最后要拼接起來
30     while recv_size < length:  #要收多大?,要先判斷接收的尺寸<length
31         recv_msg += tcp_client.recv(buffer_size)  #接收到的數據,拼接buffer_size,
32         recv_size=len(recv_msg) #1024  #衡量自己接收了多少數據,有沒有收完(統計recv_msg的長度)
33 
34 
35     print('命令的執行結果是 ',recv_msg.decode('gbk'))
36 tcp_client.close()

執行結果:

#客戶端執行命令:
>>: cat /etc/passwd
命令的執行結果是  'cat' is not recognized as an internal or external command,
operable program or batch file.

>>: dir
命令的執行結果是   Volume in drive D is SSD
 Volume Serial Number is 687D-EF64

 Directory of D:\python\day30

2017/01/04  00:36    <DIR>          .
2017/01/04  00:36    <DIR>          ..
2017/01/03  11:39               613 client.py
2017/01/03  11:40               597 client_01.py
2017/01/03  11:40               597 client_02.py
2017/01/04  00:35               770 low_socket_client.py
2017/01/04  00:36             1,408 low_socket_server.py
2017/01/03  15:54               438 ntp_clinet.py
2017/01/03  16:01               591 ntp_server.py
2017/01/03  19:36               206 s1_server.py
2017/01/03  11:26               588 server.py
2017/01/03  11:55               717 server01.py
2017/01/03  23:57               603 socket_clinet_tcp.py
2017/01/04  00:12               531 socket_clinet_udp.py
2017/01/03  17:48             1,301 socket_server_tcp.py
2017/01/03  18:27               897 socket_server_udp.py
2017/01/03  15:38               440 udp_clinet.py
2017/01/03  15:23               355 udp_server.py
              16 File(s)         10,652 bytes
               2 Dir(s)  638,994,411,520 bytes free

>>: cd ..
命令的執行結果是  執行成功


#服務端返回結果:
新的客戶端鏈接 ('127.0.0.1', 54395)
收到客戶端的命令 b'cat /etc/passwd'
收到客戶端的命令 b'dir'
收到客戶端的命令 b'cd ..'
View Code

總結:

(為何low):  程序的運行速度遠快於網絡傳輸速度,所以在發送一段字節前,先用send去發送該字節流長度,這種方式會放大網絡延遲帶來的性能損耗。

 

法二:節省網絡傳輸版本(牛逼版本)

  為字節流加上自定義固定長度報頭,報頭中包含字節流長度,然后一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然后再取真實數據。

示例:(沒實現多客戶端並發)

 tcp_socket_server服務端:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-      
 3 #Author: nulige
 4  
 5 from socket import *
 6 import subprocess
 7 import struct
 8 ip_port=('127.0.0.1',8080)
 9 back_log=5
10 buffer_size=1024
11 
12 tcp_server=socket(AF_INET,SOCK_STREAM)
13 tcp_server.bind(ip_port)
14 tcp_server.listen(back_log)
15 
16 while True:
17     conn,addr=tcp_server.accept()
18     print('新的客戶端鏈接',addr)
19     while True:
20         #收
21         try:
22             cmd=conn.recv(buffer_size)
23             if not cmd:break
24             print('收到客戶端的命令',cmd)
25 
26             #執行命令,得到命令的運行結果cmd_res
27             res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
28                                  stderr=subprocess.PIPE,
29                                  stdout=subprocess.PIPE,
30                                  stdin=subprocess.PIPE)
31             err=res.stderr.read()
32             if err:
33                 cmd_res=err
34             else:
35                 cmd_res=res.stdout.read()
36 
37             #發
38             if not cmd_res:
39                 cmd_res='執行成功'.encode('gbk')
40 
41             length=len(cmd_res)
42 
43             data_length=struct.pack('i',length)
44             conn.send(data_length)
45             conn.send(cmd_res)
46         except Exception as e:
47             print(e)
48             break

 tcp_socket_client客戶端

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-      
 3 #Author: nulige
 4  
 5 from socket import *
 6 import struct
 7 from functools import partial
 8 ip_port=('127.0.0.1',8080)
 9 back_log=5
10 buffer_size=1024
11 
12 tcp_client=socket(AF_INET,SOCK_STREAM)
13 tcp_client.connect(ip_port)
14 
15 while True:
16     cmd=input('>>: ').strip()
17     if not cmd:continue
18     if cmd == 'quit':break
19 
20     tcp_client.send(cmd.encode('utf-8'))
21 
22 
23     #解決粘包
24     length_data=tcp_client.recv(4)
25     length=struct.unpack('i',length_data)[0]
26 
27     recv_size=0
28     recv_data=b''
29     while recv_size < length:
30         recv_data+=tcp_client.recv(buffer_size)
31         recv_size=len(recv_data)
32     print('命令的執行結果是 ',recv_data.decode('gbk'))
33 tcp_client.close()

執行結果:

#客戶端向服務器發送消息
>>: dir
命令的執行結果是   Volume in drive D is SSD
 Volume Serial Number is 687D-EF64

 Directory of D:\python\day31

2017/01/05  18:31    <DIR>          .
2017/01/05  18:31    <DIR>          ..
2017/01/05  14:33               244 s1.py
2017/01/05  18:29               752 s1_tcp_socket_client.py
2017/01/05  18:31               679 s1_tcp_socket_client01.py
2017/01/05  18:28             1,325 s1_tcp_socket_server.py
2017/01/05  15:01               498 tcp_socket_client.py
2017/01/05  15:00               670 tcp_socket_server.py
2017/01/05  17:16               391 udp_socket_clinet.py
2017/01/05  17:20               512 udp_socket_server.py
               8 File(s)          5,071 bytes
               2 Dir(s)  609,921,380,352 bytes free

#服務端返回結果:
新的客戶端鏈接 ('127.0.0.1', 53585)
收到客戶端的命令 b'dir'
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM