网络编程的一些基本概念:
1.地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。
主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址。
收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
2
.tcp协议:TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
tcp 的链接有三次握手,断开有四次挥手,四次挥手的原因是因为tcp下一的半关闭原则。
3.udp协议:UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快
互联网协议与osi模型
互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层
每层运行常见物理设备
一.套接字(socket)初使用
基于TCP协议的socket
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
服务端 import socket s = socket.socket() #买电话 id_port = ('127.0.0.1',8000) #买电话卡 s.bind(id_port) #把电话装上电话卡
s.listen() #等待电话打进来 conn,adress = s.accept() #接受到了消息 msg = conn.recv(1024) #接受信息,接受的一点是字节类型 print(msg.decode()) # 打印,解码 inp = input('<<<<') #输入要发送的信息 new_msg = conn.send(inp.encode('utf-8)) # 把发送的信息编码,发送 s.close()
客户端 import socket s = socket.socket() id_port = ('127.0.0.1',8000) s.connect(id_port) msg = input('<<<') s.send(msg.encode('utf-8') new_msg = s.recv(1024) print(new_msg.decode())
s.close()
基于UDP协议的socket
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接
简单使用
服务端 import socket udp_s = socket.socket(type = socket.SOCK_DGRAM)# 创建套接字 id_port = ('127.0.01',8000) udp_s.bind(id_port) #为套接字绑定ip msg,addr = udp_s.recvfrom(1024) print(msg.decode()) inp = input('<<<') udp_s.sendto(inp.encode('utf-8),addr) #这里发送消息给客户端要带上地址, udp_s.close()
客户端 import socket s = socket.socket(type = socket.SOCK_DRGAM) id_port = ('127.0.0.1',8000) msg = input('<<<') s.sendto(msg.encode('utf-8'),idport) msg,addr = s.recv(1024) print(msg.decode('utf-8))
s.close()
udp协议和tcp的差别:
服务端:tcp的服务端需要s.listen()这个过程 并且是conn,addr = s.accept() 后面的send 和recv 都是通过conn来进行,所以后面的发送不需要把addr加上。
udp的服务端没有listen这个过程,直接是msg,addr = s.recv(1024) 后面的是s.sendto(mag,addr)
客户端:tcp的客户端是id_port 与s绑定是s.connect(id_port) s.send() msgr = s.recv(1024)
udp的客户端是不需要客户端与ip_port 绑定的的,直接发送的时候 s.sendto(msg,id_port) msg,addr = s.recv(1024)
二、黏包问题
tcp协议的黏包成因详谈:
首先明确一点,黏包只发生在TCP协议,udp协议不会产生黏包,udp要么报错,要么发送丢失,也就是不完整。至于为什么我们稍后来谈。这里只搞清楚一点只有tcp会有黏包现象。
为什么tcp会有黏包:表面上看是由于发送方和接受方的缓存机制,也就是说的拆包,和tcp'协议是面向通信流(也就是那种byte流的形式)的特点,造成了这些情况,而真正的罪魁祸首其实是,接受端根本不知道要接受的数据怎么断句和接受数据的大小,所以才造成了坑爹的黏包现象。
深层次的分析:1.tcp协议在发送消息 如果消息的数量量大于的网卡的mtu值,就会把这个数据包拆包,分几次发送过去,这样就容易造成一次性接受消息不完整的情况,分几次接受,这是容易造成黏包的第一个原因。但是同时也是tcp可靠的点,只要没有发送完会一直发送。
2.由于tcp协议面向流的通信特点和nagle算法。如果在发送两条间隔很短的消息且这样两条消息的长度特别短 ,这时候就要采用nagle算法,把这两条消息整合成一条消息打包发送。这时候接收端就无法合理的拆包。就造成消息混乱,也就是黏包。同时这也是tcp协议面向流通信的特点,这也是。
udp协议不产生黏包的原因:
主要是udp协议的通信是面向消息的,每次不管是接受还是发送都是接受一整条消息,不会去拆包,除非这个消息大于接受范围就会发送不完整,且下次也不会再继续发送,即使发送为,且每次发送消息都会自动带上端口号和ip地址,所以即使发送为空接收端也会收到消息。tcp协议如果发空消息,接收端会阻塞。
三、用struct解决黏包问题
借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。
发送时 | 接收时 |
先发送struct转换好的数据长度4字节 | 先接受4个字节使用struct转换成数字来获取要接收的数据长度 |
再发送数据 | 再按照长度接收数据 |
server端 #!user/bin/python3 #Author:Mr.Yuan #-*- coding:utf-8 -*- #@time: 2018/5/7 19:00 import socket import os import struct import json import time id_port = ('127.0.0.1',9000) s = socket.socket() s.bind(id_port) s.listen() cnng,addr = s.accept() dic = {'filename':r'H:\pycharm文件\a', 'filesize':os.path.getsize(r'D:\feiq\Recv Files\python11期day35\video2.mp4')} str_dic = json.dumps(dic).encode('utf-8') struct_dic = struct.pack('i',len(str_dic)) cnng.send(struct_dic) cnng.send(str_dic) f = open(r'D:\feiq\Recv Files\python11期day35\video2.mp4','rb') while dic['filesize']: content = f.read(1024) dic['filesize']-=len(content) print(dic['filesize']) cnng.sendall(content) cnng.close() s.close()
client端 #!user/bin/python3 #Author:Mr.Yuan #-*- coding:utf-8 -*- #@time: 2018/5/7 19:00 import socket import json import struct id_port = ('127.0.0.1',9000) s = socket.socket() s.connect(id_port) struct_message = s.recv(4) dic_len = struct.unpack('i',struct_message)[0] str_dic = json.loads(s.recv(dic_len).decode()) print(str_dic) with open('H:\pycharm文件\mp6.mp4','wb') as f : while str_dic['filesize']: recv_content = s.recv(1024) str_dic['filesize'] -= len(recv_content) print(str_dic['filesize']) f.write(recv_content) s.close()
四、一个服务端连接多个客户端写法
#服务端 import socketserver class MyServe(socketserver.BaseRequestHandler): def handle(self):#必须写这个函数名 最先执行这个方法 self.request.sendall(bytes('欢迎致电10086...巴拉巴拉一大推',encoding='utf-8')) while True: date = self.request.recv(1024) print('%s:%s'% (self.client_address,date.decode())) self.request.sendall(bytes('我收到了',encoding='utf-8')) if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8000),MyServe) server.serve_forever() #让handle方法永远执行下去
#客户端 import socket ip_port = ('127.0.0.1',8000) s = socket.socket() s.connect(ip_port) msg = s.recv(1024) print(msg.decode()) while True: inp = input('<<<<') if len(inp)==0:continue s.send(bytes(inp,encoding='utf-8')) data = s.recv(1024) print(data.decode()) s.close()