引言
因特網爲應用程序提供了兩個運輸層(傳輸層)協議,即TCP和UDP。當我們爲因特網創建一個新的應用程序時,我們首先需要考慮的是:選擇TCP還是UDP。這兩個協議都爲調用它們的應用程序提供了不同的服務集合。
一、TCP介紹
TCP大的服務模型包括:面向連接的服務、可靠的數據傳輸服務以及擁塞控制機制。
- 面向連接的服務:在應用層的數據報文開始流動之前,先進行三次握手建立連接,即TCP讓客戶和服務器相互交換運輸層的控制信息。
- 可靠的數據傳輸服務:不同的主機間的不同進程間能夠依靠TCP進行無差錯、按適當順序交付所有發送的數據。
- 擁塞控制機制:當發送方和接收方之間的網絡出現擁塞時,TCP的擁塞控制機制會抑制發送進程(客戶/服務器);TCP擁塞控制機制也試圖限制每個TCP連接,使它們達到公平共享網絡帶寬的目的。
二、UDP介紹
UDP是一種不提供不必要(no-frills)服務的輕量級運輸協議,它僅提供最小的服務。UDP是無連接的,因此在兩個進程通信前沒有握手的過程。UDP協議提供一種不可靠數據傳輸服務,也就是說,當進程將一個報文發送進UDP套接字時,UDP協議並不保證該報文將到達接收進程。不僅如此,到達接收進程的報文也可能是亂序的。
UDP也沒有擁塞控制機制,所以UDP的發送端可以用它選定的任何速率向其下層(網絡層)注入數據。
三、拓展(TCP加強版:SSL)
TCP/UDP都沒有提供任何加密機制,因此因特網界提供了一個在應用層上實現的加強版的TCP,叫作:安全套接字層(Secure Sockets Layer, SSL)。
四、TCP與UDP的比較
1、連接:TCP面向連接;UDP是無連接的。
2、可靠:TCP提供可靠傳輸服務:無差錯、按序到達;UDP盡力而爲,不提供可靠傳輸。
3、實時性:UDP有較好的實時性,效率比TCP高。
4、擁塞控制:TCP有擁塞控制機制;UDP無擁塞控制機制。
5、資源:TCP對系統資源要求多;UDP對系統資源要求少。
五、使用TCP/UDP編寫網絡聊天程序
UDP:
# Client.py
import socket
import time, threading
# 創建套接字,參數1:指示了地址簇,IF_INEF指示了底層使用IPv4;
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def tcplink():
while True:
js = input('我:')
s.sendto(js.encode('utf-8'), ('127.0.0.1', 9994))
data = s.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
print('Connection closed.')
break
print(('東方不敗:%s' % data.decode('utf-8'))+ ' ' + time.strftime("%H:%M:%S"))
s.close()
t = threading.Thread(target=tcplink, args=())
t.start()
# Server.py
import socket
import time, threading
import os
# 創建了一個socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 監聽端口
s.bind(('127.0.0.1', 9994))
print('Waiting for connection...')
while True:
message, clientAddress = s.recvfrom(2048)
time.sleep(1)
if message.decode('utf-8') == 'exit':
break
print(message.decode('utf-8'))
js = input('我:')
s.sendto(js.encode('utf-8'), clientAddress)
TCP:聊天+傳文件(附加進度條)
# TCPClient.py
import socket
import time, threading
import os
import struct
import sys
import json
from processbar import process_bar
buffsize = 1024
# 創建了一個socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 監聽端口
s.bind(('127.0.0.1', 9994))
# 調用listen()方法開始監聽端口
s.listen(5)
print('Waiting for connection...')
# 接收文件函數
def recvfile(sock):
# 接收報文的長度
head_struct = sock.recv(4)
if head_struct:
print('等待接收數據:')
# 解析出報頭的字符串大小
head_len = struct.unpack('i', head_struct)[0]
#print(head_len)
# 接收長度爲head_len的報頭內容信息(包含文件大小,文件名的內容)
data = sock.recv(head_len)
head_dir = json.loads(data.decode('utf-8'))
filesize_b = head_dir['filesize_bytes']
filename = head_dir['filename']
# 接收真實的文件內容
recv_len = 0
recv_mesg = b''
old = time.time()
f = open(filename, 'wb')
while recv_len < filesize_b:
percent = recv_len / filesize_b
process_bar(percent)
if(filesize_b - recv_len > buffsize):
recv_mesg = sock.recv(buffsize)
f.write(recv_mesg)
recv_len += len(recv_mesg)
else:
recv_mesg = sock.recv(filesize_b - recv_len)
recv_len += len(recv_mesg)
f.write(recv_mesg)
print(recv_len, filesize_b)
now = time.time()
stamp = int(now - old)
print('接收成功,總共用時%ds' % stamp)
f.close()
# 對每個連接的客戶端所做的處理
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)
# 若接收到sendfile則調用接受文件函數,接收成功後進入下一輪
if(data.decode('utf-8') == 'sendfile'):
recvfile(sock)
continue
if not data or data.decode('utf-8') == 'exit':
break
print(('西門吹雪:%s' % data.decode('utf-8')) + ' ' + time.strftime("%H:%M:%S"))
js = input('我:')
sock.send(js.encode('utf-8'))
# sock.send(('Hello, %s' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s%s closed.' % addr)
# 接下來服務器通過一個永久循環來接受來自客戶端的連接, accept()會等待並返回一個客戶端的連接
while True:
# 接受一個新連接
sock, addr = s.accept()
# 創建新線程來處理TCP連接
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
# TCPClient.py
import socket
import time, threading
import struct
import json
import os
# 創建套接字,參數1:指示了地址簇,IF_INEF指示了底層使用IPv4;參數2:表明創建了一個TCP套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接
s.connect(('127.0.0.1', 9994))
# 發送文件的函數
def file_process():
filemesg = input('請輸入傳送的文件名>>>').strip()
# 獲取文件的大小(字節)
filesize_bytes = os.path.getsize(filemesg)
# 發送文件重命名
filename = 'new' + filemesg
dirc = {
'filename' : filename,
'filesize_bytes' : filesize_bytes,
}
# 將字典轉換爲字符串
head_info = json.dumps(dirc)
# 將字符串的長度打包
head_info_len = struct.pack('i', len(head_info))
# 發送head_info的長度
s.send(head_info_len)
# 發送head_info
s.send(head_info.encode('utf-8'))
# 發送真實的文件信息
with open(filemesg, 'rb') as f:
data = f.read()
s.sendall(data)
print('發送成功')
# 接收消息的函數
def tcplink():
while True:
js = input('我:')
s.send(js.encode('utf-8'))
# 假如是sendfile則調用發送文件函數,待發送成功後重新進入下一輪
if(js == 'sendfile'):
file_process()
continue
data = s.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
print('Connection closed.')
break
print(('東方不敗:%s' % data.decode('utf-8'))+ ' ' + time.strftime("%H:%M:%S"))
s.close()
# 創建一個線程用來用於和服務器建立連接
t = threading.Thread(target=tcplink, args=())
t.start()
# processbar.py
# 進度條顯示代碼
import sys
import time
def process_bar(precent, width = 50):
use_num = int(precent*width)
space_num = int(width-use_num)
precent = precent*100
print('[%s%s]%d%%' % (use_num*'#', space_num*' ', precent), file=sys.stdout, flush=True, end='\r')
參考博文:
1、https://blog.csdn.net/xiaobangkuaipao/article/details/76793702
2、https://www.cnblogs.com/xiaomayizoe/p/5258754.html
3、https://www.cnblogs.com/HPAHPA/p/7737641.html