python的網絡編程比c語言簡單許多, 封裝許多底層的實現細節, 方便程序員使用的同時, 也使程序員比較難了解一些底層的東西。
1 TCP/IP
要想理解socket,首先得熟悉一下TCP/IP協議族,TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,定義了主機如何連入因特網及數據如何在它們之間傳輸的標准,從字面意思來看TCP/IP是TCP和IP協議的合稱,但實際上TCP/IP協議是指因特網整個TCP/IP協議族。不同於ISO模型的七個分層,TCP/IP協議參考模型把所有的TCP/IP系列協議歸類到四個抽象層中:
應用層:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet等等
傳輸層:TCP,UDP
網絡層:IP,ICMP,OSPF,EIGRP,IGMP
數據鏈路層:SLIP,CSLIP,PPP,MTU
每一抽象層建立在低一層提供的服務上,並且為高一層提供服務,看起來大概是這樣子的
在TCP/IP協議中兩個因特網主機通過兩個路由器和對應的層連接。各主機上的應用通過一些數據通道相互執行讀取操作,如下圖所示:
2 socket概念
Socket(中文譯為套接字)是操作系統內核中的一個數據結構,它幾乎是所有網絡通信的基礎。網絡通信,歸根到底還是進程間的通信(不同計算機上的進程間通信, 又稱為網絡通信, IP協議進行的主要是端到端通信)。在網絡中,每一個節點(計算機或路由)都有一個網絡地址,也就是IP地址。兩個進程通信時,首先要確定各自所在的網絡節點的網絡地址。但是,網絡地址只能確定進程所在的計算機,而一台計算機上很可能同時運行着多個進程,所以僅憑網絡地址還不能確定到底是和網絡中的哪一個進程進行通信,因此套接字中還需要包括其他的信息,也就是端口號(PORT)。在一台計算機中,一個端口號一次只能分配給一個進程,也就是說,在一台計算機中,端口號和進程之間是一 一對應的關系。
socket使用(IP地址,協議,端口號)來標識一個進程。所以,使用端口號和網絡地址的組合可以唯一的確定整個網絡中的一個網絡進程。
端口號的范圍從0~65535,一類是由互聯網指派名字和號碼公司ICANN負責分配給一些常用的應用程序固定使用的“周知的端口”,其值一般為0~1023, 用戶自定義端口號一般大於等於1024 |
每一個socket都用一個半相關描述{協議、本地地址、本地端口}來表示;一個完整的套接字則用一個相關描述{協議、本地地址、本地端口、遠程地址、遠程端口}來表示。socket也有一個類似於打開文件的函數調用,該函數返回一個整型的socket描述符,隨后的連接建立、數據傳輸等操作都是通過socket描述符來實現的。
網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。socket通常也稱作“套接字”,用於描述IP地址和端口,是一個通信鏈的句柄,應用程序通常通過“套接字”向網絡發出請求或者應答網絡請求。socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,對於文件用打開、讀寫、關閉模式來操作,socket就是該模式在網絡上的一個實現,即socket是一種特殊的文件。兩個程序通過“網絡”交互數據就使用socket,它只負責兩件事:建立連接,傳遞數據;同時socket在收發數據時遵循的原則:有發就有收,收發必相等!
文件讀寫和socket收發數據的區別:
- file模塊是針對某個指定文件進行打開、讀寫、關閉
- socket模塊是針對服務器端和客戶端socket進行打開(連接)、讀寫(收發)、關閉(斷開)
2.1 Socket類型
socket類型在Liunx和Python中是一樣的, 只是Python中的類型都定義在socket模塊中, 調用方式socket.SOCK_XXXX。
1、流式socket(SOCK_STREAM)用於TCP通信
流式套接字提供可靠的、面向連接的通信流;它使用TCP協議,從而保證了數據傳輸的正確性和順序性。
2、數據報socket(SOCK_DGRAM)用於UDP通信
數據報套接字定義了一種無連接的服務;它使用數據報協議UDP,數據通過相互獨立的報文進行傳輸,是無序的,並且不保證是可靠、無差錯的傳輸。
3、原始socket(SOCK_RAW)用於新的網絡協議實現的測試等
原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以, 其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,用戶可以通過IP_HDRINCL套接字選項構造IP頭。
3 Socket編程
TCP/UDP Socket是一種基於Client-Server的編程模型,服務端監聽客戶端的連接請求,一旦建立連接即可以進行數據傳輸。那么對TCP/UDP Socket編程的介紹也分為客戶端和服務端:
3.1 TCP Socket通信
如下圖所示,TCP通信的基本步驟如下:
服務端:socket---bind---listen---while(True){---accept----recv---send---}---close
客戶端:socket----------------------------------connect---send---recv-------close
1、socket函數
使用給定的地址族、套接字類型、協議編號(默認為0)來創建套接字。

1 socket.socket([family[, type[, proto]]]) 2 family : AF_INET (默認ipv4), AF_INET6(ipv6) or AF_UNIX(Unix系統進程間通信). 3 type : SOCK_STREAM (TCP), SOCK_DGRAM(UDP) . 4 protocol : 一般為0或者默認 5 6 如果socket創建失敗會拋出一個socket.error異常</code>
2、服務器端函數
(1)bind函數
將套接字綁定到地址, python下以元組(host,port)的形式表示地址。

1 #python 2 s.bind(address) 3 s為socket.socket()返回的套接字對象 4 address為元組(host,port) 5 host: ip地址, 為一個字符串 6 post: 自定義主機號, 為整型</code>
(2)listen函數
使服務器的這個端口和IP處於監聽狀態,等待網絡中某一客戶機的連接請求。如果客戶端有連接請求,端口就會接受這個連接。

1 #python 2 s.listen(backlog) 3 s為socket.socket()返回的套接字對象 4 backlog : 操作系統可以掛起的最大連接數量。該值至少為1,大部分應用程序設為5就可以了</code>
(3)accept函數
接受遠程計算機的連接請求,建立起與客戶機之間的通信連接。服務器處於監聽狀態時,如果某時刻獲得客戶機的連接請求,此時並不是立即處理這個請求,而是將這個請求放在等待隊列中,當系統空閑時再處理客戶機的連接請求。

1 #python 2 s.accept() 3 s為socket.socket()返回的套接字對象 4 返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址</code>
3 客戶端函數
connect函數
用來請求連接遠程服務器

1 #python 2 s.connect(address) 3 s為socket.socket()返回的套接字對象 4 address : 格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤</code>
4 通用函數
(1)recv函數
接收遠端主機傳來的數據

1 #python 2 s.recv(bufsize[,flag]) 3 s為socket.socket()返回的套接字對象 4 bufsize : 指定要接收的數據大小 5 flag : 提供有關消息的其他信息,通常可以忽略 6 返回值為數據以字符串形式</code>
(2)send函數
發送數據給指定的遠端主機

1 #python 2 s.send(string[,flag]) 3 s為socket.socket()返回的套接字對象 4 string : 要發送的字符串數據 5 flag : 提供有關消息的其他信息,通常可以忽略 6 返回值是要發送的字節數量,該數量可能小於string的字節大小。 7 s.sendall(string[,flag]) 8 #完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。 9 返回值 : 成功返回None,失敗則拋出異常。</code>
(3)close函數
關閉套接字

1 #python 2 s.close() 3 s為socket.socket()返回的套接字對象</code>
5 帶異常處理的客戶端服務端TCP連接
在進行網絡編程時, 最好使用大量的錯誤處理, 能夠盡量的發現錯誤, 也能夠使代碼顯得更加嚴謹。
服務器端代碼

1 <code>#服務器端 2 #!/usr/bin/env python 3 # -*- coding:utf-8 -*- 4 5 import sys 6 import socket #socket模塊 7 8 BUF_SIZE = 1024 #設置緩沖區大小 9 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 10 try : 11 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一個新的socket對象 12 except socket.error, msg : 13 print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] 14 sys.exit() 15 print "Socket Created!" 16 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #設置地址復用 17 try : 18 server.bind(server_addr) #綁定地址 19 except socket.error, msg : 20 print "Binding Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] 21 sys.exit() 22 print "Socket Bind!" 23 server.listen(5) #監聽, 最大監聽數為5 24 print "Socket listening" 25 while True: 26 client, client_addr = server.accept() #接收TCP連接, 並返回新的套接字和地址, 阻塞函數 27 print 'Connected by', client_addr 28 while True : 29 data = client.recv(BUF_SIZE) #從客戶端接收數據 30 print data 31 client.sendall(data) #發送數據到客戶端 32 server.close()</code>
客戶端代碼

1 <code>#客戶端 2 #!/usr/bin/env python 3 # -*- coding:utf-8 -*- 4 5 import sys 6 import socket 7 8 BUF_SIZE = 1024 #設置緩沖區的大小 9 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 10 try : 11 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket對象 12 except socket.error, msg : 13 print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] 14 sys.exit() 15 client.connect(server_addr) #要連接的服務器地址 16 while True: 17 data = raw_input("Please input some string > ") 18 if not data : 19 print "input can't empty, Please input again.." 20 continue 21 client.sendall(data) #發送數據到服務器 22 data = client.recv(BUF_SIZE) #從服務器端接收數據 23 print data 24 client.close()</code>
3.2 Socket UDP通信
UDP通信流程圖如下:
服務端:socket---bind---recvfrom---sendto---close
客戶端:socket----------sendto---recvfrom---close
(1)socket函數,同上
(2)sendto函數
發送UDP數據, 將數據發送到套接字

1 #Python 2 s.sendto(string[,flag],address) 3 s為socket.socket()返回的套接字對象 4 address : 指定遠程地址, 形式為(ipaddr,port)的元組 5 flag : 提供有關消息的其他信息,通常可以忽略 6 返回值 : 發送的字節數。</code>
(3)recvfrom函數
接受UDP套接字的數據, 與recv()類似

1 #Python 2 s.recvfrom(bufsize[.flag]) 3 返回值 : (data,address)元組, 其中data是包含接收數據的字符串,address是發送數據的套接字地址 4 bufsize : 指定要接收的數據大小 5 flag : 提供有關消息的其他信息,通常可以忽略</code>
(4)close函數,同上
(5)簡單的客戶端服務器UDP連接
服務器端代碼

1 <code>#服務器端 2 #!/usr/bin/env python 3 # -*- coding:utf-8 -*- 4 5 import socket 6 7 BUF_SIZE = 1024 #設置緩沖區大小 8 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 9 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字對象 10 server.bind(server_addr) #套接字綁定IP和端口 11 while True : 12 print "waitting for data" 13 data, client_addr = server.recvfrom(BUF_SIZE) #從客戶端接收數據 14 print 'Connected by', client_addr, ' Receive Data : ', data 15 server.sendto(data, client_addr) #發送數據給客戶端 16 server.close()</code>
客戶端代碼

1 <code>#客戶端 2 #!/usr/bin/env python 3 # -*- coding:utf-8 -*- 4 5 import socket 6 import struct 7 8 BUF_SIZE = 1024 #設置緩沖區 9 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 10 client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字對象 11 12 while True : 13 data = raw_input('Please Input data > ') 14 client.sendto(data, server_addr) #向服務器發送數據 15 data, addr = client.recvfrom(BUF_SIZE) #從服務器接收數據 16 print "Data : ", data 17 client.close()</code>
4 Python Socket更多功能
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
函數socket.socket
創建一個socket對象,返回該對象的socket描述符,該socket描述符將在后面的相關函數中使用。
參數一:地址簇
socket.AF_INET IPv4(默認)
socket.AF_INET6 IPv6socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信。
參數二:類型
socket.SOCK_STREAM 流式socket , for TCP(默認)
socket.SOCK_DGRAM 數據報式socket , for UDPsocket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。
socket.SOCK_RDM 是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在需要執行某些特殊操作時使用,如發送ICMP報文。SOCK_RAM通常僅限於高級用戶或管理員運行的程序使用。
socket.SOCK_SEQPACKET 可靠的連續數據包服務。參數三:協議
0 默認值,與特定的地址家族相關的協議,如果是0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議。
sk.bind(address)
s.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
sk.listen(backlog)
開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。backlog等於5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的連接個數最大為5,這個值不能無限 大,因為要在內核中維護連接隊列。
sk.setblocking(bool)
是否阻塞(默認True),如果設置False,那么accept和recv時一旦無數據,則報錯。
sk.accept()
接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。接收TCP客戶的連接(阻塞式)等待連接的到來。
sk.connect(address)
連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
sk.connect_ex(address)
同上,只不過會有返回值,連接成功時返回 0 ,連接失敗時候返回編碼,例如:10061
sk.close()
關閉套接字
sk.recv(bufsize[,flag])
接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略。
sk.recvfrom(bufsize[.flag])
與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
sk.send(string[,flag])
將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。
sk.sendall(string[,flag])
將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。內部通過遞歸調用send,將所有內容發送出去。
sk.sendto(string[,flag],address)
將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。
sk.settimeout(timeout)
設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作(如client連接最多等待5s)
sk.getpeername()
返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。
sk.getsockname()
返回套接字自己的地址。通常是一個元組(ipaddr,port)
sk.fileno()
套接字的文件描述符
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
允許后續使用bind()來綁定指定端口並進行監聽,即使這個端口最近被其他程序監聽。沒有這個設置的話,服務不能運行,直到一兩分鍾后,這個端口不再被之前的程序使用。
參考資料:
http://www.open-open.com/lib/view/open1418369887277.html
http://www.cnblogs.com/wupeiqi/articles/5040823.html
http://www.cnblogs.com/hazir/p/python_socket_programming.html