Python學(xué)習(xí)目錄
- 在Mac下使用Python3
- Python學(xué)習(xí)之?dāng)?shù)據(jù)類型
- Python學(xué)習(xí)之函數(shù)
- Python學(xué)習(xí)之高級(jí)特性
- Python學(xué)習(xí)之函數(shù)式編程
- Python學(xué)習(xí)之模塊
- Python學(xué)習(xí)之面向?qū)ο缶幊?/a>
- Python學(xué)習(xí)之面向?qū)ο蟾呒?jí)編程
- Python學(xué)習(xí)之錯(cuò)誤調(diào)試和測(cè)試
- Python學(xué)習(xí)之IO編程
- Python學(xué)習(xí)之進(jìn)程和線程
- Python學(xué)習(xí)之正則
- Python學(xué)習(xí)之常用模塊
- Python學(xué)習(xí)之網(wǎng)絡(luò)編程
互聯(lián)網(wǎng)的實(shí)現(xiàn),分成好幾層。每一層都有自己的功能,就像建筑物一樣,每一層都靠下一層支持。如何分層有不同的模型,有的模型分七層,有的分四層。我覺得,把互聯(lián)網(wǎng)分成五層,比較容易解釋。最底下的一層叫做"實(shí)體層"(Physical Layer),最上面的一層叫做"應(yīng)用層"(Application Layer),中間的三層(自下而上)分別是"鏈接層"(Link Layer)、"網(wǎng)絡(luò)層"(Network Layer)和"傳輸層"(Transport Layer)。越下面的層,越靠近硬件;越上面的層,越靠近用戶。
TCP編程
Socket是網(wǎng)絡(luò)編程的一個(gè)抽象概念。通常我們用一個(gè)Socket表示“打開了一個(gè)網(wǎng)絡(luò)鏈接”,而打開一個(gè)Socket需要知道目標(biāo)計(jì)算機(jī)的IP地址和端口號(hào),再指定協(xié)議類型即可。
客戶端
創(chuàng)建Socket
# 導(dǎo)入socket庫(kù):
import socket
# 創(chuàng)建一個(gè)socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接:
s.connect(('www.sina.com.cn', 80))
創(chuàng)建Socket時(shí),AF_INET指定使用IPv4協(xié)議,如果要用更先進(jìn)的IPv6,就指定為AF_INET6。SOCK_STREAM指定使用面向流的TCP協(xié)議,這樣,一個(gè)Socket對(duì)象就創(chuàng)建成功,但是還沒有建立連接。
連接服務(wù)器
s.connect(('www.sina.com.cn', 80))
注意參數(shù)是一個(gè)tuple,包含地址和端口號(hào)。
發(fā)送請(qǐng)求
# 發(fā)送數(shù)據(jù):
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
TCP連接創(chuàng)建的是雙向通道,雙方都可以同時(shí)給對(duì)方發(fā)數(shù)據(jù)。但是誰先發(fā)誰后發(fā),怎么協(xié)調(diào),要根據(jù)具體的協(xié)議來決定。例如,HTTP協(xié)議規(guī)定客戶端必須先發(fā)請(qǐng)求給服務(wù)器,服務(wù)器收到后才發(fā)數(shù)據(jù)給客戶端。
接收數(shù)據(jù)
# 接收數(shù)據(jù):
buffer = []
while True:
# 每次最多接收1k字節(jié):
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
關(guān)閉Socket
# 接收數(shù)據(jù):
buffer = []
while True:
# 每次最多接收1k字節(jié):
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
服務(wù)器
創(chuàng)建Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
創(chuàng)建一個(gè)基于IPv4和TCP協(xié)議的Socket。
綁定監(jiān)聽的地址和端口
# 監(jiān)聽端口:
s.bind(('127.0.0.1', 9999))
s.listen(5)
print('Waiting for connection...')
listen()方法傳入的參數(shù)指定等待連接的最大數(shù)量。
接受客戶端連接
while True:
# 接受一個(gè)新連接:
sock, addr = s.accept()
# 創(chuàng)建新線程來處理TCP連接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
每個(gè)連接都必須創(chuàng)建新線程(或進(jìn)程)來處理,否則,單線程在處理連接的過程中,無法接受其他客戶端的連接:
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)
UDP編程
TCP是建立可靠連接,并且通信雙方都可以以流的形式發(fā)送數(shù)據(jù)。相對(duì)TCP,UDP則是面向無連接的協(xié)議。
使用UDP協(xié)議時(shí),不需要建立連接,只需要知道對(duì)方的IP地址和端口號(hào),就可以直接發(fā)數(shù)據(jù)包。但是,能不能到達(dá)就不知道了。
雖然用UDP傳輸數(shù)據(jù)不可靠,但它的優(yōu)點(diǎn)是和TCP比,速度快,對(duì)于不要求可靠到達(dá)的數(shù)據(jù),就可以使用UDP協(xié)議。
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定端口:
s.bind(('127.0.0.1', 9999))
創(chuàng)建Socket時(shí),SOCK_DGRAM指定了這個(gè)Socket的類型是UDP。綁定端口和TCP一樣,但是不需要調(diào)用listen()方法,而是直接接收來自任何客戶端的數(shù)據(jù):
print('Bind UDP on 9999...')
while True:
# 接收數(shù)據(jù):
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
s.sendto(b'Hello, %s!' % data, addr)
recvfrom()方法返回?cái)?shù)據(jù)和客戶端的地址與端口,這樣,服務(wù)器收到數(shù)據(jù)后,直接調(diào)用sendto()就可以把數(shù)據(jù)用UDP發(fā)給客戶端。
注意這里省掉了多線程,因?yàn)檫@個(gè)例子很簡(jiǎn)單。
客戶端使用UDP時(shí),首先仍然創(chuàng)建基于UDP的Socket,然后,不需要調(diào)用connect(),直接通過sendto()給服務(wù)器發(fā)數(shù)據(jù):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Michael', b'Tracy', b'Sarah']:
# 發(fā)送數(shù)據(jù):
s.sendto(data, ('127.0.0.1', 9999))
# 接收數(shù)據(jù):
print(s.recv(1024).decode('utf-8'))
s.close()
從服務(wù)器接收數(shù)據(jù)仍然調(diào)用recv()方法。