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

    一. 软件开发的架构

     

     

       1. C/S架构:

           C/S即:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。

             这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。

               2. B/S架构 

            B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。

     

           Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),

           客户端Browser浏览器就能进行增删改查。

    二.  网络基础

      1 硬件:

          网卡:在计算机中 帮助我们完成网络通信, 这个硬件出厂的时候就被分配了一个mac地址(计算机的唯一识别码)
         交换机 :在局域网内多台机器之间通信
         路由器 :多个局域网之间的机器之间的通信
      2. 局域网 :一个区域内的多台机器组成的一个内部网络
      3. 域名 : 和ip地址有一个对应关系,我们访问的域名经过解析也能得到一个ip地址
      4. 协议类 :
       arp协议 : 通过ip地址获取mac地址
      ip协议 : ip地址的规范
    ipv4ipv6
      5. 地址:
       ipv4 由 4个点分十进制组成  0.0.0.0 ---255.255.255.255

          三个被保留作专用网络的地址块(保留字段) 

             24位块: 10.0.0.0---10.255.255.255

             16位块: 172.16.0.0---172.31.255.255

             8位块: 192.168.0.0---192.168.255.255  

        本地的回环地址: 127.0.0.1自己这台机器能找到
        全网段地址:0.0.0.0

        子掩码:255.255.255.0
    
    

       ipv6 由 8组每组包含4个十六进制字符(即0-F),而各组之间是以冒号分开

         网关ip:在一台机器对局域网外的地址进行访问的时候使用的出口ip
      6. 端口: 
       
    英文port的意译,可以认为是设备与外界通讯交流的出口,

         帮助我们找机器上的对应服务 0-65535 惯用的端口号8000之后

    三. osi协议 

        osi 七层协议



    四. TCP协议和UDP协议

      TCP协议:
        可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。
        使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
        三次握手四次挥手


    TCP协议server的四大步骤:sblac
      1. socket()
      2. bind()
      3. listen()
      4. accept()
      5. close
    #server端
    import
    socket sk = socket.socket() sk.bind(('127.0.0.1',9001)) #把地址绑定到套接字 sk.listen() #监听链接 while True: conn,addr = sk.accept() # #接受客户端链接 while True: msg = input('>>>') conn.send(msg.encode('utf-8')) #向客户端发送信息 if msg.upper() == 'Q': #q退出 break content = conn.recv(1024).decode('utf-8') # 等待客户端消息 if content.upper() == 'Q': break #和客户端一起退出 print(content) conn.close() sk.close()

      TCP协议client端的两大步骤:sc

        1 socket()

        2 connect()

    
    
    #client 端
    import socket
    
    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    while True:
        ret = sk.recv(1024).decode('utf-8')
        if ret.upper() == 'Q':break
        print(ret)
        msg = input('>>>')
        sk.send(msg.encode('utf-8'))
        if msg.upper() == 'Q':
            break
    
    sk.close()
    
    

      

      UDP协议
        不可靠的、无连接的服务,传输效率高(发送前时延小),
        一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。
        使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
      UDP协议server端的步骤
        1. socket(
    type=socket.SOCK_DGRAM)
        2. bind()
        3. recvfrom()/sendto()
        4. sendto()/recvfrom()
        5. close()
     
    #server 端
    import socket
    udp_sk = socket.socket(type=socket.SOCK_DGRAM)  #创建一个服务器的套接字
    udp_sk.bind(('127.0.0.1',9001))                  #绑定服务器套接字
    
    
    while True:         # 对话(可与多个client接收与发送)
        msg,client_addr = udp_sk.recvfrom(1024)
        print(msg.decode('utf-8'))
        content = input('>>>')
        udp_sk.sendto(content.encode('utf-8'),client_addr)
    udp_sk.close()               # 关闭服务器套接字
    #client 端
    import
    socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) server_addr = ('127.0.0.1',9001) while True: content = input('>>>') if content.upper() == 'Q':break udp_sk.sendto(content.encode('utf-8'),server_addr) msg = udp_sk.recv(1024).decode('utf-8') if msg.upper() == 'Q':break print(msg) udp_sk.close()

     五. 黏包现象

        先来看两端代码:

      

    # server端
    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()
    
    conn,addr = sk.accept()
    conn.send(b'hello')
    conn.send(b'world')
    
    conn.close()
    sk.close()
    # client端
    import time
    import socket
    
    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    time.sleep(0.1)            #睡0.1秒
    msg1 = sk.recv(1024)
    msg2 = sk.recv(1024)
    print(msg1)         # -->b'helloworld'
    print(msg2)         # --> b''
    sk.close()                        
     可以发现两次发送的内容都被一个recv接收了 而第二个recv没有接收到任何内容,这种现象就黏包现象
     黏包现象:前后发送的数据黏在一起了
    黏包现象的成因:
     1. 发送端粘 : 合包机制,发送端需要等缓冲区满才发送出去,造成粘包
     2. 接收端粘 : 接收不及时,接收方不及时接收缓冲区的包,造成多个包接收
            (客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

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

    
    

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

    
    

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

     黏包的解决方案:

       发送端:

          1. 发送的是字符串

          2. 将字符串转换成字节码

          3. 计数字节码的长度

          4. 用struct模块中的pack方法将字节码的长度转化成4个字节

          5. 发送4个字节码

          6. 发送要发送的内容的字节码

        接收端:

          1. 先接收4个字节

          2. 在把四个字节转换成数字

          3. 接收这个长度的信息

    #server 端
    import socket
    import struct
    
    
    sk = socket.socket()
    sk.bind(('192.168.16.67',9001))
    sk.listen()
    while 1:
        conn,addr = sk.accept()
        while 1:
            size = conn.recv(4)         #先接收四个字节的 代表数据总的长度
            size = struct.unpack('i',size)[0]
            msg = conn.recv(size).decode('utf-8')       #按着数据的长度接收到byte类型的数据
            if msg.upper()=='Q':break
            print(msg)
    
            content = input('>>>')
            byte_content = content.encode('utf-8')
            size = struct.pack('i', len(byte_content))
            conn.send(size)
            conn.send(byte_content)
            if content.upper()=='Q':break
        conn.close()
    sk.close()
    View Code
    #clienet端
    import socket
    import struct
    
    sk = socket.socket()
    addr = ('192.168.16.67',9001)
    sk.connect(addr)
    while 1:
        msg = input('>>>')
        byte_msg = msg.encode('utf-8')          #计算byte数据的长度
        size = struct.pack('i',len(byte_msg))     #使用struct模块将数据长度转换成4个字节的固定长度
        sk.send(size)                             #发送长度
        sk.send(byte_msg)                         #发送数据
        if msg.upper() == 'Q': break
        size = sk.recv(4)
        size = struct.unpack('i', size)[0]
        content = sk.recv(size).decode('utf-8')
        if content.upper()=='Q':break
        print(content)
    sk.close()
    View Code
    
    
    
    
          UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。 
        不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,
        在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。       对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,
        而udp是基于数据报的,
    即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。       不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失,
        这意味着udp根本不会粘包,但是会丢数据,不可靠。
     
          用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) – UDP头(8)=65507字节。
         用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。(丢弃这个包,不进行发送)     用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小)
         这是指在用send函数时,数据长度参数不受限制。

         而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,
         会被分段发送,如果比较短,可能会等待和下一次数据一起发送。
     
    六.并发的socketserver   

       调用socketserver模块实现并发

    # server端
    import socketserver
    
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):
            conn = self.request
            while 1:
                conn.send('嘿嘿嘿'.encode('utf-8'))     #可以不断的发送数据到多个client端
                print(conn.recv(1024).decode('utf-8'))    #可以不断的接收多个client端发来的数据
    
    server = socketserver.ThreadingTCPServer(('192.168.16.67', 9001), Myserver)
    server.serve_forever()

     client端没有改变:

    import socket
    
    sk = socket.socket()
    sk.connect(('192.168.16.67',9001))
    while 1:
        print(sk.recv(1024).decode('utf-8'))
    
        sk.send('1111'.encode('utf-8'))
    View Code

     也可以使用不阻塞的方法实现并发:
        

    #server端
    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.setblocking(False)   # blocking阻塞  setblocking设置阻塞  False就是不阻塞
    sk.listen()
    
    conn_l = []
    while True:
        try:
            conn,addr = sk.accept()
            conn_l.append(conn)             #将客户端的连接添加到列表
        except BlockingIOError:
            for conn in conn_l:             #将每一个客户端都拿出来收发消息
                try:
                    msg = conn.recv(1024).decode('utf-8')
                    print(msg)
                    conn.send(msg.upper().encode('utf-8'))
                except BlockingIOError:
                    pass

      client端不用改变

    七 验证客户端的合法性

       一台server端面向多台client端 采用相同的方法来验证 验证通过才能交互

        采用相同的hash算法来验证:  server端和client端都有一段相同的密钥

                    验证时server端发送给client端一段随机的字节 

                    比较server端和client端的计算结果

        server端

    # server端
    
    import os
    import hashlib
    import socket
    
    secret_key = b'hello'
    #os.urandom(32) 给每一客户端发送一个随机32位的字符串
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()
    
    conn,addr = sk.accept()
    rand = os.urandom(32)
    conn.send(rand)
    
    sha = hashlib.sha1(secret_key)
    sha.update(rand)
    res = sha.hexdigest()
    
    ret = conn.recv(1024).decode('utf-8')
    if ret == res:
        print('是合法的客户端')
        #进行接下来的交互
    else:
        print('不是合法的客户端')
        conn.close()
    sk.close()

        client端

    import socket
    import hashlib
    
    secret_key = b'hello'
    sk = socket.socket()
    sk.connect(('127.0.0.1', 9001))
    
    rand = sk.recv(32)
    
    sha = hashlib.sha1(secret_key)
    sha.update(rand)
    res = sha.hexdigest()
    
    sk.send(res.encode('utf-8'))
    
    sk.close()

    使用hmac模块更加便捷的实现验证:

      server端:

    #server端
    
    import os
    import hmac
    import socket
    
    secret_key = b'hello'
    #os.urandom(32) 给每一客户端发送一个随机的byte类型的字符串
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()
    
    conn,addr = sk.accept()
    rand = os.urandom(32) 
    conn.send(rand)
    
    hmac = hmac.new(secret_key,rand)
    res = hmac.digest()         #hmac得到的就是byte类型的字符串
    
    ret = conn.recv(1024)
    if ret == res:
        print('是合法的客户端')
    else:
        print('不是合法的客户端')
        conn.close()

      client端:

    import hmac
    import socket
    
    
    secret_key = b'hello'
    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    
    rand = sk.recv(32)
    
    hmac = hmac.new(secret_key,rand)
    res = hmac.digest()
    
    sk.send(res)
    
    sk.close()













     

        

      

  • 相关阅读:
    java中的几种对象(PO,VO,DAO,BO,POJO)
    【转】Spring boot 打成jar包问题总结
    mac 上安装lua
    Mac下更新Vim到最新版本
    刘以鬯和香港文学
    权重随机算法的java实现
    MySQL具体解释(7)-----------MySQL线程池总结(一)
    IIS PHP 配置 问题总结
    HDU 3622 Bomb Game(2-sat)
    poj 2388 Who's in the Middle
  • 原文地址:https://www.cnblogs.com/stron/p/10671269.html
Copyright © 2011-2022 走看看