IPython下的网络协议学习-------以echo回显程序为例

来源:互联网 发布:东方不败网络歌手 编辑:程序博客网 时间:2024/05/29 17:18

在进入正文之前强烈推荐IPython 来学习网络编程,和C语言繁琐的语法和编译相比,IPython交互的输入输出效率奇高,打开两个IPython页面就可以方便的编写服务端和客户端程序了。推荐anaconda环境,集成了很多必备的库,以及IPython notebook 。

废话说完了,下面进入正题。本文主要以简单的回显程序为例,实践测试了不同异常情况下服务端和客户端的反应,由此逐步提高程序的健壮性。异常情况主要参考网络编程著作《UNIX网络编程》第三版。

我的服务器代码和客户端代码是这样的:

#Echo Serverimport socketimport sysimport signalimport osdef server_echo(connefd):    while 1:        try :            print ("before recv")            mesg = connefd.recv(8)            print (mesg)            if not mesg:                return            connefd.send(mesg)        except os.error:            print ("socket error")                  return def signal_handlers(signo,frame): #信号处理函数    print ("child %d terminnated",os.wait())serverPort = 50000listenfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)listenfd.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)listenfd.bind(("",serverPort))listenfd.listen(5)signal.signal(signal.SIGCHLD,signal_handlers)  while 1:    connefd,address = listenfd.accept()    childpid = os.fork()    if(childpid == 0):        listenfd.close()        server_echo(connefd)        exit(0)    connefd.close()
#客户端import socketconnefd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)connefd.connect(('127.0.0.1',5000))while 1:    sentence = input("Enter your sentence:")    if sentence == "":            connefd.close()            break    connefd.send(sentence.encode())    mesg = connefd.recv(8)    print mesg

代码很简单,比较值得一提的是服务器程序中利用系统调用fork函数进行实现并发服务器。除此之外,必须提供一个SIGCHLD信号的处理程序。当子进程结束时,内核会自动发送SIGCHLD信号给父进程,默认处理方式是忽略。那么我们为什么要处理SIGCHLD信号呢?原因是如果不主动处理SIGCHLD会产生大量僵尸进程。

一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)

因此,仅仅调用exit()不能完全的释放进程资源,我们必须**捕捉(capture)**SIGCHLD信号,在父进程的信号处理程序中释放资源。为此,Unix系统提供了wait,waitpid等函数获取子进程的终止状态,并释放其资源。那么wait和waitpid这些函数有什么作用呢?顾名思义,wait函数是一个等待的函数,父进程调用wait函数来等待一个子进程终止,并清理第一个终止子进程的“尸体”。那么waitpid函数的作用又是什么呢?waitpid函数相当于一个加强版的wait函数,他的函数参数中可以设定需要等待的子进程ID,如果没有子进程已终止则可选择不阻塞等选项。参阅UNPV1第三版可以了解waitpid更详细的信息。

接下来就UNP 5.11节 至 5.16节的小节所述的异常情况进行模拟,并总结一些异常处理方法和编程技巧。

(1) 服务器程序在accept返回前连接终止

accept函数是服务器程序调用的一个函数,作用是服务器接受一个ESTABLISHED状态的TCP连接,并生成一个新的套接字,叫做已连接套接字(connected socket) 。函数接受的套接字是由listening socket 维护的 已连接队列中的队首的连接,如果队列为空,则accept函数阻塞。
注意:TCP连接在客户端调用connect函数返回时就已经完成连接,这意味着服务端在调用 accept函数之前TCP连接已经建立。

为了模拟accept函数在返回前连接终止,我们可以让客户端在connect函数返回后,利用SO_LINGER套接字选项来改变close函数的行为(让其向服务端发送RST报文段)。在RST报文段发送之前,我们的服务端accept函数应该返回。因此可以在服务端调用accept函数前利用input函数或是sleep函数阻塞程序,当RST报文端发送后再调用accept函数。服务端和客户端代码改变如下:

#服务端while 1:    input("Enter Any key to continue")  #添加的阻塞语句    connefd,address = listenfd.accept()
#客户端:connefd.connect(('127.0.0.1',5000))connefd.setsockopt(socket.SOL_SOCKET,                    socket.SO_LINGER,struct.pack("ii",True,0))#添加的套接字选项,改变了套接字close函数的执行内容,关于SO_LINGER套接字选项可以查阅UNP7.5节connefd.close()

让我们运行代码测试一下结果,同时可以利用tcpdump或者是Wireshark抓包工具监测RST报文段是否发送成功。
客户端代码执行时的Wireshark抓包截图
客户端代码执行时的Wireshark抓包截图

最后让我们来看一下服务端收到RST报文段后服务端的响应。

ConnectionResetError                      Traceback (most recent call last)<ipython-input-1-4ac06058bc37> in <module>()     32     if(childpid == 0):     33         listenfd.close()---> 34         server_echo(connefd)     35         os._exit(0)     36     connefd.close()<ipython-input-1-4ac06058bc37> in server_echo(connefd)      8       #  try :      9         print ("before recv")---> 10         mesg = connefd.recv(8)     11         print (mesg)     12         if not mesg:ConnectionResetError: [Errno 104] Connection reset by peer​

可以看到服务端返回一个ConnectionResetError错误,而且继续调用accept函数,之后其他客户端也能继续连接服务端正常运行程序。我们也能捕捉ConnectionResetError,进而实现自定义的错误处理方法。

后续将继续对不同异常情况进行试验,记录学习TCP/IP协议的过程与经验。

W.Richard Stevens.UNIX网络编程 卷1:套接字联网API[M].北京:人民邮电出版社,2010

0 0
原创粉丝点击