第三模块:面向对象&网络编程基础 第2章 网络编程

  • 01-计算机基础
  • 02-什么是网络
  • 03-五层协议详解
  • 04-传输层详解
  • 05-什么是Socket
  • 06-基于socket实现简单套接字通信
  • 07-在简单套接字基础上加上通信循环
  • 08-客户端与服务端代码bug修复
  • 09-实现服务端对多个客户端提供服务
  • 10-模式ssh远程执行命令-项目分析
  • 11-模拟ssh远程执行命令-代码实现
  • 12-粘包现象
  • 13-粘包底层原理分析
  • 14-解决粘包问题-伪代码实现
  • 15-解决粘包问题-简单版本
  • 16-解决粘包问题-终极版本
  • 17-文件传输功能实现
  • 18-文件传输功能-函数版
  • 19-文件传输功能-面向对象版
  • 20-基于UDP协议的套接字介绍
  • 21-作业介绍

01-计算机基础

1、1)计算机硬件->2)安装操作系统(Windows、Linux、MacOS)->3)安装应用软件(腾讯视频、QQ、Wechat、Office)

2、三层的架构:硬件、系统软件、应用软件;

3、人可以指挥机器,掌握一种机器能听懂的表达方式;实现用机器解放人类劳动力的替代;

4、计算机硬件是被操作系统操控的,应用软件是基于操作系统运行的;

5、有专人去开发OS-操作系统,我们通过接口调用操作系统,定位是:一名应用程序员;间接地去控制硬件;

6、操作系统充当一个代理的角色,生活中代理无处不在,反映了哲学的设计思想;

7、C/S架构软件,基于网络通信;学习Python是为了成为一名应用开发程序员;

数据的流向:Client-》客户端OS-》PC机-》网络设备-》服务器-》服务器OS-》Server

02-什么是网络

1、计算机通信比喻——电话通信;

1)生活中通信举例:

  • 河北人给辽宁人打电话(有线);
  • 知道对方的电话号码;
  • 说东北话,因为可能听不懂河北话(方言);
  • 大家都遵循的标准——普通话;

  • 全世界沟通交流的统一标准-英语(English)

2)计算机与计算机之间通信举例;

  • 网线连接,进行通信;
  • 计算机与计算机之间通信交流的标准-互联网协议;
  • 互联网中的协议,就是计算机界的“英语”;
  • 互联网协议可分为7层、5层或4层;

3)互联网协议-OSI七层;

  • 应用层-L7
  • 表示层-L6
  • 会话层-L5
  • 传输层-L4
  • 网络层-L3
  • 数据链路层-L2
  • 物理层-L1

03-五层协议详解

1、网络的概念;

1)、就是底层的物理链接介质+互联网协议;

2)、互联网协议就是计算机界的“英语”;

2、互联网5层协议详解;

1)、物理层-L1;

功能:发送电信号0101110001;

2)、数据链路层-L2;

形成了统一的标准-Ethernet(以太网协议);

计算机通信基本靠吼;

3)、网络层-L3;

引出IP地址的概念;

4)、传输层-L4

TCP、UDP协议;

引出端口号的概念;1~65535;

IP+Port可以表示全世界范围内唯一一个软件;

一般来说,服务端会绑定IP和端口;而客户端不需要;

5)、应用层-L5(会话、表示、应用层)

HTTP、FTP;

04-传输层详解

1、自定义与他定义协议两大类;

2、TCP协议属于是流式协议,数据传输需要建立管道;

3、TCP/IP的三次握手,四次挥手;

4、TCP叫做可靠传输协议;

5、UDP协议属于不可靠协议;

6、UDP协议传输效率高,TCP协议效率低;

05-什么是Socket

1、什么是Socket(套接字);

  • 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个端口,你的程序想从网络上收发数据,就必须绑定一个端口,这样,远程发到这个端口上的数据,就全会转给这个程序啦

2、Socket通信套路;

  • 当通过socket建立起2台机器的连接后,本质上socket只干2件事,一是收数据,一是发数据,没数据时就等着。
  • socket 建立连接的过程跟我们现实中打电话比较像,打电话必须是打电话方和接电话方共同完成的事情,我们分别看看他们是怎么建立起通话的

1)、接电话方:

1.首先你得有个电话

2.你的电话要有号码

3.你的电话必须连上电话线

4.开始在家等电话

5.电话铃响了,接起电话,听到对方的声音

2)、打电话方:

1.首先你得有个电话

2.输入你想拨打的电话

3.等待对方接听

4.say “hi 约么,我有七天酒店的打折卡噢~5.等待回应——》响应回应——》等待回应。。。。

把它翻译成socket通信

1)、接电话方(socket服务器端):

1.首先你得有个电话\(生成socket对象\)

2.你的电话要有号码\(绑定本机ip+port\)

3.你的电话必须连上电话线\(连网\)

4.开始在家等电话\(开始监听电话listen\)

5.电话铃响了,接起电话,听到对方的声音\(接受新连接\)

2)、打电话方(socket客户端)

1.首先你得有个电话\(生成socket对象\)

2.输入你想拨打的电话\(connect 远程主机ip+port\)

3.等待对方接听

4.say “hi 约么,我有七天酒店的打折卡噢~”\(send\(\) 发消息。。。\)

5.等待回应——》响应回应——》等待回应。。。。

 

 

06-基于socket实现简单套接字通信

1、简单的套接字通信;

1)server.py;

import socket

#1、买手机;
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#2、绑定手机卡;
phone.bind(('127.0.0.1',18081))#0~65535,其中0~1024,系统使用


#3、开机;
phone.listen(5)

#4、等电话来连接;
print('starting....')
# cxz = phone.accept()
# print(cxz)
conn,client_addr = phone.accept()

#5、收、发消息;
data = conn.recv(1024)#1、单位bytes;2、1024代表最大接受1024个bytes;
print('客户端数据',data)
conn.send(data.upper())

#6、挂电话;
conn.close()
#7、关机;
phone.close()

2) client.py

import socket

#1、买手机;
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#print(phone)#<socket.socket fd=428, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>

#2、拨号;
phone.connect(('127.0.0.1',18081))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。

#3、发、收消息;
phone.send('hello'.encode('utf-8'))
data = phone.recv(1024)
print(data)
#4、关机;
phone.close()

07-在简单套接字基础上加上通信循环

1、加上循环通信;

1)server.py

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',18080))#0~65535,其中0~1024,系统使用;
phone.listen(5)
print('starting....')
conn,client_addr = phone.accept()
print(client_addr)

while True:
    data = conn.recv(1024)#1、单位bytes;2、1024代表最大接受1024个bytes;
    print('客户端数据',data)
    conn.send(data.upper())
conn.close()
phone.close()

2)client.py

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',18080))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    msg = input('>>>:').strip()
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data)
phone.close()

08-客户端与服务端代码bug修复

1、server.py

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',18080))#0~65535,其中0~1024,系统使用;
phone.listen(5)
print('starting....')
conn,client_addr = phone.accept()
print(client_addr)

while True:
    try:
        data = conn.recv(1024)#1、单位bytes;2、1024代表最大接受1024个bytes;
        if not data:break#仅适用于Linux操作系统;
        print('客户端数据',data)
        conn.send(data.upper())
    except ConnectionResetError:#适用于Windows操作系统;
        break
conn.close()
phone.close()

2、client.py

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',18080))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    msg = input('>>>:').strip()
    if not msg:continue
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data.decode('utf-8'))

phone.close()

09-实现服务端对多个客户端提供服务

1、实现链接循环;

1)server.py

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',18080))#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)#1、单位bytes;2、1024代表最大接受1024个bytes;
            if not data:break#仅适用于Linux操作系统;
            print('客户端数据',data)
            conn.send(data.upper())
        except ConnectionResetError:#适用于Windows操作系统;
            break
    conn.close()
phone.close()

 

2)client1.py

 

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',18080))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    msg = input('>>>:').strip()
    if not msg:continue
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data.decode('utf-8'))

phone.close()

3)client2.py

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',18080))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    msg = input('>>>:').strip()
    if not msg:continue
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data.decode('utf-8'))

phone.close()

10-模式ssh远程执行命令-项目分析

11-模拟ssh远程执行命令-代码实现

1、server.py;

import socket
import os
import subprocess

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',18080))#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(1024)#1、单位bytes;2、1024代表最大接受1024个bytes;
            if not cmd:break#仅适用于Linux操作系统;
            print('客户端数据',cmd)
            #2、执行命令,拿到结果;
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            # obj = subprocess.Popen('xxxxxipconfig',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read() # stdout1--->: b'\r\nWindows IP \xc5\xe4\xd6\xc3\r\n\r\n\r\n\xd2\xd4\xcc\xab\xcd\xf8\xca\xca\xc5\xe4\xc6\xf7 \xd2\xd4\xcc\xab\xcd\xf8 2:\r\n\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n   \xb1\xbe\xb5\xd8\xc1\xb4\xbd\xd3 IPv6 \xb5\xd8\xd6\xb7. . . . . . . . : fe80::fdc4:7f23:1928:6026%11\r\n   IPv4 \xb5\xd8\xd6\xb7 . . . . . . . . . . . . : 192.168.1.5\r\n   \xd7\xd3\xcd\xf8\xd1\xda\xc2\xeb  . . . . . . . . . . . . : 255.255.255.0\r\n   \xc4\xac\xc8\xcf\xcd\xf8\xb9\xd8. . . . . . . . . . . . . : fe80::1%11\r\n                                       192.168.1.1\r\n\r\n\xd2\xd4\xcc\xab\xcd\xf8\xca\xca\xc5\xe4\xc6\xf7 \xd2\xd4\xcc\xab\xcd\xf8 3:\r\n\r\n   \xc3\xbd\xcc\xe5\xd7\xb4\xcc\xac  . . . . . . . . . . . . : \xc3\xbd\xcc\xe5\xd2\xd1\xb6\xcf\xbf\xaa\xc1\xac\xbd\xd3\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n\r\n\xcb\xed\xb5\xc0\xca\xca\xc5\xe4\xc6\xf7 Teredo Tunneling Pseudo-Interface:\r\n\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n   IPv6 \xb5\xd8\xd6\xb7 . . . . . . . . . . . . : 2001:0:9d38:6ab8:242a:88b7:20b7:be6c\r\n   \xb1\xbe\xb5\xd8\xc1\xb4\xbd\xd3 IPv6 \xb5\xd8\xd6\xb7. . . . . . . . : fe80::242a:88b7:20b7:be6c%14\r\n   \xc4\xac\xc8\xcf\xcd\xf8\xb9\xd8. . . . . . . . . . . . . : ::\r\n'
            stderr = obj.stderr.read() # stdout3--->: 'xxxxxipconfig' 不是内部或外部命令,也不是可运行的程序或批处理文件。

            #3、把命令结果返回给客户端;
            conn.send(stdout+stderr)#+号是一个可以优化的点子;

        except ConnectionResetError:#适用于Windows操作系统;
            break
    conn.close()
phone.close()

2、client.py;

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',18080))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    #发送命令;
    cmd = input('>>>:').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    #2、拿到命令的结果
    data = phone.recv(1024)
    print(data.decode('gbk'))

phone.close()
"""
C:\Users\Administrator\PycharmProjects\LFXC2018\venv\Scripts\python.exe "C:/Users/Administrator/PycharmProjects/LFXC2018/第三模块 面向对象/网络编程/11-模拟ssh远程执行命令-代码实现/11-client.py"
>>>:ipconfig

Windows IP 配置


以太网适配器 以太网 2:

   连接特定的 DNS 后缀 . . . . . . . : 
   本地链接 IPv6 地址. . . . . . . . : fe80::fdc4:7f23:1928:6026%11
   IPv4 地址 . . . . . . . . . . . . : 192.168.1.5
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : fe80::1%11
                                       192.168.1.1

以太网适配器 以太网 3:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . : 

隧道适配器 Teredo Tunneling Pseudo-Interface:

   连接特定的 DNS 后缀 . . . . . . . : 
   IPv6 地址 . . . . . . . . . . . . : 2001:0:9d38:6ab8:242a:88b7:20b7:be6c
   本地链接 IPv6 地址. . . . . . . . : fe80::242a:88b7:20b7:be6c%14
   默认网关. . . . . . . . . . . . . : ::

>>>:
"""

12-粘包现象

1、粘包现象初识

某个命令的输出结果字节比较长,但客户端只recv(1024), 可结果比1024长呀,那怎么办,只好在服务器端的IO缓冲区里把客户端还没收走的暂时存下来,等客户端下次再来收。同志们,这个现象叫做粘包,就是指两次结果粘到一起了。它的发生主要是因为socket缓冲区导致的,来看一下

2、粘包问题只存在于TCP中,Not UDP;

还是看上图,发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

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

小结:

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

phone.bind(('127.0.0.1',9901))#0~65525,其中0~1024给操作系统使用;
phone.listen(5)#5表示最大挂起的链接数;

print('starting...')
while True:#链接循环
    conn,client_addr = phone.accept()
    print(client_addr)

    while True:#通信循环
        try:
            #1、接收命令;
            cmd = conn.recv(1024)#接收数据的最大字节数;1、单位:bytes;2、最大接收1024个bytes;
            #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、把命令的结果返回给客户端;
            print(len(stdout)+ len(stderr))
            conn.send(stdout + stderr)#+号是一个可以优化的点
        except ConnectionResetError:#异常处理;
            break
    conn.close()
phone.close()
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# __Author__:TQTL911
# Version:python3.6.6
# Time:2018/7/14 16:17
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',9901))#0~65525,其中0~1024给操作系统使用;

while True:
    #1、发送命令;
    cmd = input('>>:').strip()#ls /etc
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    #2、拿到命令结果;
    data = phone.recv(1024)#有可能数据包的大小超过1024bytes;
    print(data.decode('gbk'))

phone.close()

13-粘包底层原理分析

1、send与recv的对比;

  • 不管是recv还是send,都不是直接接受对方的数据,而是操作自己的操作系统;
  • 不是一个send对应一个recv;
  • recv阶段:wait data阶段耗时过长;copy数据,耗时较短;
  • send阶段:copy数据耗时较短;

2、粘包存在与客户端或者服务端;

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# __Author__:TQTL911
# Version:python3.6.6
# Time:2018/7/14 16:11
import socket
import subprocess

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',9001))#0~65525,其中0~1024给操作系统使用;
server.listen(5)#5表示最大挂起的链接数;

conn,addr = server.accept()

res1 = conn.recv(1024)
print('第1次',res1)

res2 = conn.recv(1024)
print('第2次',res2)
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# __Author__:TQTL911
# Version:python3.6.6
# Time:2018/7/14 16:17
import socket
import time

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',9001))

client.send('hello'.encode('utf-8'))
time.sleep(5)
client.send('world'.encode('utf-8'))

 

 

14-解决粘包问题-伪代码实现

1、伪代码;

1)server.py

import socket
import os
import subprocess

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',18080))#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(1024)#1、单位bytes;2、1024代表最大接受1024个bytes;
            if not cmd:break#仅适用于Linux操作系统;
            print('客户端数据',cmd)
            #2、执行命令,拿到结果;
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            # obj = subprocess.Popen('xxxxxipconfig',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read() # stdout1--->: b'\r\nWindows IP \xc5\xe4\xd6\xc3\r\n\r\n\r\n\xd2\xd4\xcc\xab\xcd\xf8\xca\xca\xc5\xe4\xc6\xf7 \xd2\xd4\xcc\xab\xcd\xf8 2:\r\n\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n   \xb1\xbe\xb5\xd8\xc1\xb4\xbd\xd3 IPv6 \xb5\xd8\xd6\xb7. . . . . . . . : fe80::fdc4:7f23:1928:6026%11\r\n   IPv4 \xb5\xd8\xd6\xb7 . . . . . . . . . . . . : 192.168.1.5\r\n   \xd7\xd3\xcd\xf8\xd1\xda\xc2\xeb  . . . . . . . . . . . . : 255.255.255.0\r\n   \xc4\xac\xc8\xcf\xcd\xf8\xb9\xd8. . . . . . . . . . . . . : fe80::1%11\r\n                                       192.168.1.1\r\n\r\n\xd2\xd4\xcc\xab\xcd\xf8\xca\xca\xc5\xe4\xc6\xf7 \xd2\xd4\xcc\xab\xcd\xf8 3:\r\n\r\n   \xc3\xbd\xcc\xe5\xd7\xb4\xcc\xac  . . . . . . . . . . . . : \xc3\xbd\xcc\xe5\xd2\xd1\xb6\xcf\xbf\xaa\xc1\xac\xbd\xd3\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n\r\n\xcb\xed\xb5\xc0\xca\xca\xc5\xe4\xc6\xf7 Teredo Tunneling Pseudo-Interface:\r\n\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n   IPv6 \xb5\xd8\xd6\xb7 . . . . . . . . . . . . : 2001:0:9d38:6ab8:242a:88b7:20b7:be6c\r\n   \xb1\xbe\xb5\xd8\xc1\xb4\xbd\xd3 IPv6 \xb5\xd8\xd6\xb7. . . . . . . . : fe80::242a:88b7:20b7:be6c%14\r\n   \xc4\xac\xc8\xcf\xcd\xf8\xb9\xd8. . . . . . . . . . . . . : ::\r\n'
            stderr = obj.stderr.read() # stdout3--->: 'xxxxxipconfig' 不是内部或外部命令,也不是可运行的程序或批处理文件。

            #3、把命令结果返回给客户端;
            #3-1把数据的长度发给客户端;
            #3-2再发送真实的数据
            #把报头(固定长度)发送给客户端;
            total_size = print(len(stdout)+len(stderr))
            conn.send(str(total_size).encode('utf-8'))

            #conn.send(stdout+stderr)#+号是一个可以优化的点子;
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionResetError:#适用于Windows操作系统;
            break
    conn.close()
phone.close()

2)client.py

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',18080))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    cmd = input('>>>:').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))

    total_size =102400
    #第一步,先拿到数据的长度;
    #接受真实的数据;
    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()

15-解决粘包问题-简单版本

1、简单版本;

1)、sever.py

import socket
import os
import subprocess
import struct

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',18081))#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(1024)#1、单位bytes;2、1024代表最大接受1024个bytes;
            if not cmd:break#仅适用于Linux操作系统;
            print('客户端数据',cmd)
            #2、执行命令,拿到结果;
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            # obj = subprocess.Popen('xxxxxipconfig',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read() # stdout1--->: b'\r\nWindows IP \xc5\xe4\xd6\xc3\r\n\r\n\r\n\xd2\xd4\xcc\xab\xcd\xf8\xca\xca\xc5\xe4\xc6\xf7 \xd2\xd4\xcc\xab\xcd\xf8 2:\r\n\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n   \xb1\xbe\xb5\xd8\xc1\xb4\xbd\xd3 IPv6 \xb5\xd8\xd6\xb7. . . . . . . . : fe80::fdc4:7f23:1928:6026%11\r\n   IPv4 \xb5\xd8\xd6\xb7 . . . . . . . . . . . . : 192.168.1.5\r\n   \xd7\xd3\xcd\xf8\xd1\xda\xc2\xeb  . . . . . . . . . . . . : 255.255.255.0\r\n   \xc4\xac\xc8\xcf\xcd\xf8\xb9\xd8. . . . . . . . . . . . . : fe80::1%11\r\n                                       192.168.1.1\r\n\r\n\xd2\xd4\xcc\xab\xcd\xf8\xca\xca\xc5\xe4\xc6\xf7 \xd2\xd4\xcc\xab\xcd\xf8 3:\r\n\r\n   \xc3\xbd\xcc\xe5\xd7\xb4\xcc\xac  . . . . . . . . . . . . : \xc3\xbd\xcc\xe5\xd2\xd1\xb6\xcf\xbf\xaa\xc1\xac\xbd\xd3\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n\r\n\xcb\xed\xb5\xc0\xca\xca\xc5\xe4\xc6\xf7 Teredo Tunneling Pseudo-Interface:\r\n\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n   IPv6 \xb5\xd8\xd6\xb7 . . . . . . . . . . . . : 2001:0:9d38:6ab8:242a:88b7:20b7:be6c\r\n   \xb1\xbe\xb5\xd8\xc1\xb4\xbd\xd3 IPv6 \xb5\xd8\xd6\xb7. . . . . . . . : fe80::242a:88b7:20b7:be6c%14\r\n   \xc4\xac\xc8\xcf\xcd\xf8\xb9\xd8. . . . . . . . . . . . . : ::\r\n'
            stderr = obj.stderr.read() # stdout3--->: 'xxxxxipconfig' 不是内部或外部命令,也不是可运行的程序或批处理文件。

            #3、把命令结果返回给客户端;
            total_size = print(len(stdout) + len(stderr))
            header = struct.pack('i',total_size)
            #第1步:制作固定长度的报头;
            conn.send(header)
            # 第2步:把报头发送给客户端;

            conn.send(str(total_size).encode('utf-8'))

            # 第3步:再发送真实的数据;
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionResetError:#适用于Windows操作系统;
            break
    conn.close()
phone.close()

2)、struct模块的使用

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __Author__:Administrator
# Version:Python3.6.5
# Date:2018/6/23 0023 9:32
import struct


res = struct.pack('i',1230)
print(res,type(res),len(res))
#client.recv(4)
obj = struct.unpack('i',res)
print(obj[0])

3)、client.py的使用

import socket
import struct

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',18081))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    #1、发送命令;
    cmd = input('>>>:').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    #2、拿到命令的结果,并打印;

    #第1步:先收报头;
    header = phone.recv(4)
    # 第2步,从报头中解析出真实的数据;
    total_size = struct.unpack('i',header)[0]
    # 第2步:接受真实的数据;
    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()

16-解决粘包问题-终极版本

1、终极版本;

1)、server.py

import socket
import os
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',18080))#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)#1、单位bytes;2、1024代表最大接受1024个bytes;
            if not cmd:break#仅适用于Linux操作系统;
            #2、执行命令,拿到结果;
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            # obj = subprocess.Popen('xxxxxipconfig',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read() # stdout1--->: b'\r\nWindows IP \xc5\xe4\xd6\xc3\r\n\r\n\r\n\xd2\xd4\xcc\xab\xcd\xf8\xca\xca\xc5\xe4\xc6\xf7 \xd2\xd4\xcc\xab\xcd\xf8 2:\r\n\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n   \xb1\xbe\xb5\xd8\xc1\xb4\xbd\xd3 IPv6 \xb5\xd8\xd6\xb7. . . . . . . . : fe80::fdc4:7f23:1928:6026%11\r\n   IPv4 \xb5\xd8\xd6\xb7 . . . . . . . . . . . . : 192.168.1.5\r\n   \xd7\xd3\xcd\xf8\xd1\xda\xc2\xeb  . . . . . . . . . . . . : 255.255.255.0\r\n   \xc4\xac\xc8\xcf\xcd\xf8\xb9\xd8. . . . . . . . . . . . . : fe80::1%11\r\n                                       192.168.1.1\r\n\r\n\xd2\xd4\xcc\xab\xcd\xf8\xca\xca\xc5\xe4\xc6\xf7 \xd2\xd4\xcc\xab\xcd\xf8 3:\r\n\r\n   \xc3\xbd\xcc\xe5\xd7\xb4\xcc\xac  . . . . . . . . . . . . : \xc3\xbd\xcc\xe5\xd2\xd1\xb6\xcf\xbf\xaa\xc1\xac\xbd\xd3\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n\r\n\xcb\xed\xb5\xc0\xca\xca\xc5\xe4\xc6\xf7 Teredo Tunneling Pseudo-Interface:\r\n\r\n   \xc1\xac\xbd\xd3\xcc\xd8\xb6\xa8\xb5\xc4 DNS \xba\xf3\xd7\xba . . . . . . . : \r\n   IPv6 \xb5\xd8\xd6\xb7 . . . . . . . . . . . . : 2001:0:9d38:6ab8:242a:88b7:20b7:be6c\r\n   \xb1\xbe\xb5\xd8\xc1\xb4\xbd\xd3 IPv6 \xb5\xd8\xd6\xb7. . . . . . . . : fe80::242a:88b7:20b7:be6c%14\r\n   \xc4\xac\xc8\xcf\xcd\xf8\xb9\xd8. . . . . . . . . . . . . : ::\r\n'
            stderr = obj.stderr.read() # stdout3--->: 'xxxxxipconfig' 不是内部或外部命令,也不是可运行的程序或批处理文件。

            #3、把命令结果返回给客户端;
            # 第1步:制作固定长度的报头;
            header_dic = {
                'filename':'a.txt',
                'md5':'xxxx',
                'total_size':len(stdout) + len(stderr)
            }
            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode('utf-8')
            # 第2步:先发送报头的长度;
            conn.send(struct.pack('i',len(header_bytes)))
            # 第3步:把报头发送给客户端;
            conn.send(header_bytes)
            # 第4步:再发送真实的数据;
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionResetError:#适用于Windows操作系统;
            break
    conn.close()
phone.close()

2)、client.py

import socket
import struct
import json

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',18080))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    #1、发送命令;
    cmd = input('>>>:').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    #2、拿到命令的结果,并打印;

    #第1步:先收报头的长度;
    obj = phone.recv(4)
    header_size = struct.unpack('i',obj)[0]
    #第2步,再收报头;
    header_bytes = phone.recv(header_size)
    #第3步,从报头中解析出真实的数据的描述信息;
    header_json= header_bytes.decode('utf-8')
    header_dic = header_json.loads(header_json)
    print(header_dic)
    total_size = header_dic['total_size']
    # 第4步:接受真实的数据;
    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()

3)、struct.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __Author__:Administrator
# Version:Python3.6.5
# Date:2018/6/23 0023 9:32
import struct
import json

header_dic = {
    'filename': 'a.txt',
    'md5': 'xxxx',
    'total_size': 33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
}
header_json = json.dumps(header_dic)
#print(header_json,type(header_json))#{"filename": "a.txt", "md5": "xxxx", "total_size": 33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333} <class 'str'>
header_bytes = header_json.encode('utf-8')
print(type(header_bytes))#<class 'bytes'>
print(len(header_bytes))#171
# res = struct.pack('i',1230)
# print(res,type(res),len(res))
# #client.recv(4)
# obj = struct.unpack('i',res)
# print(obj[0])
res = struct.pack('l',len(header_bytes))
print(res,len(res))

17-文件传输功能实现

1、简单版本;

1)、server.py

 

import socket
import os
import subprocess
import struct
import json

share_dir = r'C:\Users\Administrator\PycharmProjects\LFXC2018\第三模块 面向对象\网络编程\17-文件传输功能实现\server\share'

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',28080))#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)#1、单位bytes;2、1024代表最大接受1024个bytes;
            if not res:break#仅适用于Linux操作系统;
            #2、解析命令,提取相应命令参数;
            cmds = res.decode('utf-8').split()#['get','a.txt']
            filename = cmds[1]
            #3、以读的方式打开文件,读取文件内容,发送给客户端;
            # 第1步:制作固定长度的报头;
            header_dic = {
                'filename':filename,#'filename':'xxx'
                'md5':'xxdxxx',
                'file_size':os.path.getsize(r'%s\%s'%(share_dir,filename))
            }
            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode('utf-8')
            # 第2步:先发送报头的长度;
            conn.send(struct.pack('i',len(header_bytes)))
            # 第3步:把报头发送给客户端;
            conn.send(header_bytes)
            # 第4步:再发送真实的数据;
            with open('%s\%s'%(share_dir,filename),'rb') as f:
                #conn.send(f.read())#避免使用该方法,大文件将会占用过多内存;
                for line in f:#节省内存的方案;
                    conn.send(line)

        except ConnectionResetError:#适用于Windows操作系统;
            break
    conn.close()
phone.close()

 

2)、client.py

 

import socket
import struct
import json

download_dir =r'C:\Users\Administrator\PycharmProjects\LFXC2018\第三模块 面向对象\网络编程\17-文件传输功能实现\client\download'
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',28080))#ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
while True:
    #1、发送命令;
    cmd = input('>>>:').strip()#get a.txt
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))

    #2、以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户的新文件;
    #第1步:先收报头的长度;
    obj = phone.recv(4)
    header_size = struct.unpack('i',obj)[0]
    #第2步,再收报头;
    header_bytes = phone.recv(header_size)
    #第3步,从报头中解析出真实的数据的描述信息;
    header_json= header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    print(header_dic)
    total_size = header_dic['file_size']
    filename = header_dic['filename']
    # 第4步:接收真实的数据;
    with open('%s\%s'%(download_dir,filename),'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = phone.recv(1024)
            f.write(line)
            recv_size += len(line)
            print('总大小%s 已下载大小:%s'%(total_size,recv_size))

phone.close()

 

3)、struct.py

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __Author__:Administrator
# Version:Python3.6.5
# Date:2018/6/23 0023 9:32
import struct
import json

header_dic = {
    'filename': 'a.txt',
    'md5': 'xxxx',
    'total_size': 33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
}
header_json = json.dumps(header_dic)
#print(header_json,type(header_json))#{"filename": "a.txt", "md5": "xxxx", "total_size": 33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333} <class 'str'>
header_bytes = header_json.encode('utf-8')
print(type(header_bytes))#<class 'bytes'>
print(len(header_bytes))#171
# res = struct.pack('i',1230)
# print(res,type(res),len(res))
# #client.recv(4)
# obj = struct.unpack('i',res)
# print(obj[0])
res = struct.pack('l',len(header_bytes))
print(res,len(res))

 

18-文件传输功能-函数版

19-文件传输功能-面向对象版

20-基于UDP协议的套接字介绍

21-作业介绍

posted @ 2018-06-18 14:07  天晴天朗  阅读(407)  评论(0编辑  收藏  举报