• 网络编程


    知识储备

    C/S和B/S架构

    1 # C/S架构
    2 client<---->server
    3 # B/S架构
    4 browser<---->server
    View Code

    物理层:

    物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0

    数据链路层

    数据链路层由来:单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思

    数据链路层功能:定义了电信号的分组方式

    以太网协议:

    形成统一标准,以太网协议ethernet

    ethernet规定

    • 一组电信号构成一个数据包,叫做’帧‘
    • 每一数据帧分成:包头head和数据data两部分

    head包含:(固定18个字节)

    • 源,6个字节
    • 目,6个字节
    • 数据类型,6个字节

    data包含:(最短46字节,最长1500字节)

    • 数据包的具体内容
    head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送
    数据包内容 

    网络层

    网络层由来:有了mac地址,同一网络内的两台主机就可以通信了(一台主机通过arp协议获取另外一台主机的mac地址),ethernet采用最原始的方式,广播的方式进行通信,即计算机通信基本靠吼。

    现在还需要一种手段来判定,必须找出一种方法来区分哪些计算机属于同一广播域,哪些不是,如果是就采用广播的方式发送,如果不是,就采用路由的方式(向不同广播域/子网分发数据包)。

    网络层功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址

    传输层

    传输层的由来:传输层以下都是由操作系统控制,网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,

    那么我们通过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口端口即应用程序与网卡关联的编号

    传输层功能:建立端口到端口的通信

    补充:端口范围0-65535,0-1023为系统占用端口

    tcp协议:

    可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。

    以太网头 ip 头               tcp头               数据                                                    

    udp协议:

    不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。

    以太网头 ip头                      udp头                            数据                                           

    tcp报文

    tcp三次握手和四次挥手

    tcp建立的是双向链接

    C -----------------------》 S
    C <----------------------- S
    TCP建立的是双向链接
    C ---- syn=1 seq=x ----> S
    S ---- ack=1+x syn=1 seq=y ----> C
    C ---- ack=1+y ----> S
    TCP三次握手连接
    S ---- fin=1 ---->C
    C ---- ack=1 ----> S
    C ---- fin=1 ----> S
    S ---- ack=1 ----> C
    四次挥手断连

    应用层

    应用层由来:用户使用的都是应用程序,均工作于应用层,互联网是开发的,大家都可以开发自己的应用程序,数据多种多样,必须规定好数据的组织形式 

    应用层功能:规定应用程序的数据格式。

    例:TCP协议可以为各种各样的程序传递数据,比如Email、WWW、FTP等等。那么,必须有不同协议规定电子邮件、网页、FTP数据的格式,这些应用程序协议就构成了”应用层”。

     

     TCP/UDP

     什么是网络?

    1、物理连接介质;

    2、互联网协议。

     MAC+IP:定位全世界一台独一无二的计算机

    MAC+IP+PORT:定位全世界一台独一无二的计算机上的程序

    但在程序中,因为操作系统配备了ARP协议,可实现IP和MAC的转换

    总结:因此【IP+PORT】即可定位定位全世界一台独一无二的计算机上的程序

    TCP UDP
    TCP与UDP基本区别
      1.基于连接与无连接
      2.TCP要求系统资源较多,UDP较少; 
      3.UDP程序结构较简单 
      4.流模式(TCP)与数据报模式(UDP); 
      5.TCP保证数据正确性,UDP可能丢包 
      6.TCP保证数据顺序,UDP不保证 
      
    UDP应用场景:
      1.面向数据报方式
      2.网络数据大多为短消息 
      3.拥有大量Client
      4.对数据安全性无特殊要求
      5.网络负担非常重,但对响应速度要求高
     
    具体编程时的区别
       1.socket()的参数不同 
       2.UDP Server不需要调用listen和accept 
       3.UDP收发数据用sendto/recvfrom函数 
       4.TCP:地址信息在connect/accept时确定 
       5.UDP:在sendto/recvfrom函数中每次均 需指定地址信息 
       6.UDP:shutdown函数无效
     
    编程区别
       通常我们在说到网络编程时默认是指TCP编程,即用前面提到的socket函数创建一个socket用于TCP通讯,函数参数我们通常填为SOCK_STREAM。即socket(PF_INET, SOCK_STREAM, 0),这表示建立一个socket用于流式网络通讯。 
       SOCK_STREAM这种的特点是面向连接的,即每次收发数据之前必须通过connect建立连接,也是双向的,即任何一方都可以收发数据,协议本身提供了一些保障机制保证它是可靠的、有序的,即每个包按照发送的顺序到达接收方。 
    
      而SOCK_DGRAM这种是User Datagram Protocol协议的网络通讯,它是无连接的,不可靠的,因为通讯双方发送数据后不知道对方是否已经收到数据,是否正常收到数据。任何一方建立一个socket以后就可以用sendto发送数据,也可以用recvfrom接收数据。根本不关心对方是否存在,是否发送了数据。它的特点是通讯速度比较快。大家都知道TCP是要经过三次握手的,而UDP没有。 
    
    基于上述不同,UDP和TCP编程步骤也有些不同,如下:
    
    TCP: 
    TCP编程的服务器端一般步骤是: 
      1、创建一个socket,用函数socket(); 
      2、设置socket属性,用函数setsockopt(); * 可选 
      3、绑定IP地址、端口等信息到socket上,用函数bind(); 
      4、开启监听,用函数listen(); 
      5、接收客户端上来的连接,用函数accept(); 
      6、收发数据,用函数send()和recv(),或者read()和write(); 
      7、关闭网络连接; 
      8、关闭监听; 
    
    TCP编程的客户端一般步骤是: 
      1、创建一个socket,用函数socket(); 
      2、设置socket属性,用函数setsockopt();* 可选 
      3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 
      4、设置要连接的对方的IP地址和端口等属性; 
      5、连接服务器,用函数connect(); 
      6、收发数据,用函数send()和recv(),或者read()和write(); 
      7、关闭网络连接;
    
    UDP:
    与之对应的UDP编程步骤要简单许多,分别如下: 
      UDP编程的服务器端一般步骤是: 
      1、创建一个socket,用函数socket(); 
      2、设置socket属性,用函数setsockopt();* 可选 
      3、绑定IP地址、端口等信息到socket上,用函数bind(); 
      4、循环接收数据,用函数recvfrom(); 
      5、关闭网络连接; 
    
    UDP编程的客户端一般步骤是: 
      1、创建一个socket,用函数socket(); 
      2、设置socket属性,用函数setsockopt();* 可选 
      3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 
      4、设置对方的IP地址和端口等属性; 
      5、发送数据,用函数sendto(); 
      6、关闭网络连接;
    
    TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。
    
    UDP补充:
       UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。
    
    TCP补充:
      TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。
    
    
    TCP与UDP区别总结:
    1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
    2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保   证可靠交付
    3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
      UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
    4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
    5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
    6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
    TCP/UDP 编程区别

    套接字

     TCP套接字工作流程:

     套接字程序编码:

      1、client.connect() ----》server.accept(),此过程就实现封装三次握手过程

      2、服务器端的conn套接字对象起到收发消息的作用,即可收消息,可发消息

    socket服务端(单步)详细步骤说明:

     1 import socket
     2 
     3 # 买电话
     4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     5 # 默认socket.AF_INET
     6 # socket.SOCK_STREAM称为流式协议(TCP),socket.SOCK_DGRAM为UDP协议
     7 
     8 # 插手机卡
     9 phone.bind(('127.0.0.1',8080)) # 手机卡里面关键是绑定自己的手机号,教室IP:192.168.12.112,端口,0-65535,0-1024给系统用
    10 
    11 # 开机
    12 phone.listen(5) # 同一时间请求数,开启半链接池,以后一般都是写在配置文件里,并不是只能建立5个链接,
    13 print('start...')
    14 
    15 # 等电话链接
    16 conn,client_addr = phone.accept() # conn,套接字的收发消息对象,客户端client_addr(随机变)
    17 print('链接来了:',conn,client_addr)
    18 
    19 # 收发消息
    20 msg = conn.recv(1024) #收消息,1024是一个最大的限制,这里假设不超过1024的情况
    21 print('客户端的消息:',msg)
    22 conn.send(msg.upper())
    23 
    24 # 挂电话
    25 conn.close() #回收系统资源,跟打开文件后关闭文件是一个道理
    26 
    27 # 关机
    28 phone.close()
    Socket服务端(单步)详细说明

    服务端Sever(循环):

     1 import socket
     2 
     3 # 买电话
     4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     5 
     6 # 插手机卡
     7 phone.bind(('127.0.0.1',8081))
     8 
     9 # 开机
    10 phone.listen(5)
    11 print('start...')
    12 # 链接循环
    13 while True:
    14     conn,client_addr = phone.accept()
    15     print('客户端:',client_addr)
    16 
    17     # 收发消息,通信循环
    18     while True:
    19         try:
    20             msg = conn.recv(1024) #收消息,1024是一个最大的限制,这里假设不超过1024的情况
    21             print('客户端的消息:',msg)
    22             conn.send(msg.upper())
    23         except ConnectionResetError:
    24             break # 此刻异常出现,conn是无意义对象,故不可用continue,而采用break
    25 
    26     conn.close()
    27     # 运行至此步,进行下一步链接
    28 
    29 phone.close()
    Socket服务器端

    客户端Client:

    (补充:程序结束后,回收程序资源的同时也要回收系统资源。最后应补充一段代码:client.close())

     1 import socket
     2 
     3 # 买电话
     4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     5 
     6 # 拨电话,服务端IP和端口
     7 phone.connect(('127.0.0.1',8081))
     8 
     9 # 发消息
    10 while True:
    11 
    12     msg = input('>>>:').strip()
    13     phone.send(msg.encode('utf-8'))
    14     # 收消息
    15     data = phone.recv(1024)
    16     print(data.decode('utf-8'))
    Socket客户端

     编写C/S架构的程序,实现远程执行命令: 

    #subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码
     1 import socket
     2 import subprocess
     3 
     4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     5 phone.bind(('127.0.0.1',8082))
     6 phone.listen(5)
     7 print('连线中...')
     8 
     9 while True:
    10     conn,client_addr = phone.accept()
    11     print('建立连接客户端:',client_addr)
    12 
    13     while True:
    14         try:
    15             cmd = conn.recv(1024)
    16             print('客户端的消息:',cmd.decode('utf-8'))
    17             obj = subprocess.Popen(cmd.decode('utf-8'),
    18                              shell=True,
    19                              stdout=subprocess.PIPE,
    20                              stderr=subprocess.PIPE
    21                              )
    22             stdout = obj.stdout.read() # 成功结果
    23             stderr = obj.stderr.read() # 错误结果
    24 
    25             conn.send(stdout+stderr)
    26 
    27         except ConnectionResetError:
    28             print('一个客户端中止链接')
    29             break
    30     conn.close()
    31 
    32 phone.close()
    远程执行命令-服务器端
     1 import socket
     2 import subprocess
     3 
     4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     5 phone.connect(('127.0.0.1',8082))
     6 
     7 
     8 while True:
     9     cmd = input('请输入通信内容>>:').strip()
    10 
    11     phone.send(cmd.encode('utf-8'))
    12     msg = phone.recv(1024)
    13     print(msg.decode('gbk')) # 服务器端返回命令行内容,需gbk解码
    14 
    15 phone.close()
    远程执行命令-客户端

    粘包问题

    这里,会出现一个问题,当命令执行结果大于1024字节时,会有消息冗余问题。

    在找到解决方案前,需了解,不管收发,其实数据都是先要经过应用程序内存操作系统缓存的处理过程。 

    其次,tcp流协议有两个特点:

    1,像水流一样,源源不断从源头向对方堆砌数据

    2,negal算法:优化数据传输,将时间间隔比较短和数据比较小的数据预留

    那么,现在只要知道对方要发多少数据,那么我就能知道如何判断是否收到全部数据

    在此之前补充一下,使用stuct模块可高度定制化的报头数据

    最后,解决粘包的方法见下:

     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 # @Time    : 2018/04/19 14:03
     4 # @Author  : MJay_Lee
     5 # @File    : Server.py
     6 # @Contact : limengjiejj@hotmail.com
     7 
     8 import socket
     9 import subprocess
    10 import struct
    11 import json
    12 
    13 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    14 phone.bind(('127.0.0.1',8082))
    15 phone.listen(5)
    16 print('连线中...')
    17 
    18 while True:
    19     conn,client_addr = phone.accept()
    20     print('建立连接客户端:',client_addr)
    21 
    22     while True:
    23         try:
    24             cmd = conn.recv(1024)
    25             print('客户端的消息:',cmd.decode('utf-8'))
    26             obj = subprocess.Popen(cmd.decode('utf-8'),
    27                              shell=True,
    28                              stdout=subprocess.PIPE,
    29                              stderr=subprocess.PIPE
    30                              )
    31             stdout = obj.stdout.read() # 成功结果
    32             stderr = obj.stderr.read() # 错误结果
    33 
    34             print(len(stdout)+len(stderr)) # 打印出服务器端返回消息的长度
    35             # conn.send(stdout+stderr)
    36 
    37 
    38             #1、发送数据长度,发送数据长度,制作报头
    39             header_dic = {
    40                 'total_size':len(stdout) + len(stderr),
    41                 'md5':'asdasdqasdasd',
    42                 'filename':'test.txt'
    43             }
    44             header_json = json.dumps(header_dic)
    45             header_bytes = header_json.encode('utf-8')
    46 
    47             # 2 先发送报头的长度
    48             header_size = len(header_bytes)
    49             conn.send(struct.pack('i',header_size))
    50 
    51             #3、发送报头
    52             conn.send(header_bytes)
    53 
    54             #4、发送真实数据
    55             conn.send(stdout)
    56             conn.send(stderr)
    57 
    58         except ConnectionResetError:
    59             print('一个客户端中止链接')
    60             break
    61     conn.close()
    62 phone.close()
    服务器端终极版
     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 # @Time    : 2018/04/19 14:03
     4 # @Author  : MJay_Lee
     5 # @File    : Client.py
     6 # @Contact : limengjiejj@hotmail.com
     7 
     8 import socket
     9 import subprocess
    10 import struct
    11 import json
    12 
    13 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    14 client.connect(('127.0.0.1',8082))
    15 
    16 
    17 while True:
    18     cmd = input('请输入通信内容>>:').strip()
    19     client.send(cmd.encode('utf-8'))
    20     #1、先收报头长度
    21     header_size = struct.unpack('i',client.recv(4))[0]
    22     #2、接收报头
    23     header_bytes = client.recv(header_size)
    24     #3、解析报头
    25     header_json = header_bytes.decode('utf-8')
    26     header_dic = json.loads(header_json)
    27     print(header_dic)
    28 
    29 
    30     total_size = header_dic['total_size']
    31     print(total_size)
    32     #4、根据报头内的信息,收取真实的数据
    33     recv_size = 0
    34     res = b''
    35     while recv_size < total_size:
    36         recv_data = client.recv(1024)
    37         res += recv_data
    38         recv_size += len(recv_data)
    39     print(res.decode('gbk'))
    40 
    41 client.close()
    客户端终极版

    补充:客户端连接个数超出服务器设置的backlog值,出现:“ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。”,解决方案如下“优化客户端终极版”

     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 # @Time    : 2018/04/23 14:08
     4 # @Author  : MJay_Lee
     5 # @File    : client.py
     6 # @Contact : limengjiejj@hotmail.com
     7 
     8 import socket
     9 import time
    10 import json
    11 import struct
    12 
    13 while True:
    14     try:
    15         client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    16         client.connect(('127.0.0.1', 8080))
    17         break
    18     except ConnectionRefusedError:
    19         print('等待3秒')
    20         time.sleep(3)
    21 
    22 
    23 while True:
    24     cmd = input('请输入通信内容:').strip()
    25     if not cmd:continue
    26     client.send(cmd.encode('utf-8'))
    27     # 获得报头长度
    28     header_size = struct.unpack('i',client.recv(4))[0]
    29     # 获取报头
    30     header_bytes = client.recv(header_size)
    31     # 解析报头
    32     header_json = header_bytes.decode('utf-8')
    33     header_dic = json.loads(header_json)
    34 
    35     total_size = header_dic['header_len']
    36     recv_size = 0
    37     res = b''
    38 
    39     while recv_size < total_size:
    40         recv_data = client.recv(1024)
    41         res += recv_data
    42         recv_size += len(recv_data)
    43 
    44     print('来自服务器的消息:',res.decode('gbk'))
    45 
    46 client.close()
    优化客户端终极版

     基于UDP协议通信的套接字

    相比较于UDP,TCP之所以可靠,是因为TCP的工作原理(每发一个数据,得到回应,才会清掉系统缓存里的数据。),而非多一个链接。

    场景:多用于查询场景,可靠性要求不高

    注意:UDP协议发消息的时候,有效传输最大51bytes,否则就容易出现丢包

    套接字常识:WEB服务端端口80,DNS端口53

    进程

    三个状态

    正文请移步至此

  • 相关阅读:
    温故而知新-错误和异常处理
    温故而知新-面向对象的PHP
    Django框架之模板语法【转载】
    django2.0实现数据详情页展示的流程
    django2.0表的ORM字段类型和展示
    Fatal error: Cannot use object of type PHPExcel_RichText as array
    django2.0数据展示流程
    django2.0模板相关设置
    django2.0新增功能流程
    django2.0设置默认访问路由
  • 原文地址:https://www.cnblogs.com/limengjie0104/p/8873157.html
Copyright © 2020-2023  润新知