python之網絡編程


本地的進程間通信(IPC)有很多種方式,但可以總結為下面4類:

  • 消息傳遞(管道、FIFO、消息隊列)
  • 同步(互斥量、條件變量、讀寫鎖、文件和寫記錄鎖、信號量)
  • 共享內存(匿名的和具名的)
  • 遠程過程調用(Solaris門和Sun RPC)

但這些都不是本文的主題!我們要討論的是網絡中進程之間如何通信?首要解決的問題是如何唯一標識一個進程,否則通信無從談起!在本地可以通過進程PID來唯一標識一個進程,但是在網絡中這是行不通的。其實TCP/IP協議族已經幫我們解決了這個問題,網絡層的“ip地址”可以唯一標識網絡中的主機,而傳輸層的“協議+端口”可以唯一標識主機中的應用程序(進程)。這樣利用三元組(ip地址,協議,端口)就可以標識網絡的進程了,網絡中的進程通信就可以利用這個標志與其它進程進行交互。

使用TCP/IP協議的應用程序通常采用應用編程接口:UNIX  BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通信。就目前而言,幾乎所有的應用程序都是采用socket,而現在又是網絡時代,網絡中進程通信是無處不在,這就是我為什么說“一切皆socket”。


網絡通信就是兩個進程在通信

網絡編程對所有開發語言都是一樣的,Python也不例外。用Python進行網絡編程,就是在Python程序本身這個進程內,連接別的服務器進程的通信端口進行通信。

 

   (1) IP、TCP和UDP
    當您編寫socket應用程序的時候,您可以在使用TCP還是使用UDP之間做出選擇。它們都有各自的優點和缺點。
    TCP是流協議,而UDP是數據報協議。換句話說,TCP在客戶機和服務器之間建立持續的開放連接,在該連接的生命期內,字節可以通過該連接寫出(並且保證順序正確)。然而,通過 TCP 寫出的字節沒有內置的結構,所以需要高層協議在被傳輸的字節流內部分隔數據記錄和字段。
    另一方面,UDP不需要在客戶機和服務器之間建立連接,它只是在地址之間傳輸報文。UDP的一個很好特性在於它的包是自分隔的(self-delimiting),也就是一個數據報都准確地指出它的開始和結束位置。然而,UDP的一個可能的缺點在於,它不保證包將會按順序到達,甚至根本就不保證。當然,建立在UDP之上的高層協議可能會提供握手和確認功能。
    對於理解TCP和UDP之間的區別來說,一個有用的類比就是電話呼叫和郵寄信件之間的區別。在呼叫者用鈴聲通知接收者,並且接收者拿起聽筒之前,電話呼叫不是活動的。只要沒有一方掛斷,該電話信道就保持活動,但是在通話期間,他們可以自由地想說多少就說多少。來自任何一方的談話都按臨時的順序發生。另一方面,當你發一封信的時候,郵局在投遞時既不對接收方是否存在作任何保證,也不對信件投遞將花多長時間做出有力保證。接收方可能按與信件的發送順序不同的順序接收不同的信件,並且發送方也可能在他們發送信件是交替地接收郵件。與(理想的)郵政服務不同,無法送達的信件總是被送到死信辦公室處理,而不再返回給發送。

    (2)對等方、端口、名稱和地址
    除了TCP和UDP協議以外,通信一方(客戶機或者服務器)還需要知道的關於與之通信的對方機器的兩件事情:IP地址或者端口。IP地址是一個32位的數據值,為了人們好記,一般用圓點分開的4組數字的形式來表示,比如:64.41.64.172。端口是一個16位的數據值,通常被簡單地表示為一個小於65536的數字。大多數情況下,該值介於10到100的范圍內。一個IP地址獲取送到某台機器的一個數據包,而一個端口讓機器決定將該數據包交給哪個進程/服務(如果有的話)。這種解釋略顯簡單,但基本思路是正確的。
    上面的描述幾乎都是正確的,但它也遺漏了一些東西。大多數時候,當人們考慮Internet主機(對等方)時,我們都不會記憶諸如64.41.64.172這樣的數字,而是記憶諸如gnosis.cx這樣的名稱。為了找到與某個特定主機名稱相關聯的IP地址,一般都使用域名服務器(DNS),但是有時會首先使用本地查找(經常是通過/etc/hosts的內容)。對於本教程,我們將一般地假設有一個IP地址可用,不過下面討論編寫名稱查找代碼。

    (3)主機名稱解析
    命令行實用程序nslookup可以被用來根據符號名稱查找主機IP地址。實際上,許多常見的實用程序,比如ping或者網絡配置工具,也會順便做同樣的事情。但是以編程方式做這樣的事情很簡單。

======================TCP/IP======================
應用層: 它只負責產生相應格式的數據 ssh ftp nfs cifs dns http smtp pop3
-----------------------------------
傳輸層: 定義數據傳輸的兩種模式:
TCP(傳輸控制協議:面向連接,可靠的,效率相對不高)
UDP(用戶數據報協議:非面向連接,不可靠的,但效率高)
-----------------------------------
網絡層: 連接不同的網絡如以太網、令牌環網
IP (路由,分片) 、ICMP、 IGMP
ARP ( 地址解析協議,作用是將IP解析成MAC )
-----------------------------------
數據鏈路層: 以太網傳輸
-----------------------------------
物理層: 主要任務是規定各種傳輸介質和接口與傳輸信號相關的一些特性
-----------------------------------



TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標准的協議集,它是為廣域網(WANs)設計的。

TCP socket 由於在通向前需要建立連接,所以其模式較 UDP socket 負責些。

 

 

UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。如圖:




UDP Socket圖:

 

UDP socket server 端代碼在進行bind后,無需調用listen方法。

TCP/IP協議族包括運輸層、網絡層、鏈路層,
而socket所在位置如圖,Socket是應用層與TCP/IP協議族通信的中間軟件抽象層。


 

Socket是什么

 

socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現,socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉).
說白了Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

 

注意:其實socket也沒有層的概念,它只是一個facade設計模式的應用,讓編程變的更簡單。是一個軟件抽象層。在網絡編程中,我們大量用的都是通過socket實現的。

Socket是網絡編程的一個抽象概念。通常我們用一個Socket表示“打開了一個網絡鏈接”,而打開一個Socket需要知道目標計算機的IP地址和端口號,再指定協議類型即可。

http://images2015.cnblogs.com/blog/720333/201611/720333-20161126215744846-706023541.png

TCP編程

Socket是網絡編程的一個抽象概念。通常我們用一個Socket表示“打開了一個網絡鏈接”,而打開一個Socket需要知道目標計算機的IP地址和端口號,再指定協議類型即可。
TCP連接簡圖: 三次握手,數據傳輸,四次揮手

socket中TCP的三次握手建立連接詳解

我們知道tcp建立連接要進行“三次握手”,即交換三個分組。大致流程如下:

  • 客戶端向服務器發送一個SYN J
  • 服務器向客戶端響應一個SYN K,並對SYN J進行確認ACK J+1
  • 客戶端再想服務器發一個確認ACK K+1

只有就完了三次握手,但是這個三次握手發生在socket的那幾個函數中呢?請看下圖:

image

圖1、socket中發送的TCP三次握手

從圖中可以看出,當客戶端調用connect時,觸發了連接請求,向服務器發送了SYN J包,這時connect進入阻塞狀態;服務器監聽到連接請求,即收到SYN J包,調用accept函數接收請求向客戶端發送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到服務器的SYN K ,ACK J+1之后,這時connect返回,並對SYN K進行確認;服務器收到ACK K+1時,accept返回,至此三次握手完畢,連接建立。

總結:客戶端的connect在三次握手的第二個次返回,而服務器端的accept在三次握手的第三次返回。

5、socket中TCP的四次握手釋放連接詳解

上面介紹了socket中TCP的三次握手建立過程,及其涉及的socket函數。現在我們介紹socket中的四次握手釋放連接的過程,請看下圖:

image

圖2、socket中發送的TCP四次握手

圖示過程如下:

  • 某個應用進程首先調用 close主動關閉連接,這時TCP發送一個FIN M;
  • 另一端接收到FIN M之后,執行被動關閉,對這個FIN進行確認。它的接收也作為文件結束符傳遞給應用進程,因為FIN的接收意味着應用進程在相應的連接上再也接收不到額外數據;
  • 一段時間之后,接收到文件結束符的應用進程調用 close關閉它的socket。這導致它的TCP也發送一個FIN N;
  • 接收到這個FIN的源發送端TCP對它進行確認。

這樣每個方向上都有一個FIN和ACK。

Python3 網絡編程

Python 提供了兩個級別訪問的網絡服務。:

  • 低級別的網絡服務支持基本的 Socket,它提供了標准的 BSD Sockets API,可以訪問底層操作系統Socket接口的全部方法。
  • 高級別的網絡服務模塊 SocketServer, 它提供了服務器中心類,可以簡化網絡服務器的開發。

什么是 Socket?

Socket又稱"套接字",應用程序通常通過"套接字"向網絡發出請求或者應答網絡請求,使主機間或者一台計算機上的進程間可以通訊。

socket和file的區別:

  • file模塊是針對某個指定文件進行【打開】【讀寫】【關閉】
  • socket模塊是針對 服務器端 和 客戶端Socket 進行【打開】【讀寫】【關閉】

 

 服務器端先初始化Socket,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束。


socket()函數

Python 中,我們用 socket()函數來創建套接字,語法格式如下:

socket.socket([family[, type[, proto]]])

參數

  • family: 套接字家族可以使AF_UNIX或者AF_INET
  • type: 套接字類型可以根據是面向連接的還是非連接分為SOCK_STREAMSOCK_DGRAM
  • protocol: 一般不填默認為0.

簡單實例

服務端

我們使用 socket 模塊的 socket 函數來創建一個 socket 對象。socket 對象可以通過調用其他函數來設置一個 socket 服務。

現在我們可以通過調用 bind(hostname, port) 函數來指定服務的 port(端口)

接着,我們調用 socket 對象的 accept 方法。該方法等待客戶端的連接,並返回 connection 對象,表示已連接到客戶端。

完整代碼如下:

#!/usr/bin/python3
# 文件名:server.py

# 導入 socket、sys 模塊
import socket
import sys

# 創建 socket 對象
serversocket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM) 

# 獲取本地主機名
host = socket.gethostname()

port = 9999

# 綁定端口
serversocket.bind((host, port))

# 設置最大連接數,超過后排隊
serversocket.listen(5)

while True:
    # 建立客戶端連接
    clientsocket,addr = serversocket.accept()      

    print("連接地址: %s" % str(addr))
    
    msg='歡迎訪問python教程!'+ "\r\n"
    clientsocket.send(msg.encode('utf-8'))
    clientsocket.close()

 

客戶端

接下來我們寫一個簡單的客戶端實例連接到以上創建的服務。端口號為 12345。

socket.connect(hosname, port ) 方法打開一個 TCP 連接到主機為 hostname 端口為 port 的服務商。連接后我們就可以從服務端后期數據,記住,操作完成后需要關閉連接。

完整代碼如下:

#!/usr/bin/python3
# 文件名:client.py

# 導入 socket、sys 模塊
import socket
import sys

# 創建 socket 對象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

# 獲取本地主機名
host = socket.gethostname() 

# 設置端口好
port = 9999

# 連接服務,指定主機和端口
s.connect((host, port))

# 接收小於 1024 字節的數據
msg = s.recv(1024)

s.close()

print (msg.decode('utf-8'))

 先執行server端,然后打開client端就能看到結果

 

客戶端

大多數連接都是可靠的TCP連接。創建TCP連接時,主動發起連接的叫客戶端,被動響應連接的叫服務器。

舉個例子,當我們在瀏覽器中訪問新浪時,我們自己的計算機就是客戶端,瀏覽器會主動向新浪的服務器發起連接。如果一切順利,新浪的服務器接受了我們的連接,一個TCP連接就建立起來的,后面的通信就是發送網頁內容了。

所以,我們要創建一個基於TCP連接的Socket,可以這樣做:

# 導入socket庫:
import socket

# 創建一個socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接:
s.connect(('www.sina.com.cn', 80))

 

創建Socket時,AF_INET指定使用IPv4協議,如果要用更先進的IPv6,就指定為AF_INET6SOCK_STREAM指定使用面向流的TCP協議,這樣,一個Socket對象就創建成功,但是還沒有建立連接。

客戶端要主動發起TCP連接,必須知道服務器的IP地址和端口號。新浪網站的IP地址可以用域名www.sina.com.cn自動轉換到IP地址,但是怎么知道新浪服務器的端口號呢?

答案是作為服務器,提供什么樣的服務,端口號就必須固定下來。由於我們想要訪問網頁,因此新浪提供網頁服務的服務器必須把端口號固定在80端口,因為80端口是Web服務的標准端口。其他服務都有對應的標准端口號,例如SMTP服務是25端口,FTP服務是21端口,等等。端口號小於1024的是Internet標准服務的端口,端口號大於1024的,可以任意使用。

因此,我們連接新浪服務器的代碼如下:

s.connect(('www.sina.com.cn', 80))

 

注意參數是一個tuple,包含地址和端口號。

建立TCP連接后,我們就可以向新浪服務器發送請求,要求返回首頁的內容:

# 發送數據:
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')

 

TCP連接創建的是雙向通道,雙方都可以同時給對方發數據。但是誰先發誰后發,怎么協調,要根據具體的協議來決定。例如,HTTP協議規定客戶端必須先發請求給服務器,服務器收到后才發數據給客戶端。

發送的文本格式必須符合HTTP標准,如果格式沒問題,接下來就可以接收新浪服務器返回的數據了:

接收數據時,調用recv(max)方法,一次最多接收指定的字節數,因此,在一個while循環中反復接收,直到recv()返回空數據,表示接收完畢,退出循環。

當我們接收完數據后,調用close()方法關閉Socket,這樣,一次完整的網絡通信就結束了:

# 關閉連接:
s.close()

 

接收到的數據包括HTTP頭和網頁本身,我們只需要把HTTP頭和網頁分離一下,把HTTP頭打印出來,網頁內容保存到文件:

header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的數據寫入文件:
with open('sina.html', 'wb') as f:
    f.write(html)

 

現在,只需要在瀏覽器中打開這個sina.html文件,就可以看到新浪的首頁了。

服務器

和客戶端編程相比,服務器編程就要復雜一些。

服務器進程首先要綁定一個端口並監聽來自其他客戶端的連接。如果某個客戶端連接過來了,服務器就與該客戶端建立Socket連接,隨后的通信就靠這個Socket連接了。

所以,服務器會打開固定端口(比如80)監聽,每來一個客戶端連接,就創建該Socket連接。由於服務器會有大量來自客戶端的連接,所以,服務器要能夠區分一個Socket連接是和哪個客戶端綁定的。一個Socket依賴4項:服務器地址、服務器端口、客戶端地址、客戶端端口來唯一確定一個Socket。

但是服務器還需要同時響應多個客戶端的請求,所以,每個連接都需要一個新的進程或者新的線程來處理,否則,服務器一次就只能服務一個客戶端了。

我們來編寫一個簡單的服務器程序,它接收客戶端連接,把客戶端發過來的字符串加上Hello再發回去。

首先,創建一個基於IPv4和TCP協議的Socket:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

 

然后,我們要綁定監聽的地址和端口。服務器可能有多塊網卡,可以綁定到某一塊網卡的IP地址上,也可以用0.0.0.0綁定到所有的網絡地址,還可以用127.0.0.1綁定到本機地址。127.0.0.1是一個特殊的IP地址,表示本機地址,如果綁定到這個地址,客戶端必須同時在本機運行才能連接,也就是說,外部的計算機無法連接進來。

端口號需要預先指定。因為我們寫的這個服務不是標准服務,所以用9999這個端口號。請注意,小於1024的端口號必須要有管理員權限才能綁定:

# 監聽端口:
s.bind(('127.0.0.1', 9999))

 

緊接着,調用listen()方法開始監聽端口,傳入的參數指定等待連接的最大數量:

s.listen(5)
print('Waiting for connection...')

 

接下來,服務器程序通過一個永久循環來接受來自客戶端的連接,accept()會等待並返回一個客戶端的連接:

while True:
    # 接受一個新連接:
    sock, addr = s.accept()
    # 創建新線程來處理TCP連接:
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.start()

 

每個連接都必須創建新線程(或進程)來處理,否則,單線程在處理連接的過程中,無法接受其他客戶端的連接:

def tcplink(sock, addr):
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Welcome!')
    while True:
        data = sock.recv(1024)
        time.sleep(1)
        if not data or data.decode('utf-8') == 'exit':
            break
        sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
    sock.close()
    print('Connection from %s:%s closed.' % addr)

 

連接建立后,服務器首先發一條歡迎消息,然后等待客戶端數據,並加上Hello再發送給客戶端。如果客戶端發送了exit字符串,就直接關閉連接。

要測試這個服務器程序,我們還需要編寫一個客戶端程序:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接:
s.connect(('127.0.0.1', 9999))
# 接收歡迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
    # 發送數據:
    s.send(data)
    print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

 

我們需要打開兩個命令行窗口,一個運行服務器程序,另一個運行客戶端程序,就可以看到效果了:

UDP編程

TCP是建立可靠連接,並且通信雙方都可以以流的形式發送數據。相對TCP,UDP則是面向無連接的協議。

使用UDP協議時,不需要建立連接,只需要知道對方的IP地址和端口號,就可以直接發數據包。但是,能不能到達就不知道了。

雖然用UDP傳輸數據不可靠,但它的優點是和TCP比,速度快,對於不要求可靠到達的數據,就可以使用UDP協議。

我們來看看如何通過UDP協議傳輸數據。和TCP類似,使用UDP的通信雙方也分為客戶端和服務器。服務器首先需要綁定端口:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定端口:
s.bind(('127.0.0.1', 9999))

 

創建Socket時,SOCK_DGRAM指定了這個Socket的類型是UDP。綁定端口和TCP一樣,但是不需要調用listen()方法,而是直接接收來自任何客戶端的數據:

print 'Bind UDP on 9999...'
while True:
    # 接收數據:
    data, addr = s.recvfrom(1024)
    print 'Received from %s:%s.' % addr
    s.sendto('Hello, %s!' % data, addr)

 

recvfrom()方法返回數據和客戶端的地址與端口,這樣,服務器收到數據后,直接調用sendto()就可以把數據用UDP發給客戶端。

注意這里省掉了多線程,因為這個例子很簡單。

客戶端使用UDP時,首先仍然創建基於UDP的Socket,然后,不需要調用connect(),直接通過sendto()給服務器發數據:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in ['Michael', 'Tracy', 'Sarah']:
    # 發送數據:
    s.sendto(data, ('127.0.0.1', 9999))
    # 接收數據:
    print s.recv(1024)
s.close()

 

從服務器接收數據仍然調用recv()方法。

小結

UDP的使用與TCP類似,但是不需要建立連接。此外,服務器綁定UDP端口和TCP端口互不沖突,也就是說,UDP的9999端口與TCP的9999端口可以各自綁定。

 

Python 提供了兩個級別訪問的網絡服務。:

  • 低級別的網絡服務支持基本的 Socket,它提供了標准的 BSD Sockets API,可以訪問底層操作系統Socket接口的全部方法。
  • 高級別的網絡服務模塊 SocketServer, 它提供了服務器中心類,可以簡化網絡服務器的開發。

 

Socket 對象(內建)方法

函數 描述
服務器端套接字
s.bind() 綁定地址(host,port)到套接字, 在AF_INET下,以元組(host,port)的形式表示地址。
s.listen() 開始TCP監聽。backlog指定在拒絕連接之前,操作系統可以掛起的最大連接數量。該值至少為1,大部分應用程序設為5就可以了。
s.accept() 被動接受TCP客戶端連接,(阻塞式)等待連接的到來
客戶端套接字
s.connect() 主動初始化TCP服務器連接,。一般address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
公共用途的套接字函數
s.recv() 接收TCP數據,數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其他信息,通常可以忽略。
s.send() 發送TCP數據,將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。
s.sendall() 完整發送TCP數據,完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。
s.recvform() 接收UDP數據,與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
s.sendto() 發送UDP數據,將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。
s.close() 關閉套接字
s.getpeername() 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一個元組(ipaddr,port)
s.setsockopt(level,optname,value) 設置給定套接字選項的值。
s.getsockopt(level,optname[.buflen]) 返回套接字選項的值。
s.settimeout(timeout) 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作(如connect())
s.gettimeout() 返回當前超時期的值,單位是秒,如果沒有設置超時期,則返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag為0,則將套接字設為非阻塞模式,否則將套接字設為阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那么將引起socket.error異常。
s.makefile() 創建一個與該套接字相關連的文件

 

server端:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

ip_port = ('127.0.0.1',9999)

sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)

while True:
    print 'server waiting...'
    conn,addr = sk.accept()

    client_data = conn.recv(1024)
    print client_data
    conn.sendall('不要回答,不要回答,不要回答')

    conn.close()

socket server

 client:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket()
sk.connect(ip_port)

sk.sendall('請求占領地球')

server_reply = sk.recv(1024)
print server_reply

sk.close()

socket client

 WEB服務應用:

#!/usr/bin/env python
#coding:utf-8
import socket
 
def handle_request(client):
    buf = client.recv(1024)
    client.send("HTTP/1.1 200 OK\r\n\r\n")
    client.send("Hello, World")
 
def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost',8080))
    sock.listen(5)
 
    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()
 
if __name__ == '__main__':
  main()

 

 

Socket 對象(內建)方法:

服務器端

s.bind()     
# 綁定地址(host,port)到套接字, 在AF_INET下,以元組(host,port)的形式表示地址。
s.listen()     
# 開始TCP監聽。backlog指定在拒絕連接之前,操作系統可以掛起的最大連接數量。該值至少為1,大部分應用程序設為5就可以了。
s.accept()     
# 被動接受TCP客戶端連接,(阻塞式)等待連接的到來


客戶端

s.connect()     
# 主動初始化TCP服務器連接,。一般address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
s.connect_ex()     
# connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常


公共用途的函數

s.recv()     
# 接收TCP數據,數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其他信息,通常可以忽略。
s.send()     
# 發送TCP數據,將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。
s.sendall()     
# 完整發送TCP數據,完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。
s.close()
# 關閉套接字


s.recvform()     
# 接收UDP數據,與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
s.sendto()     
# 發送UDP數據,將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。
s.getpeername()     
# 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。
s.getsockname()     
# 返回套接字自己的地址。通常是一個元組(ipaddr,port)
s.setsockopt(level,optname,value)     
# 設置給定套接字選項的值。
s.getsockopt(level,optname[.buflen])      
# 返回套接字選項的值。
s.settimeout(timeout)     
# 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作(如connect())
s.gettimeout()     
# 返回當前超時期的值,單位是秒,如果沒有設置超時期,則返回None。
s.fileno()     
# 返回套接字的文件描述符。
s.setblocking(flag)     
# 如果flag為0,則將套接字設為非阻塞模式,否則將套接字設為阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那么將引起socket.error異常。
s.makefile()     
# 創建一個與該套接字相關連的文件

 

更多功能

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

參數一:地址簇

  socket.AF_INET IPv4(默認)
  socket.AF_INET6 IPv6

  socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信

參數二:類型

  socket.SOCK_STREAM  流式socket , for TCP (默認)
  socket.SOCK_DGRAM   數據報式socket , for UDP

  socket.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 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議

UDP Demo

import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data = sk.recv(1024)
    print data




import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = raw_input('數據:').strip()
    if inp == 'exit':
        break
    sk.sendto(inp,ip_port)

sk.close()

UDP Demo

 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()

  套接字的文件描述符

# 服務端
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data,(host,port) = sk.recvfrom(1024)
    print(data,host,port)
    sk.sendto(bytes('ok', encoding='utf-8'), (host,port))


#客戶端
import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = input('數據:').strip()
    if inp == 'exit':
        break
    sk.sendto(bytes(inp, encoding='utf-8'),ip_port)
    data = sk.recvfrom(1024)
    print(data)

sk.close()

UDP

 實例:智能機器人

服務端:

#!/usr/bin/env python
# -*- coding:utf-8 -*-


import socket

ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)

while True:
    conn,address =  sk.accept()
    conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.')
    Flag = True
    while Flag:
        data = conn.recv(1024)
        if data == 'exit':
            Flag = False
        elif data == '0':
            conn.sendall('通過可能會被錄音.balabala一大推')
        else:
            conn.sendall('請重新輸入.')
    conn.close()

服務端

 

客戶端:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket


ip_port = ('127.0.0.1',8005)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
    data = sk.recv(1024)
    print 'receive:',data
    inp = raw_input('please input:')
    sk.sendall(inp)
    if inp == 'exit':
        break

sk.close()

客戶端

 

IO多路復用

I/O多路復用指:通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。

Linux

Linux中的 select,poll,epoll 都是IO多路復用的機制。

select
 
select最早於1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組,當select()返回后,該數組中就緒的文件描述符便會被內核修改標志位,使得進程可以獲得這些文件描述符從而進行后續的讀寫操作。
select目前幾乎在所有的平台上支持,其良好跨平台支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。
select的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。
另外,select()所維護的存儲大量文件描述符的數據結構,隨着文件描述符數量的增大,其復制的開銷也線性增長。同時,由於網絡響應時間的延遲使得大量TCP連接處於非活躍狀態,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。
 
poll
 
poll在1986年誕生於System V Release 3,它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制。
poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增加而線性增大。
另外,select()和poll()將就緒的文件描述符告訴進程后,如果進程沒有對其進行IO操作,那么下次調用select()和poll()的時候將再次報告這些文件描述符,所以它們一般不會丟失就緒的消息,這種方式稱為水平觸發(Level Triggered)。
 
epoll
 
直到Linux2.6才出現了由內核直接支持的實現方法,那就是epoll,它幾乎具備了之前所說的一切優點,被公認為Linux2.6下性能最好的多路I/O就緒通知方法。
epoll可以同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有采取行動,那么它將不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當復雜。
epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這里也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時復制的開銷。
另一個本質的改進在於epoll采用基於事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。 

 

Python

Python中有一個select模塊,其中提供了:select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路復用。

Windows Python:
    提供: select
Mac Python:
    提供: select
Linux Python:
    提供: select、poll、epoll

 

注意:網絡操作、文件操作、終端操作等均屬於IO操作,對於windows只支持Socket操作,其他系統支持其他IO操作,但是無法檢測 普通文件操作 自動上次讀取是否已經變化。

對於select方法:

 

句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超時時間)
 
參數: 可接受四個參數(前三個必須)
返回值:三個列表
 
select方法用來監視文件句柄,如果句柄發生變化,則獲取該句柄。
1、當 參數1 序列中的句柄發生可讀時(accetp和read),則獲取發生變化的句柄並添加到 返回值1 序列中
2、當 參數2 序列中含有句柄時,則將該序列中所有的句柄添加到 返回值2 序列中
3、當 參數3 序列中的句柄發生錯誤時,則將該發生錯誤的句柄添加到 返回值3 序列中
4、當 超時時間 未設置,則select會一直阻塞,直到監聽的句柄發生變化
   當 超時時間 = 1時,那么如果監聽的句柄均無任何變化,則select會阻塞 1 秒,之后返回三個空列表,如果監聽的句柄有變化,則直接執行。

 

 利用select監聽終端操作實例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import select
import threading
import sys

while True:
    readable, writeable, error = select.select([sys.stdin,],[],[],1)
    if sys.stdin in readable:
        print 'select get stdin',sys.stdin.readline()

利用select監聽終端操作實例

 利用select實現偽同時處理多個socket客戶端請求:服務端

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
import select

sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk1.bind(('127.0.0.1',8002))
sk1.listen(5)
sk1.setblocking(0)

inputs = [sk1,]

while True:
    readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 1)
    for r in readable_list:
        # 當客戶端第一次連接服務端時
        if sk1 == r:
            print 'accept'
            request, address = r.accept()
            request.setblocking(0)
            inputs.append(request)
        # 當客戶端連接上服務端之后,再次發送數據時
        else:
            received = r.recv(1024)
            # 當正常接收客戶端發送的數據時
            if received:
                print 'received data:', received
            # 當客戶端關閉程序時
            else:
                inputs.remove(r)

sk1.close()

利用select實現偽同時處理多個Socket客戶端請求:服務端

 利用select實現偽同時處理多個socket客戶端請求:客戶端

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket

ip_port = ('127.0.0.1',8002)
sk = socket.socket()
sk.connect(ip_port)

while True:
    inp = raw_input('please input:')
    sk.sendall(inp)
sk.close()

利用select實現偽同時處理多個Socket客戶端請求:客戶端

 此處的Socket服務端相比與原生的Socket,他支持當某一個請求不再發送數據時,服務器端不會等待而是可以去處理其他請求的數據。但是,如果每個請求的耗時比較長時,select版本的服務器端也無法完成同時操作。

基於select實現socket服務端

#!/usr/bin/env python
#coding:utf8

'''
 服務器的實現 采用select的方式
'''

import select
import socket
import sys
import Queue

#創建套接字並設置該套接字為非阻塞模式

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setblocking(0)

#綁定套接字
server_address = ('localhost',10000)
print >>sys.stderr,'starting up on %s port %s'% server_address
server.bind(server_address)

#將該socket變成服務模式
#backlog等於5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的連接個數最大為5
#這個值不能無限大,因為要在內核中維護連接隊列

server.listen(5)

#初始化讀取數據的監聽列表,最開始時希望從server這個套接字上讀取數據
inputs = [server]

#初始化寫入數據的監聽列表,最開始並沒有客戶端連接進來,所以列表為空

outputs = []

#要發往客戶端的數據
message_queues = {}
while inputs:
    print >>sys.stderr,'waiting for the next event'
    #調用select監聽所有監聽列表中的套接字,並將准備好的套接字加入到對應的列表中
    readable,writable,exceptional = select.select(inputs,outputs,inputs)#列表中的socket 套接字  如果是文件呢? 
    #監控文件句柄有某一處發生了變化 可寫 可讀  異常屬於Linux中的網絡編程 
    #屬於同步I/O操作,屬於I/O復用模型的一種
    #rlist--等待到准備好讀
    #wlist--等待到准備好寫
    #xlist--等待到一種異常
    #處理可讀取的套接字

    '''
        如果server這個套接字可讀,則說明有新鏈接到來
        此時在server套接字上調用accept,生成一個與客戶端通訊的套接字
        並將與客戶端通訊的套接字加入inputs列表,下一次可以通過select檢查連接是否可讀
        然后在發往客戶端的緩沖中加入一項,鍵名為:與客戶端通訊的套接字,鍵值為空隊列
        select系統調用是用來讓我們的程序監視多個文件句柄(file descrīptor)的狀態變化的。程序會停在select這里等待,
        直到被監視的文件句柄有某一個或多個發生了狀態改變
        '''

    '''
        若可讀的套接字不是server套接字,有兩種情況:一種是有數據到來,另一種是鏈接斷開
        如果有數據到來,先接收數據,然后將收到的數據填入往客戶端的緩存區中的對應位置,最后
        將於客戶端通訊的套接字加入到寫數據的監聽列表:
        如果套接字可讀.但沒有接收到數據,則說明客戶端已經斷開。這時需要關閉與客戶端連接的套接字
        進行資源清理
        '''
        
    for s in readable: 
        if s is server:
            connection,client_address = s.accept()
            print >>sys.stderr,'connection from',client_address
            connection.setblocking(0)#設置非阻塞
            inputs.append(connection)
            message_queues[connection] = Queue.Queue()
        else:
            data = s.recv(1024)
            if data:
                print >>sys.stderr,'received "%s" from %s'% \
                (data,s.getpeername())
                message_queues[s].put(data)
                if s not in outputs:
                    outputs.append(s)
            else:
                print >>sys.stderr,'closing',client_address
                if s in outputs:
                    outputs.remove(s)
                inputs.remove(s)
                s.close()
                del message_queues[s]
                    
    #處理可寫的套接字
    '''
        在發送緩沖區中取出響應的數據,發往客戶端。
        如果沒有數據需要寫,則將套接字從發送隊列中移除,select中不再監視
        '''

    for s in writable:
        try:
            next_msg = message_queues[s].get_nowait()

        except Queue.Empty:
            print >>sys.stderr,'  ',s,getpeername(),'queue empty'
            outputs.remove(s)
        else:
            print >>sys.stderr,'sending "%s" to %s'% \
            (next_msg,s.getpeername())
            s.send(next_msg)



    #處理異常情況

    for s in exceptional:
        for s in exceptional:
            print >>sys.stderr,'exception condition on',s.getpeername()
            inputs.remove(s)
            if s in outputs:
                outputs.remove(s)
            s.close()
            del message_queues[s]

基於select實現socket服務端

 

SocketServer模塊

SocketServer內部使用 IO多路復用 以及 “多線程” 和 “多進程” ,從而實現並發處理多個客戶端請求的Socket服務端。即:每個客戶端請求連接到服務器時,Socket服務端都會在服務器是創建一個“線程”或者“進程” 專門負責處理當前客戶端的所有請求。

ThreadingTCPServer

ThreadingTCPServer實現的Soket服務器內部會為每個client創建一個 “線程”,該線程用來和客戶端進行交互。

1、ThreadingTCPServer基礎

使用ThreadingTCPServer:

  • 創建一個繼承自 SocketServer.BaseRequestHandler 的類
  • 類中必須定義一個名稱為 handle 的方法
  • 啟動ThreadingTCPServer

socketserver實現服務器

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import SocketServer

class MyServer(SocketServer.BaseRequestHandler):

    def handle(self):
        # print self.request,self.client_address,self.server
        conn = self.request
        conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.')
        Flag = True
        while Flag:
            data = conn.recv(1024)
            if data == 'exit':
                Flag = False
            elif data == '0':
                conn.sendall('通過可能會被錄音.balabala一大推')
            else:
                conn.sendall('請重新輸入.')


if __name__ == '__main__':
    server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
    server.serve_forever()

SocketServer實現服務器

 客戶端:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket


ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
    data = sk.recv(1024)
    print 'receive:',data
    inp = raw_input('please input:')
    sk.sendall(inp)
    if inp == 'exit':
        break

sk.close()

客戶端

 ThreadingTCPServer源碼剖析

ThreadingTCPServer的類圖關系如下:

 

內部調用流程為:

  • 啟動服務端程序
  • 執行 TCPServer.__init__ 方法,創建服務端Socket對象並綁定 IP 和 端口
  • 執行 BaseServer.__init__ 方法,將自定義的繼承自SocketServer.BaseRequestHandler 的類 MyRequestHandle賦值給 self.RequestHandlerClass
  • 執行 BaseServer.server_forever 方法,While 循環一直監聽是否有客戶端請求到達 ...
  • 當客戶端連接到達服務器
  • 執行 ThreadingMixIn.process_request 方法,創建一個 “線程” 用來處理請求
  • 執行 ThreadingMixIn.process_request_thread 方法
  • 執行 BaseServer.finish_request 方法,執行 self.RequestHandlerClass()  即:執行 自定義 MyRequestHandler 的構造方法(自動調用基類BaseRequestHandler的構造方法,在該構造方法中又會調用 MyRequestHandler的handle方法)

ThreadingTCPServer相關源碼:

BaseServer

class BaseServer:

    """Base class for server classes.

    Methods for the caller:

    - __init__(server_address, RequestHandlerClass)
    - serve_forever(poll_interval=0.5)
    - shutdown()
    - handle_request()  # if you do not use serve_forever()
    - fileno() -> int   # for select()

    Methods that may be overridden:

    - server_bind()
    - server_activate()
    - get_request() -> request, client_address
    - handle_timeout()
    - verify_request(request, client_address)
    - server_close()
    - process_request(request, client_address)
    - shutdown_request(request)
    - close_request(request)
    - handle_error()

    Methods for derived classes:

    - finish_request(request, client_address)

    Class variables that may be overridden by derived classes or
    instances:

    - timeout
    - address_family
    - socket_type
    - allow_reuse_address

    Instance variables:

    - RequestHandlerClass
    - socket

    """

    timeout = None

    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False

    def server_activate(self):
        """Called by constructor to activate the server.

        May be overridden.

        """
        pass

    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()

    def shutdown(self):
        """Stops the serve_forever loop.

        Blocks until the loop has finished. This must be called while
        serve_forever() is running in another thread, or it will
        deadlock.
        """
        self.__shutdown_request = True
        self.__is_shut_down.wait()

    # The distinction between handling, getting, processing and
    # finishing a request is fairly arbitrary.  Remember:
    #
    # - handle_request() is the top-level call.  It calls
    #   select, get_request(), verify_request() and process_request()
    # - get_request() is different for stream or datagram sockets
    # - process_request() is the place that may fork a new process
    #   or create a new thread to finish the request
    # - finish_request() instantiates the request handler class;
    #   this constructor will handle the request all by itself

    def handle_request(self):
        """Handle one request, possibly blocking.

        Respects self.timeout.
        """
        # Support people who used socket.settimeout() to escape
        # handle_request before self.timeout was available.
        timeout = self.socket.gettimeout()
        if timeout is None:
            timeout = self.timeout
        elif self.timeout is not None:
            timeout = min(timeout, self.timeout)
        fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
        if not fd_sets[0]:
            self.handle_timeout()
            return
        self._handle_request_noblock()

    def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that select.select has returned that the socket is
        readable before this function was called, so there should be
        no risk of blocking in get_request().
        """
        try:
            request, client_address = self.get_request()
        except socket.error:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except:
                self.handle_error(request, client_address)
                self.shutdown_request(request)

    def handle_timeout(self):
        """Called if no new request arrives within self.timeout.

        Overridden by ForkingMixIn.
        """
        pass

    def verify_request(self, request, client_address):
        """Verify the request.  May be overridden.

        Return True if we should proceed with this request.

        """
        return True

    def process_request(self, request, client_address):
        """Call finish_request.

        Overridden by ForkingMixIn and ThreadingMixIn.

        """
        self.finish_request(request, client_address)
        self.shutdown_request(request)

    def server_close(self):
        """Called to clean-up the server.

        May be overridden.

        """
        pass

    def finish_request(self, request, client_address):
        """Finish one request by instantiating RequestHandlerClass."""
        self.RequestHandlerClass(request, client_address, self)

    def shutdown_request(self, request):
        """Called to shutdown and close an individual request."""
        self.close_request(request)

    def close_request(self, request):
        """Called to clean up an individual request."""
        pass

    def handle_error(self, request, client_address):
        """Handle an error gracefully.  May be overridden.

        The default is to print a traceback and continue.

        """
        print '-'*40
        print 'Exception happened during processing of request from',
        print client_address
        import traceback
        traceback.print_exc() # XXX But this goes to stderr!
        print '-'*40

BaseServer

 

Tcpserver

class TCPServer(BaseServer):

    """Base class for various socket-based server classes.

    Defaults to synchronous IP stream (i.e., TCP).

    Methods for the caller:

    - __init__(server_address, RequestHandlerClass, bind_and_activate=True)
    - serve_forever(poll_interval=0.5)
    - shutdown()
    - handle_request()  # if you don't use serve_forever()
    - fileno() -> int   # for select()

    Methods that may be overridden:

    - server_bind()
    - server_activate()
    - get_request() -> request, client_address
    - handle_timeout()
    - verify_request(request, client_address)
    - process_request(request, client_address)
    - shutdown_request(request)
    - close_request(request)
    - handle_error()

    Methods for derived classes:

    - finish_request(request, client_address)

    Class variables that may be overridden by derived classes or
    instances:

    - timeout
    - address_family
    - socket_type
    - request_queue_size (only for stream sockets)
    - allow_reuse_address

    Instance variables:

    - server_address
    - RequestHandlerClass
    - socket

    """

    address_family = socket.AF_INET

    socket_type = socket.SOCK_STREAM

    request_queue_size = 5

    allow_reuse_address = False

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise

    def server_bind(self):
        """Called by constructor to bind the socket.

        May be overridden.

        """
        if self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()

    def server_activate(self):
        """Called by constructor to activate the server.

        May be overridden.

        """
        self.socket.listen(self.request_queue_size)

    def server_close(self):
        """Called to clean-up the server.

        May be overridden.

        """
        self.socket.close()

    def fileno(self):
        """Return socket file number.

        Interface required by select().

        """
        return self.socket.fileno()

    def get_request(self):
        """Get the request and client address from the socket.

        May be overridden.

        """
        return self.socket.accept()

    def shutdown_request(self, request):
        """Called to shutdown and close an individual request."""
        try:
            #explicitly shutdown.  socket.close() merely releases
            #the socket and waits for GC to perform the actual close.
            request.shutdown(socket.SHUT_WR)
        except socket.error:
            pass #some platforms may raise ENOTCONN here
        self.close_request(request)

    def close_request(self, request):
        """Called to clean up an individual request."""
        request.close()

TCPServer

 

ThreadingMixIn

class ThreadingMixIn:
    """Mix-in class to handle each request in a new thread."""

    # Decides how threads will act upon termination of the
    # main process
    daemon_threads = False

    def process_request_thread(self, request, client_address):
        """Same as in BaseServer but as a thread.

        In addition, exception handling is done here.

        """
        try:
            self.finish_request(request, client_address)
            self.shutdown_request(request)
        except:
            self.handle_error(request, client_address)
            self.shutdown_request(request)

    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()

ThreadingMixIn

 

ThreadingTCPServer

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

 

Socket套接字的概念


Socket(翻譯為套接字, 我覺得很挫),是操作系統內核中的一個數據結構,它是網絡中的節點進行相互通信的門戶。它是網絡進程的ID。網絡通信,歸根到底還是進程間的通信(不同計算機上的進程間通信, 又稱進程間通信, IP協議進行的主要是端到端通信)。在網絡中,每一個節點(計算機或路由)都有一個網絡地址,也就是IP地址。兩個進程通信時,首先要確定各自所在的網絡節點的網絡地址。但是,網絡地址只能確定進程所在的計算機,而一台計算機上很可能同時運行着多個進程,所以僅憑網絡地址還不能確定到底是和網絡中的哪一個進程進行通信,因此套接口中還需要包括其他的信息,也就是端口號(PORT)。在一台計算機中,一個端口號一次只能分配給一個進程,也就是說,在一台計算機中,端口號和進程之間是一一對應關系。
所以,使用端口號和網絡地址的組合可以唯一的確定整個網絡中的一個網絡進程.

端口號的范圍從0~65535,一類是由互聯網指派名字和號碼公司ICANN負責分配給一些常用的應用程序固定使用的“周知的端口”,其值一般為0~1023, 用戶自定義端口號一般大於等於1024, 我比較喜歡用8888

每一個socket都用一個半相關描述{協議、本地地址、本地端口}來表示;一個完整的套接字則用一個相關描述{協議、本地地址、本地端口、遠程地址、遠程端口}來表示。socket也有一個類似於打開文件的函數調用,該函數返回一個整型的socket描述符,隨后的連接建立、數據傳輸等操作都是通過socket來實現的。

流程描述:

# 流程描述:
# 
# 1 服務器根據地址類型(ipv4,ipv6)、socket類型、協議創建socket
# 
# 2 服務器為socket綁定ip地址和端口號
# 
# 3 服務器socket監聽端口號請求,隨時准備接收客戶端發來的連接,這時候服務器的socket並沒有被打開
# 
# 4 客戶端創建socket
# 
# 5 客戶端打開socket,根據服務器ip地址和端口號試圖連接服務器socket
# 
# 6 服務器socket接收到客戶端socket請求,被動打開,開始接收客戶端請求,直到客戶端返回連接信息。這時候socket進入阻塞狀態,
#   所謂阻塞即accept()方法一直等到客戶端返回連接信息后才返回,開始接收下一個客戶端連接請求
# 
# 7 客戶端連接成功,向服務器發送連接狀態信息
# 
# 8 服務器accept方法返回,連接成功
# 
# 9 客戶端向socket寫入信息(或服務端向socket寫入信息)
# 
# 10 服務器讀取信息(客戶端讀取信息)
# 
# 11 客戶端關閉
# 
# 12 服務器端關閉

 

1.1. Socket類型

 

Socket 類型

套接字格式:

socket(family,type[,protocal]) 使用給定的地址族、套接字類型、協議編號(默認為0)來創建套接字。

 

socket類型

描述

socket.AF_UNIX

只能夠用於單一的Unix系統進程間通信

socket.AF_INET

服務器之間網絡通信

socket.AF_INET6

IPv6

socket.SOCK_STREAM

流式socket , for TCP

socket.SOCK_DGRAM

數據報式socket , for UDP

socket.SOCK_RAW

原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。

socket.SOCK_SEQPACKET

可靠的連續數據包服務

創建TCP Socket

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

創建UDP Socket

s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

 

socket類型在Liunx和Python是一樣的, 只是Python中的類型都定義在socket模塊中, 調用方式socket.SOCK_XXXX

  • 流式socket(SOCK_STREAM) 用於TCP通信

流式套接字提供可靠的、面向連接的通信流;它使用TCP協議,從而保證了數據傳輸的正確性和順序性

  • 數據報socket(SOCK_DGRAM) 用於UDP通信

數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,並且不保證是可靠、無差錯的。它使用數據報協議UDP

  • 原始socket(SOCK_RAW) 用於新的網絡協議實現的測試等

原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以, 其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。

Socket編程


TCP通信

TCP通信的基本步驟如下:
服務端:socket---bind---listen---while(True){---accept---recv---send----}---close
客戶端:socket----------------------------------connect---send---recv-------close

TCP
TCP

socket函數
使用給定的地址族、套接字類型、協議編號(默認為0)來創建套接字

#Linux
int socket(int domain, int type, int protocol);
domain:AF_INET:Ipv4網絡協議 AF_INET6:IPv6網絡協議
type : tcp:SOCK_STREAM   udp:SOCK_DGRAM
protocol : 指定socket所使用的傳輸協議編號。通常為0.
返回值:成功則返回套接口描述符,失敗返回-1。

#python
socket.socket([family[, type[, proto]]])
family : AF_INET (默認ipv4), AF_INET6(ipv6) or AF_UNIX(Unix系統進程間通信). 
type : SOCK_STREAM (TCP), SOCK_DGRAM(UDP) . 
protocol : 一般為0或者默認

  如果socket創建失敗會拋出一個socket.error異常

服務器端函數

bind函數
將套接字綁定到地址, python下,以元組(host,port)的形式表示地址, Linux下使用sockaddr_in結構體指針

#Linux
int bind(int sockfd, struct sockaddr * my_addr, int addrlen);
sockfd : 前面socket()的返回值
my_addr : 結構體指針變量
#####
struct sockaddr_in  //常用的結構體
{
unsigned short int sin_family;  //即為sa_family AF_INET
uint16_t sin_port;  //為使用的port編號
struct in_addr sin_addr;  //為IP地址
unsigned char sin_zero[8];  //未使用
};
struct in_addr
{
uint32_t s_addr;
};
####
addrlen : sockaddr的結構體長度。通常是計算sizeof(struct sockaddr);
返回值:成功則返回0,失敗返回-1



#python
s.bind(address)
s為socket.socket()返回的套接字對象
address為元組(host,port)
host: ip地址, 為一個字符串
post: 自定義主機號, 為整型

listen函數
使服務器的這個端口和IP處於監聽狀態,等待網絡中某一客戶機的連接請求。如果客戶端有連接請求,端口就會接受這個連接

#Linux
int listen(int sockfd,int backlog);
sockfd : 為前面socket的返回值.
backlog : 指定同時能處理的最大連接要求,通常為10或者5。最大值可設至128
返回值:成功則返回0,失敗返回-1

#python
s.listen(backlog)
s為socket.socket()返回的套接字對象
backlog : 操作系統可以掛起的最大連接數量。該值至少為1,大部分應用程序設為5就可以了

accept函數
接受遠程計算機的連接請求,建立起與客戶機之間的通信連接。服務器處於監聽狀態時,如果某時刻獲得客戶機的連接請求,此時並不是立即處理這個請求,而是將這個請求放在等待隊列中,當系統空閑時再處理客戶機的連接請求。

#Linux
int accept(int s,struct sockaddr * addr,int * addrlen);
sockfd : 為前面socket的返回值.
addr : 為結構體指針變量,和bind的結構體是同種類型的,系統會把遠程主機的信息(遠程主機的地址和端口號信息)保存到這個指針所指的結構體中。
addrlen : 表示結構體的長度,為整型指針 
返回值:成功則返回新的socket處理代碼new_fd,失敗返回-1

#python
s.accept()
s為socket.socket()返回的套接字對象
返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址

客戶端函數

connect函數
用來請求連接遠程服務器

#Linux
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
sockfd : 為前面socket的返回值.
serv_addr : 為結構體指針變量,存儲着遠程服務器的IP與端口號信息
addrlen : 表示結構體變量的長度
返回值:成功則返回0,失敗返回-1

#python
s.connect(address)
s為socket.socket()返回的套接字對象
address : 格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤

通用函數

接收遠端主機傳來的數據

recv函數

#Linux
int recv(int sockfd,void *buf,int len,unsigned int flags);
sockfd : 為前面accept的返回值.也就是新的套接字。
buf : 表示緩沖區
len : 表示緩沖區的長度
flags : 通常為0
返回值:成功則返回實際接收到的字符數,可能會少於你所指定的接收長度。失敗返回-1


#python
s.recv(bufsize[,flag])
s為socket.socket()返回的套接字對象
bufsize : 指定要接收的數據大小
flag : 提供有關消息的其他信息,通常可以忽略
返回值為數據以字符串形式

send函數
發送數據給指定的遠端主機

#Linux
int send(int s,const void * msg,int len,unsigned int flags);
sockfd : 為前面socket的返回值.
msg : 一般為常量字符串
len : 表示長度
flags : 通常為0
返回值:成功則返回實際傳送出去的字符數,可能會少於你所指定的發送長度。失敗返回-1

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

close函數
關閉套接字

#Linux
int close(int fd);
fd : 為前面的sockfd
返回值:若文件順利關閉則返回0,發生錯誤時返回-1

#python
s.close()
s為socket.socket()返回的套接字對象

簡單的客戶端服務器TCP連接

一個簡單的回顯服務器和客戶端模型, 客戶端發出的數據, 服務器會回顯到客戶端的終端上(只是一個簡單的模型, 沒考慮錯誤處理等問題)

#服務器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket   #socket模塊
import commands   #執行系統命令模塊


BUF_SIZE = 1024  #設置緩沖區大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構成表示地址
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #生成一個新的socket對象
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  #設置地址復用
server.bind(server_addr)  #綁定地址
server.listen(5)  #監聽, 最大監聽數為5
while True:
    client, client_addr = server.accept()  #接收TCP連接, 並返回新的套接字和地址
    print 'Connected by', client_addr
    while True :
        data = client.recv(BUF_SIZE)  #從客戶端接收數據
        print data
        client.sendall(data)  #發送數據到客戶端
server.close()

#客戶端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

BUF_SIZE = 1024  #設置緩沖區的大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構成表示地址
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #返回新的socket對象
client.connect(server_addr)  #要連接的服務器地址
while True:
    data = raw_input("Please input some string > ")  
    client.sendall(data)  #發送數據到服務器
    data = client.recv(BUF_SIZE)  #從服務器端接收數據
    print data
client.close()

 

 帶錯誤處理的客戶端服務器TCP連接

在進行網絡編程時, 最好使用大量的錯誤處理, 能夠盡量的發現錯誤, 也能夠使代碼顯得更加嚴謹

#服務器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket   #socket模塊

BUF_SIZE = 1024  #設置緩沖區大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構成表示地址
try :
  server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #生成一個新的socket對象
except socket.error, msg :
    print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1]
    sys.exit()
print "Socket Created!"
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  #設置地址復用
try : 
    server.bind(server_addr)  #綁定地址
except socket.error, msg :
  print "Binding Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1]
  sys.exit()
print "Socket Bind!"
server.listen(5)  #監聽, 最大監聽數為5
print "Socket listening"
while True:
    client, client_addr = server.accept()  #接收TCP連接, 並返回新的套接字和地址, 阻塞函數
    print 'Connected by', client_addr
    while True :
        data = client.recv(BUF_SIZE)  #從客戶端接收數據
        print data
        client.sendall(data)  #發送數據到客戶端
server.close()

#客戶端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket


BUF_SIZE = 1024  #設置緩沖區的大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構成表示地址
try : 
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #返回新的socket對象
except socket.error, msg :
    print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1]
    sys.exit()
client.connect(server_addr)  #要連接的服務器地址
while True:
    data = raw_input("Please input some string > ")  
    if not data :
        print "input can't empty, Please input again.."
        continue
    client.sendall(data)  #發送數據到服務器
    data = client.recv(BUF_SIZE)  #從服務器端接收數據
    print data
client.close()

UDP通信

UDP通信流程圖如下:
服務端:socket---bind---recvfrom---sendto---close
客戶端:socket----------sendto---recvfrom---close

UDP
UDP

sendto()函數
發送UDP數據, 將數據發送到套接字

#Linux
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
sockfd : 為前面socket的返回值.
msg : 一般為常量字符串
len : 表示長度
flags : 通常為0
to : 表示目地機的IP地址和端口號信息, 表示地址的結構體
tolen : 常常被賦值為sizeof (struct sockaddr)
返回值 : 返回實際發送的數據字節長度或在出現發送錯誤時返回-1。


#Python
s.sendto(string[,flag],address)
s為socket.socket()返回的套接字對象
address : 指定遠程地址, 形式為(ipaddr,port)的元組
flag : 提供有關消息的其他信息,通常可以忽略
返回值 : 發送的字節數。

recvfrom()函數
接受UDP套接字的數據, 與recv()類似

#Linux
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
sockfd : 為前面socket的返回值.
msg : 一般為常量字符串
len : 表示長度
flags : 通常為0
from :是一個struct sockaddr類型的變量,該變量保存連接機的IP地址及端口號
fromlen : 常置為sizeof (struct sockaddr)。
返回值 : 返回接收到的字節數或當出現錯誤時返回-1,並置相應的errno。

#Python
s.recvfrom(bufsize[.flag])
返回值 : (data,address)元組, 其中data是包含接收數據的字符串,address是發送數據的套接字地址
bufsize : 指定要接收的數據大小
flag : 提供有關消息的其他信息,通常可以忽略

簡單的客戶端服務器UDP連接

#服務器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

BUF_SIZE = 1024  #設置緩沖區大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構成表示地址
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  #生成新的套接字對象
server.bind(server_addr)  #套接字綁定IP和端口
while True :
    print "waitting for data"
    data, client_addr = server.recvfrom(BUF_SIZE)  #從客戶端接收數據
    print 'Connected by', client_addr, ' Receive Data : ', data
    server.sendto(data, client_addr)  #發送數據給客戶端
server.close()

#客戶端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
import struct

BUF_SIZE = 1024  #設置緩沖區
server_addr = ('127.0.0.1', 8888)  #IP和端口構成表示地址
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  #生成新的套接字對象

while True :
    data = raw_input('Please Input data > ')
    client.sendto(data, server_addr)  #向服務器發送數據
    data, addr = client.recvfrom(BUF_SIZE)  #從服務器接收數據
    print "Data : ", data
client.close()

 

其他

s.getpeername()
#返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。

s.getsockname()
#返回套接字自己的地址。通常是一個元組(ipaddr,port)

s.setsockopt(level,optname,value)
#設置給定套接字選項的值。

s.getsockopt(level,optname[.buflen])
#返回套接字選項的值。

s.settimeout(timeout)
#設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作(如connect())

s.gettimeout()
#返回當前超時期的值,單位是秒,如果沒有設置超時期,則返回None。

s.fileno()
#返回套接字的文件描述符。

s.setblocking(flag)
#如果flag為0,則將套接字設為非阻塞模式,否則將套接字設為阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那么將引起socket.error異常。

s.makefile()
#創建一個與該套接字相關連的文件

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM