简介
- 应用层:为用户提供所需要的各种服务,包括FTP,Telnet,DNS,SMTP等
- 传输层:TCP和UDP
- 网络层:IP,IGMP,ICMP
- 链路层:负责监视数据在主机和网络之间的交换
IP协议
负责把数据从一台计算机通过网络发送给另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。
不保证都能到达,也不保证顺序到达。
TCP协议
TCP协议建立在ip基础上,负责在俩台计算机之间建立可靠连接,保证数据包按顺序到达,通过3次握手建立可靠连接
创建TCP服务器
流程
- 使用socket创建一个套接字
- 使用bind绑定IP和端口
- 使用listen使套接字变为可以被动连接
- 使用accept等待客户端的连接
- 使用recv/send接收发送数据
具体可参考SOCKET
host = '127.0.0.1'
port = 8080
web = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
web.bind((host,port))
web.listen(5)
sock,addr = web.accept()
print('已经建立好连接')
info = sock.recv(1024).decode()
while info != 'byebye':
if info:
print('接收到的内容',info,sep=':')
send_data = input('输入发送的内容:')
sock.send(send_data.encode())
if send_data == 'byebye':
break
info = sock.recv(1024).decode()
sock.close()
web.close()
创建客户端
import socket
s = socket.socket()
host = '127.0.0.1'
port = 8080
s.connect((host,port))
print('已连接')
info = ''
while info != 'byebye':
send_data = input('输入发送的内容:')
s.send(send_data.encode())
if send_data == 'byebye':
break
info = s.recv(1024).decode()
print('接收到的内容',info,sep=':')
s.close()
通信模型
粘包
现象
发送方发送俩个字符串”hello”+”world”,接收方却一次性接收到了”helloworld”
原因
TCP为了提高网络的利用率,会使用一个叫做Nagle的算法。该算法是指,发送端即使有要发送的数据,如果很少的话,会延迟发送。如果应用层给TCP传送数据很快的话,就会把两个应用层数据包“粘”在一起,TCP最后只发一个TCP数据包给接收端。
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
解决办法
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
是有struct模块把一个类型转化为固定长度的bytes
客户端
#! /usr/bin/env python3
#conding=utf-8
import json
import struct
import os
import hashlib
import socket
cur_path = os.path.dirname(os.path.abspath(__file__))
file_name = os.path.join(cur_path,'a.txt')
file_size = os.path.getsize(file_name)
f = open(file_name,'rb')
file_md5 = hashlib.md5(f.read()).hexdigest()
#自定义包头
header={'file_size':file_size,'file_name':file_name,'md5':file_md5}
#序列化包头并转为bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8')
#用struct将包头长度这个数字转化为固定的长度4字节
head_len_bytes=struck.pack('i',len(head_bytes))
#客户端开始发送
host = '127.0.0.1'
port = 8080
conn = socket.socket()
conn.connect((host,port))
conn.send(head_len_bytes) #先发送报头的长度
conn.send(head_bytes)#再发送报头的字节格式
conn.sendall(f.read()) #再发送真实内容的字节格式
服务器
import socket
import json
host = '127.0.0.1'
port = 8080
web = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
web.bind((host,port))
web.listen(5)
sock,addr = web.accept()
head_len_bytes = sock.recv(4) #收报头4个bytes,得到报头长度的字节格式
header_len = struck.unpack('i',head_len_bytes)[0] #提取报头的长度
head_bytes = sock.recv(header_len) #按照报头的长度接收报头
header = json.loads(head_bytes.decode('utf-8')) #解码并反序列化得到报头的json内容
real_data_len = header['file_size'] #提取出报头中数据的长度
real_data = s.recv(real_data_len) #根据报头中文件的长度获取到实际的文件内容