简介


  • 应用层:为用户提供所需要的各种服务,包括FTP,Telnet,DNS,SMTP等
  • 传输层:TCP和UDP
  • 网络层:IP,IGMP,ICMP
  • 链路层:负责监视数据在主机和网络之间的交换

TCP/IP - 图1

IP协议


负责把数据从一台计算机通过网络发送给另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。
不保证都能到达,也不保证顺序到达。

TCP协议


TCP协议建立在ip基础上,负责在俩台计算机之间建立可靠连接,保证数据包按顺序到达,通过3次握手建立可靠连接
TCP/IP - 图2

创建TCP服务器


流程

  1. 使用socket创建一个套接字
  2. 使用bind绑定IP和端口
  3. 使用listen使套接字变为可以被动连接
  4. 使用accept等待客户端的连接
  5. 使用recv/send接收发送数据

具体可参考SOCKET

  1. host = '127.0.0.1'
  2. port = 8080
  3. web = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  4. web.bind((host,port))
  5. web.listen(5)
  6. sock,addr = web.accept()
  7. print('已经建立好连接')
  8. info = sock.recv(1024).decode()
  9. while info != 'byebye':
  10. if info:
  11. print('接收到的内容',info,sep=':')
  12. send_data = input('输入发送的内容:')
  13. sock.send(send_data.encode())
  14. if send_data == 'byebye':
  15. break
  16. info = sock.recv(1024).decode()
  17. sock.close()
  18. web.close()

创建客户端


  1. import socket
  2. s = socket.socket()
  3. host = '127.0.0.1'
  4. port = 8080
  5. s.connect((host,port))
  6. print('已连接')
  7. info = ''
  8. while info != 'byebye':
  9. send_data = input('输入发送的内容:')
  10. s.send(send_data.encode())
  11. if send_data == 'byebye':
  12. break
  13. info = s.recv(1024).decode()
  14. print('接收到的内容',info,sep=':')
  15. s.close()

通信模型


TCP/IP - 图3

粘包


现象

发送方发送俩个字符串”hello”+”world”,接收方却一次性接收到了”helloworld”

原因

TCP为了提高网络的利用率,会使用一个叫做Nagle的算法。该算法是指,发送端即使有要发送的数据,如果很少的话,会延迟发送。如果应用层给TCP传送数据很快的话,就会把两个应用层数据包“粘”在一起,TCP最后只发一个TCP数据包给接收端。
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

解决办法

为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
是有struct模块把一个类型转化为固定长度的bytes
客户端

  1. #! /usr/bin/env python3
  2. #conding=utf-8
  3. import json
  4. import struct
  5. import os
  6. import hashlib
  7. import socket
  8. cur_path = os.path.dirname(os.path.abspath(__file__))
  9. file_name = os.path.join(cur_path,'a.txt')
  10. file_size = os.path.getsize(file_name)
  11. f = open(file_name,'rb')
  12. file_md5 = hashlib.md5(f.read()).hexdigest()
  13. #自定义包头
  14. header={'file_size':file_size,'file_name':file_name,'md5':file_md5}
  15. #序列化包头并转为bytes
  16. head_bytes=bytes(json.dumps(header),encoding='utf-8')
  17. #用struct将包头长度这个数字转化为固定的长度4字节
  18. head_len_bytes=struck.pack('i',len(head_bytes))
  19. #客户端开始发送
  20. host = '127.0.0.1'
  21. port = 8080
  22. conn = socket.socket()
  23. conn.connect((host,port))
  24. conn.send(head_len_bytes) #先发送报头的长度
  25. conn.send(head_bytes)#再发送报头的字节格式
  26. conn.sendall(f.read()) #再发送真实内容的字节格式

服务器

  1. import socket
  2. import json
  3. host = '127.0.0.1'
  4. port = 8080
  5. web = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  6. web.bind((host,port))
  7. web.listen(5)
  8. sock,addr = web.accept()
  9. head_len_bytes = sock.recv(4) #收报头4个bytes,得到报头长度的字节格式
  10. header_len = struck.unpack('i',head_len_bytes)[0] #提取报头的长度
  11. head_bytes = sock.recv(header_len) #按照报头的长度接收报头
  12. header = json.loads(head_bytes.decode('utf-8')) #解码并反序列化得到报头的json内容
  13. real_data_len = header['file_size'] #提取出报头中数据的长度
  14. real_data = s.recv(real_data_len) #根据报头中文件的长度获取到实际的文件内容