TCP简介
UDP的全称是User Datagram Protocol(用户数据报协议),只提供面向无连接的通信服务,而且也没有流量控制、重发纠错等机制,因此只能适用于包总量比较少的服务,例如DNS、SNMP,或者多媒体即时通信、LAN内通信和广播通信等对于信息完整性要求不高的场景。
TCP的全称是传输控制协议,不仅是面向连接的,防止流量的浪费,而且实现了数据传输的各种控制,例如丢包时的重发控制、次序混乱时的顺序控制。
问题:TCP如何保证可靠传输?
- 发送应答机制
- 超时重传机制
- 错误校验
- 流量控制和阻塞控制
UDP和TCP的区别
UDP通信模型,类似于写信,写信之前并不确定对方是否存在,因此是不可靠的
TCP模型类似于打电话,相当于建立了一条虚拟电路,通过应答机制保证对方接受到的信息是完整的,因此是可靠传输。
而且TCP模型中会严格区分服务器端和客户端,而UDP模型则并不会严格区分client和server,都是采用recvfrom和sendto。
TCP客户端
- 创建套接字
- 连接服务器
- 发送/接收数据
- 关闭套接字
import socket
def main():
# 创建套接字
tcp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 连接服务器
server_ip = input("输入要连接的服务器的IP: ")
server_port = int(input("输入要连接的服务器的port"))
server_addr = (server_ip,server_port)
tcp_client_socket.connect(server_addr)
# 发送/接收数据
send_data = input("输入要发送的数据")
tcp_client_socket.send(send_data)
# 关闭套接字
tcp_client.close()
if __name__=="__main__":
main()
TCP服务器端
- 创建监听套接字
- 监听套接字绑定ip和port(思考为什么)
- listen,将监听套接字变成被动接收
- accept,监听套接字等待客户端的连接
- 如果有客户端连接,创建服务套接字
- recv/send,服务套接字接收发送数据(思考是否还需要recv_addr)
- 服务完毕,服务套接字被释放
创建并将监听套接字设置为等待客户连接的过程很像买手机:
- 买手机——创建监听套接字
- 插手机卡——listen使监听套接字可以被动接受
- 设置手机状态可以接听——accept使监听套接字等待客户端连接
总之,监听套接字,负责等待新的客户端连接;服务套接字,可以为这个客户服务,这个过程也很像通信公司的呼叫中心。
按照这个思路,我们设计TCP服务器如下:
import socket
# 创建TCP服务器
def Tcp_server():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定IP和port
tcp_server_socket.bind("127.0.0.1",7788)
# 让默认套接字由主动变被动
tcp_server_socket.listen(128)
# 等待客户端的连接_拥塞
new_client_socket,client_addr = tcp_server_socket.accept()
# 接收客户端发送来的数据,注意TCP的recv不需要addr,因为已经建立了连接
recv_data = new_client_socket.recv(1024)
print("接收来自%s的消息:%s"%(client_addr,recv_data))
new_client_socket.send('okk'.encode('utf-8'))
# 关闭服务套接字
new_client_socket.close()
if __name__=="__main__":
Tcp_server()
注意:tcp_server_socket,listen(128)
表示监听套接字最多能够同时接收128个客户端的响应。
当然,TCP支持server端接收不定长的消息,因为recv解堵塞有两种情况,要么接收到数据,要么客户端关闭,此时接收到的信息为None,我们据此修改上面的代码:
# 支持不定长的消息的TCP server
import socket
# 创建TCP服务器
def Tcp_server():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定IP和port
tcp_server_socket.bind("127.0.0.1",7788)
# 让默认套接字由主动变被动,最多接收128字节的数据
tcp_server_socket.listen(128)
while True:
# 等待客户端的连接_拥塞
new_client_socket,client_addr = tcp_server_socket.accept()
while True:
# 接收客户端发送来的数据
recv_data = new_client_socket.recv(1024)
# recv 解堵塞有两种情况:接收到消息/客户端关闭
if recv_data is None:
# recv_data is None, 表示客户端已关闭
new_client_socket.close()
break
else:
print("接收来自%s的消息:%s"%(client_addr,recv_data))
new_client_socket.send('okk'.encode('utf-8'))
# 关闭服务套接字
new_client_socket.close()
print("客户已经服务完毕")
tcp_server_socket.close() # 理想情况下,服务器不会关闭
if __name__=="__main__":
Tcp_server()
案例:基于TCP的文件下载器
我们基于上面讨论的内容,实现一个TCP文件下载器的client端和server端
client端
import socket
FILE_NAME = './download.txt'
def main():
# 创建socket
tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 连接服务器
server_ip = input("输入服务器IP:")
server_port = input("输入服务器PORT:")
server = (server_ip,server_port)
tcp_socket.connect(server)
# 接收数据
recv_data = tcp_socket.recv(1024) # 1024-->1k, 1024*1024-->1M
# 如果接收到数据在创建文件,否则不创建
# 注意:接收到的数据一般是二进制的,直接写入二进制即可
if recv_data:
with open(FILE_NAME,'wb') as f:
f.wirte(recv_data)
else:
print("没有接收到文件")
# 关闭套接字
tcp_socket.close()
if __name__=="__main__":
main()
server端
import socket
# 服务器
DOWNLOAD_PATH = './download/'
def send_file_2_client(new_client_socket,client_addr):
file_name = new_client_socket.recv(1024).decode("utf-8")
print("%s要下载%s"%(client_addr,file_name))
file_content = None
# 打开文件
try:
f = open(DOWNLOAD_PATH+file_name)
file_content = f.read()
f.close()
except Exception as ret:
print("没有要下载的文件")
new_client_socket.send(file_content.decode("utf-8"))
def main():
"""服务器接收来自客户端的请求,并发送文件"""
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定本地信息
tcp_server_socket.bind(("",7788))
# 主动变被动
tcp_server_socket.listen(128)
# 等待
client_socket,client_addr = tcp_server_socket.accept()
# 接收要下载的文件名,并返回
send_file_2_client(client_socket,client_addr)
# 关闭套接字
client_socket.close()
if __name__=="__main__":
main()
TCP补充
- TCP服务器端一般情况都需要绑定IP和端口,而客户端一般不需要,因而一般来说是客户端主动连接服务器;
- tcp服务器端的监听套接字的listen是必须的,参数为监听套接字能够同时响应的客户端请求的最大数目;
- 客户端连接服务器时,需要用connect连接,不需要地址,这一点与UDP不同;
- 当客户端的套接字调用close后,服务器端的套接字也会解堵塞,并返回None,服务器端可据此判断客户端是否已经下线。