Python應用-[用Python實現一個socket echo程序 && tcp socket的幾個關閉狀態]


這里用Python實現了一個echo程序的服務端和客戶端,客戶端發出的東西,服務端打上一個時間戳后給客戶端發回去。主要是實踐一下Python的socket編程

Python的socket相關的比較低層的接口都在標准庫中的socket module來實現的,這個module中定義的屬性包括一些常量,如下面34行的AF_INET,SOCK_STREAM,全局函數ntohl(byte order translation),另外還有一個類socket,這個Socket Object里面包裝了像listen, accept這些函數。socket module里面的全局函數socket就返回這樣一個Socket Object, 如下server端程序34行

1,服務端程序, 程序中的注釋有比較詳細的說明。

 1 #!/usr/bin/python
 2 #encoding=utf-8
 3 
 4 #
 5 # echo server, 對每一個請求都單獨fork出一個進程來處理
 6 #
 7 
 8 from socket import *  #low-level networking interface
 9 import time  # Time access and conversions
10 import os  # for  os.fork()
11 import sys # for sys.exit()
12 
13 def func(tcpCliSock, addr):  # python中沒有單獨的聲明,函數必須在使用前定義
14     try:    
15         while True:
16             data = tcpCliSock.recv(BUFSIZE) # BUFSIZE表示一次返回的最大的接收數據量,這個接口和unix中的recv
17             if not data:
18                 print 'connection end with : ', addr
19                 break
20             print ' request from ', addr , 'data :', data
21             tcpCliSock.send('[%s] %s' % (time.ctime(), data))
22 
23         tcpCliSock.close()
24     except KeyboardInterrupt: #從輸出可以看出若父進程的ctl-c異常會先在子進程中捕獲,再在父進程中捕獲
25         print ' Ctl-c caught in chlid Process'27         tcpCliSock.close()
28         sys.exit()
29 
30 HOST = 'localhost'
31 PORT = 2012 # port是integer
32 BUFSIZE = 1024
33 ADDR = (HOST, PORT)
34 
35 tcpServerSock = socket(AF_INET, SOCK_STREAM)  #這里soket函數,以及括號里的參數都是 socket module里的提拱的函數(全局), 而返回的是一個Socket Object, 是socket module里定義的一個class
36 tcpServerSock.bind(ADDR)
37 tcpServerSock.listen(10) #10表accept的等待隊列最大數
38 
39 try:
40     while True:
41         print 'waiting for connection ...'
42         tcpCliSock, addr = tcpServerSock.accept() #accept返回的是一個pair,(conn, address), 其中conn是一個Socket Object, address是另一端發起連接的端口地址,地址也是一個pair, (ip, port)
43         print 'connected from :', addr, '  local:', tcpCliSock.getsockname(), '------remote:', tcpCliSock.getpeername()
44         
45         # fork出一個子進程來處理單獨的請求
46         pid = os.fork()
47 
48         if pid > 0:
49             print 'fork child process pid = ', pid, 'to process ', addr
50 #           os.waitpid(pid, os.WNOHANG)  #還是沒有起到回收的作用
51             tcpCliSock.close()  #這個close很重要
52 
53         if pid == 0 :
54             tcpServerSock.close() #在子進程中關掉 tcpServerSock
55             func(tcpCliSock, addr) #子進程中運行func
56             sys.exit() #可以簡單理解為退出子進程
57     
58     tcpServerSock.close()
59 
60 except  KeyboardInterrupt:    # 截獲Ctr-c 異常,來關掉Server
61     print ' Ctl-C stop server'
62     tcpServerSock.close()
63     sys.exit()

對於服務端的這個小程序,盡量做了一些完善,在fork出子進程后關掉tcpServerSock, 在子進程和父進程中都捕獲Ctrl-c終止程序的異常,讓server退出優雅些;

對於34行調用socket.socket()傳的兩個參數AF_INET,SOCK_STREAM表示創建基於網絡的socket(套接字socket分為Unix socket和Internet socket),SOCK_STREAM表示是tcp socket。

用單獨的進程處理每個獨立的客戶端,45行用了os.fork(),這里subprocess模塊不適用。os.fork()同樣的在父進程中返回子進程id,在子進程中返回0, 48行打印出子進程id

第50行的os.waitpid(),本來是想子進程退出的時候被父進程及時回收,不要變成defunct , 僵屍進程,但是沒有達到效果,注掉了

 

2. 客戶端程序

 1 #!/usr/bin/python
 2 #encoding=utf-8
 3 
 4 #
 5 # echo程序客戶端,服務端把每條消息打上時間戳后返回
 6 #
 7 
 8 from socket import *
 9 import sys
10 
11 BUFSIZE = 1024
12 serverAddr = ('localhost', 2012)
13 clientSock = socket(AF_INET, SOCK_STREAM)
14 
15 try:
16     clientSock.connect(serverAddr) #若要連接的服務端沒有啟動,這里會拋出異常
17 except Exception as e: #不知道這里捕獲什么具體的異常了,就用的一個比較基層的類Exception
18     print 'exception catched : ' ,e
19     sys.exit()
20 
21 while True:
22     data = raw_input('input data :')
23     if not data:  # 直接回車就沒有輸入內容,退出
24         print 'client quit'
25         break    
26     clientSock.send(data)
27     data = clientSock.recv(BUFSIZE)
28     print 'recieve : ', data
29 
30 clientSock.close() # 要close一下

 注意第17行捕獲異常的語法。

 

 3,根據上面的程序來看看tcp socket的幾個關閉狀態

        tcp有5個和關閉相關的狀態, 見節末的那張圖

  上面服務端程序51行的代碼很關鍵,在父進程在把tcpCliSock關掉,實際上父進程的server的2012端口一直在listen,而fork出的子進程的那個tcpCliSock和client建立傳輸數據的連接。當我們啟動server,再啟動一個client連上一個server的時候,查看相關的tcp連接是這樣的。   第一行是父進程用來listen的, 后兩個依次是server fork出的子進程和client進程

這時候如果用ctr-c停掉server, 相關的tcp狀態變為

LISTEN的tcp socket在close()后就自動變成CLOSED的狀態了,可以看到這一對確定連接的tcp socket,服務端主動發起的關閉請求(根據上面的代碼,捕獲KeyboardException的時候有發起關閉),server端發送一個FIN然后接收到ACK變成了 FIN_WAIT2狀態,而client端接收到了FIN然后回一個ACK成了CLOSE_WAIT狀態。 注意client端沒有發起FIN,也就是client socket沒有close()

如果是client端主動關閉,則相關的tcp狀態變為

 這里因為是新起的進程來實驗,所以pid跟上面的不一樣了,下面那個TIME_WAIT是client端socket的狀態。而server端的socket是CLOSED狀態了。TIME_WAIT是主動關閉一端最終的狀態。這里client主動發起關閉請求時,server端接到回ACK后也主到發起FIN關閉了socket。所以這里的處理是完整的。

關於這幾個tcp的狀態,這篇來自coolshell.cn上的文章有較為詳細的說明。下面引用一篇這篇文章中的圖

 

后續

在上面server端主動關閉tcp連接時,關閉並不完整,如果client端回ACK后能再主動發起關閉請求,就完整了,server端可以這樣做,在 ctl-c中止服務時向client發送一個空串,client接到后也主動關閉socket,然后退出程序就好了。

server端如下修改, 在25行后加上一行代碼。

25         print ' Ctl-c caught in chlid Process'
26         tcpCliSock.send('')
27         tcpCliSock.close()

但是這里有一個問題,client端阻塞在raw_input那個地方,實際上必須要輸入一點東西才能接收到server端的內容,如何比如用多線程改到能即時做到接收這個空串然后關掉socket,然后退出程序?

 

 

 

 

 

 

 

 

 

 


免責聲明!

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



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