• Python网络编程总结


    ----learn from luffycity----

    1. 什么是C/S架构?

    C指的是client(客户端软件),S指的是Server(服务端软件),C/S架构就是基于网络实现客户端与服务端通信的软件架构,能够实现服务端软件与客户端软件基于网络通信。
    View Code

     

     

    2. 互联网协议是什么?分别介绍五层协议中每一层的功能?

       网络就是物理链接介质+互联网协议。计算机之间的通信标准,这个标准称之为互联网协议,按照功能不同,人们将互联网协议分为 OSI七层 或 TCP/IP五层 或 四层(只需要掌握tcp/ip五层协议即可)

         

    (1)物理层

    物理层由来:上面提到,孤立的计算机之间要想通信,就必须接入internet,言外之意就是计算机之间必须完成组网

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

    (2)数据链路层

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

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

    以太网协议:

    早期的时候各个公司都有自己的分组方式,后来形成了统一的标准,即以太网协议ethernet

    ethernet规定

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

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

      • 发送者/源地址,6个字节
      • 接收者/目标地址,6个字节
      • 数据类型,6个字节

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

      • 数据包的具体内容

    head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送

    mac地址:

    head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址

    mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)

    广播:

    有了mac地址,同一网络内的两台主机就可以通信了(一台主机通过arp协议获取另外一台主机的mac地址)

    ethernet采用最原始的方式,广播的方式进行通信,即计算机通信基本靠吼

    (3)网络层

    网络层由来:有了ethernet、mac地址、广播的发送方式,世界上的计算机就可以彼此通信了,问题是世界范围的互联网是由

    一个个彼此隔离的小的局域网组成的,那么如果所有的通信都采用以太网的广播方式,那么一台机器发送的包全世界都会收到,

    这就不仅仅是效率低的问题了,这会是一种灾难

    上图结论:必须找出一种方法来区分哪些计算机属于同一广播域,哪些不是,如果是就采用广播的方式发送,如果不是,就采用路由的方式(向不同广播域/子网分发数据包),mac地址是无法区分的,它只跟厂商有关

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

    IP协议:
      • 规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示
      • 范围0.0.0.0-255.255.255.255
      • 一个ip地址通常写成四段十进制数,例:172.16.10.1

    子网掩码

    所谓”子网掩码”,就是表示子网络特征的一个参数。它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。比如,IP地址172.16.10.1,如果已知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。

    子网掩码是用来标识一个IP地址的哪些位是代表网络位,以及哪些位是代表主机位。子网掩码不能单独存在,它必须结合IP地址一起使用子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分

     

    区分网络位和主机位是为了划分子网,就是把一个大网络分成多个小网络,为什么要分子网呢?

    • 广播风暴:6万台主机在一个网段里,通信基本靠吼,任何一个人要吼一嗓子,6万多个人必须被动听着,一会你的网络就瘫痪啦。
    • 地址浪费:运营商在公网上有很多级联的路由器,有时候2个路由器之间只会用掉几个IP,如果不进行子网划分,那同网段的其它主机也就都不能用了。举例两个级联路由器的接口ip分别为222.34.24.12/24,222.34.24.13/24, 此可承载255个主机的网段只用了2个IP,那其它的就全浪费了,因为不能再分配给别人。

    划分子网本质上就是借主机位到给网络位,每借一位主机位,这个网段的可分配主机就会越少,比如192.168.1.0/24可用主机255个,借一位变成192.168.1.0/25,那可用主机就从255-128=127个了(从最大的值开始借),再借一位192.168.1.0/26,那可用主机数就变成了255-(128+64)=63个啦

    IP地址分类

    IP地址根据网络ID的不同分为5种类型,A类地址、B类地址、C类地址、D类地址和E类地址。

    1. A类IP地址:一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”, 地址范围从1.0.0.0 到126.0.0.0。可用的A类网络有126个,每个网络能容纳1亿多个主机。
    2. B类IP地址 :一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,地址范围从128.0.0.0到191.255.255.255。可用的B类网络有16382个,每个网络能容纳6万多个主机 。
    3. C类IP地址:一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”。范围从192.0.0.0到223.255.255.255。C类网络可达209万余个,每个网络能容纳254个主机。
    4. D类地址用于多点广播(Multicast): D类IP地址第一个字节以“lll0”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。
    5. E类IP地址 以“llll0”开始,为将来使用保留。

    全零(“0.0.0.0”)地址对应于当前主机。全“1”的IP地址(“255.255.255.255”)是当前子网的广播地址。

    回环地址(127.0.0.1) 又称为本机地址,那它跟0.0.0.0是什么区别呢?那得先了解回环接口

    环回接口(loopback)。平时我们用127.0.0.1来尝试自己的机器服务器好使不好使。走的就是这个loopback接口。对于环回接口,有如下三点值得注意:

      • 传给环回地址(一般是127.0.0.1)的任何数据均作为IP输入。
      • 传给广播地址或多播地址的数据报复制一份传给环回接口,然后送到以太网上。这是 因为广播传送和多播传送的定义包含主机本身。
      • 任何传给该主机IP地址的数据均送到环回接口。

    IP报文

    IP协议是TCP/IP协议的核心,所有的TCP,UDP,IMCP,IGCP的数据都以IP数据格式传输,要注意的是,IP不是可靠的协议,这是说,IP协议没有提供一种数据未传达以后的处理机制--这被认为是上层协议--TCP或UDP要做的事情。所以这也就出现了TCP是一个可靠的协议,而UDP就没有那么可靠的区别。这是后话,暂且不提。

    IP协议头

    挨个解释它是教科书的活计,我感兴趣的只是那八位的TTL字段,还记得这个字段是做什么的么?这个字段规定该数据包在穿过多少个路由之后才会被抛弃(这里就体现出来IP协议包的不可靠性,它不保证数据被送达),某个ip数据包每穿过一个路由器,该数据包的TTL数值就会减少1,当该数据包的TTL成为零,它就会被自动抛弃。这个字段的最大值也就是255,也就是说一个协议包也就在路由器里面穿行255次就会被抛弃了,根据系统的不同,这个数字也不一样,一般是32或者是64

    ARP协议

    arp协议由来:计算机通信基本靠吼,即广播的方式,所有上层的包到最后都要封装上以太网头,然后通过以太网协议发送,在谈及以太网协议时候,我门了解到

    通信是基于mac的广播方式实现,计算机在发包时,获取自身的mac是容易的,如何获取目标主机的mac,就需要通过arp协议

    arp协议功能:广播的方式发送数据包,获取目标主机的mac地址

    协议工作方式:每台主机ip都是已知的

    例如:主机172.16.10.10/24访问172.16.10.11/24

    一:首先通过ip地址和子网掩码区分出自己所处的子网

    场景数据包地址
    同一子网 目标主机mac,目标主机ip
    不同子网 网关mac,目标主机ip

    二:分析172.16.10.10/24与172.16.10.11/24处于同一网络(如果不是同一网络,那么下表中目标ip为172.16.10.1,通过arp获取的是网关的mac)

     源mac目标mac源ip目标ip数据部分
    发送端主机 发送端mac FF:FF:FF:FF:FF:FF 172.16.10.10/24 172.16.10.11/24 数据

    三:这个包会以广播的方式在发送端所处的子网内传输,所有主机接收后拆开包,发现目标ip为自己的,就响应,返回自己的mac

    查看本机arp表的命令

    Alexs-MacBook-Pro:~ alex$ arp -a
    ? (192.168.0.3) at 0:21:cc:65:52:f0 on en4 ifscope [ethernet]
    ? (192.168.0.64) at 68:f7:28:d0:4b:21 on en4 ifscope [ethernet]
    ? (192.168.0.233) at 84:d9:31:3:ae:8b on en4 ifscope [ethernet]
    ? (192.168.0.254) at 60:da:83:be:c2:5a on en4 ifscope [ethernet]
    ? (192.168.0.255) at (incomplete) on en4 ifscope [ethernet]
    ? (224.0.0.251) at 1:0:5e:0:0:fb on en4 ifscope permanent [ethernet]
    

    ICMP

    前面讲到了,IP协议并不是一个可靠的协议,它不保证数据被送达,那么,自然的,保证数据送达的工作应该由其他的模块来完成。其中一个重要的模块就是ICMP(网络控制报文)协议。

    当传送IP数据包发生错误--比如主机不可达,路由不可达等等,ICMP协议将会把错误信息封包,然后传送回给主机。给主机一个处理错误的机会.

    我们一般主要用ICMP协议检测网络是否通畅,基于ICMP协议的工具主要有ping 和traceroute

    ping

    Alexs-MacBook-Pro:~ alex$ ping www.baidu.com
    PING www.a.shifen.com (111.13.100.91): 56 data bytes
    64 bytes from 111.13.100.91: icmp_seq=0 ttl=54 time=6.563 ms
    64 bytes from 111.13.100.91: icmp_seq=1 ttl=54 time=6.320 ms
    64 bytes from 111.13.100.91: icmp_seq=2 ttl=54 time=6.698 ms
    64 bytes from 111.13.100.91: icmp_seq=3 ttl=54 time=6.445 ms
    ^C
    --- www.a.shifen.com ping statistics ---
    4 packets transmitted, 4 packets received, 0.0% packet loss
    round-trip min/avg/max/stddev = 6.320/6.506/6.698/0.140 ms
    

    ping这个单词源自声纳定位,而这个程序的作用也确实如此,它利用ICMP协议包来侦测另一个主机是否可达。原理是用类型码为0的ICMP发请 求,受到请求的主机则用类型码为8的ICMP回应。ping程序来计算间隔时间,并计算有多少个包被送达。用户就可以判断网络大致的情况。我们可以看到, ping给出来了传送的时间和TTL的数据。

    traceroute

    用来查看从当前主机到某地址一共经过多少跳路由

    $ traceroute www.baidu.com
    
    traceroute: Warning: www.baidu.com has multiple addresses; using 111.13.100.91
    traceroute to www.a.shifen.com (111.13.100.91), 64 hops max, 52 byte packets
     1  192.168.0.254 (192.168.0.254)  0.458 ms  0.274 ms  0.246 ms
     2  122.71.64.1 (122.71.64.1)  2.006 ms  1.788 ms  1.626 ms
     3  * 222.35.254.253 (222.35.254.253)  2.024 ms  2.243 ms
     4  222.35.254.241 (222.35.254.241)  9.333 ms
        61.233.9.50 (61.233.9.50)  4.960 ms
        61.233.9.93 (61.233.9.93)  3.010 ms
     5  61.237.2.202 (61.237.2.202)  3.442 ms  3.497 ms
    
     ......
    

    (4)传输层

     

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

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

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

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

    传输层有两种协议,TCP和UDP,见下图

    tcp协议

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

    以太网头ip 头tcp头数据

    为什么tcp是可靠的数据传输呢?

    最可靠的方式就是只要不得到确认,就重新发送数据报,直到得到对方的确认为止。

    tcp报文

    tcp的3次握手和4四挥手

    udp协议

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

    以太网头ip头udp头数据

    总结

    TCP协议虽然安全性很高,但是网络开销大,而UDP协议虽然没有提供安全机制,但是网络开销小,在现在这个网络安全已经相对较高的情况下,为了保证传输的速率,我们一般还是会优先考虑UDP协议!

     

    3. 基于tcp协议通信,为何建立链接需要三次握手,而断开链接却需要四次挥手

      因为建立链接时server端的ACK和SYN是一起发的,而断开时,为了保证数据的完整发送到client是分开发送的,详细

    4. 为何基于tcp协议的通信比基于udp协议的通信更可靠?

     TCP的方式就是只要不得到确认,就重新发送数据报,直到得到对方的确认为止。UDP发了就不管了。

    TCP VS UDP

     

    tcp基于链接通信

    1. 基于链接,则需要listen(backlog),指定连接池的大小
    2. 基于链接,必须先运行的服务端,然后客户端发起链接请求
    3. 对于mac系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)
    4. 对于windows/linux系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)

    udp无链接

    1. 无链接,因而无需listen(backlog),更加没有什么连接池之说了
    2. 无链接,udp的sendinto不用管是否有一个正在运行的服务端,可以己端一个劲的发消息,只不过数据丢失
    3. recvfrom收的数据小于sendinto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错
    4. 只有sendinto发送数据没有recvfrom收数据,数据丢失

    5‍.流式协议指的是什么协议,数据报协议指的是什么协议?

       TCP      UDP

    6.什么是socket?简述基于tcp协议的套接字通信流程

     

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。

     

     

    socket起源于Unix,而Unix/Linux 基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式 来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

     

    你想给另一台计算机发消息,你知道他的IP地址,他的机器上同时运行着qq、迅雷、word、浏览器等程序,你想给他的qq发消息,那想一下,你现在只能通过ip找到他的机器,但如果让这台机器知道把消息发给qq程序呢?答案就是通过port,一个机器上可以有0-65535个端口,你的程序想从网络上收发数据,就必须绑定一个端口,这样,远程发到这个端口上的数据,就全会转给这个程序啦

     

    Socket通信套路(10分钟)

    Socket套接字方法

     

    socket 实例类

     

    socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
    

     

    family(socket家族)

     

    • socket.AF_UNIX:用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成
    • socket.AF_INET:(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

     

    socket type类型

     

    • socket.SOCK_STREAM #for tcp
    • socket.SOCK_DGRAM #for udp
    • socket.SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
    • socket.SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
    • socket.SOCK_SEQPACKET #废弃了

     

    (Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)

     

    proto=0 请忽略,特殊用途

     

    fileno=None 请忽略,特殊用途

     

    服务端套接字函数

     

    • s.bind() 绑定(主机,端口号)到套接字
    • s.listen() 开始TCP监听
    • s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

     

    客户端套接字函数

     

    • s.connect() 主动初始化TCP服务器连接
    • s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

     

    公共用途的套接字函数

     

    • s.recv() 接收数据
    • s.send() 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面通过实例解释)
    • s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
    • s.recvfrom() Receive data from the socket. The return value is a pair (bytes, address)
    • s.getpeername() 连接到当前套接字的远端的地址
    • s.close() 关闭套接字
    • socket.setblocking(flag) #True or False,设置socket为非阻塞模式,以后讲io异步时会用
    • socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) 返回远程主机的地址信息,例子 socket.getaddrinfo('luffycity.com',80)
    • socket.getfqdn() 拿到本机的主机名
    • socket.gethostbyname() 通过域名解析ip地址

     

     

    7.什么是粘包? socket 中造成粘包的原因是什么? 哪些情况会发生粘包现象?

    所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

     

    接收方不知道接受消息的界限(可能没有接受完,留在管道里,下次会继续接受,和后面的黏在一起了)。另一方面是,底层优化算法,将时间间隔短,小的数据包合成一个包发送。接收端不知道是哪一次的,小的数据就会一次接受出现粘包

    总结

    1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
    2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
    3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。

     

    8.基于socket开发一个聊天程序,实现两端互相发送和接收消息

    import socket
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8083))
    
    while True:
        msg=input('>>: ').strip() #msg=''
        if not msg:continue
        phone.send(msg.encode('utf-8')) #phone.send(b'')
       
        data=phone.recv(1024)
       
        print(data.decode('utf-8'))
    
    phone.close()
    client
    import socket
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8083)) #0-65535:0-1024给操作系统使用
    phone.listen(5)
    
    print('starting...')
    while True: # 链接循环
        conn,client_addr=phone.accept()
        print(client_addr)
    
        while True: #通信循环
            try:
                data=conn.recv(1024)
                if not data:break #适用于linux操作系统
                print('客户端的数据',data)
                response = input('>>>:').strip()
                conn.send(response.encode())
            except ConnectionResetError: #适用于windows操作系统
                break
        conn.close()
    
    phone.close()
    server

     

    9.基于tcp socket,开发简单的远程命令执行程序,允许用户执行命令,并返回结果

    import socket
    import subprocess
    import struct
    import json
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.bind(('127.0.0.1',9909)) #0-65535:0-1024给操作系统使用
    phone.listen(5)
    
    print('starting...')
    while True: # 链接循环,保证客户端断开时,服务端不断开
        conn,client_addr=phone.accept()
        print(client_addr)
    
        while True: #通信循环
            try:
                #1、收命令
                cmd=conn.recv(8096)
                if not cmd:break #适用于linux操作系统
    
                #2、执行命令,拿到结果
                obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
    
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
    
                #3、把命令的结果返回给客户端
                #第一步:制作固定长度的报头(包含数据的长度)
                header_dic={
                    'filename':'%s.txt'%cmd,
                    'md5':'xxdxxx',
                    'total_size': len(stdout) + len(stderr)
                }
                header_json=json.dumps(header_dic)
                header_bytes=header_json.encode('utf-8')
    
                #第二步:利用struct后,将处理后的报头长度发送(这样报头长度固定的)
                conn.send(struct.pack('i',len(header_bytes)))
                #json处理后,不用担心数据长度过长,导致使用struct出错
    
                #第三步:再发报头(包含数据的长度)
                conn.send(header_bytes)
    
                #第四步:再发送真实的数据(直接发数据)
                conn.send(stdout)
                conn.send(stderr)
    
            except ConnectionResetError: #适用于windows操作系统
                break
        conn.close()
    
    phone.close()
    ssh server
    import socket
    import struct
    import json
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    phone.connect(('127.0.0.1',9909))
    
    while True:
        #1、发命令
        cmd=input('>>: ').strip() #ls /etc
        if not cmd:continue
        phone.send(cmd.encode('utf-8'))
    
        #2、拿命令的结果,并打印
    
        #第一步:先收报头的长度
        obj=phone.recv(4)
        header_size=struct.unpack('i',obj)[0]
    
        #第二步:再收报头(此处的报头是struct处理过的)
        header_bytes=phone.recv(header_size)
    
        #第三步:从报头中解析出对真实数据的描述信息
        header_json=header_bytes.decode('utf-8')
        header_dic=json.loads(header_json)
        print(header_dic)
        total_size=header_dic['total_size']
    
        #第四步:接收真实的数据
        recv_size=0
        recv_data=b''
        while recv_size < total_size:
            res=phone.recv(1024) 
            recv_data+=res
            recv_size+=len(res)
    
        print(recv_data.decode('utf-8'))
    
    phone.close()
    ssh client

    10.基于tcp协议编写简单FTP程序,实现上传、下载文件功能,并解决粘包问题

    import socket
    import struct
    import json
    import os
    
    BASE_DIR = os.path.dirname(__file__)
    db_dir=os.path.join(BASE_DIR,'download')
    def get(phone,cmds):
        # 2、以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户的新文件
        # 第一步:先收报头的长度
        obj = phone.recv(4)
        header_size = struct.unpack('i', obj)[0]
    
        # 第二步:再收报头
        header_bytes = phone.recv(header_size)
    
        # 第三步:从报头中解析出对真实数据的描述信息
        header_json = header_bytes.decode('utf-8')
        header_dic = json.loads(header_json)
        '''
                header_dic={
                    'filename': filename, #'filename':'1.mp4'
                    'md5':'xxdxxx',
                    'file_size': os.path.getsize(filename)
                }
        '''
        print(header_dic)
        total_size = header_dic['file_size']
        filename = header_dic['filename']
    
        # 第四步:接收真实的数据
        with open('%s/%s' % (db_dir, filename), 'wb') as f:
            recv_size = 0
            while recv_size < total_size:
                line = phone.recv(1024)  # 1024是一个坑
                f.write(line)
                recv_size += len(line)
                print('总大小:%s   已下载大小:%s' % (total_size, recv_size))
    
    def put(conn,cmds):
        filename = cmds[1]
    
        # 3、以读的方式打开文件,读取文件内容发送给客户端
        # 第一步:制作固定长度的报头
        header_dic = {
            'filename': filename,  # 'filename':'1.mp4'
            'md5': 'xxdxxx',
            'file_size': os.path.getsize(r'%s/%s' % (db_dir, filename))
        # os.path.getsize(r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/server/share/1.mp4')
        }
    
        header_json = json.dumps(header_dic)
    
        header_bytes = header_json.encode('utf-8')
    
        # 第二步:先发送报头的长度
        conn.send(struct.pack('i', len(header_bytes)))
    
        # 第三步:再发报头
        conn.send(header_bytes)
    
        # 第四步:再发送真实的数据
        with open('%s/%s' % (db_dir, filename), 'rb') as f:
            # conn.send(f.read())
            for line in f:
                conn.send(line)
    
    def run():
        phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
        phone.connect(('127.0.0.1',8912))
    
        while True:
            #1、发命令
            inp=input('>>: ').strip() #get a.txt
            if not inp:continue
            phone.send(inp.encode('utf-8'))
    
            cmds=inp.split() #['get','a.txt']
            if cmds[0] == 'get':
                get(phone,cmds)
            elif cmds[0] == 'put':
                put(phone,cmds)
    
        phone.close()
    
    
    
    if __name__ == '__main__':
        run()
    ftp client
    import socket
    import subprocess
    import struct
    import json
    import os
    
    BASE_DIR = os.path.dirname(__file__)
    db_dir=os.path.join(BASE_DIR,'share')
    
    def get(conn,cmds):
        filename = cmds[1]
    
        # 3、以读的方式打开文件,读取文件内容发送给客户端
        # 第一步:制作固定长度的报头
        header_dic = {
            'filename': filename,  # 'filename':'1.mp4'
            'md5': 'xxdxxx',
            'file_size': os.path.getsize(r'%s/%s' % (db_dir, filename))
        # os.path.getsize(r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/server/share/1.mp4')
        }
    
        header_json = json.dumps(header_dic)
    
        header_bytes = header_json.encode('utf-8')
    
        # 第二步:先发送报头的长度
        conn.send(struct.pack('i', len(header_bytes)))
    
        # 第三步:再发报头
        conn.send(header_bytes)
    
        # 第四步:再发送真实的数据
        with open('%s/%s' % (db_dir, filename), 'rb') as f:
            # conn.send(f.read())
            for line in f:
                conn.send(line)
    
    def put(phone,cmds):
        # 2、以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户的新文件
        # 第一步:先收报头的长度
        obj = phone.recv(4)
        header_size = struct.unpack('i', obj)[0]
    
        # 第二步:再收报头
        header_bytes = phone.recv(header_size)
    
        # 第三步:从报头中解析出对真实数据的描述信息
        header_json = header_bytes.decode('utf-8')
        header_dic = json.loads(header_json)
        '''
                header_dic={
                    'filename': filename, #'filename':'1.mp4'
                    'md5':'xxdxxx',
                    'file_size': os.path.getsize(filename)
                }
        '''
        print(header_dic)
        total_size = header_dic['file_size']
        filename = header_dic['filename']
    
        # 第四步:接收真实的数据
        with open('%s/%s' % (db_dir, filename), 'wb') as f:
            recv_size = 0
            while recv_size < total_size:
                line = phone.recv(1024)  # 1024是一个坑
                f.write(line)
                recv_size += len(line)
                print('总大小:%s   已下载大小:%s' % (total_size, recv_size))
    
    def run():
        phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        phone.bind(('127.0.0.1',8912)) #0-65535:0-1024给操作系统使用
        phone.listen(5)
        print('starting...')
        while True: # 链接循环
            conn,client_addr=phone.accept()
            print(client_addr)
            while True: #通信循环
                try:
                    #1、收命令
                    res=conn.recv(8096) # b'put 1.mp4'
                    if not res:break #适用于linux操作系统
                    #2、解析命令,提取相应命令参数
                    cmds=res.decode('utf-8').split() #['put','1.mp4']
                    if cmds[0] == 'get':
                        get(conn,cmds)
                    elif cmds[0] == 'put':
                        put(conn,cmds)
                except ConnectionResetError: #适用于windows操作系统
                    break
            conn.close()
    
        phone.close()
    
    
    if __name__ == '__main__':
        run()
    ftp server

     

    基于udp协议编写程序,实现功能

     

    1. 执行指定的命令,让客户端可以查看服务端的时间
    2. 执行指定的命令,让客户端可以与服务的的时间同步
  • 相关阅读:
    Python基础语法 第2节课(数据类型转换、运算符、字符串)
    python基础语法 第5节课 ( if 、 for )
    python基础语法 第4节课 (字典 元组 集合)
    Python基础语法 第3节课 (列表)
    A. Peter and Snow Blower 解析(思維、幾何)
    C. Dima and Salad 解析(思維、DP)
    D. Serval and Rooted Tree (樹狀DP)
    C2. Balanced Removals (Harder) (幾何、思維)
    B. Two Fairs 解析(思維、DFS、組合)
    D. Bash and a Tough Math Puzzle 解析(線段樹、數論)
  • 原文地址:https://www.cnblogs.com/wenyule/p/9105999.html
Copyright © 2020-2023  润新知