本文介紹使用Python進(jìn)行Socket網(wǎng)絡(luò)編程,假設(shè)讀者已經(jīng)具備了基本的網(wǎng)絡(luò)編程知識(shí)和Python的基本語(yǔ)法知識(shí),本文中的代碼如果沒(méi)有說(shuō)明則都是運(yùn)行在Python 3.4下。
Python的socket功能封裝在socket庫(kù)中,要使用socket,記得先import socket,socket庫(kù)的詳細(xì)介紹參見(jiàn)官方文檔。
創(chuàng)建Socket
首先創(chuàng)建一個(gè)socket,使用socket庫(kù)中得socket函數(shù)創(chuàng)建。
1
|
import socket |
1
2
|
# create an INET, STREAM socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
例子中創(chuàng)建了一個(gè)TCP socket,socket.socket函數(shù)的前兩個(gè)參數(shù)的默認(rèn)值是socket.AF_INET和socket.SOCK_STREAM,創(chuàng)建TCP socket時(shí)可以直接寫(xiě)成socket.socket()。
連接服務(wù)器
使用socket的connect函數(shù)連接到服務(wù)器,以下幾種參數(shù)都是合法的。
1
2
3
|
s.connect(( 'localhost' , 8000 )) s.connect(( '127.0.0.1' , 8000 )) s.connect(( 'www.baidu.com' , 80 )) |
發(fā)送數(shù)據(jù)
發(fā)送數(shù)據(jù)有兩個(gè)方法send和sendall,send不能保證所有的數(shù)據(jù)都發(fā)送完了,它會(huì)返回已發(fā)送數(shù)據(jù)的長(zhǎng)度,程序要循環(huán)發(fā)送數(shù)據(jù)直到所有數(shù)據(jù)都已發(fā)送完畢。
1
2
3
4
5
6
7
8
|
def mysend(s, msg): total_len = len (msg) total_sent = 0 while total_sent < total_len: sent = s.send(msg[total_sent:]) if sent = = 0 : raise RuntimeError( "socket connection broken" ) total_sent + = sent |
sendall能夠保證所有的數(shù)據(jù)都已發(fā)送完畢,除非發(fā)送過(guò)程中出現(xiàn)了錯(cuò)誤,它實(shí)際上也是循環(huán)發(fā)送數(shù)據(jù)直到所有數(shù)據(jù)發(fā)送完成。
這里還要講一個(gè)需要特別注意的地方,從一個(gè)例子開(kāi)始吧:
1
2
3
4
|
import socket s = socket.socket() s.connect(( 'www.baidu.com' , 80 )) s.sendall( 'test' ) |
都是上面講過(guò)的東西,沒(méi)什么特別的,分別在Python 2和Python 3中執(zhí)行以上的代碼,結(jié)果是:
1
2
3
4
5
|
# Python 2.7 >>> import socket >>> s = socket.socket() >>> s.connect(( 'www.baidu.com' , 80 )) >>> s.sendall( 'test' ) |
Python 2中執(zhí)行成功。
1
2
3
4
5
6
7
8
|
# Python 3.4 >>> import socket >>> s = socket.socket() >>> s.connect(( 'www.baidu.com' , 80 )) >>> s.sendall( 'test' ) Traceback (most recent call last): File "<stdin>" , line 1 , in <module> TypeError: 'str' does not support the buffer interface |
Python 3中卻發(fā)生了異常。
同樣的代碼換個(gè)環(huán)境卻不能執(zhí)行了,我沒(méi)有寫(xiě)錯(cuò)呀,怒砸電腦。好吧,你確實(shí)沒(méi)寫(xiě)錯(cuò),是環(huán)境變了,導(dǎo)致這個(gè)結(jié)果的變化請(qǐng)移步官方的說(shuō)明。
接收數(shù)據(jù)
使用recv函數(shù)接收數(shù)據(jù):
1
|
data = s.recv( 4096 ) |
在Python 3中返回的是bytes對(duì)象,在Python 2中返回的是string。注意函數(shù)返回的數(shù)據(jù)長(zhǎng)度是小于或者等于參數(shù)指定的長(zhǎng)度的,要接收到指定長(zhǎng)度的數(shù)據(jù),需要循環(huán)接收數(shù)據(jù)。
1
2
3
4
5
6
7
8
9
10
|
def myreceive(s, msglen): chunks = [] bytes_recd = 0 while bytes_recd < msglen: chunk = s.recv( min (msglen - bytes_recd, 2048 )) if chunk = = b'': raise RuntimeError( "socket connection broken" ) chunks.append(chunk) bytes_recd = bytes_recd + len (chunk) return b''.join(chunks) |
關(guān)閉連接
當(dāng)連接不再需要時(shí)可以使用close關(guān)閉socket連接,關(guān)閉后的連接不能再進(jìn)行任何操作。當(dāng)一個(gè)socket被回收時(shí)會(huì)自動(dòng)關(guān)閉,但是不要依賴(lài)這種機(jī)制,不需要socket時(shí)就主動(dòng)的close。
服務(wù)端
服務(wù)端程序執(zhí)行的步驟:
1. 創(chuàng)建服務(wù)端socket
1. 將服務(wù)端socket綁定到指定的地址和端口
1. 監(jiān)聽(tīng)連接
1. 接受客戶(hù)端連接
1. 處理客戶(hù)端的數(shù)據(jù)
1. 關(guān)閉客戶(hù)端連接
一個(gè)簡(jiǎn)單的echo server示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import socket HOST = '' PORT = 10022 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen( 10 ) conn, addr = s.accept() while True : data = conn.recv( 1024 ) if not data: break conn.sendall(data) conn.close() |
客戶(hù)端程序:
1
2
3
4
5
6
7
8
9
10
11
|
import socket HOST = 'localhost' PORT = 10022 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) s.sendall(b 'hello socket' ) data = s.recv( 1024 ) print ( 'Received' , repr (data)) s.close() |
錯(cuò)誤處理
socket處理過(guò)程中發(fā)生錯(cuò)誤會(huì)拋出異常,socket相關(guān)的異常有:
- - socket.error
- - socket.herror
- - socket.gaierror
- - socket.timeout
1
2
3
4
5
6
7
8
9
10
11
|
import socket HOST = None PORT = 10022 try : s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen( 10 ) except : socket.error as msg: print (msg) |