zoukankan      html  css  js  c++  java
  • 19.网络编程

    网络架构

    单机

    • 单机游戏

    以下两个基于网络的

    CS构架

    客户端client/服务端server

    • 服务端统一处理有更好的安全性和稳定性而且升级比较容易,不过服务器负担就增加.
    • 客户端将负担分配到每个用户,从而可以节约服务器资源,安全性比较低,客户端不联网,数据不更新.

    BS构架

    Browser浏览器/服务端server

    将应用程序放在浏览器上

    互联网与互联网的组成

    接两台计算机之间的internet实际上就是一系列统一的标准,这些标准称之为互联网协议,互联网的本质就是一系列的协议,总称为‘互联网协议’.

    边缘部分:客户端/服务端,这些普通的计算机,负责接收/发送数据

    核心部分:传输网络的设备,路由,基站,负责数据的传输

    互联网的组成:

    • 硬件:提供给你网络
    • 软件:应用程序,完成具体的任务

    OSI七层

    刚才所说互联网本质是一系列协议,互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层.

    我们按照tcp/ip五层阐述每层的由来与功能,理解了整个互联网通信的原理。

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

    (单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思)
    2. 数据链接层:这里分组方式用的是以太网协议.

    1. ethernet规定

      • 一组电信号构成一个数据包,叫做‘帧’
      • 每一数据帧分成:报头head和数据data两部分
    2. mac地址:head中包含的源和目标地址由来:以太网协议规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址

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

    (以太网协议采用最原始的方式,广播的方式进行通信如果所有的通信都采用以太网的广播方式,那么一台机器发送的包全世界都会收到,这样不仅效率太低,且不符实际)

    1. 网络层:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
      1. 规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4
      2. ip数据包也分为head和data部分,也就是在数据链路层的基础上在包一层ip头.
      3. 通过mac地址(局域网)+IP地址(互联网)就能找到全世界独一无二的电脑

    (我们通过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口,端口即应用程序与网卡关联的编号。)
    4. 传输层:建立端口到端口的通信

    1. tcp/udp协议
    2. mac地址(局域网)+IP地址(互联网)+端口号就能找到全世界独一无二的电脑上的独一无二的应用程序
    3. 应用层:规定应用程序的数据格式,将0和1转换成具体的功能

    socket抽象层

    Socket是应用层与运输层之间的抽象层,它可以把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信.

    TCP协议的三次握手和四次挥手

    三次握手建立连接

    1. 客户端像服务端发出连接的带上SYN的请求给服务端
    2. 服务接收到后,返回一个带上SYN和ACK的请求给客户端
    3. 客户端进入连接状态,并且发送一个带上ACK的请求给服务端
    4. 服务端收到进入连接状态

    四次挥手断开连接

    1. 客户端发出带有FIN的请求给服务端
    2. 服务端返回一个带有ACK的请求个客户端,说他已经知道了

    然后服务端还有可能会有遗留的数据返回给客户端,会在这个时候发完

    1. 服务端发完之后才会发送一个带有FIN和ACK的请求给客户端

    如果客户端没有接收到这条请求,就没有第四条请求给服务端,服务端会隔一段时间再发一次带有FIN和ACK的请求给客户端...如果在2MSL时间内,客户端一直没有响应,则强行关闭

    1. 客户端返回一个带有ACK请求给服务端,连接正常关闭

    基于TCP协议的socket套接字编程

    服务端

    import socket
    
    # 1. 符合TCP协议的手机
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # TCP
    
    # 2. 绑定手机号 110
    server.bind(('127.0.0.1', 8000))  # 127.0.0.1代表本地
    # server.bind(('192.168.11.210',8000))  # 127.0.0.1代表本地
    
    server.listen(5)  # 半连接池
    
    # 3. 等待客户端连接
    print('start...')
    # 链接循环
    
    
    while True:
        # 通信循环
        conn, client_addr = server.accept()
        while True:
            try:
                # 4. 收到消息receive
                data = conn.recv(1024)
                print(data)
    
                # 5. 回消息
                conn.send(data.upper())
            except ConnectionAbortedError:
                continue
            except ConnectionResetError:
                break
    
    

    客户端

    import socket
    
    # 1. 创建符合TCp协议的手机
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    # 2. 拨号
    client.connect(('127.0.0.1',8000))
    
    while True:
        msg = input('please enter your msg')  # dir
        # 3. 发送消息
        client.send(msg.encode('utf8'))
    
        # 4. 接收消息
        data = client.recv(1024)
        print(data)
    

    模拟ssh远程执行命令

    在客户端处模拟ssh发送指令,服务端通过subprocess执行该命令,然后返回命令的结果

    服务端

    import socket
    import subprocess
    
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    server.bind(('192.168.11.210', 8000))
    server.listen(5)
    
    print('start...')
    while True:
        conn, client_addr = server.accept()
        print(client_addr)
    
        while True:
            try:
                cmd = conn.recv(1024)  # dir
                print(cmd)
    
                # 帮你执行cmd命令,然后把执行结果保存到管道里
                pipeline = subprocess.Popen(cmd.decode('utf8'),
                                            shell=True,
                                            stderr=subprocess.PIPE,
                                            stdout=subprocess.PIPE)
    
                stderr = pipeline.stderr.read()
                stdout = pipeline.stdout.read()
    
                conn.send(stderr)
                conn.send(stdout)
    
            except ConnectionResetError:
                break
    
    

    客户端

    import socket
    
    # 1. 创建符合TCp协议的手机
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    # 2. 拨号
    client.connect(('192.168.11.210',8000))
    
    while True:
        msg = input('please enter your msg')  # dir
        # 3. 发送消息
        client.send(msg.encode('utf8'))
    
        # 4. 接收消息
        data = client.recv(10)
        print(data.decode('gbk'))
    

    粘包问题

    只有TCP协议会有粘包现象.

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

    两种情况下会发生粘包:

    1. 两个数据非常小,然后间隔时间又短,造成会合到一起,产生粘包
    2. 数据太大,一次取不完,下一次还会取上次遗留的数据,这也会产生粘包

    解决粘包问题

    解决粘包的方法就是让发送端在发送数据前,把自己将要发送的数据大小让接收端知晓,通过设定一个定长接收数据,这样就不会出现粘包.

    这里我们使用到struct模块.

    该模块可以把一个类型,如数字,转成固定长度的bytes.

    具体实现步骤:

    1. 建立通信
    2. 客户端发送请求(send)
    3. 服务端接受请求(revc)
    4. 通过struct模块的pack方法打包数据的长度,固定为4,并且发送给客户端
    5. 客户端接受到打包的长度,通过unpack解压缩打包的信息,还原得到真实长度
    6. 服务端发送数据
    7. 客户端此时可以接受到指定长度的数据
    # 服务端
    from socket import *
    import subprocess
    import struct
    
    server = socket(AF_INET, SOCK_STREAM)
    
    server.bind(('127.0.0.1', 8000))
    server.listen(5)
    
    print('start...')
    while True:
        conn, client_addr = server.accept()
    
        while True:
            print('from client:', client_addr)
    
            cmd = conn.recv(1024)   # 2.接受请求
            if len(cmd) == 0: break
            print('cmd:', cmd)
    
            obj = subprocess.Popen(cmd.decode('utf8'),  # 输入的cmd命令
                                   shell=True,  # 通过shell运行
                                   stderr=subprocess.PIPE,  # 把错误输出放入管道,以便打印
                                   stdout=subprocess.PIPE)  # 把正确输出放入管道,以便打印
    
            stdout = obj.stdout.read()  # 打印正确输出
            stderr = obj.stderr.read()  # 打印错误输出
    
    
            count_len=len(stdout)+len(stderr)
            guding_bytes=struct.pack('i',count_len) # 3.打包信息内容,长度为4
    
            conn.send(guding_bytes)  # 4.发送指定长度
    
            conn.send(stdout)    # 7.发送数据
            conn.send(stderr)
            # data = conn.recv(1024)
            #
            # conn.send(data)
    
        conn.close()
    
    server.close()
    
    # 客户端
    import socket
    import struct
    
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    client.connect(('127.0.0.1', 8000))
    
    while True:
        data = input('please enter your data')
        client.send(data.encode('utf8'))  # 1.发送请求
        data = client.recv(4)  # 5.接受打包的长度
        count_len=struct.unpack('i',data)[0]  # 6.解压缩打包的信息,还原长度
        data=client.recv(count_len)    # 8.接受指定长度的数据
    
        print('from server:', data.decode('gbk'))
    
    client.close()
    

    基于UDP协议的socket套接字编程

    UDP是不需要通过像TCP协议那样的连接,启动了可以直接使用recvfrom接收数据,sendto发送数据,不过这里客户端可以是很多个,所以客户端的端口地址是随机的.

    # 服务端
    import socket
    
    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server.bind(('127.0.0.1', 8000))
    
    print('start...')
    while True:
        data, client_addr = server.recvfrom(1024)
        print(client_addr)
        print(data)
        server.sendto(data.upper(), client_addr)
    
    
    # 客户端
    import socket
    
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    while True:
        msg = input('please enter your msg:')
        client.sendto(msg.encode('utf8'), ('127.0.0.1', 8000))
    
        data = client.recvfrom(1024)
        print(data)
    
    

    基于socketserver实现并发的socket套接字编程

    之前通过TCP协议服务端虽然可以同时监听多个客户端,但只能与一个客户端传输数据,这里我们可以通过socketserver实现并发的socket套接字编程,从而同时与多个客户端进行数据传输.

    服务端

    # 同一时刻有多个人在接听
    import socketserver
    import subprocess
    import struct
    
    
    class MyHandler(socketserver.BaseRequestHandler):
        # 通信循环
        def handle(self):
    
            while True:
                try:
                    cmd = self.request.recv(1024)
                    print(cmd)
    
                    pipeline = subprocess.Popen(cmd.decode('utf8'),
                                                shell=True,
                                                stderr=subprocess.PIPE,
                                                stdout=subprocess.PIPE)
    
                    stdout = pipeline.stdout.read()
                    stderr = pipeline.stderr.read()
    
                    count_len = len(stdout) + len(stderr)
                    guding_bytes = struct.pack('i', count_len)
    
                    self.request.send(guding_bytes)  # 4
    
                    self.request.send(stderr + stdout)
    
                except ConnectionResetError:
                    break
    
    
    # 使用socketserver的连接循环(并发),但是使用了自己的通信循环
    # myhandler = MyHandler()
    if __name__ == '__main__':
        server = socketserver.ThreadingTCPServer(('127.0.0.1', 8000), MyHandler, bind_and_activate=True)
        print('start...')
        server.serve_forever()
    
    
  • 相关阅读:
    html5数字和颜色输入框
    WinForm设置右键菜单
    设置窗体透明C#代码
    C#调用windows api示例
    使用VS GDB扩充套件在VS上远端侦错Linux上的C/C++程序
    javascript系统时间测试题
    博客园学习的好地方
    基于jQuery的自适应图片左右切换
    HTML+CSS代码橙色导航菜单
    ASP.NET使用UpdatePanel实现AJAX
  • 原文地址:https://www.cnblogs.com/yellowcloud/p/11086758.html
Copyright © 2011-2022 走看看