一、初識socket
socket 是網絡連接端點,每個socket都被綁定到一個特定的IP地址和端口。IP地址是一個由4個數組成的序列,這4個數均是范圍 0~255中的值(例如, 220,176,36,76);端口數值的取值范圍是0~65535。端口數小於1024的都是為眾所周知的網絡服務所保留的 (例如Web服務使用的80端口);最大的保留數被存 儲在socket模塊的IPPORT_RESERVED變量中。你也可以為你的程序使用另外的端口數 值。
不是所有的IP地址都對世界的其它地方可見。實際上,一些是專門為那些非公共的地址所保留的(比如形如192.168.y.z或10.x.y.z)。地址127.0.0.1是本機地址;它始 終指向當前的計算機。程序可以使用這個地址來連接運行在同一計算機上的其它程序。
IP地址不好記,你可以花點錢為特定的IP地址注冊一個主機名或域名(比如使用www.jb51.net代替222.76.216.16)。域名服務器(DNS)處理名字到IP地址的映射。
多少信息通過一個網絡被傳送基於許多因素,其中之一就是使用的協議。許多的協議是基於簡單的、低級協議以形成一個協議棧。例如HTTP協議,它是用在Web瀏覽器 與 Web服務器之間通信的協議,它是基於TCP協議,而TCP協議又基於IP協議。
當 在你自己的兩個程序間傳送信息的時候,你通常選擇TCP或UDP協議。TCP協議在兩端間建立一個持續的連接,並且你所發送的信息有保證的按順序到達它們 的目的 地。UDP不建立連接,它的速度快但不可靠。你發送的信息也可能到不了另一端;或它們沒有按順序到達。有時候一個信息的多個復制到達接收端,即使你 只發送了一次。
二、使用地址和主機名
socket模塊提供了幾個函數用於使用主機名和地址來工作。
socket 也定義了一些變量來代表保留的IP地址。INADDR_ANY和INADDR_BROADCAST是被保留的IP地址分別代表任意IP地址和廣播地 址; INADDR_LOOPBACK 代表loopback設備,總是地址127.0.0.1。這些變量是32位字節數字形式的。
getfqdn([name])函數返回關於給定主機名的全域名(如果省略,則返回本機的全域名)。
三、使用低級的socket通信
盡管Python提供了一些封裝,使得使用socket更容易,但是你也可以直接使用socket來工作。
1、創建和銷毀socket
socket 模塊中的socket(family,type[,proto])函數創建一個新的socket對象。family的取值通常是AF_INET。type 的取值通常是SOCK_STREAM(用於定向的連接,可靠的TCP連接)或SOCK_DGRAM(用於UDP):
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
family和type參數暗指了一個協議,但是你可以使用socket的第三個可選的參數(proto的取值如IPPROTO_TCP或IPPROTO_RAW)來指定所使用的協議。代替使用IPPROTO_XX變量,你可以使用函數getprotobyname:
>>> getprotobyname('tcp')
6
>>> IPPROTO_TCP
6
fromfd(fd,type[,proto]) 是一個很少被使用的函數,它用來從打開的一個文件描述符創建一個socket對象(文件描述符由文件的fileno()方法返回)。文件描述符與一個真實 的socket連接,而非一個文件。socket對象的fileno()方法返回關於這個socket的文件描述符。
當你使用完工 socket對象時,你應調用close()方法顯式的關閉socket以盡快釋放資源(盡管socket被垃圾回收器回收時將自動被關閉)。另外,你也 可以使用shutdown(how)方法來關閉連接一邊或兩邊。參數0阻止socket接收數據,1阻止發送,2阻止接收和發送。
2、連接socket
當 兩個socket連接時(例如使用TCP),一端監聽和接收進來的連接,而另一端發起連接。臨聽端創建一個socket,調用bind(address) 函數去綁定一個特定的地址和端口,調用listen(backlog)來臨聽進來的連接,最后調用accept()來接收這個新的,進來的連接,下面是在 服務器端的代碼:
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.bind(('127.0.0.1',44444))
>>> s.listen(1)
>>> q,v=s.accept() #返回socket q和地址v
注意:上面的代碼將一直處於等待直到連接被建立。下面我們再打開另一個Python解釋器,用作客戶端;然后鍵入如下代碼:
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('127.0.0.1',44444) #發起連接
好了,我們驗證一下連接是否建立了。我們在服務器端鍵入以下代碼來發送一條信息:
>>> q.send('hello,i come from pythontik.com') 注:有時可能出現send() argument 1 must be string or buffer,not str 錯誤,原因可能是您的機器不支持UTF-8字符集,臨時解決方案是q.send(b' hello...')
31 #發送的字節數
在客戶端鍵入以下代碼來接收信息:
>>> s.recv(1024)
'hello,i come from pythontik.com'
你 傳遞給bind和connect的地址是一個關於AF_INET的socket的元組(ipAddress,port)。代替connect,你也可以調 用connect_ex(address)方法。如果背后對C的connect的調用返回一個錯誤,那么connect_ex也將返回一個錯誤(否則返回 0代表成功),代替引發一個異常。
當你調用listen時,你給了它一個參數,這個數值表示在等待隊列中允許放置的進來的連接總數。當等待隊列已滿時,如果有更多的連接到達,那么遠程端將被告知連接被拒絕。在socket模塊中的SOMAXCONN變量表明了等待隊列所能容納的最大量。
accept()方法返回形如bind和connect的一個地址,代表遠程socket的地址。下面顯示變量v的值:
>>> v
('127.0.0.1', 1334)
UDP是不定向的連接,但是你仍然可以使用給定的目的地址和端口來調用connect去關聯一個socket。
3、發送和接收數據
函 數send(string[,flags])發送給定的字符串到遠程socket。sendto(string[,flags],address)發送給 定的字符串到一個特定的地址。通常,send方法用於可靠連接的socket,sendto方法用於不可靠連接的socket,但是如果你在一個 UDP socket上調用connect來使它與一個特定的目標建立聯系,那么這時你也可以使用send方法來代替sendto。
send和sendto都返回實際發送的字節數。當你快速發送大量的數據的時候,你可能想去確保全部信息已被發送,那么你可以使用如下的一個函數:
def safeSend(sock,msg):
sent=0
while msg:
i=sock.send(msg)
if i==-1: #發生了錯誤
return -1
sent+=i
msg=msg[i:]
time.sleep(25)
return sent
recv(bufsize[,flags]) 方法接收一個進來的消息。如果有大量的數據在等待,它只返回前面的bufsize字節數的數據。recvfrom(bufsize[,flags])做同 樣的事,除了它使用AF_INET socket的返回值是(data,(ipAddress,port)),這便於你知道消息來自哪兒(這對於非連接的 socket是有用的)。
send,sendto,recv和recvfrom方法都有一個可選的參數flags,默認值為0。你可以通過對socket.MSG_*變量進行組合(按位或)來建立flags的值。這些值因平台而有所不同,但是最通用的值如下所示:
MSG_OOB:處理帶外數據(既TCP緊急數據)。
MSG_DONTROUTE:不使用路由表;直接發送到接口。
MSG_PEEK:返回等待的數據且不把它們從隊列中刪除。
例如,如果你有一個打開的socket,它有一個消息等待被接收,你可以接收這個消息后並不把它從進來的數據的隊列中刪除:
>>> q.recv(1024,MSG_PEEK)
'hello'
>>> q.recv(1024,MSG_PEEK) #因為沒有刪除,所以你可以再得到它。
'hello'
makefile([mode[,bufsize]]) 方法返回一個文件類對象,其中封裝了socket,以便於你以后將它傳遞給要求參數為一個文件的代碼(或許你喜歡使用文件的方法來代替send和 recv)。這個可選的mode和bufsize參數的取值和內建的open函數一樣。
4、使用socket選項
socket對象的getpeername()和 getsockname()方法都返回包含一個IP地址和端口的二元組(這個二元組的形式就像你傳遞給connect和bind的)。 getpeername返回所連接的遠程socket的地址和端口,getsockname返回關於本地socket的相同信息。
在默認 情況下,socket是阻塞式的,意思就是socket的方法的調用在任務完成之前是不會返回的。例如,如果存儲向外發送的數據的緩存已滿,你又企圖發送 更多的數據,那么你對send的調用將被阻塞直到它能夠將更多的數據放入緩存。你可以通過調用setblocking(flag)方法(其中flag取值 是0,setblocking(0))來改變這個默認行為,以使socket為非阻塞式。當socket為非阻塞式的時候,如果所做的動作將導致阻塞,將 會引起error異常。下面一段代碼將試圖不斷地接受新的連接並使用函數processRequest來處理。如果一個新連接無效,它將間隔半秒再試。另 一方法是在你的監聽socket上調用select或poll來檢測一個新的連接的到達。
別的socket的選項可以使用 setsockopt(level,name,value)和getsockopt(level,name[,buflen])方法來設置和獲取。 socket代表了一個協議棧的不同層,level參數指定了選項應用於哪一層。level的取值以SOL_開頭(SOL_SOCKET,SOL_TCP 等等)。name表明你涉及的是哪個選項。對於value,如果該選項要求數值的值,value只能傳入數字值。你也可以傳遞入一個緩存(一個字符串), 但你必須使用正確的格式。對getsockopt,不指定buflen參數意味你要求一個數字值,並返回這個值。如果你提供了 buflen,getsockopt返回代表一個緩存的字符串,它的最大長度是buflen的字節數。下面的例子設置了一個socket的用於發送的緩存 尺寸為64KB:
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.setsockopt(SOL_SOCKET,SO_SNDBUF,65535)
要得到一個包在被路由丟棄前所能有的生命周期(TTL)和跳數,你可以使用如下代碼:
>>> s.getsockopt(SOL_IP,IP_TTL)
32
5、數值轉換
由於不同平台的字節順序不一樣,所以當在網絡中傳輸數據時我們使用標准的網絡字節順序。nthol(x)和ntohs(x)函數要求一個網絡字節順序的數值並把它轉換為當前主機字節順序的相同數值,而htonl(x)和htons(x)則相反:
>>> import.socket
>>> socket.htons(20000) #轉換為一個16位的值
8270
>>> socket.htonl(20000) #轉換為一個32位的值
541982720
>>> socket.ntohl(541982720)
20000
使用SocketServers
SocketServers模塊為一組socket服務類定義了一個基類,這組類壓縮和隱藏了監聽、接受和處理進入的socket連接的細節。
1、SocketServers家族
TCPServer和UDPServer都是SocketServer的子類,它們分別處理TCP和UDP信息。
注意:SocketServer也提供UnixStreamServer(TCPServer的子類)和UNIXdatagramServer(UDPServer的子類),它們都如同其父類一樣除了在創建監聽socket時使用AF_UNIX代替了AF_INET。
默 認情況下,socket服務一次處理一個連接,但是你可以使用ThreadingMixIN和ForkingMixIn類來創建任一 SocketServer的線程和子進程。實際上,SocketServer模塊提供了一些對些有用的類來解決你的麻煩,它們 是:ForkingUDPServer、ForkingTCPServer、ThreadingUDPServer、 ThreadingTCPServer、ThreadingUnixStreamServer和 ThreadingUnixDatagramServer。
SocketServer以通常的方法處理進入的連接;要使它更有用,你應該 提供你自己的請求處理器類給它以便它傳遞一個socket去處理。SocketServer模塊中的BaseRequestHandler類是所有請求處 理器的父類。假設,例如你需要寫一個多線程的電子郵件服務器,首先你要創建一個MailRequestHandler,它是 BaseRequestHandler的子類,然后把它傳遞給一個新創建的SocketServer:
import SocketServer
...#創建你的MailRequestHandler
addr=('220.172.20.6',25) #監聽的地址和端口
server=SocketServer.ThreadingTCPServer(addr,MailRequestHandler)
server.serve_forever()
每 次一個新的連接到來時,這個server創建一個新的MailRequestHandler實例並調用它的handle()方法來處理這個新的請求。因為 server繼承自ThreadingTCPServer,對於每個新的請求它都啟動一個單獨的線程來處理這個請求,以便於多個請求能夠被同時處理。如果 用handle_request()代替server_forever,它將一個一個的處理連接請求。server_forever 只是反復調用 handle_request而已。
一般來說,你只需使用socket服務之一,但是如果你需要創建你自己的子類的話,你可以覆蓋我們下面提到的方法來定制它。
當 服務被第一次創建的時候,__init__函數調用server_bind()方法來綁定監聽socket(self.socket)到正確的地址 (self.server_address)。然后調用server_activate()來激活這個服務(默認情況下,調用socket的listen 方法)。
這個socket服務不做任何事情直到調用了handle_request或serve_forever方法。 handle_request調用get_request()去等待和接收一個新的socket連接,然后調用 verify_request(request,client_address)去看服務是否會處理這個連接(你可以在訪問控制中使用這個,默認情況下 verify_request總是返回true)。如果會處理這個請求,handle_request然后調用 process_request(request,client_address),如果 process_request(request,client_address)導致一個異常的話,將調用 handle_error(request,client_address)。默認情況下,process_request簡單地調用 finish_request(request,client_address);子進程和線程類覆蓋了這個行為去開始一新的進程或線程,然后調用 finish_request。finish_request實例化一個新的請求處理器,請求處理器輪流調用它們的handle()方法。
當SocketServer創建一個新的請求處理器時,它傳遞給這個處理器的__init__函數的self變量,以便於這個處理器能夠訪問關於這個服務的信息。
SocketServer 的fileno()方法返回監聽socket的文件描述符。address_family成員變量指定了監聽socket的socket族(如 AF_INET),server_address包含了監聽socket被綁定到的地址。socket變量包含監聽socket自身。
2、請求處理器
請 求處理器有setup()、handle()和finish()方法,你可以覆蓋它們來定制你自己的行為。一般情況下,你只需要覆蓋handle方法。 BaseRequestHandler的__init__函數調用setup()方法來做初始化的工作,handle()服務於請求,finish()用 於執行清理工作,如果handle或setup導致一個異常,finish不會被調用。記住,你的請求處理器會為每個請求創建一個新的實例。
request 成員變量有關於流(TCP)服務的最近接受的socket;對於數據報服務,它是一個包含進入消息和監聽socket的元組。 client_address包含發送者的地址,server有對SocketServer的一個引用(通過這你可以訪問它的成員,如 server_address)。
下面的例子實現了一個EchoRequestHandler,這作為一個服務端它將客戶端所發送的數據再發送回客戶端:
>>> import SocketServer
>>> class EchoRequestHandler(SocketServer.BaseRequestHandler):
... def handle(self):
... print 'Got new connection!'
... while 1:
... mesg=self.request.recv(1024)
... if not msg:
... break
... print 'Received:',msg
... self.request.send(msg)
... print 'Done with connection'
>>> server=SocketServer.ThreadingTCPServer(('127.0.0.1',12321),EchoReuestHandler)
>>> server.handle_request() #執行后將等待連接
Got new connection!
Received: Hello!
Received: I like Tuesdays!
Done with connection
打開另一個Python解釋器作為客戶端,然后執行如下代碼:
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('120.0.0.1',12321))
>>> s.send('Hello!')
6
>>> print s.recv(1024)
Hello!
>>> s.send('I like Tuesdays!')
16
>>> print s.recv(1024)
I like Tuesdays!
>>> s.close()
SocketServer 模塊也定義了BaseRequestHandler的兩個子類:StreamRequestHandler和 DatagramRequestHandler。它們覆蓋了setup和finish方法並創建了兩個文件對象rfile和wfile,你可以用這兩個文 件對象來向客戶端讀寫數據,從而代替使用socket方法。
socket的阻塞或同步編程
三、使用socket
網 絡編程中最基本的部分就是socket(套接字)。socket有兩種:服務端socket和客戶端 socket。在你創建了一個服務端socket之 后,你告訴它去等待連接。然后它將監聽某個網絡地址(形如:xxx.xxx.xxx.xxx:xxx) 直到客戶端連接。然后這兩端就可以通信了。
處理客戶端socket通常比處理服務端socket要容易一點,因為服務端必須時刻准備處理來自客戶端的連接,並且它必須處理多個連接,而客戶端只需要簡單的連接,然后做點什么,然后斷開連接。
實 例化一個socket時,可以指定三個參數:地址系列(默認為socket.AF_INET)、流socket(這是個默認 值: socket.SOCK_STREAM)或數據報socket(socket.SOCK_DGRAM)、協議(默認值是0)。對於簡單的 socket,你可以不指定任何參數而全部使用默認值。
服務端socket在使用bind方法之后調用listen方法去監聽一個給定的 地址。然后,客戶端socket就可以通過使用connect方法(connect方法所使用的地址參數與bind相同)去連接服務端。listen方法 要求一個參數,這個參數就是等待連接隊列中所能包含的連接數。
一旦服務端socket調用了listen方法,就進入了臨聽狀態,然后通 常使用一個無限的循環:1、開始接受客房端的連接,這通過調用accept方法來實現。調用了這個方法后將處於阻塞狀態(等待客戶端發起連接)直到一個客 戶端連接,連接后,accept返回形如(client,address)的一個元組,其中client是一個用於與客戶端通信的 socket,address是客戶端的形如xxx.xxx.xxx.xxx:xxx的地址;2、然后服務端處理客戶端的請求;3、處理完成之后又調用 1。
關於傳輸數據,socket有兩個方法:send和recv。send使用字符串參數發送數據;recv參數是字節數,表示一次接受的數據量,如果你不確定一次該接受的數據量的話,最好使用1024。
下面給出一個最小的服務器/客戶機的例子:
服務端:
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
s.listen(5)
while True:
c, addr = s.accept()
print 'Got connection from', addr
c.send('Thank you for connecting')
c.close()
客戶端:
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print s.recv(1024)
注意:如果你使用Ctrl-C來停止服務端的話,如果再次使用相同的端口可能需要等待一會兒。
四、使用SocketServer
SocketServer模塊簡單化了編寫網絡服務器的工作。
它提供了四個基本的服務類:TCPServer(使用TCP協議)、UDPServer(使用數據報)、UnixStreamServer、
UnixDatagramServer。UnixStreamServer和UnixDatagramServer用於類Unix平台。
這四個類處理請求都使用同步的方法,也就是說,在下一個請求處理開始之前當前的請求處理必須已完成
。
用SocketServer創建一個服務器需要四步:
1、通過子類化BaseRequestHandler類和覆蓋它的handle()方法來創建一個請求處理器類,用於處理進來
的請求;
2、實例化服務類如TCPServer,並傳遞給它參數:服務器地址和請求處理器類;
3、調用服務實例對象的handle_request()或serve_forever()方法去處理請求。
下面使用SocketServer用同步的方法寫一個最簡單的服務器:
from SocketServer import TCPServer, StreamRequestHandler
#第一步。其中StreamRequestHandler類是BaseRequestHandler類的子類,它為流socket定義了
#rfile和wfile方法
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'Got connection from', addr
self.wfile.write('Thank you for connecting')
#第二步。其中''代表運行服務器的主機
server = TCPServer(('', 1234), Handler)
#第三步。serve_forever()導致進入循環狀態
server.serve_forever()
注意:使用阻塞或同步的方法一次只能連接一個客戶端,處理完成后才能連接下一個客戶端。
非阻塞或異步編程
五、簡單的例子
1.socket server
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Alex Li
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#import SocketServer
import socketserver,json,sys,subprocess
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# print self.request,self.client_address,self.server
self.request.sendall(bytes('歡迎進入管理界面.',encoding="utf-8"))
while True:
#第一次獲取客服端發送內容
data = self.request.recv(1024)
#如果輸入為空,繼續下一次輸入
if len(data) == 0:break
#如果輸入為查看目錄內容
if str(data,encoding='utf-8')=='dir':
cmd=subprocess.Popen(data.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
cmd_res=cmd.stdout.read()
if not cmd_res:
cmd_res=cmd.stderr.read()
if len(cmd_res)==0:
cmd_res=bytes("result",encoding='gbk')
self.request.send(cmd_res)
#接受下一次任務
continue
#目錄切換,如果接收內容包含cd
if 'cd' in str(data,encoding='utf-8'):
cmd=subprocess.Popen(data.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
cmd_res=cmd.stdout.read()
if not cmd_res:
cmd_res=cmd.stderr.read()
if len(cmd_res)==0:
cmd_res=bytes("result",encoding='gbk')
self.request.send(bytes("%s成功"%data,encoding='gbk'))
continue
print("data", data)
print("[%s] says:%s" % (self.client_address,data.decode() ))
#執行傳輸任務
task_data = json.loads( data.decode() )
task_action = task_data.get("action")
if hasattr(self, "task_%s"%task_action):
func = getattr(self,"task_%s" %task_action)
func(task_data)
else:
print("task action is not supported",task_action)
def task_put(self,*args,**kwargs):
print("---put",args,kwargs)
filename = args[0].get('filename')
filesize = args[0].get('file_size')
server_response = {"status":200}
self.request.send(bytes( json.dumps(server_response), encoding='utf-8' ))
f = open(filename,'wb')
recv_size = 0
while recv_size < filesize:
data = self.request.recv(4096)
f.write(data)
recv_size += len(data)
#進度條
rate = recv_size / filesize
rate_num= int(rate * 100)
r = '\r[%-100s]%d%%' % ('=' * rate_num,rate_num, )
sys.stdout.write(r)
print("connect success")
sys.stdout.flush()
f.close()
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()
2.socket client
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Alex Li
import socket
import os ,json
ip_port=('127.0.0.1',8009)
#買手機
s=socket.socket()
#撥號
s.connect(ip_port)
#發送消息
welcome_msg = s.recv(1024)
print("from server:",welcome_msg.decode())
while True:
send_data=input(">>: ").strip()
if len(send_data) == 0:continue
#查看目錄下的文件
if send_data=='dir':
s.send(bytes(send_data,encoding='utf-8'))
recv_data=s.recv(1024)
print(str(recv_data,encoding='gbk'))
continue
cmd_list = send_data.split( ' ')
if len(cmd_list) <2:continue
task_type = cmd_list[0]
#目錄切換方法
if task_type=='cd':
s.send(bytes(send_data,encoding='utf-8'))
recv_data=s.recv(1024)
print(str(recv_data,encoding='gbk'))
continue
#文件傳輸
if task_type == 'put':
abs_filepath = cmd_list[1]
if os.path.isfile(abs_filepath):
file_size = os.stat(abs_filepath).st_size
filename = abs_filepath.split("\\")[-1]
print('file:%s size:%s' %(abs_filepath,file_size))
msg_data = {"action":"put",
"filename":filename,
"file_size":file_size}
s.send( bytes(json.dumps(msg_data),encoding="utf-8") )
server_confirmation_msg = s.recv(1024)
confirm_data = json.loads(server_confirmation_msg.decode())
if confirm_data['status'] ==200:
print("start sending file ",filename)
f = open(abs_filepath,'rb')
for line in f:
s.send(line)
print("send file done ")
#跳出本次任務,開始下個任務
continue
else:
print("\033[31;1mfile [%s] is not exist\033[0m" % abs_filepath)
continue
else:
print("doesn't support task type",task_type)
continue
#s.send(bytes(send_data,encoding='utf8'))
#收消息
recv_data=s.recv(1024)
print(str(recv_data,encoding='utf8'))
#掛電話
s.close()
六、socket之IO多路復用
Python中有一個select模塊,其中提供了:select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路復用
rlist,wlist,elist=select.select(inputs,outputs,[sk,],1)
select()方法提供四個參數 並提取返回值,rlist監聽socket對象的連接變化,wlist監聽服務端和客戶端的收發消息變化,elist監聽socket對象的操作正確與否,最后一個參數表明該服務端沒個多少秒去監聽一次;
socket單進程單線程只提供了一個服務端,一個客戶端的交互,采用了select的IO多路復用后,避免了客戶端連接及收發消息時的阻塞,從而達到了偽多線程的效果,以下是簡單代碼:
服務端:
# -*- coding: utf-8 -*-
__author__ = 'pxb'
import socket,select
sk=socket.socket()
sk.bind(('127.0.0.1',9999),)
sk.listen(5)
inputs=[sk,]
outputs=[]
#按用戶存放收到的客戶端消息
messages={}
while True:
#IO多路復用,將每個變化的socket對象存到rlist列表中
rlist,wlist,elist=select.select(inputs,outputs,[sk,],1)
print(len(inputs),len(rlist),len(wlist),len(outputs))
for r in rlist:
if r==sk:
conn,address=r.accept()
inputs.append(conn)
messages[conn]=[]
conn.sendall(bytes('hello',encoding='utf-8'))
print('hello')
else:
print("=====================")
try:
ret=r.recv(1024)
if not ret:
raise Exception('斷開連接')
else:
outputs.append(r)
messages(r).append(ret)
except Exception as e:
inputs.remove(r)
del messages[r]
for w in wlist:
#w.sendall(bytes('response',encoding='utf-8'))
msg=messages[w].pop()
resp=msg+bytes('response',encoding='utf-8')
w.sendall(resp)
outputs.remove(w)
客戶端:
# -*- coding: utf-8 -*-
__author__ = 'pxb'
import socket
sk=socket.socket()
sk.connect(('127.0.0.1',9999),)
data=sk.recv(1024)
print(data)
while True:
inp=input('>>>>')
sk.sendall(bytes(inp,encoding='utf-8'))
print(sk.recv(1024))
sk.close()
七、作用域
1、python中無塊級作用域
if 1==1:
name='alex'
print('無塊級作用域測試',name)
for i in range(10):
name1=i
print('無塊級作用域測試',name1)
2、python以函數為作用域(函數以外調用局部變量會報錯)
def fun():
name2='alex'
fun()
print('函數作用域測試',name2)
3、python的作用域在執行之前已經確定(作用域不關乎程序的執行順序,基於以上兩條進行)
name='mqq'
def f1():
print(name)
def f2():
name='pxb'
return f1
ret=f2()
ret()
八、多線程
多線程是為了更好地利用資源及節省時間而形成的一套獨有的編碼方式,結合alex甄嬛西游傳,python的多線程有一定的局限性,即每個進程同時只能派出一個線程去執行,io操作不占用cpu資源,計算代碼會消耗cpu
多線程演示小代碼:
import time
def f1(arg):
time.sleep(1)
print(arg)
#單進程,單線程的應用程序
import threading
t=threading.Thread(target=f1,args=(123,))
t.setDaemon(True)#true表示主線程不在此等子線程
t.start()#不代表當前線程會被立刻執行,取決於操作系統
t.join(2)#表示主線程在此等待,直到子線程完畢
#參數,表示主線程在此最多等待n秒
print('end')
print('end')
print('end')
f1(888)
九、socket源碼查看
以下面集成關系為例,class A,class B(A),class C(A),class D(B),class E(C)
以最底層 方法開始集成,不同版本的python集成順序是不同的
python2.7:D、B、A、E、C
python3.5:D、B、E、C、A