對 Python Socket 編程的初探
對于python網絡編程來說,免不了要用到socket模塊。下面主要分享一下個人對python socket的一些理解。
socket編程步驟
- 服務端創建一個socket,綁定地址和端口,然后監聽端口上傳入的連接,一旦有連接進來,就通過accept函數接收傳入的連接。
- 客戶端也是創建一個socket。綁定遠程地址和端口,然后建立連接,發送數據。
服務端socket
下面通過一段實例代碼來詳細說明
服務端 socker_server.py
import socket
import sys
HOST = "127.0.0.1"
PORT = 10000
s = None
for resin 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.erroras msg:
s = None
continue
try:
s.bind(sa)
s.listen(5)
except socket.erroras 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()
首先我們通過socket.getaddrinnfo函數將host/port轉換成一個包含5元組的序列。這個5元組包含我們創建一個socket連接所需要的所有必要參數。返回的5元組分別是 (family, sockettype, proto, canonname, sockaddr)
family 地址簇,用與socket()函數的第一個參數。主要有以下幾個
- socket.AF_UNIX 用與單一機器下的進程通信
- socket.AF_INET 用與服務器之間相互通信,通常都用這個。
- socket.AF_INET6 支持IPv6
sockettype socket類型,用與socket()函數的第二個參數,常用的有
- socket.SOCK_STREAM 默認,用于TCP協議
- socket.SOCK_DGRAM 用于UDP協議
proto 協議,用于socket()函數的第三個參數。 getaddrinnfo函數會 根據地址格式和socket類型,返回合適的協議
canonname 一個規范化的host name。
sockaddr 描述了一個socket address .是一個二元組,主要用于bind()和connect()函數
接下來創建一個socket對象,傳入getaddrinnfo函數返回的af,sockettype,proto。
s = socket.socket(af, socktype, proto)
然后綁定socket address
s.bind(sa)
開啟監聽模式
s.listen(5)
listen函數會監聽連接到socket上的連接,參數表示在拒絕連接之前系統可以掛起的最大連接隊列數量為5。這些連接還沒有被accept處理。數量不能無限大,通常指定5。
一旦我們監聽到了連接,就會調用accept函數接收連接
conn, addr = s.accept()
accept函數返回一個二元組,conn是一個新的socket對象,用來接收和發送數據。addr表示另一端的socket地址。
接下來我們就可以用conn對象發送和接收數據了
data = conn.recv(1024) # 接收數據, 這里指定一次最多接收的字符數量為1024
conn.send(data) # 發送數據
這里我們接收到一個連接socket就會停止運行,所以如果要循環連接的話,將accept函數放入到一個死循環里。
客戶端socket
客戶端socket編程相對比較簡單,通過connect和服務端建立連接之后,就可以相互通信了。socket_client.py如下
for resin 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.erroras msg:
s = None
continue
try:
s.connect(sa)
except socket.erroras 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)
以上主要是針對TCP流數據的socket編程。對于UDP協議的數據,處理略有不同。譬如發送接收UDP數據包處理函數為:
socket.sendto(string, flags, address)
socket.recvfrom(bufsize[, flags]) #返回(string, address),string是返回的數據,address是發送方的socket地址
SocketServer模塊
python中網絡編程除了socket模塊還提供了SocketServer模塊,這一模塊主要是對socket模塊進行了封裝,將socket的對象的創建,綁定,連接,接收,發送,關閉都封裝在里面,大大簡化了網絡服務的編程。
此模塊提供了以下2個主要的網絡服務類,用于創建相應的套接字流
- TCPServer 創建TCP協議的套接字流
- UDPServer 創建UDP協議的套接字流
我們有了套接字流對象,還需要一個請求處理類。SocketServer模塊提供了請求處理類有BaseRequestHandler,以及它的派生類StreamRequestHandler和DatagramRequestHandler。所以只要繼承這3個類中的一個,然后重寫handle函數,此函數將用來處理接收到的請求。下面看一個服務端的代碼示例
import SocketServer
class MyTCPHandler(SocketServer.StreamRequestHandler):
"""創建請求處理類,重寫handle方法。此外也可以重寫setup()和finish()來做一些請求處理前和處理后的一些工作"""
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() # 一直循環接收請求
# server.handle_request() # 只處理一次請求就退出
看著是不是代碼簡單了很多,而且SocketServer模塊內部使用了多路復用IO技術,可以實現更好的連接性能。看serve_forever函數的源代碼用到了select模塊。通過傳入socket對象調用select.select()來監聽socket對象的文件描述符,一旦發現socket對象就緒,就通知應用程序進行相應的讀寫操作。源代碼如下:
defserve_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技術,TCPServer,UDPServer處理請求仍然是同步的,意味著一個請求處理完,才能處理下一個請求。但SocketServer模塊提供了另外2個類用來支持異步的模式。
- ForkingMixIn 利用多進程實現異步
- ThreadingMixIn 利用多線程實現異步
看名字就知道用mixin模式來實現異步。而mixin模式可以通過多繼承來實現,所以通過對網絡服務類進行多繼承的方式就可以實現異步模式
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
針對ThreadindMixIn,實現異步的原理也就是在內部對每個請求創建一個線程來處理。看源碼
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()
下面提供一個異步模式的示例
importsocket
importthreading
importSocketServer
class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
defhandle(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
defclient(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()
以上是本人對socket相關的理解,有什么不當或錯誤之處,還請指出。
來自:http://python.jobbole.com/86837/