摘要:對(duì)于網(wǎng)絡(luò)編程來(lái)說(shuō),免不了要用到模塊。表示另一端的地址。以上主要是針對(duì)流數(shù)據(jù)的編程。對(duì)于協(xié)議的數(shù)據(jù),處理略有不同。通過(guò)傳入對(duì)象調(diào)用來(lái)監(jiān)聽(tīng)對(duì)象的文件描述符,一旦發(fā)現(xiàn)對(duì)象就緒,就通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫(xiě)操作。
對(duì)于python網(wǎng)絡(luò)編程來(lái)說(shuō),免不了要用到socket模塊。下面分享一下個(gè)人對(duì)python socket的一些理解。
socket編程步驟服務(wù)端創(chuàng)建一個(gè)socket,綁定地址和端口,然后監(jiān)聽(tīng)端口上傳入的連接,一旦有連接進(jìn)來(lái),就通過(guò)accept函數(shù)接收傳入的連接。
客戶端也是創(chuàng)建一個(gè)socket。綁定遠(yuǎn)程地址和端口,然后建立連接,發(fā)送數(shù)據(jù)。
服務(wù)端socket下面通過(guò)一段實(shí)例代碼來(lái)詳細(xì)說(shuō)明 服務(wù)端 socker_server.py
import socket import sys HOST = "127.0.0.1" PORT = 10000 s = None for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE): af, socktype, proto, canonname, sa = res try: s = socket.socket(af, socktype, proto) except socket.error as msg: s = None continue try: s.bind(sa) s.listen(5) except socket.error as msg: s.close() s = None continue break if s is None: print "could not open socket" sys.exit(1) conn, addr = s.accept() print "Connected by", addr while 1: data = conn.recv(1024) if not data: break conn.send(data) conn.close()
首先我們通過(guò)socket.getaddrinnfo函數(shù)將host/port轉(zhuǎn)換成一個(gè)包含5元組的序列。這個(gè)5元組包含我們創(chuàng)建一個(gè)socket連接所需要的所有必要參數(shù)。返回的5元組分別是 (family, sockettype, proto, canonname, sockaddr)
family 地址簇,用與socket()函數(shù)的第一個(gè)參數(shù)。主要有以下幾個(gè)
socket.AF_UNIX 用與單一機(jī)器下的進(jìn)程通信
socket.AF_INET 用與服務(wù)器之間相互通信,通常都用這個(gè)。
socket.AF_INET6 支持IPv6
sockettype socket類型,用與socket()函數(shù)的第二個(gè)參數(shù),常用的有
socket.SOCK_STREAM 默認(rèn),用于TCP協(xié)議
socket.SOCK_DGRAM 用于UDP協(xié)議
proto 協(xié)議,用于socket()函數(shù)的第三個(gè)參數(shù)。 getaddrinnfo函數(shù)會(huì)根據(jù)地址格式和socket類型,返回合適的協(xié)議
canonname 一個(gè)規(guī)范化的host name。
sockaddr 描述了一個(gè)socket address .是一個(gè)二元組,主要用于bind()和connect()函數(shù)
接下來(lái)創(chuàng)建一個(gè)socket對(duì)象,傳入getaddrinnfo函數(shù)返回的af,sockettype,proto。
s = socket.socket(af, socktype, proto)
然后綁定我的socket address
s.bind(sa)
開(kāi)啟監(jiān)聽(tīng)模式
s.listen(5)
listen函數(shù)會(huì)監(jiān)聽(tīng)連接到socket上的連接,參數(shù)表示在拒絕連接之前系統(tǒng)可以掛起的最大連接隊(duì)列數(shù)量為5。這些連接還沒(méi)有被accept處理。數(shù)量不能無(wú)限大,通常指定5。
一旦我們監(jiān)聽(tīng)到了連接,就會(huì)調(diào)用accept函數(shù)接收連接
conn, addr = s.accept()
accept函數(shù)返回一個(gè)二元組,conn是一個(gè)新的socket對(duì)象,用來(lái)接收和發(fā)送數(shù)據(jù)。addr表示另一端的socket地址。
接下來(lái)我們就可以用conn對(duì)象發(fā)送和接收數(shù)據(jù)了
data = conn.recv(1024) # 接收數(shù)據(jù), 這里指定一次最多接收的字符數(shù)量為1024 conn.send(data) # 發(fā)送數(shù)據(jù)
這里我們接收到一個(gè)連接socket就會(huì)停止運(yùn)行,所以如果要循環(huán)連接的話,將accept函數(shù)放入到一個(gè)死循環(huán)里。
客戶端socket客戶端socket編程相對(duì)比較簡(jiǎn)單,通過(guò)connect和服務(wù)端建立連接之后,就可以相互通信了。socket_client.py如下
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: s = socket.socket(af, socktype, proto) except socket.error as msg: s = None continue try: s.connect(sa) except socket.error as msg: s.close() s = None continue break if s is None: print "could not open socket" sys.exit(1) s.sendall("Hello, world") data = s.recv(1024) s.close() print "Received", repr(data)
備注: 對(duì)于getaddrinfo函數(shù),可以參考下http://baike.baidu.com/link?u... 這個(gè)函數(shù)的作用是把協(xié)議相關(guān)性安全隱藏在這個(gè)底層庫(kù)函數(shù)內(nèi)部。應(yīng)用程序只要處理由getaddrinfo函數(shù)返回的數(shù)據(jù)即可。
以上主要是針對(duì)TCP流數(shù)據(jù)的socket編程。對(duì)于UDP協(xié)議的數(shù)據(jù),處理略有不同。譬如發(fā)送接收UDP數(shù)據(jù)包處理函數(shù)為:
socket.sendto(string, flags, address) socket.recvfrom(bufsize[, flags]) #返回(string, address),string是返回的數(shù)據(jù),address是發(fā)送方的socket地址
其他詳細(xì)內(nèi)容可以參考 http://python.usyiyi.cn/trans...
SocketServer模塊python中網(wǎng)絡(luò)編程除了socket模塊還提供了SocketServer模塊,這一模塊主要是對(duì)socket模塊進(jìn)行了封裝,將socket的對(duì)象的創(chuàng)建,綁定,連接,接收,發(fā)送,關(guān)閉都封裝在里面,大大簡(jiǎn)化了網(wǎng)絡(luò)服務(wù)的編程。
此模塊提供了以下2個(gè)主要的網(wǎng)絡(luò)服務(wù)類,用于創(chuàng)建相應(yīng)的套接字流
TCPServer 創(chuàng)建TCP協(xié)議的套接字流
UDPServer 創(chuàng)建UDP協(xié)議的套接字流
我們有了套接字流對(duì)象,還需要一個(gè)請(qǐng)求處理類。SocketServer模塊提供了請(qǐng)求處理類有BaseRequestHandler,以及它的派生類StreamRequestHandler和DatagramRequestHandler。所以只要繼承這3個(gè)類中的一個(gè),然后重寫(xiě)handle函數(shù),此函數(shù)將用來(lái)處理接收到的請(qǐng)求。下面看一個(gè)服務(wù)端的代碼示例
import SocketServer class MyTCPHandler(SocketServer.StreamRequestHandler): """創(chuàng)建請(qǐng)求處理類,重寫(xiě)handle方法。此外也可以重寫(xiě)setup()和finish()來(lái)做一些請(qǐng)求處理前和處理后的一些工作""" def handle(self): # self.request is the TCP socket connected to the client self.data = self.request.recv(1024).strip() print "{} wrote:".format(self.client_address[0]) print self.data # just send back the same data, but upper-cased self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "localhost", 10000 server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler) # Activate the server; this will keep running until you # interrupt the program with Ctrl-C # server.shutdown() server.serve_forever() # 一直循環(huán)接收請(qǐng)求 # server.handle_request() # 只處理一次請(qǐng)求就退出
看著是不是代碼簡(jiǎn)單了很多,而且SocketServer模塊內(nèi)部使用了多路復(fù)用IO技術(shù),可以實(shí)現(xiàn)更好的連接性能??磗erve_forever函數(shù)的源代碼用到了select模塊。通過(guò)傳入socket對(duì)象調(diào)用select.select()來(lái)監(jiān)聽(tīng)socket對(duì)象的文件描述符,一旦發(fā)現(xiàn)socket對(duì)象就緒,就通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫(xiě)操作。源代碼如下:
def serve_forever(self, poll_interval=0.5): """Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores self.timeout. If you need to do periodic tasks, do them in another thread. """ self.__is_shut_down.clear() try: while not self.__shutdown_request: # XXX: Consider using another file descriptor or # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: self._handle_request_noblock() finally: self.__shutdown_request = False self.__is_shut_down.set()
即使使用了select技術(shù),TCPServer,UDPServer處理請(qǐng)求仍然是同步的,意味著一個(gè)請(qǐng)求處理完,才能處理下一個(gè)請(qǐng)求。但SocketServer模塊提供了另外2個(gè)類用來(lái)支持異步的模式。
ForkingMixIn 利用多進(jìn)程實(shí)現(xiàn)異步
ThreadingMixIn 利用多線程實(shí)現(xiàn)異步
看名字就知道使用了mixin模式。而mixin模式可以通過(guò)多繼承來(lái)實(shí)現(xiàn),所以通過(guò)對(duì)網(wǎng)絡(luò)服務(wù)類進(jìn)行多繼承的方式就可以實(shí)現(xiàn)異步模式
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass
針對(duì)ThreadindMixIn,實(shí)現(xiàn)異步的原理也就是在內(nèi)部對(duì)每個(gè)請(qǐng)求創(chuàng)建一個(gè)線程來(lái)處理。看源碼
def process_request(self, request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) t.daemon = self.daemon_threads t.start()
下面提供一個(gè)異步模式的示例
import socket import threading import SocketServer class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): def handle(self): data = self.request.recv(1024) cur_thread = threading.current_thread() response = "{}: {}".format(cur_thread.name, data) self.request.sendall(response) class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass def client(ip, port, message): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, port)) try: sock.sendall(message) response = sock.recv(1024) print "Received: {}".format(response) finally: sock.close() if __name__ == "__main__": # Port 0 means to select an arbitrary unused port HOST, PORT = "localhost", 0 server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) ip, port = server.server_address # Start a thread with the server -- that thread will then start one # more thread for each request server_thread = threading.Thread(target=server.serve_forever) # Exit the server thread when the main thread terminates server_thread.daemon = True server_thread.start() print "Server loop running in thread:", server_thread.name client(ip, port, "Hello World 1") client(ip, port, "Hello World 2") client(ip, port, "Hello World 3") server.shutdown() server.server_close()
更多對(duì)SocketServer模塊的了解參考https://docs.python.org/2/lib... 本文所使用的示例就來(lái)自官網(wǎng)。畢竟官網(wǎng)的例子實(shí)在太好了。
以上是本人對(duì)socket相關(guān)的理解,有什么不當(dāng)或錯(cuò)誤之處,還請(qǐng)指出。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/44280.html
摘要:從導(dǎo)入相應(yīng)函數(shù),即傳入一個(gè)上一步中的類,返回對(duì)應(yīng)的網(wǎng)頁(yè)內(nèi)容,具體實(shí)現(xiàn)將在后續(xù)講解。后續(xù)只要去處理解析請(qǐng)求和響應(yīng)部分即可,主程序可以不用再修改。下一篇文章編程解析請(qǐng)求頭 Flask或者其他框架都是封裝的比較完善,我們可以不去關(guān)注路由、SESSION等到底是怎么實(shí)現(xiàn)的,現(xiàn)在我們使用socket來(lái)實(shí)現(xiàn)一個(gè)帶有注冊(cè)、登錄功能的To do網(wǎng)站,這樣能對(duì)后端框架了解的稍微更深入一點(diǎn)(當(dāng)然你也可以直...
摘要:請(qǐng)求頭的換行使用的是。編寫(xiě)一個(gè)類,來(lái)解析請(qǐng)求的方法路徑和,如下下一篇文章編程響應(yīng) 前文:【python socket編程】—— 1.初探 在上一篇文章中我們知道,socket.accept()接受的數(shù)據(jù)是請(qǐng)求頭,請(qǐng)求頭格式是這樣的: POST /login HTTP/1.1 Host: 127.0.0.1:1207 User-Agent: Mozilla/5.0 (X11; Ubunt...
摘要:原文地址的中文名字叫做套接字,這種東西就是對(duì)的封裝。運(yùn)行結(jié)果如下簡(jiǎn)單解析一下上述代碼來(lái)說(shuō)明一下服務(wù)器的流程首先,根據(jù)協(xié)議族或地址族套接字類型以及具體的的某個(gè)協(xié)議來(lái)創(chuàng)建一個(gè)。很容易受到攻擊,造成拒絕服務(wù)。 [原文地址:https://blog.ti-node.com/blog...] socket的中文名字叫做套接字,這種東西就是對(duì)TCP/IP的封裝?,F(xiàn)實(shí)中的網(wǎng)絡(luò)實(shí)際上只有四層而已,從上...
摘要:原文地址要想更好了解編程,有一個(gè)不可繞過(guò)的環(huán)節(jié)就是在中,一切皆文件實(shí)際上要文件干啥不就是讀寫(xiě)么所以,這句話本質(zhì)就是才是王道用的打開(kāi)文件關(guān)閉文件讀讀寫(xiě)寫(xiě),這叫本地文件在編程中,本質(zhì)就是網(wǎng)絡(luò)所以,在開(kāi)始進(jìn)一步的編程前,我們必須先從概念上認(rèn)識(shí)好 [原文地址:https://blog.ti-node.com/blog...] 要想更好了解socket編程,有一個(gè)不可繞過(guò)的環(huán)節(jié)就是IO.在Lin...
閱讀 2917·2021-11-24 09:39
閱讀 1175·2021-11-02 14:38
閱讀 4170·2021-09-10 11:26
閱讀 2760·2021-08-25 09:40
閱讀 2318·2019-08-30 15:54
閱讀 489·2019-08-30 10:56
閱讀 2756·2019-08-26 12:14
閱讀 3226·2019-08-26 12:13