python的網絡編程比c語言簡單許多, 封裝許多底層的實現細節, 方便程序員使用的同時, 也使程序員比較難了解一些底層的東西。html
要想理解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
每一抽象層創建在低一層提供的服務上,而且爲高一層提供服務,看起來大概是這樣子的
python
在TCP/IP協議中兩個因特網主機經過兩個路由器和對應的層鏈接。各主機上的應用經過一些數據通道相互執行讀取操做,以下圖所示:程序員
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進行打開(鏈接)、讀寫(收發)、關閉(斷開)
數據結構
socket類型在Liunx和Python中是同樣的, 只是Python中的類型都定義在socket模塊中, 調用方式socket.SOCK_XXXX。socket
一、流式socket(SOCK_STREAM)用於TCP通訊ide
流式套接字提供可靠的、面向鏈接的通訊流;它使用TCP協議,從而保證了數據傳輸的正確性和順序性。函數
二、數據報socket(SOCK_DGRAM)用於UDP通訊
數據報套接字定義了一種無鏈接的服務;它使用數據報協議UDP,數據經過相互獨立的報文進行傳輸,是無序的,而且不保證是可靠、無差錯的傳輸。
三、原始socket(SOCK_RAW)用於新的網絡協議實現的測試等
原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠, 其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,用戶能夠經過IP_HDRINCL套接字選項構造IP頭。
TCP/UDP Socket是一種基於Client-Server的編程模型,服務端監聽客戶端的鏈接請求,一旦創建鏈接便可以進行數據傳輸。那麼對TCP/UDP Socket編程的介紹也分爲客戶端和服務端:
以下圖所示,TCP通訊的基本步驟以下:
服務端:socket---bind---listen---while(True){---accept----recv---send---}---close
客戶端:socket----------------------------------connect---send---recv-------close
一、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>
二、服務器端函數
(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>
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