zoukankan      html  css  js  c++  java
  • 网络编程协议

    CS架构:client server(客户端-服务端)
           安装一个客户端应用程序EXE,就可以与服务端进行网络交互 

    BS架构:browser server (浏览器-服务端)
                所有的bs都需要一个浏览器才能访问,
                浏览器是一个软件,是特殊的客户端,
                所有的bs架构也都是cs架构
    基础知识:
      网卡:计算机硬件,出厂的时候被分配的一个mac地址
      MAC地址:唯一标识了一台机器(8C-I3-O3-DD-9D)
      交换机: 用于局域网内多台机器间通信,只认识mac地址,可用arp协议通过ip找到mac地址
      局域网:一个区域内多台电脑组成的一个内部网络(区域内电脑ip前缀相同)
      IP地址:iP地址是指互联网协议地址(英语:Internet Protocol Address,又译为网际协议地址),是IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

        规定网络地址的协议叫ip协议,它定义的地址称之为ip地址
      IP协议:在传输过程中规定位数.顺序等传输规则内容(IP地址规范)
                   ipv4:四位的点分十进制(32位二进制)
        ipv6:六位的冒分十六进制

      子网掩码:就是表示子网络特征的一个参数(局域网共同的网段)。它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。

      IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。

      端口:帮助我们在机器上的对应服务使用端口, 0-65535,使用8000以后

      arp协议:交换机地址解析协议是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址。收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。


      路由器:能够在局域网与局域网间通信,是连接因特网中各局域网、广域网的设备,
      网关ip:在一台机器对局域网外的地址进行访问时使用的出口ip,路由器具有判断网络地址和选择IP路径的功能,用于连接多个逻辑上分开(不同网段192.168._._)的网络,

      ip地址在网络上可以定位一台机器,端口port能够在网络上定位一台机器上的一个服务
    本地回环地址:127.0.0.1          
    全网段地址:0.0.0.0
    保留字段:192.168.0.0 - 192.168.255.255
         172.16.0.0 - 172.32.255.255
         10.0.0.1 - 10.255.255.255

    TCP协议

    全双工通信 两台机器之间要想传递信息,必须先建立双向连接

    • 建立连接:3次握手, 断开连接:4次挥手

    • 在建立起连接之后 发送的每一条信息都有回执 为了保证数据的完整性,还有重传机制

    • 长连接 :会一直占用双方的端口

    • 特点:

      可靠 在连接的基础上数据传输,不会丢失,不被重复接收 慢 每一次发送的数据还要等待结果(三次握手)(四次挥手) 对传递数据的长短没有要求

    • 常用于发邮件,传文件时候

    UDP协议

    无连接

    • 机器之间传递信息不需确认建立连接,直接发送

    • 即时传递,不占用端口

    • 速度快,可能丢失,

    • 不能传输过长的数据(1500字节)

    • 常用于即时通信消息传输

    OSI七层协议

    • 协议就是标准,所有的计算机都就可以按照统一的标准去包装和识别信息,从而完成通信。

    通信: 数据传输经过一层层协议包装,直到物理层传输,接收信息通过反向的一层层协议解析来识别数据

     

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。帮助我们完成了所有协议信息的组织和拼接.在设计模式中,Socket其实就是一个门面模式,

    它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

    基于TCP协议的socket

    tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

    1 -------------------------client客户端------------------------------------
    2 import socket                        #导入套接口模块
    3 sk=socket.socket()                   #创建一个套接口(对象)
    4 sk.connect(('127.0.0.1',9999))       #要访问的服务器地址,共同端口
    5 sk.send('好不好'.encode('utf-8'))     #给服务器传送消息
    6 msg=sk.recv(1024).decode('utf-8')    #接收服务器消息
    7 print(msg)
    8 sk.close()                           #关闭套接口(与服务器连接对象)
    9 -------------------------------------------------------------------------
     1 #=======================serever服务器端==================================
     2 import socket                        #导入套接口模块
     3 sk = socket.socket()                 #创建一个套接口(对象)
     4 sk.bind(('127.0.0.1',9999))          #定自己的服务器地址,共同端口
     5                                          #(空.0.0.0自己电脑ip)
     6 sk.listen()                          #侦听服务器请求消息
     7 conn,addr=sk.accept()                #阻塞接口#接受连接,储存地址端口
     8 msg =conn.recv(1024).decode('utf-8') #阻塞#接受连接地址传入字节
     9 print(msg,addr)                           #打印消息
    10 conn.send('你好'.encode('utf-8'))    #给连接到(我)服务器的地址发消息
    11 conn.close()                         #关闭地址端口
    12 sk.close()                           #关闭套接口(对象接入)
    13 #=========================================================================

     socket对象,实际上是存储了所有的操作系统提供给我们的网络资源

     

    基于UDP协议的socket

    udp是无链接的,启动服务之后可以直接接受消息不需要提前建立链接

     1 #-----------------------------基于udp协议的客户端------------------------------------
     2 import socket
     3 sk=socket.socket(type=socket.SOCK_DGRAM)    #使用udp协议需指定socket参数type
     4 server_addr = ('127.0.0.1',9999)            #指定服务器地址端口
     5 content = input('>>>')
     6 sk.sendto(content.encode('utf-8'),server_addr) #直接给服务器地址传输数据
     7 msg=sk.recv(1024).decode('utf-8')              #接收来自服务器的数据
     8 print(msg)
     9 sk.close()
    10 #----------------------------------------------------------------------------------
     1 #=======================基于udp协议的服务器端=================================
     2 import socket
     3 sk=socket.socket(type=socket.SOCK_DGRAM)  #使用udp协议需指定socket参数type
     4 sk.bind(('127.0.0.1',9999))               #固定自己地址,给客户端链接用
     5 msg,client_addr=sk.recvfrom(1024)         #直接接受连接服务器的客户端数据
     6 print(msg.decode('utf-8'),client_addr)
     7 content = input('>>>')
     8 sk.sendto(content.encode('utf-8'),client_addr)#给客户端发消息
     9 sk.close()
    10 # 打印的地址是一个元祖
    11 #========================================================================

    两种协议都有使用场景,且可以循环传输,在连接建立后在输入与接收时建立while循环,同时可设置退出条件.

    tcp黏包

    黏包:tcp传输时数据通信是无消息保护边界的。

    tcp协议中,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。
    TCP协议发送时,由于TCP是数据流协议,#数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。
    • 1.小数据封包:

      • 发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。一次发送出去,这样接收方就收到了粘包数据。

    • 2.大数据拆包:

      • 当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

    • 3.接收方的缓存机制

      • 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

      总结

    黏包现象只发生在tcp协议中:

    1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

    2.'实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
    • 解决方法

    • 为了避免黏包,我们先制作一个包含传输信息的字典,

      • dumps为str字典,encode转码字节,len获取字节长度,

      • 用struck模块把字节长度转为4字节

    • 发送4字节包头长度信息---->对应接收4字节,struck解包长度

    • 再发送报头字典序列化.转码后信息--->接收解包长度报头内容,decode,loads

    • 最后发送内容--->提取报头字典内容组织接收信息长度

    • 我们还可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

      发送时接收时
      制作信息报头字典,dumps为str字典,  
      encode转码字节,len获取字节长度,  
      struck模块把字节长度转为4字节  
      先发4字节报头长度 先收4字节报头长度,用struct取出长度值
      再发送字典dumps.encode转码报头 再接收字节decode,loads报头字典,提取信息
      最后发真实内容 安排报头内容信息执行提取真实的数据

      import json,struct
      #假设通过客户端上传1MB:的文件a.txt
      
      #1.为避免粘包,必须自定制报头
      header={'file_size':1048576,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #数据大小(字节),文件路径和md5值
      
      #2.为了该报头能传送,需要序列化并且转为bytes
      head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输
      
      #3.用struck将报头长度这个数字转成固定长度:4个字节
      head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里指代一个数字,该数字是报头的长度,为了让客户端知道报头的长度,
      
      #4.客户端开始发送
      conn.send(head_len_bytes) #先发报头的长度,4个bytes
      conn.send(head_bytes)      #再发报头的字节格式
      conn.sendall(文件内容)         #然后发真实内容的字节格式
      
      #服务端开始接收
      #1.先收报头4个bytes,得到报头长度的字节格式
      head_len_bytes=s.recv(4) 
      x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度(_,)
      
      #2.按照报头长度x,收取报头的bytes格式
      head_bytes=s.recv(x) 
      header=json.loads(header_bytes.decode('utf-8')) #提取报头
      
      #3.最后根据报头细心提取真实的数据
      real_data_len=s.recv(header['file_size'])
      s.recv(real_data_len)    
  • 相关阅读:
    逆序数 POJ 2299 Ultra-QuickSort
    DP URAL 1244 Gentlemen
    找规律 SGU 107 987654321 problem
    找规律 SGU 126 Boxes
    DP VK Cup 2012 Qualification Round D. Palindrome pairs
    模拟 Coder-Strike 2014
    模拟 Codeforces Round #203 (Div. 2) C. Bombs
    DFS HDOJ 2614 Beat
    最短路(Floyd_Warshall) POJ 2240 Arbitrage
    最短路(Floyd_Warshall) POJ 1125 Stockbroker Grapevine
  • 原文地址:https://www.cnblogs.com/OB19227/p/10677615.html
Copyright © 2011-2022 走看看