zoukankan      html  css  js  c++  java
  • (三)通过线程编写一个简单的并发服务器

    概述

    之前在上一节通过使用fork来实现了一个并发程序,它很经典但是效率不高主要是太消耗资源因为fork一个进程的开销很大,假如100客户端连接就需要100个进程,这样不是不可以只是这种方式不太高级,下面我们通过使用线程来实现并发,因为产生一个线程的开销要小的多,当然对于大规模并发的场景使用线程也不是最好的选择,但是学习socket编程的过程中这些东西是需要了解的。

    代码段

    服务器端

     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 # Author: rex.cheny
     4 # E-mail: rex.cheny@outlook.com
     5 
     6 import socket
     7 import time
     8 import threading
     9 
    10 
    11 def echoStr(connFd):
    12     print("新连接:", connFd.getpeername())
    13     while True:
    14         bytesData = connFd.recv(1024)
    15         data = bytesData.decode(encoding="utf-8")
    16         print("收到客户端消息:", data)
    17         if data == "Bye":
    18             """
    19             这里需要关闭连接,在之前的fork模式中这里是直接返回的
    20             不过使用线程则需要先关闭再返回。
    21             """
    22             connFd.close()
    23             return
    24         else:
    25             time.sleep(1)
    26             connFd.send(data.encode(encoding="utf-8"))
    27 
    28 
    29 def main():
    30     sockFd = socket.socket()
    31     sockFd.bind(("", 5555))
    32     sockFd.listen(5)
    33 
    34     print("等待客户端连接......")
    35     while True:
    36         connFd, remAddr = sockFd.accept()
    37         try:
    38             """
    39             这里产生一个线程来处理连接,我们在这里启动线程后不能像fork模式那样关闭连接套接字
    40             因为线程是共享进程资源的所以你这里如果关闭那么这个TCP连接也就断了。而之前在fork
    41             模式中需要关闭是因为进程的资源是隔离的父子进程对同一个文件描述符的两次引用,而在
    42             线程里对这个一个文件描述符只引用了一次,所以这里不能关闭。
    43             """
    44             t = threading.Thread(target=echoStr, args=(connFd,))
    45             t.start()
    46         except Exception as err:
    47             print(err)
    48 
    49 
    50 if __name__ == '__main__':
    51     main()

    客户端代码

    与之前的相同,不过这里还是放进来

     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 # Author: rex.cheny
     4 # E-mail: rex.cheny@outlook.com
     5 
     6 import socket
     7 
     8 
     9 def echoStr(sockFd, data):
    10     sockFd.send(data)
    11     bytesData = sockFd.recv(1024)
    12     data = bytesData.decode(encoding="utf-8")
    13     print(data)
    14 
    15 
    16 def main():
    17     sockFd = socket.socket()
    18     sockFd.connect(("127.0.0.1", 5555))
    19 
    20     for i in range(1, 11):
    21         data = "第:" + str(i) + " 条消息。"
    22         echoStr(sockFd, data.encode(encoding="utf-8"))
    23 
    24     echoStr(sockFd, "Bye".encode(encoding="utf-8"))
    25     sockFd.close()
    26 
    27 
    28 if __name__ == '__main__':
    29     main()

    结果演示

    效果一样。不过这里有一个问题其实包括之前的fork版本的程序也有问题就是如果处理客户端请求的进程崩溃,那么服务器端TCP协议栈会发送FIN,这时候客户端肯定可以收到并回复ACK,但是如果客户端这时候刚好要发送下一条消息,这时候客户端就是在一个已收到FIN的套接字里写数据,那么它会收到一个RST,它写完了马上会调用读(从代码里可以看出来)那么读一个收到RST的套接字将会报错。而如果它收到FIN的时候刚好是读则会得到一个EOF。这也就是上一节里末尾的图所说明的内容。其实如果客户端能够及时对收到的FIN做出反应那么将避免对收到FIN的套接字进行操作,其实在我们的例子中效果不明显因为我的客户端程序是自动发送消息的,如果你改成输入消息则会非常明显。因为会阻塞在用户输入的地方,因为这种阻塞模式的套接字程序,收到FIN后它无法处理,换句话说对于客户端来说它在处理两个文件描述符一个是套接字的一个是标准输入的,要想解决这个问题我们就需要用到多路复用。

  • 相关阅读:
    Tab支持的DHTML Window控件
    仿新浪游戏频道js多栏目全屏下拉菜单导航条
    Jquery实现超酷的时间轴特效
    DIV+CSS专题:第一天 XHTML CSS基础知识
    网站建设:详解网页扁平化设计
    简洁的支持展开关闭的tab标签代码
    JS总结
    codevs 1296 营业额统计 (splay 点操作)
    codeforces gym 100357 H (DP 高精度)
    codeforces gym 100357 K (表达式 模拟)
  • 原文地址:https://www.cnblogs.com/rexcheny/p/9720966.html
Copyright © 2011-2022 走看看