目錄
一、客戶端實現(xiàn)
二、單進程服務(wù)器
2.1 單進程實現(xiàn)
2.2 單進程非阻塞實現(xiàn)
2.3 TCP服務(wù)器(select版)
2.4 epoll版服務(wù)器實現(xiàn)
三、多進程服務(wù)器和多線程服務(wù)器
四、協(xié)程
4.1 協(xié)程的生成器實現(xiàn)
4.2 協(xié)程的greenlet實現(xiàn)
4.3 協(xié)程的gevent實現(xiàn)
4.3.1 gevent的使用
4.3.2 gevent的切換執(zhí)行
4.3.3 gevent的服務(wù)器實現(xiàn)
一、客戶端實現(xiàn)
客戶端比較簡單,并且適用于與不同服務(wù)器通信,代碼如下:
#coding=utf-8
from socket import *
import random
import time
serverIp = raw_input("請輸?服務(wù)器的ip:")
connNum = raw_input("請輸?要鏈接服務(wù)器的次數(shù)(例如1000):")
g_socketList = []
for i in range(int(connNum)):
s = socket(AF_.NET, SOCK_STREAM)
s.connect((serverIp, 7788))
g_socketList.Append(s)
print(i)
while True:
for s in g_socketList:
s.send(str(random.randint(0,100)))
# ?來測試?
#time.sleep(1)
二、單進程服務(wù)器
2.1 單進程實現(xiàn)
linux服務(wù)器開發(fā)學(xué)習(xí)視頻資料,包括Linux,Nginx,ZeroMQ,MySQL,redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK等等,需要知識技術(shù)學(xué)習(xí)視頻文檔資料的朋友可以后臺私信【架構(gòu)】獲取
單進程完成一個tcp服務(wù)器,同時只能為一個客戶端服務(wù)。
from socket import *
serSocket = socket(AF_INET, SOCK_STREAM)
# 重復(fù)使?綁定的信息,若服務(wù)器先close,則不用等待2MSL,可以直接綁定下一個客戶端
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
serSocket.listen(5)
while True:
print('-----主進程, , 等待新客戶端的到來------')
newSocket,destAddr = serSocket.accept()
print('-----主進程, , 接下來負責(zé)數(shù)據(jù)處理[%s]-----'%str(destAddr))
try:
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(destAddr), recvData))
else:
print('[%s]客戶端已經(jīng)關(guān)閉'%str(destAddr))
break
finally:
newSocket.close()
serSocket.close()
2.2 單進程非阻塞實現(xiàn)
上面單進程實現(xiàn)同時只能為一個服務(wù)端服務(wù),如果第二個while中不阻塞,則可以實現(xiàn)多用戶同時服務(wù)。代碼如下:
#coding=utf-8
from socket import *
import time
# ?來存儲所有的新鏈接的socket
g_socketList = []
def main():
serSocket = socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
#可以適當修改listen中的值來看看不同的現(xiàn)象
serSocket.listen(1000)
#將套接字設(shè)置為?堵塞
#設(shè)置為?堵塞后, 如果accept時, 恰巧沒有客戶端connect, 那么accept會
#產(chǎn)??個異常, 所以需要try來進?處理
serSocket.setblocking(False)
while True:
#?來測試
#time.sleep(0.5)
try:
newClientInfo = serSocket.accept()
except Exception as result:
pass
else:
print("?個新的客戶端到來:%s"%str(newClientInfo))
newClientInfo[0].setblocking(False)
g_socketList.append(newClientInfo)
# ?來存儲需要刪除的客戶端信息
needDelClientInfoList = []
# 為列表中每個客戶端服務(wù)
for clientSocket,clientAddr in g_socketList:
try:
recvData = clientSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(clientAddr), recvData))
else:
print('[%s]客戶端已經(jīng)關(guān)閉'%str(clientAddr))
clientSocket.close()
g_needDelClientInfoList.append((clientSocket,clientAddr))
except Exception as result:
pass
for needDelClientInfo in needDelClientInfoList:
g_socketList.remove(needDelClientInfo)
if __name__ == '__main__':
main()
2.3 TCP服務(wù)器(select版)
tcp/ip學(xué)習(xí)資料獲取后臺私信【tcp/ip】
在非阻塞版本中使用for循環(huán)為列表中的每個客戶端服務(wù),而select版是通過調(diào)用select函數(shù)直接返回列表中接收到數(shù)據(jù)的socket,不必循環(huán)遍歷。
優(yōu)點:幾乎所有平臺都支持,有良好的跨平臺性。
缺點:select的?個缺點在于單個進程能夠監(jiān)視的?件描述符的數(shù)量存在最?限制,在Linux上?般為1024, 可以通過修改宏定義甚?重新編譯內(nèi)核的?式提升這?限制, 但是這樣也會造成效率的降低。
?般來說這個數(shù)? 和系統(tǒng)內(nèi)存關(guān)系很?, 具體數(shù)? 可以cat /proc/sys/fs/filemax查看。 32位機默認是1024個。 64位機默認是2048.個。對socket進?掃描時是依次掃描的, 即采?輪詢的?法, 效率較低。
當套接字?較多的時候, 每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度, 不管哪個Socket是活躍的, 都遍歷?遍。 這會浪費很多CPU時間。
select函數(shù)解釋如圖:
select版tcp服務(wù)器代碼如下:
import select
import socket
import sys
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('', 7788))
server.listen(5)
inputs = [server, sys.stdin]
running = True
while True:
# 調(diào)用 select 函數(shù),阻塞等待
readable, writeable, exceptional = select.select(inputs, [], [])
# 數(shù)據(jù)抵達,循環(huán)
for sock in readable:
# 監(jiān)聽到有新的連接
if sock == server:
conn, addr = server.accept()
# select 監(jiān)聽的socket
inputs.append(conn)
# 監(jiān)聽到鍵盤有輸入
elif sock == sys.stdin:
cmd = sys.stdin.readline()
running = False
break
# 有數(shù)據(jù)到達
else:
# 讀取客戶端連接發(fā)送的數(shù)據(jù)
data = sock.recv(1024)
if data:
sock.send(data)
else:
# 移除select監(jiān)聽的socket
inputs.remove(sock)
sock.close()
# 如果檢測到用戶輸入敲擊鍵盤,那么就退出
if not running:
break
server.close()
2.4 epoll版服務(wù)器實現(xiàn)
為了解決select版并發(fā)連接數(shù)目的限制,出現(xiàn)了poll版,與select版幾乎相同,唯一不同的是數(shù)量不受限制,仍是用的輪詢方式。后來為了解決poll版輪詢監(jiān)測方式低下的問題出現(xiàn)了epoll版,epoll版相當于“有問題舉手”,而不是“挨個問是否有問題”。
epoll版的優(yōu)點:
1. 沒有最?并發(fā)連接的限制, 能打開的FD(指的是?件描述符), 通俗的理解就是套接字對應(yīng)的數(shù)字編號)的上限遠?于1024
2. 效率提升, 不是輪詢的?式, 不會隨著FD數(shù)?的增加效率下降。 只有活躍可?的FD才會調(diào)?callback函數(shù); 即epoll最?的優(yōu)點就在于它只管你“活躍”的連接, ?跟連接總數(shù)?關(guān), 因此在實際的?絡(luò)環(huán)境中, epoll的效率就會遠遠?于select和poll。
epoll版tcp服務(wù)器代碼如下:
代碼解釋:
epoll的三種事件:
EPOLLIN (可讀)
EPOLLOUT (可寫)
EPOLLET (ET模式)
epoll對?件描述符的操作有兩種模式: LT(level trigger 水平觸發(fā)) 和ET(edge trigger 邊沿觸發(fā)) 。 LT模式是默認模式, LT模式與ET模式的區(qū)別如下:
LT模式: 當epoll檢測到描述符事件發(fā)?并將此事件通知應(yīng)?程序, 應(yīng)?程序可以不?即處理該事件
ET模式: 當epoll檢測到描述符事件發(fā)?并將此事件通知應(yīng)?程序, 應(yīng)?程序必須?即處理該事件,否則會丟失
import socket
import select
# 創(chuàng)建套接字
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 設(shè)置可以重復(fù)使?綁定的信息
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 綁定本機信息
s.bind(("",7788))
# 變?yōu)楸粍?/p>
s.listen(10)
# 創(chuàng)建?個epoll對象
epoll=select.epoll()
# 測試, ?來打印套接字對應(yīng)的?件描述符
# print s.fileno()
# print select.EPOLLIN|select.EPOLLET
# 注冊事件到epoll中
# epoll.register(fd, [eventmask])
# 注意, 如果fd已經(jīng)注冊過, 則會發(fā)?異常
# 將創(chuàng)建的socket添加到epoll的事件監(jiān)聽中
# [eventmask]為監(jiān)聽的事件列表,有列表中的事件時才會放入epoll列表,
# 事件有三種,EPOLLIN接收數(shù)據(jù)事件,EPOLLOUT發(fā)送數(shù)據(jù),EPOLLET模式(水平觸發(fā)或邊沿觸發(fā))
epoll.register(s.fileno(),select.EPOLLIN|select.EPOLLET)
# connections用于存儲socket,addresses用于存儲端口,
# 它們都為字典,key為socket的文件描述符,value為socket或端口!
connections = {}
addresses = {}
# 循環(huán)等待客戶端的到來或者對?發(fā)送數(shù)據(jù)
while True:
# epoll 進? fd 掃描的地? -- 未指定超時時間則為阻塞等待
# 等價于select版本中的 readable,xxx,yyy = select([],[],[])
# 不為輪詢,使用的是事件通知機制,為本代碼的核心
epoll_list=epoll.poll()
# 對事件進?判斷
for fd,events in epoll_list:
# print fd
# print events
# 如果是socket創(chuàng)建的套接字被激活
if fd == s.fileno():
conn,addr=s.accept()
print('有新的客戶端到來%s'%str(addr))
# 將 conn 和 addr 信息分別保存起來
# 注意connections和address為字典,以key、value存儲
connections[conn.fileno()] = conn
addresses[conn.fileno()] = addr
# 向 epoll 中注冊 連接 socket 的 可讀 事件
epoll.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
elif events == select.EPOLLIN:
# 從激活 fd 上接收
recvData = connections[fd].recv(1024)
if len(recvData)>0:
print('recv:%s'%recvData)
else:
# 從 epoll 中移除該 連接 fd
epoll.unregister(fd)
# server 則主動關(guān)閉該 連接 fd
connections[fd].close()
print("%s---offline---"%str(addresses[fd]))
三、多進程服務(wù)器和多線程服務(wù)器
代碼說明:
1、多進程實現(xiàn)和多線程實現(xiàn)幾乎相同,不同點:1、創(chuàng)建的時候;2、while中多進程實現(xiàn)中,由于子進程復(fù)制了一份,所以可以關(guān)閉,多線程中,子線程之間共享資源,所以在while中不能關(guān)閉。
2、代碼中使用try...finally,目的是可以使用Ctrl+C強制結(jié)束進程或線程。
多進程服務(wù)器代碼如下:
#coding=utf-8
from socket import *
from multiprocessing import *
from time import sleep
# 處理客戶端的請求并為其服務(wù)
def dealWithClient(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(destAddr), recvData))
else:
print('[%s]客戶端已經(jīng)關(guān)閉'%str(destAddr))
break
newSocket.close()
def main():
serSocket = socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
serSocket.listen(5)
try:
while True:
print('-----主進程,,等待新客戶端的到來------')
newSocket,destAddr = serSocket.accept()
print('-----主進程,,接下來創(chuàng)建?個新的進程負責(zé)數(shù)據(jù)處理------')
client = Process(target=dealWithClient, args=(newSocket,destAddr))
client.start()
#因為已經(jīng)向?進程中copy了?份(引?),并且?進程中這個套接字所以關(guān)閉
newSocket.close()
finally:
#當為所有的客戶端服務(wù)完之后再進?關(guān)閉,表示不再接收新的客戶端的鏈接
serSocket.close()
if __name__ == '__main__':
main()
多線程服務(wù)器代碼如下:
#coding=utf-8
from socket import *
from threading import Thread
from time import sleep
# 處理客戶端的請求并執(zhí)?事情
def dealWithClient(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(destAddr), recvData))
else:
print('[%s]客戶端已經(jīng)關(guān)閉'%str(destAddr))
break
newSocket.close()
def main():
serSocket = socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
serSocket.listen(5)
try:
while True:
print('-----主進程, , 等待新客戶端的到來------')
newSocket,destAddr = serSocket.accept()
print('-----主進程, , 接下來創(chuàng)建?個新的進程負責(zé)數(shù)據(jù)處理[%s]-----')
client = Thread(target=dealWithClient, args=(newSocket,destAddr))
client.start()
#因為線程中共享這個套接字, 如果關(guān)閉了會導(dǎo)致這個套接字不可?,
#但是此時在線程中這個套接字可能還在收數(shù)據(jù), 因此不能關(guān)閉
#newSocket.close()
finally:
serSocket.close()
if __name__ == '__main__':
main()
四、協(xié)程
協(xié)程學(xué)習(xí)資后臺私信【協(xié)程】獲取文檔代碼
進程里面有線程,線程里面有協(xié)程。協(xié)程不牽扯到切換,并且能完成多任務(wù)。
注意:計算密集型時用多進程;IO密集型使用多線程、多協(xié)程。
通俗的理解: 在?個線程中的某個函數(shù), 可以在任何地?保存當前函數(shù)的?些臨時變量等信息, 然后切換到另外?個函數(shù)中執(zhí)?, 注意不是通過調(diào)?函數(shù)的?式做到的, 并且切換的次數(shù)以及什么時候再切換到原來的函數(shù)都由開發(fā)者??確定。
協(xié)程和線程的區(qū)別:線程切換從系統(tǒng)層?遠不?保存和恢復(fù) CPU上下?這么簡單。 操作系統(tǒng)為了程序運營的?效性每個線程都有??緩存Cache等等數(shù)據(jù), 操作系統(tǒng)還會幫你做這些數(shù)據(jù)的恢復(fù)操作。所以線程的切換?常耗性能。 但是協(xié)程的切換只是單純的操作CPU的上下?, 所以?秒鐘切換個上百萬次系統(tǒng)都抗的住。
協(xié)程調(diào)度:操作系統(tǒng)不感知協(xié)程,所以操作系統(tǒng)不會對協(xié)程調(diào)度。 ?前的協(xié)程框架?般都是設(shè)計成 1:N 模式。 所謂 1:N 就是?個線程作為?個容器??放置多個協(xié)程。 那么誰來適時的切換這些協(xié)程? 答案是有協(xié)程自己主動讓出CPU, 也就是每個協(xié)程池??有?個調(diào)度器, 這個調(diào)度器是被動調(diào)度的。 意思就是他不會主動調(diào)度。 ?且當?個協(xié)程發(fā)現(xiàn)自己執(zhí)行不下去了(比如異步等待?絡(luò)的數(shù)據(jù)回來, 但是當前還沒有數(shù)據(jù)到), 這個時候就可以由這個協(xié)程通知調(diào)度器, 這個時候執(zhí)?到調(diào)度器的代碼, 調(diào)度器根據(jù)事先設(shè)計好的調(diào)度算法找到當前最需要CPU的協(xié)程。 切換這個協(xié)程的CPU上下?把CPU的運?權(quán)交給這個協(xié)程, 直到這個協(xié)程出現(xiàn)執(zhí)行不下去需要等等的情況, 或
者它調(diào)?主動讓出CPU的API之類, 觸發(fā)下?次調(diào)度。
協(xié)程調(diào)度存在問題:假設(shè)一個線程中有?個協(xié)程是CPU密集型的他沒有IO操作, 也就是自己不會主動觸發(fā)調(diào)度器調(diào)度的過程, 那么就會出現(xiàn)其他協(xié)程得不到執(zhí)?的情況, 所以這種情況下需要程序員? ?避免。 這是?個問題, 假設(shè)業(yè)務(wù)開發(fā)的?員并不懂這個原理的話就可能會出現(xiàn)問題。
協(xié)程的優(yōu)點:在IO密集型的程序中由于IO操作遠遠慢于CPU的操作, 所以往往需要CPU去等IO操作。 同步IO下系統(tǒng)需要切換線程, 讓操作系統(tǒng)可以在IO過程中執(zhí)?其他的東?。 這樣雖然代碼是符合?類的思維習(xí)慣但是由于?量的線程切換帶來了?量的性能的浪費, 尤其是IO密集型的程序。
所以?們發(fā)明了異步IO。 就是當數(shù)據(jù)到達的時候觸發(fā)我的回調(diào)。 來減少線程切換帶來性能損失。 但是這樣的壞處也是很?的, 主要的壞處就是操作被“分片” 了, 代碼寫的不是 “一氣呵成” 這種。 而是每次來段數(shù)據(jù)就要判斷 數(shù)據(jù)夠不夠處理, 夠處理就處理, 不夠處理就再等等。 這樣代碼的可讀性很低, 其實也不符合?類的習(xí)慣。
但是協(xié)程可以很好解決這個問題。 比如 把?個IO操作 寫成?個協(xié)程。 當觸發(fā)IO操作的時候就自動讓出CPU給其他協(xié)程。 要知道協(xié)程的切換很輕的。 協(xié)程通過這種對異步IO的封裝 既保留了性能也保證了代碼的容易編寫和可讀性。在?IO密集型的程序下很好。 但是高CPU密集型的程序下沒啥好處。
4.1 協(xié)程的生成器實現(xiàn)
協(xié)程使用生成器來實現(xiàn)的,代碼如下(只切換了函數(shù)調(diào)用,所以效率比較高):
import time
def A():
while True:
print("----A---")
yield
time.sleep(0.5)
def B(c):
while True:
print("----B---")
c.next()
time.sleep(0.5)
if __name__=='__main__':
a = A()
B(a)
結(jié)果如下:
--B--
--A--
--B--
--A--
--B--
--A--
--B--
--A--
...省略...
4.2 協(xié)程的greenlet實現(xiàn)
與生成器實現(xiàn)類似。
注意:進程、線程的調(diào)用是操作系統(tǒng)決定的,執(zhí)行順序不可預(yù)測,而協(xié)程是程序員決定的執(zhí)行順序可預(yù)測,這由以下代碼可知(當執(zhí)行到xx.switch()時會切換)。
使用下面命令安裝greenlet:
sudo pip install greenlet #Python2的安裝方式
sudo pip3 install greenlet #python3的安裝方式
協(xié)程的greenlet實現(xiàn)代碼如下:
#coding=utf-8
from greenlet import greenlet
import time
def test1():
while True:
print "---A--"
gr2.switch() # 切換到gr2(即test2)中執(zhí)行,test2執(zhí)行切換時會從當前接著執(zhí)行
time.sleep(0.5)
def test2():
while True:
print "---B--"
gr1.switch() # 切換到gr1(即test1)中執(zhí)行,test1執(zhí)行切換時會從當前接著執(zhí)行
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
#切換到gr1(即test1函數(shù))中執(zhí)行
gr1.switch()
結(jié)果如下:
--A--
--B--
--A--
--B--
--A--
--B--
--A--
...省略...
4.3 協(xié)程的gevent實現(xiàn)
gevent是對greenlet的再次封裝,不用程序員自己編程切換,當遇到需要切換的地方會自動切換。
4.3.1 gevent的使用
#coding=utf-8
#請使?python 2 來執(zhí)?此程序
import gevent
def f(n):
for i in range(n):
print gevent.getcurrent(), i
g1 = gevent.spawn(f, 5) # 綁定f函數(shù),執(zhí)行5次
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
# 清除協(xié)程
g1.join()
g2.join()
g3.join()
執(zhí)行結(jié)果:
一瞬間執(zhí)行完畢,g1、g2、g3依次順序執(zhí)行,并非交替執(zhí)行,不是我們想要的結(jié)果。
<Greenlet at 0x10e49f550: f(5)> 0
<Greenlet at 0x10e49f550: f(5)> 1
<Greenlet at 0x10e49f550: f(5)> 2
<Greenlet at 0x10e49f550: f(5)> 3
<Greenlet at 0x10e49f550: f(5)> 4
<Greenlet at 0x10e49f910: f(5)> 0
<Greenlet at 0x10e49f910: f(5)> 1
<Greenlet at 0x10e49f910: f(5)> 2
<Greenlet at 0x10e49f910: f(5)> 3
<Greenlet at 0x10e49f910: f(5)> 4
<Greenlet at 0x10e49f4b0: f(5)> 0
<Greenlet at 0x10e49f4b0: f(5)> 1
<Greenlet at 0x10e49f4b0: f(5)> 2
<Greenlet at 0x10e49f4b0: f(5)> 3
<Greenlet at 0x10e49f4b0: f(5)> 4
4.3.2 gevent的切換執(zhí)行
上面順序執(zhí)行的原因是在f函數(shù)中沒有調(diào)用延時,所以不會切換。gevent當遇到耗時操作時才會切換,所以增加一個延時函數(shù)使它能夠切換,代碼如下:
import gevent
def f(n):
for i in range(n):
print gevent.getcurrent(), i
#?來模擬?個耗時操作, 注意不是time模塊中的sleep
gevent.sleep(1)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
結(jié)果如下:
<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4
4.3.3 gevent的服務(wù)器實現(xiàn)
注意:要使用gevent實現(xiàn)服務(wù)器,不能使用默認的socket,而是使用gevent自己的socket,gevent將常用的耗時操作都重寫了一遍,用于檢測是否為耗時操作。
import sys
import time
import gevent
from gevent import socket,monkey
# 此語句會將本代碼改寫,位于編譯器級的,具體不清楚!(python為動態(tài)語言在執(zhí)行中可以修改)
# 必須使用!!!
monkey.patch_all()
def handle_request(conn):
while True:
#--------------#1處#-----------------
data = conn.recv(1024) # 這是gevent中的recv,為耗時操作,會切換到2處!
if not data:
conn.close()
break
print("recv:", data)
conn.send(data)
def server(port):
s = socket.socket()
s.bind(('', port))
s.listen(5)
while True:
#--------------#2處#-----------------
cli, addr = s.accept() # 這是gevent中的accept,為耗時操作,會進行切換!
# 注意:第一次到這里時只有一個協(xié)程,不需要切換,在此等待!
# 不為第一次時切換到1處!
gevent.spawn(handle_request, cli)
if __name__ == '__main__':
server(7788)