二十二、网络编程

网络编程的一些基本概念:

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()