zoukankan      html  css  js  c++  java
  • 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,然后退出程序?

     

     

  • 相关阅读:
    在日本被禁止的コンプガチャ設計
    Starling常见问题解决办法
    Flixel引擎学习笔记
    SQLSERVER中修复状态为Suspect的数据库
    T4 (Text Template Transformation Toolkit)实现简单实体代码生成
    创建Linking Server in SQL SERVER 2008
    Linq to Sql 与Linq to Entities 生成的SQL Script与分页实现
    Linq to Entity 的T4 模板生成代码
    在VisualStudio2008 SP1中调试.net framework 源代码
    使用HttpModules实现Asp.net离线应用程序
  • 原文地址:https://www.cnblogs.com/livingintruth/p/2748595.html
Copyright © 2011-2022 走看看