python 之线程、进程、协程
更多方法:
- start 线程准备就绪,等待CPU调度
- setName 为线程设置名称
- getName 获取线程名称
- setDaemon 设置为后台线程或前台线程(默认)
- 如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
- 如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
- join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
- run 线程被cpu调度后执行Thread类对象的run方法
#!/usr/bin/env python# -*-coding:utf-8 -*-import threadingimport timedef test(num):print("runing num %s" %num)time.sleep(5)if __name__ == "__main__":t1 = threading.Thread(target=test,args=(1,))#注意这里的test是上面的方法,这里不要加括号,加括号就直接调用了。#这里args里面的内容是以元组或列表的形式将方法的内容传入进去的(上面的()可以改成[]),这里必须加逗号,若没有传入参数,则直接写成args[]t2 = threading.Thread(target=test,args=(2,))t1.start() #启动第一个线程t2.start() #启动第二个线程t2.setName("111111111111111") #更改线程名称print(t1.getName()) #获取线程名print(t2.getName())print("The threading done")#启动序号10-19为一个主线程,而t1.start()、t2.star()为主线程的子线程,也就是说这个时候一共有三个线程#在主线程后,子线程就并行执行了,而主线程默认是不会等子线程执行完毕的。因此作为主线程中的print("The threading done")会执行完毕,然后等待子线程了。
C:\Python34\python.exe E:/Python/threading_test.pyruning num 1runing num 2Thread-1111111111111111The threading doneProcess finished with exit code 0
#!/usr/bin/env python# -*-coding:utf-8 -*-import threadingimport timedef test(num):print("runing num %s" %num)time.sleep(5)if __name__ == "__main__":# t1 = threading.Thread(target=test,args=(1,)) #注意这里args里面的内容就是test方法的num输入参数,这里必须加逗号# t2 = threading.Thread(target=test,args=(2,))# t1.start() #启动第一个线程# t2.start() #启动第二个线程test_threading = []for i in range(10): #启动10个线程i = threading.Thread(target=test,args=(i,)) #进行实例化i.start()test_threading.append(i) #把实例加入列表里面i.setName("test%s" %i)print(i.getName())for i in test_threading: #这一步是确保所有线程都执行完毕后在往下走,假如放入上面的for循环中,就会存在需要等待每个都执行完,这样就变成串行的了。i.join()print("The threading done")
import threadingimport timeclass test(threading.Thread):def __init__(self,num):threading.Thread.__init__(self) #继承threading.Thread的方法#super(test,self).__init__() #与上面功能以上,是两种不同的类的集成写法self.num = numdef info(self):print("test class %s" %self.num)time.sleep(5)if __name__ == "__main__":t1 = test(1)t2 = test(2)t1.start()t2.start()print(t2.getName())
import timeimport threadingdef run(n):print('[%s]------running----\n' % n)time.sleep(3)print("----run done----")def test():for i in range(3):t = threading.Thread(target=run,args=[i,])t.start()print('starting thread', t.getName())if __name__ == "__main__":m = threading.Thread(target=test,args=[])m.setDaemon(True) #将主线程设置为Daemon线程,它退出时,其它子线程会同时退出,不管是否执行完任务m.start()print("---main thread done---")
threading.activeCount()方法返回当前进程中线程的个数。返回的个数中包含主线程。
threading.enumerate()方法返回当前运行中的Thread对象列表。
import threadingimport timedef test(num):print("runing num %s" %num)time.sleep(1)if __name__ == "__main__":# t1 = threading.Thread(target=test,args=(1,)) #注意这里args里面的内容就是test方法的num输入参数,这里必须加逗号# t2 = threading.Thread(target=test,args=(2,))# t1.start() #启动第一个线程# t2.start() #启动第二个线程test_threading = []for i in range(3): #启动10个线程i = threading.Thread(target=test,args=(i,)) #进行实例化i.start()test_threading.append(i) #把实例加入列表里面i.setName("test%s" %i)print(i.getName())print("目前的线程数",threading.active_count())print(threading.enumerate())for i in test_threading: #这一步是确保所有线程都执行完毕后在往下走,假如放入上面的for循环中,就会存在需要等待每个都执行完,这样就变成串行的了。i.join()print("The threading done")print("现在的线程数",threading.active_count())执行结果:
C:\Python34\python.exe E:/Python/threading_test.pyruning num 0test<Thread(Thread-1, started 8256)>runing num 1test<Thread(Thread-2, started 8312)>runing num 2test<Thread(Thread-3, started 8324)>目前的线程数 4[<Thread(test<Thread(Thread-1, started 8256)>, started 8256)>, <Thread(test<Thread(Thread-2, started 8312)>, started 8312)>, <Thread(test<Thread(Thread-3, started 8324)>, started 8324)>, <_MainThread(MainThread, started 4788)>]The threading done现在的线程数 1Process finished with exit code 04.python中的多线程,有一个GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,他底层会自动进行上下文切换.GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。5.互斥锁、递归锁、信号量a).互斥锁互斥锁是为了解决程序在同时调用一个数据时产生的
import timeimport threadingdef addNum():global num #在每个线程中都获取这个全局变量print('--get num:',num )time.sleep(1)num -=1 #对此公共变量进行-1操作num = 100 #设定一个共享变量thread_list = []for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list: #等待所有线程执行完毕t.join()print('final num:', num )一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,正常来讲,这个num结果应该是0, 多运行几次,会发现,最后打印出来的num结果不总是0。主要是由于,由于线程之间是进行随机调度 。假设有A和B两个线程, 会同时拿到相同的数据,交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
不加锁版本:
import timeimport threadingdef addNum():global num #在每个线程中都获取这个全局变量print('--get num:',num )time.sleep(1)num -=1 #对此公共变量进行-1操作num = 100 #设定一个共享变量thread_list = []for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list: #等待所有线程执行完毕t.join()print('final num:', num )加锁后的版本:
import timeimport threadingdef addNum():global num #在每个线程中都获取这个全局变量print('--get num:',num )time.sleep(1)lock.acquire() #修改前加锁num -=1 #对此公共变量进行-1操作lock.release() #修改后释放num = 100 #设定一个共享变量thread_list = []lock = threading.Lock() #生成全局锁for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list: #等待所有线程执行完毕t.join()print('final num:', num )b).RLock(递归锁)在一个大锁中还要再包含子锁,这个是保证数据唯一性方法,常用方法。
import threading,timedef run1():print("grab the first part data")lock.acquire()global numnum +=1lock.release()return numdef run2():print("grab the second part data")lock.acquire()global num2num2+=1lock.release()return num2def run3(): #为了确保run1 和run2 同时运行完毕。 这里可以理解RUN3为RUN2和RUN1的总方法。lock.acquire()res = run1()print('--------between run1 and run2-----')res2 = run2()lock.release()print(res,res2)if __name__ == '__main__':num,num2 = 0,0lock = threading.RLock() #设置一个递归锁#这里若不加Rlock使用lock会出现太多的锁,会进入死循环。for i in range(10):t = threading.Thread(target=run3)t.start()while threading.active_count() != 1: #现在还有几个线程print(threading.active_count())else:print('----all threads done---')print(num,num2)c).Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 。比如有10个线程,设置最多允许3个线程允许,后面的线程只能等待前面计算完成后才能运行。(保护资源利用率和程序最优)
#!/user/bin/env python#-*- coding:utf-8 -*-author = 'yangrf'import threadingimport timedef test(n):locks.acquire() #加锁print("test111")time.sleep(1)print("%s" %n)locks.release() #释放锁if __name__ == "__main__":num = 0locks = threading.BoundedSemaphore(3) #最多同时允许3个进程运行for i in range(50): #启动10个进程t = threading.Thread(target=test,args=[i,])t.start()print(threading.active_count())while threading.active_count() != 1:passelse:print('----all threads done---')print(num)6.Events作用:用主线程控制子线程合适执行,可以让子线程停下来,也可以让线程继续!
实现的机制就是:标志位“Flag”事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
- clear:将“Flag”设置为False
- set:将“Flag”设置为True
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。以下为金角的例子。
import threading,timeimport randomdef light():if not event.isSet():event.set() #wait就不阻塞 #绿灯状态count = 0while True:if count < 10:print('\033[42;1m--green light on---\033[0m')elif count <13:print('\033[43;1m--yellow light on---\033[0m')elif count <20:if event.isSet():event.clear()print('\033[41;1m--red light on---\033[0m')else:count = 0event.set() #打开绿灯time.sleep(1)count +=1def car(n):while 1:time.sleep(random.randrange(10))if event.isSet(): #绿灯print("car [%s] is running.." % n)else:print("car [%s] is waiting for the red light.." %n)if __name__ == '__main__':event = threading.Event()Light = threading.Thread(target=light)Light.start()for i in range(3):t = threading.Thread(target=car,args=(i,))t.start()7.queue队列天生的线程的安全classqueue.Queue(maxsize=0) #先入先出
- class
queue.LifoQueue(maxsize=0) #last in fisrt out 先进后出,或最后的先出- class
queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列 ,使用元组进行表达,只要做了优先级,put 都需要做优先级设置。- 优先级必须以元组的形成进行写,优先级越小越高
- 可以放置列表、元组、字典、类等数据
#!/user/bin/env python#-*- coding:utf-8 -*-import queue##########没有数据,就阻塞了################# q = queue.Queue(maxsize=3) #设置Q的大小# q.get(timeout=3)q = queue.PriorityQueue(maxsize=30)q.put((5,[123])) #元组中第一个数字为优先级q.put((2,"test"))q.put((6,{1,"111",2,"33333"}))q.put((3,33333333333333333333333),timeout=3)q.put((4,456),timeout=3)#print(q.full())#q.get(timeout=3)print(q.get_nowait())print(q.get_nowait())#get_nowait() 是无论里面有没有数据,都不等待,而get会等待,若没有数据就直接阻塞了#print(q.full())#print(q.empty())q的几个方法:exceptionqueue.Empty 判断Q是否为空queue.Full 判断是否为Q满了Queue.qsize() 设置Q的长度Queue.get(block=True, timeout=None) #设置超时,当Q满了就会报异常,若不写则Q满了,就一致等Queue.put_nowait(item) #不等,如果Q满了,就直接抛出异常。Queue.task_done() 起到信号作用,在生产者消费者模型使用8.生产者消费者模型主要保持供需平衡来自互联网:生产者和消费者之间用中间类似一个队列一样的中间媒介,这里定义为“仓库”。对于生产者只需要关心这个“仓库”,并不需要关心具体的消费者,对于生产者而言甚至都不知道有这些消费者存在。对于消费者而言他也不需要关心具体的生产者,到底有多少生产者也不是他关心的事情,他只要关心这个“仓库”中还有没有东西。只要仓库没有东西了生产者就生产,只要仓库有东西消费者就能获取。就可以实现点对点和广播两种方式进行消息的分发。在python里实现生产者消费者模型主要是使用queue.Queue( )的 task_done( ) 和 join( )方法互动来实现。
import queue,threading,timeq = queue.Queue()def consumer(n):while True:print("\033[32;1mconsumer [%s]\033[0m get task: %s" %(n,q.get()))time.sleep(1)q.task_done() #通知队列获取了一个内容,队列减一def producer(n):count = 1while True:time.sleep(1)if q.qsize() <3 : #如果小于3,产量就不够了,就开始生产print("producer [%s] produced a new task: %s" %(n,count))q.put(count)count += 1q.join() #queue is emtpy #这里就开始阻塞了,直到为空,线程在这里就挂起了,不会占CPU资源,等待这个emtpy才会执行print("all tasks has been cosumed by consumers...")c1 = threading.Thread(target=consumer,args=[1,])c2 = threading.Thread(target=consumer,args=[2,])c3 = threading.Thread(target=consumer,args=[3,])p1 = threading.Thread(target=producer,args=["test",])p2 = threading.Thread(target=producer,args=["soft",])c1.start()c2.start()c3.start()p1.start()p2.start()二、进程IO密集型的使用。可以充分使用CPU的多核特性。简单的例子:
#!/user/bin/env python#-*- coding:utf-8 -*-from multiprocessing import Process #导入进程模块import timedef f(name):time.sleep(2)print('hello',name)if __name__ == "__main__":p = Process(target=f,args=['test',]) #启动一个进程p.start() #启动进程p.join()1.可以通过以下方法获取子进程的ID
from multiprocessing import Processimport osdef info(title):print(title)print('module name:', __name__)print('主进程ID', os.getppid()) #打印父进程IDprint('本程序进程(子进程)ID:', os.getpid()) #打印子线程IDprint("\n\n")def f(name):info('\033[31;1mfunction f\033[0m')print('hello', name)if __name__ == '__main__':info('\033[32;1mmain process line\033[0m')#这个时候打印的父进程ID是运行这个程序的ID(使用pycharm,这个运行就是pycharm的ID),子进程是这个程序的主IDp = Process(target=info, args=('test',))#这个时候打印的父进程ID为这个主程序的ID,子进程为启动的这个子进程的IDp.start()p.join()执行结果:
C:\Python34\python.exe E:/Python/multiprocess_test.pymain process linemodule name: __main__主进程ID 7912本程序进程(子进程)ID: 11444testmodule name: __mp_main__主进程ID 11444本程序进程(子进程)ID: 9368Process finished with exit code 0由于本程序是使用pycharm运行的,因此 7912 为pycharm的ID,在任务管理器查看情况如下:2.进程间通讯
不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:
a).Queues
使用方法跟threading里的queue差不多
Queues 有两个方法put 和get
进程通过Queues模块实现进程间的通信。Q的好处是线程安全的。无论有多少的进程去Q中放数据还是取数据都能保持数据的一致性。同时间只处理一个数据,自带加锁,Q通常用于多线程中。
这里使用的不是通常的Q,是在进程中做了一个封装,实现了子进程和父进程都能访问这个Q
from multiprocessing import Process,Queuedef f(q): #把Q传进去q.put([1111,2222])#这里必须传进去,否则子进程无法取到数据if __name__ == '__main__':q = Queue()p1 = Process(target=f, args=(q,))p2 = Process(target=f, args=(q,))p1.start()p2.start()print(q.get()) # prints "[42, None, 'hello']"print(q.qsize())p1.join()注:以上的方式:当启动多进程时候,取第一个数据具体是什么是不知道的,因为子进程执行顺序是不定的。b).Pipes 管道功能,两头取数据。
管道一头赋给父进程,另一个赋给子进程,两者通过管道取数据。
可以实现一对多。实现进程间的数据传递
from multiprocessing import Process, Pipedef f(conn):conn.send([42, None, 'hello'])#子进程send一个数据conn.close()#把管道关闭if __name__ == '__main__':parent_conn, child_conn = Pipe() #生成一个管道p = Process(target=f, args=(child_conn,))p.start()print(parent_conn.recv()) # 父进程来接收p.join()c).Managers 进程间的共享,同一个数据,能被多个进程修改
可以支持
list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,ValueandArray等同时被多个进程访问。
from multiprocessing import Process,Managerdef test(d,l,n): #同时传入了两个类型数据d[1] = "test1"d[2] = "test2"d[3] = "test3"l.append(n)print(l)if __name__ == "__main__":#with Manager() as managerd = Manager().dict() #通过Manger生成一个dict,这是必须的l = Manager().list(range(10)) #通过Manger生成一个list,放入10个值print("111111111111111111",l)p_list = []for i in range(10):p = Process(target=test, args=(d, l,"list_%s" %i))p.start()p_list.append(p)for res in p_list:res.join()print(d)print(l)执行结果:
C:\Python34\python.exe E:/Python/manager_test.py111111111111111111 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7', 'list_9'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7', 'list_9', 'list_8'][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7', 'list_9', 'list_8', 'list_5']#通过输出结果发现,每个数据是被共享的{1: 'test1', 2: 'test2', 3: 'test3'}[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7', 'list_9', 'list_8', 'list_5']Process finished with exit code 0
3.进程同步,为了保障数据的唯一性。加锁(这个问题3.0已经接近,在2.7存在乱写数据的问题)
from multiprocessing import Process, Lockdef f(l, i):l.acquire() #加锁try:print('hello world', i)finally:l.release() #去锁if __name__ == '__main__':lock = RLock() #设置一个锁for num in range(100):Process(target=f, args=(lock, num)).start()
4.进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
applyapply_async
限制进程数
from multiprocessing import Process,Pool,freeze_supportimport timedef Foo(i):time.sleep(2)return i+100def test(arg):print('-->exec done:',arg)if __name__ == '__main__':freeze_support()#windows 无法产生一个新的进程,需要加入freeze_support()pool = Pool(3) #允许最大5个进程运行for i in range(10):#生成10个进程pool.apply_async(func=Foo, args=(i,),callback=test)#pool.apply(func=Foo, args=(i,))#callback 为回调,执行这个进程完成后要执行的动作#使用callback ,Foo的return,会在test里面打印出来。print('The list end')pool.close()pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
三、协程
协程,又称微线程,纤程。英文名Coroutine。协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
适用场景:其实在其他语言中,协程的其实是意义不大的多线程即可已解决I/O的问题,但是在python因为他有GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,所以:如果一个线程里面I/O操作特别多,协程就比较适用
协程的好处:
无需线程上下文切换的开销无需原子操作锁定及同步的开销方便切换控制流,简化编程模型高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序1.greentet
from greenlet import greenletdef test1():print (12)gr2.switch()#切换到协程2执行#这里的gr2在下面已经定义了print (34) #2切回来之后,在这里和yield类似gr2.switch()def test2():print (56)gr1.switch()#上面执行了一句,在切换到协程1里去了print (78)gr1 = greenlet(test1) #创建了一个协程gr2 = greenlet(test2)gr1.switch() #执行test1
2.Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
遇到IO进行自动切换,不等待进行其他协程,实现了上面例子的手动设置效果。
import geventdef foo():print('Running in foo')gevent.sleep(1)print('Explicit context switch to foo again')def bar():print('Explicit context to bar')gevent.sleep(1)print('Implicit context switch back to bar')gevent.joinall([gevent.spawn(foo), #启动一个协程gevent.spawn(bar), #启动一个协程])以上效果是,当启动foo协程时候,遇到sleep就会切换到bar去执行,然后再切换到foo,就实现并行效果不阻塞了,这个切换过程是随机的。另一个例子,遇到IO阻塞时会自动切换任务
from gevent import monkey; monkey.patch_all()import geventfrom urllib.request import urlopendef f(url):print('GET: %s' % url)resp = urlopen(url) #输入一个urldata = resp.read() #把结果读下来print('%d bytes received from %s.' % (len(data), url)) #获取的字节数gevent.joinall([gevent.spawn(f, 'https://www.python.org/'),gevent.spawn(f, 'https://www.baidu.com/'),gevent.spawn(f, 'https://github.com/'),])通过gevent实现单线程下的多socket并发服务器端:
import sysimport socketimport timeimport geventfrom gevent import socket,monkeymonkey.patch_all() #def server(port):s = socket.socket()s.bind(('0.0.0.0', port))s.listen(500)while True:cli, addr = s.accept()gevent.spawn(handle_request, cli) #启动一个新的协程,把客户端对象传入近期。def handle_request(s): #s 是每个客户端对象try:while True:data = s.recv(1024)print("recv:", data)s.send(data)if not data:s.shutdown(socket.SHUT_WR)except Exception as ex:print(ex)finally:s.close()if __name__ == '__main__':server(8001)客户端:
import socketHOST = 'localhost' # The remote hostPORT = 8001 # The same port as used by the servers = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((HOST, PORT))while True:msg = bytes(input(">>:"),encoding="utf8")s.sendall(msg)data = s.recv(1024)#print(data)print('Received', repr(data))s.close()


浙公网安备 33010602011771号