网络编程

一. 软件开发的架构

 

 

   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地址的规范
ipv4\ipv6
  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()

 

 













 

 

 

 

    

 

  

posted @ 2019-04-11 15:50  RunningTron  阅读(170)  评论(0编辑  收藏  举报