目录
引言
因特网为应用程序提供了两个运输层(传输层)协议,即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在IP尽力而为服务之上不提供不必要服务,因此不提供可靠传输。
3、实时性:UDP有较好的实时性,效率比TCP高。
4、拥塞控制:TCP有拥塞控制机制;UDP无拥塞控制机制。
5、流量控制:TCP有流量控制;UDP无流量控制。
6、资源: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