第八章网络编程进阶
Posted bj-mr-li
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第八章网络编程进阶相关的知识,希望对你有一定的参考价值。
####一、问答题
1、简述计算机操作系统中的“中断”的作用?
中断指当出现需要时,CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程 计算机执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得cpu暂时中断当前正在执行的程序而转去执行相应的事件处理程序。 待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。它使计算机可以更好更快利用有限的系统资源解决系统响应速度和运行效率的一种控制技术。 实时响应 + 系统调用
2、简述计算机内存中的“内核态”和“用户态”;
操作系统由操作系统的内核(运行于内核态,管理硬件资源)以及 系统调用(运行于用户态,为应用程序员写的应用程序提供系统调用接口)两部分组成; 内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。 用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。 用户态的应用程序可以通过三种方式来访问内核态的资源: 1)系统调用 2)库函数 3)Shell脚本 用户态到内核态的切换: 1.系统调用 用户程序主动发起的 软中断 os.fork() process 2.异常 被动的 当CPU正在执行运行在用户态的程序时,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程 转向内核态执行相关的异常事件,典型的如缺页异常。 3.外围设备的硬中断 被动的 外围设备完成用户的请求操作后,会像CPU发出中断信号,此时,CPU就会暂停执行下一条即将要执行的指令, 转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就发生从用户态到内核态的转换。
3、什么是进程?
进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。
4、什么是线程?
线程顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于cpu),而一条流水线必须属于一个车间, 一个车间的工作过程是一个进程,车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一条流水线。 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
5、简述程序的执行过程;
1.激活了python的解释器,有一个解释器级别的垃圾回收线程(GIL锁)。 2.一个进程下的多个线程去访问解释器的代码,拿到执行权限,将程序当作参数传递给解释器的代码去执行。 3.保护不同的数据应该用不同的锁。 4.python程序是顺序执行的! 5.一段python程序以.py文件运行时,文件属性__name__==__main__;作为模块导入时,文件属性__name__为文件名。
6、什么是“系统调用”?
所有用户程序都是运行在用户态的,但是有时候程序确实需要做一些内核态的事情,例如从硬盘读取数据,或者从键盘获取输入等, 而唯一能做这些事情的就是操作系统,所以此时程序就需要向操作系统请求以程序的名义来执行这些操作。 这时,就需要一个机制:用户态程序切换到内核态,但是不能控制在内核态中执行的指令。这种机制就叫系统调用。
7、threading模块event和condition的区别;
condition参考:https://blog.csdn.net/a349458532/article/details/51590040 https://blog.csdn.net/u013346751/article/details/78500412 condition: 某些事件触发或达到特定的条件后才处理数据,默认创建了一个lock对象。 con = threading.Condition() con.acquire() con.notify() con.wait() con.release() event:其他线程需要通过判断某个线程的状态来确定自己的下一步操作,就可以用event。 from threading import Event event = Event() event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.is_set():返回event的状态值; event.wait():如果 event.is_set()==False将阻塞线程; event.clear():恢复event的状态值为False。
8、进程间通信方式有哪些?
管道、信号量、信号、消息队列、共享内存、套接字 1)管道 管道分为有名管道和无名管道 无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。无明管道一般用于两个不同进程之间的通信。 当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。 有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。 2)信号量 信号量是一个计数器,可以用来控制多个线程对共享资源的访问.,它不是用于交换大批数据,而用于多线程之间的同步.它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源. 因此,主要作为进程间以及同一个进程内不同线程之间的同步手段. 3)信号 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生. 4)消息队列 消息队列是消息的链表,存放在内核中并由消息队列标识符标识.消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点. 消息队列是UNIX下不同进程之间可实现共享资源的一种机制,UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程. 对消息队列具有操作权限的进程都可以使用msget完成对消息队列的操作控制.通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序. 5)共享内存 共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.共享内存是最快的IPC(进程间通信)方式, 它是针对其它进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信. 6)套接字:可用于不同及其间的进程通信
9、简述你对管道、队列的理解;
管道通常指无名管道 1、它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端 2、它只能用于具有亲缘关系的进程中通信(也就是父与子进程或者兄弟进程之间) 3、数据不可反复读取了,即读了之后欢喜红区中就没有了 消息队列 1、消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级 2、消息队列独立于发送与接收进程。进程终止时,消息队列及其内容不会被删除。 3、消息队列可以实现消息随机查询。 mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。 队列和管道都是将数据存放于内存中,而队列又是基于(管道+锁)实现的, 可以让我们从复杂的锁问题中解脱出来,因而队列才是进程间通信的最佳选择。 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题, 而且在进程数目增多时,往往可以获得更好的可展性。 队列 = 管道 + 锁 from multiprocessing import Queue,Process queue = Queue() queue.put(url) url = queue.get() from multiprocessing import Pipe,Process pipe = Pipe() pipe.send(url) pipe.recv()
10、请简述你对join、daemon方法的理解,举出它们在生产环境中的使用场景;
join: 等待一个任务执行完毕;可以将并发变成串行。 daemon: 守护进程(守护线程)会等待主进程(主线程)运行完毕后被销毁。 运行完毕: 1.对主进程来说,运行完毕指的是主进程代码运行完毕。 2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕。
11、请简述IO多路复用模型的工作原理
IO多路复用实际上就是用select,poll,epoll监听多个io对象,当io对象有变化(有数据)的时候就通知用户进程。好处就是单个进程可以处理多个socket。 1.当用户进程调用了select,那么整个进程会被block; 2.而同时,kernel会“监视”所有select负责的socket; 3.当任何一个socket中的数据准备好了,select就会返回; 4.这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。 总结: 1.I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。 2.IO多路复用:需要两个系统调用,system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是, 用select的优势在于它可以同时处理多个connection。 3.如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用多线程 + 阻塞 IO的web server性能更好,可能延迟还更大。 4.select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
12、threading中Lock和RLock的相同点和不同点;
Lock():互斥锁,只能被acquire一次,可能会发生死锁情况。 RLock():递归锁,可以连续acquire多次。 RLock = Lock + counter counter:记录了acquire的次数,直到一个线程所有的acquire都被release,其他线程才能获得资源。
*13、什么是select,请简述它的工作原理,简述它的优缺点;
python中的select模块专注于I/O多路复用,提供了select poll epoll三个方法;后两个在linux中可用,windows仅支持select。 fd:文件描述符 fd_r_list,fd_w_list,fd_e_list = select.select(rlist,wlist,xlist,[timeout]) 参数:可接受四个参数(前三个必须) rlist:等到准备好阅读 wlist:等到准备写作 xlist:等待“异常情况” 超时:超时时间 返回值:三个列表 select监听fd变化的过程分析: 用户进程创建socket对象,拷贝监听的fd到内核空间,每一个fd会对应一张系统文件表,内核空间的fd响应到数据后, 就会发送信号给用户进程数据已到; 用户进程再发送系统调用,比如(accept)将内核空间的数据copy到用户空间,同时作为接受数据端内核空间的数据清除, 这样重新监听时fd再有新的数据又可以响应到了(发送端因为基于TCP协议所以需要收到应答后才会清除)。 该模型的优点: 相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。 如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。 该模型的缺点: 首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。 很多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。 如果需要实现更高效的服务器程序,类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异, 所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。 其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。
*14、什么是epoll,请简述它的工作原理,简述它的优缺点;
epoll: 性能最好的多路复用I/O就绪通知方法。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。 因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。 epoll:同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值, 你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。 另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描, 而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符, 当进程调用epoll_wait()时便得到通知。从以上可知,epoll是对select、poll模型的改进,提高了网络编程的性能,广泛应用于大规模并发请求的C/S架构中。 python中的epoll: 只适用于unix/linux操作系统
*15、简述select和epoll的区别;
select: 调用select()时 1、上下文切换转换为内核态 2、将fd从用户空间复制到内核空间 3、内核遍历所有fd,查看其对应事件是否发生 4、如果没发生,将进程阻塞,当设备驱动产生中断或者timeout时间后,将进程唤醒,再次进行遍历 5、返回遍历后的fd 6、将fd从内核空间复制到用户空间 select: 缺点 1、当文件描述符过多时,文件描述符在用户空间与内核空间进行copy会很费时 2、当文件描述符过多时,内核对文件描述符的遍历也很浪费时间 3、select最大仅仅支持1024个文件描述符 epoll很好的改进了select: 1、epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时,会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。 2、epoll会在epoll_ctl时把指定的fd遍历一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表。 epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd。 3、epoll对文件描述符没有额外限制。
16、简述多线程和多进程的使用场景;
多进程用于计算密集型,如金融分析;利用多核实现并发。 多线程用于IO密集型,如socket,爬虫,web。
17、请分别简述threading.Condition、threading.event、threading.semaphore、的使用场景;
condition: 某些事件触发或达到特定的条件后才处理数据。 event: 用来通知线程有一些事情已发生,从而启动后继任务的开始。 semaphore: 为控制一个具有有限数量用户资源而设计。
19、简述你对Python GIL的理解;
GIL(global interpreter lock)全局解释器锁 GIL是CPython的一个概念,本质是一把互斥锁,将并发运行变成串行。 解释器的代码是所有线程共享的,所以垃圾回收线程也有可能访问到解释器的代码去执行。 因此需要有GIL锁,保证python解释器同一时间只能执行一个任务的代码。 GIL:解释器级别的锁(保护的是解释器级别的数据,比如垃圾回收的数据) Lock:应用程序的锁(保护用户自己开发的应用程序的数据)
20、请列举你知道的进程间通信方式;
消息队列 管道 信号量 信号 共享内存 套接字
21、什么是同步I/O,什么是异步I/O?
同步I/O,用户进程需要主动读写数据。 异步I/O,不需要主动读写数据,只需要读写数据完成的通知。
22、什么是管道,如果两个进程尝试从管道的同一端读写数据,会出现什么情况?
管道:是两个进程间进行单向通信的机制。由于管道传递数据的单向性。管道又称为半双工管道。 管道传递数据是单向性的,读数据时,写入管道应关闭。写数据时,读取管道应关闭。
23、为什么要使用线程池/进程池?
对服务端开启的进程数或线程数加以控制,让机器在一个自己可以承受的范围内运行,这就是进程池或线程池的用途.
24、如果多个线程都在等待同一个锁被释放,请问当该锁对象被释放的时候,哪一个线程将会获得该锁对象?
这个由操作系统的调度决定。
25、import threading;s = threading.Semaphore(value=-1)会出现什么情况?
threading.Semaphore(1) 为1时,表示只有一个线程能够拿到许可,其他线程都处于阻塞状态,直到该线程释放为止。 当然信号量不可能永久的阻塞在那里。信号量也提供了超时处理机制。如果传入了 -1,则表示无限期的等待。
26、请将二进制数10001001转化为十进制;
1 0 0 0 1 0 0 1 128 64 32 16 8 4 2 1
137
27、某进程在运行过程中需要等待从磁盘上读入数据,此时该进程的状态将发生什么变化?
一个程序有三种状态:运行态,阻塞态,就绪态; 遇到IO阻塞,进程从运行态转到阻塞态,cpu切走,保存当前状态;
29、简述异步I/O的原理;
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后, 首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存, 当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
30、请问multiprocessing模块中的Value、Array类的作用是什么?举例说明它们的使用场景
通常,进程之间彼此是完全孤立的,唯一的通信方式是队列或管道。但可以使用两个对象来表示共享数据。其实,这些对象使用了共享内存(通过mmap模块)使访问多个进程成为可能。
Value( typecode, arg1, … argN, lock ) 在共享内容中常见ctypes对象。typecode要么是包含array模块使用的相同类型代码(如’i’,’d’等)的字符串,要么是来自ctypes模块的类型对象(如ctypes.c_int、ctypes.c_double等)。 所有额外的位置参数arg1, arg2 ….. argN将传递给指定类型的构造函数。lock是只能使用关键字调用的参数,如果把它置为True(默认值),将创建一个新的锁定来包含对值的访问。 如果传入一个现有锁定,比如Lock或RLock实例,该锁定将用于进行同步。如果v是Value创建的共享值的实例,便可使用v.value访问底层的值。例如,读取v.value将获取值,而赋值v.value将修改值。 RawValue( typecode, arg1, … ,argN) 同Value对象,但不存在锁定。 Array( typecode, initializer, lock ) 在共享内存中创建ctypes数组。typecode描述了数组的内容,意义与Value()函数中的相同。initializer要么是设置数组初始大小的整数,要么是项目序列,其值和大小用于初始化数组。lock是只能使用关键字调用的参数,意义与Value()函数中相同。 如果a是Array创建的共享数组的实例,便可使用标准的python索引、切片和迭代操作访问它的内容,其中每种操作均由锁定进行同步。对于字节字符串,a还具有a.value属性,可以吧整个数组当做一个字符串进行访问。 RawArray(typecode, initializer ) 同Array对象,但不存在锁定。当所编写的程序必须一次性操作大量的数组项时,如果同时使用这种数据类型和用于同步的单独锁定(如果需要的话),性能将得到极大的提升。
31、请问multiprocessing模块中的Manager类的作用是什么?与Value和Array类相比,Manager的优缺点是什么?
可以通过使用Value或者Array把数据存储在一个共享的内存表中;Manager()返回一个manager类型,控制一个server process,可以允许其它进程通过代理复制一些python objects 支持list,dict,Namespace,Lock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value,Array ; Manager类的作用共享资源,manger的的优点是可以在poor进程池中使用,缺点是windows下环境下性能比较差,因为windows平台需要把Manager.list放在if name=‘main‘下, 而在实例化子进程时,必须把Manager对象传递给子进程,否则lists无法被共享,而这个过程会消耗巨大资源,因此性能很差。 参考:http://www.kaka-ace.com/python-multiprocessing-module-2-managers-first-profile/ https://blog.csdn.net/alvine008/article/details/24310939
32、请说说你对multiprocessing模块中的Queue().put(), Queue.put_nowait()
q = Queue(3) 队列 先进先出 进程间通信; 队列 = 管道 + 锁 q.put() q.put_nowait() # 无阻塞,当队列满时,直接抛出异常queue.Full q.get() q.get_nowait() # 无阻塞,当队列为空时,直接抛出异常queue.Empty
33、什么是协程?使用协程与使用线程的区别是什么?
协程:单线程下的并发。协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。 1.python的线程是属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限, 切换其他的线程运行) 2.单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率 (!!非io操作的切换与效率无关)
34、asyncio的实现原理是什么?
https://www.cnblogs.com/earendil/p/7411115.html Python异步编程:asyncio库和async/await语法 asyncio是Python 3.4 试验性引入的异步I/O框架,提供了基于协程做异步I/O编写单线程并发代码的基础设施。 其核心组件有事件循环(Event Loop)、协程(Coroutine)、任务(Task)、未来对象(Future)以及其他一些扩充和辅助性质的模块。 synchronous io: 做”IO operation”的时候会将process阻塞;”IO operation”是指真实的IO操作 blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO这一类. asynchronous io: 当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号, 告诉进程说IO完成。在这整个过程中,进程完全没有被block。异步io的实现会负责把数据从内核拷贝到用户空间。
#### 二、编程题
1、请写一个包含10个线程的程序,主线程必须等待每一个子线程执行完成之后才结束执行,每一个子线程执行的时候都需要打印当前线程名、当前活跃线程数量以及当前线程名称;
# -*- coding:utf-8 -*- from threading import Thread,currentThread,activeCount import time def task(n): print("线程名: %s %s" % (currentThread(),n)) time.sleep(2) print("当前活跃进程数量", activeCount()) if __name__ == ‘__main__‘: li = [] for i in range(10): t = Thread(target=task, args=(i,)) t.start() li.append(t) for t in li: t.join() print("主,---end---")
2、请写一个包含10个线程的程序,并给每一个子线程都创建名为"name"的线程私有变量,变量值为“Alex”;
# -*- coding:utf-8 -*- from threading import Thread def task(name): print(‘%s is running‘ % name) if __name__ == ‘__main__‘: for i in range(10): t = Thread(target=task, args=("alex_%s"%i,)) t.start() print("主")
3、请使用协程写一个消费者生产者模型;
# -*- coding:utf-8 -*- def consumer(): while True: x = yield print("消费", x) def product(): c = consumer() next(c) for i in range(10): print("生产", i) c.send(i) product()
4、写一个程序,包含十个线程,子线程必须等待主线程sleep 10秒钟之后才执行,并打印当前时间;
# -*- coding:utf-8 -*- from threading import Thread,Event import time import datetime def task(): event.wait(10) print("time:", datetime.datetime.now()) if __name__ == ‘__main__‘: event = Event() for i in range(10): t = Thread(target=task) t.start() time.sleep(10) event.set()
5、写一个程序,包含十个线程,同时只能有五个子线程并行执行;
# -*- coding:utf-8 -*- from threading import Thread,Semaphore,currentThread import time sem = Semaphore(5) def task(): with sem: print("%s in" % currentThread().getName()) time.sleep(10) if __name__ == ‘__main__‘: for i in range(10): t = Thread(target=task,) t.start()
6、写一个程序 ,包含一个名为hello的函数,函数的功能是打印字符串“Hello, World!”,该函数必须在程序执行30秒之后才开始执行(不能使用time.sleep());
# -*- coding:utf-8 -*- from threading import Timer def task(): print("Hello,World!") if __name__ == ‘__main__‘: t = Timer(30, task,) t.start()
7、写一个程序,利用queue实现进程间通信;
# -*- coding:utf-8 -*- from multiprocessing import Process,current_process,Queue import time def consumer(q): while True: res = q.get() if not res:break print("消费了:", res, "---", current_process().name) def producter(q): for i in range(5): print("生产了", i) time.sleep(1) q.put(i) if __name__ == ‘__main__‘: q = Queue() p1 = Process(target=producter, args=(q,)) p2 = Process(target=producter, args=(q,)) c1 = Process(target=consumer, args=(q,)) c2 = Process(target=consumer, args=(q,)) c3 = Process(target=consumer, args=(q,)) p1.start() p2.start() c1.start() c2.start() c3.start() p1.join() p2.join() q.put(None) # None代表结束信号,有几个消费者来几个信号 q.put(None) # 在主进程里边确保所有的生产者都生产结束之后才发结束信号 q.put(None) print("主")
8、写一个程序,利用pipe实现进程间通信;
# -*- coding:utf-8 -*- from multiprocessing import Pipe,Process def task(conn): conn.send(‘Hello,World!‘) conn.close() if __name__ == ‘__main__‘: parent_conn,child_conn = Pipe() p = Process(target=task, args=(child_conn,)) p.start() p.join() print(parent_conn.recv())
9、使用selectors模块创建一个处理客户端消息的服务器程序;
# server import selectors import socket sel = selectors.DefaultSelector() def accept(server_fileobj, mask): conn, addr = server_fileobj.accept() print(addr) sel.register(conn, selectors.EVENT_READ, read) def read(conn, mask): try: data = conn.recv(1024) if not data: print("closing....", conn) sel.unregister(conn) conn.close() return conn.send(data.upper()) except Exception: print("closing....", conn) sel.unregister(conn) conn.close() server_fileobj = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_fileobj.bind(("127.0.0.1", 8080)) server_fileobj.listen(5) server_fileobj.setblocking(False) sel.register(server_fileobj, selectors.EVENT_READ, accept) while True: events = sel.select() for sel_obj,mask in events: callback = sel_obj.data callback(sel_obj.fileobj, mask)
#client import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 8080)) while True: msg = input(">>>:").strip() if not msg:continue client.send(msg.encode("utf-8")) data = client.recv(1024) print(data.decode("utf-8"))
11、请使用asyncio实现一个socket服务器端程序;
以上是关于第八章网络编程进阶的主要内容,如果未能解决你的问题,请参考以下文章