网络编程

一、软件开发的架构

1. C/S架构:
Client(客户端)与Server(服务端)架构这种架构也是从用户层面(物理层面)划分的,
客户端泛指客户应用程序EXE,对用户的电脑操作系统环境依赖较大

2. B/S架构:
Browser(浏览器端)与Server端架构,它隶属于C/S架构,Browser也是一种CLient,
但不需要安装什么应用程序,只需要在浏览器上通过HTTP请求服务器端相关的资源(网页资源)

3. B/S架构有点:
统一了应用的入口(是一个趋势)
网络编程基础

二、通信

1. 同一台电脑两个py文件通信:——文件 a.py———t.txt———b.py

2. 早期: 联机(两台电脑用一根网线连接)

3. 以太网:局域网与交换机(几十台电脑通信)

1) 交换机通信:
- 广播: 主机间‘一对所有’的通讯模式(吼一嗓子)
- 单播: 方向固定的向某一个主机发送
- 组播: 向某一部分或某一组主机发送

2) 同一局域网在同一网段(192.168.12.xx)
- 一个网段最多放256台电脑(0--255)
- 局域网是指在某一区域内由多台计算机互联成的计算机组,一般是方圆几千米内

3) ip地址与ip协议:
- 规定网络地址的协议叫IP协议,他定义的地址称之为ip地址,广泛采用ipv4,他规定网络地址由32位2进制表示
- 范围: 0.0.0.0----255.255.255.255
一个IP地址通常写成四位点分十进制数字

4) mac地址:
- Ethernet规定接入Internet的设备都必须具备网卡,发送端和接收端的地址便是值网卡的地址,即mac地址
- 每块网卡出厂时都被烧制上一个全世界唯一的mac地址,(物理地址)长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)

5) arp协议:
- 地址解析协议,是根据IP地址获取物理地址的一个TCP/IP协议

6) 子网掩码:
- 计算机根据IP地址产生的表示网络特征的一个参数,形式上等同于IP地址,他的网络部分全部为1,主机部分全部为0.(255.255.255.0)

7) 网段:
- IP地址和子网掩码进行 &(与) 计算
ip: 192. 168. 12. 84
11000000. 101010000. 00001100. 01010100

         

===============> 11000000. 10101000. 00001100. 00000000(与0得0)
===========>网段:      192.         168.       12.         0
 


子网: 255. 255. 255. 0
11111111. 11111111. 11111111. 00000000



8) 查看地址命令: ipconfig - all

4. 多个局域网之间通信: 广域网: 交换机 + 路由器 + 代理ip
1) 路由器(网关设备):
- 连接因特网中各局域网,广域网的设备,路由器通过路由表解析去判断网络地址和选择ip路径,
只接收源站或其他路由器的信息,一个路由器可以组成一个局域网
- 访问百度: 主机找路由器 ——> 找代理ip ——> 通过代理ip找到百度的路由器

三、TCP协议和UDP协议(发送数据的协议)

- TCP应用: web浏览器、电子邮件、 文件传输
- UDP应用: 域名系统(DNS)、视频流、IP语音

1. 端口:
- 计算机每一个程序启动都有一个随机且唯一的端口号(port)
- 端口号范围(0-65535)
- IP地址 + port ——> 唯一确定某个电脑上的某一程序。允许开发人员使用的端口号范围从8000开始(8000--10000)

2. TCP协议:
- 当应用程序希望通过TCP与另一个应用程序通信是,它会发送一个通信请求,这个请求必须被送到一个确切的地址,在双方“握手”
之后,TCP将在两个应用程序之间建立一个全双工的通信,TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端

— 建立连接三次握手 —— 数据传输 —— 断开连接四次挥手

1) 三次握手:

① 客户端发送SYN(SEQ = x)报文给服务器端,进入SYN-SEND状态
② 服务器端收到SYN报文,回应一个SYN(SEQ=y)ACK(ACK=x+1)报文,进入SYN-RECV状态
③ 客户端收到服务期短的报文,回应一个ACK(ACK=y+1)报文,进入Established状态

2) 四次挥手:(终止连接需四次挥手,这由TCP的半关闭造成)

① 某个应用程序首先调用close,称该端执行‘主动关闭’,该端的TCP于是发送一个FIN分节,表示数据发送完毕
② 接收到这个FIN的对端执行‘被动关闭’,这个FIN由TCP确认
③ 一段时间后,接收到这个文件结束符的应用进程将调用close关闭他的套接字,他的TCP也发送一个FIN
④ 接受这个最终FIN的原发送端TCP(执行主动关闭的那一端)确认这个FIN,

- TCP三次握手 / 四次挥手

            socket客户端向服务端发起连接请求:三次握手

                        client.connect((......))
                    ---------------------------------------
                        客户端                  服务端
                           |                      |
                        能睡你吗
                                                来啊
                        好的,我来了
                    ----------------------------------------
                        client.send("发送数据")
                        收发数据                收发数据

            客户端和服务端断开连接: 四次挥手

                            client.close() 或 conn.close()
                    ---------------------------------------
                        客户端                     服务端
                           |                         |
                        我要断开连接
                                                断开就断开,等我处理一些事
                                                。。。。
                                                我处理完了,断开吧
                        拜拜

            补充:断开连接时,反映到代码上: 抛出异常/发送空内容

            

3. UDP协议:
- 当应用程序希望通过UDP与另一个应用程序通信时,传输数据之前源端和终端不建立连接,当它想传送时就简单的
去抓取来自应用程序的数据,并尽可能快的把它扔到网络上
4. 对比:
- TCP:
① 传输控制协议,提供的是面向连接,可靠的字节流服务
② 建立全双工的通信(谁先通信都可以)
③ 不允许在同一时间点同时和多个客户端连接通信
- UDP:
① 用户数据报协议,是面向数据报的传输层协议,不可靠
② 只能客户端先通信,服务器端才能知道地址找到客户端
③ 允许在同一时间点同时和多个客户端连接通信
④ 没有超时重发等机制,不用建连接,所以传输速度很快

四、互联网协议与osi模型

- 按功能不同分为osi七层模型或tcp/ip五层或tcp/ip四层
- OSI七层模型

7层:
自己写的代码: 自己代码 + 框架

- 应用层:py文件,https,http,使用软件
- 表示层:看到数据,如图片和视频
- 会话层:保持登录或链接状态

socket模块:
- 传输层:TCP/UDP协议,端口
- 网络层:IP协议,IP地址,路由器,三层交换机
- 数据链路层:MAC,arp协议,网卡,二层交换机
- 物理层:网卡,电信号,hub集线器,将数据转换成电信号发送

5层:
- 应用层
- 应用层 - 表示层
- 会话层

- 传输层
- 网络层
- 数据链路层
- 物理层

4层:
- 应用层
- 应用层 - 表示层
- 会话层

- 传输层
- 网络层
- 物理层 - 数据链路层
- 物理层

五、套接字(socket)

- socket 位于应用层和传输层之间的虚拟层的接口,socket是应用层与TCP/IP协议簇通信的中间软件抽象层,
它是一组接口,其实socket就是一个模块

- 分类:
- 基于文件类型的套接字家族,AF_UNIX(unix里一切皆文件)
- 基于网络类型的套接字家族:AF_INET,现只使用网络的socket里的family参数

六、套接字初使用  

  1)TCP
    - server端:

import socket

# 创建服务端socket对象
server = socket.socket()

# 绑定IP和端口
server.bind(('127.0.0.1',8000))

# 后面可以再等5个人
server.listen()

#等待客户端连接,如果没人来就一直死等着
# conn是客户端和服务端连接的对象(伞),服务端以后要通过该对象进行收发数据
# addr是客户端的地址信息
conn,addr = server.accept()

# 通过对象去获取(通过伞给我发送的消息)
# 1024表示:服务端通过对象(伞)获取数据时,一次性做多拿1024字节
data = conn.recv(1024)
print(data)

# 服务端通过连接对象(伞)给客户端回复了一条消息
conn.send(b'stop')

#与服务端断开连接(断开伞)
conn.close()

#关闭服务器的服务
server.close()

    - client端

 
import socket

# 创建套接字
client = socket.socket()

# 向服务端发阿松连接请求(递伞)
# 阻塞,去连接,直到连接成功才继续往下走
client.connect(('127.0.0.1',8000))

# 连接上服务端以后,向服务端发送消息
client.send(b'love you')

data = client.recv(1024)
print(data)

# 关闭自己
client.close()


  2)UDP
    - server端:


import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8000))
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.sendto(b'hi',addr)
sk.close()

    - client端:

import socket

ip_port = ('127.0.0.1',8000)

sk = socket.socket(type=socket.SOCK_DGRAM)

sk.sendto(b'hello',ip_port)

msg,addr = sk.recvfrom(1024)

print(msg.decode('utf-8'),addr)

sk.close()


  3)解决UDP协议反复代码问题

from socket import *

class My_socket(socket):

    def __init__(self,coding='utf-8'):
        self.coding = coding
        super(My_socket,self).__init__(type=SOCK_DGRAM)
    
    def my_recv(self,num):
        msg,addr = self.recvfrom(num)
        return msg.decode(self.coding),addr
    
    def my_send(self,msg,addr):
        return self.sendto(msg.encoding(self.coding),addr)


sk = My_socket()
msg,addr = sk.my_recv(1024)


七、远程执行命令

import subprocess

r = subprocess.Popen(
    'dir',shell=True,           # 告诉系统把我要执行的命令当坐系统命令来执行
    stdout = subprocess.PIPE,    # 正确的结果
    stderr = subprocess.PIPE)    # 错误的结果

print(r.stderr.read().decode('gbk'))    # 读一次就没了,以后取不到了,
print(r.stderr.read().decode('gbk'))    # 若想重复使用,需赋值给一个变量

八、黏包(只有TCP有黏包,UDP永远不会黏包
  1. 成因:
    1)TCP协议的拆包机制:
      - 当发送端缓冲区的长度大雨网卡的MTU时,TCP会将这次发送的数据拆成几个数据包发送出去,MTU是网络上传送的最大数据包,
       单位是字节,大部分网络设备MTU是1500,如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据
       包碎片,增加丢包率,降低网速
    2)面向流的通信特点和Nagle算法
      - 发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法,(Nagle算法),将多次间隔较小且数据量小的数据,
       合并成一个大的数据块,然后进行封包,而接收端必须要有科学的拆包机制才能分辨,否则会黏包。
  2.现象:
    ① client连续发送少量数据,时间间隔短,Nagle算法进行合包,server端不知要接收多少,造成数据混乱

    ② client直接发送大量数据,但server端接收的长度不够造成数据混乱
  
  3. TCP和UDP区别:
    ① TCP基于数据流的,收发的消息不能为空,而UDP可以为空,会封装一个(ip+port)的消息头发送过去
    ② UDP最大发送数据长度为:65535 - IP头(20) - UDP(头)(8):65507字节。若大于此长度,会报错,而TCP不限制大小
  
  4. 黏包的解决方案
    - 发送端在发送数据之前,把自己将要发送的字节流总大小让接收端知晓,然后接收端死循环接受完所有数据
    - struct模块: 把一个类型,如数字,转成固定长度的bytes。
      - 发送时: 先发送struct转换好的数据,长度4字节,再发送数据
      - 接收时: 先接收4个字节使用struct.unpack转换成数字来获取要接收的长度,再按照长度接受数据

b1 = struct.pack('i',123456)        # 4字节bytes长度

b1 = struct.ubpack('i',b1)          # 转换回来


九、socket的常用方法

sk.bind()                       # 绑定到套接字
sk.listen()                     # 监听
sk.accept()                     # 阻塞等待连接
sk.connect()                    # 主动初始化TCP服务器的连接
sk.connect_ex()                 # connect()扩展版,出错时返回出错码,不报错
sk.recv()                       # 接收TCP数据
sk.send()                       # 发送TCP数据
sk.sendall()                    # 发送TCP数据
sk.recvfrom()                   # 接收UDP数据
sk.sendto()                     # 发送UDP数据
sk.getpeername()                # 连接到当前套接字的远端的地址
sk.getsockname()                # 当前套接字的地址
sk.getsockopt()                 # 返回指定套接字的参数
sk.setsockopt()                 # 设置指定套接字的参数
sk.close()                      # 关闭套接字
sk.setblocking(True或False)     # 设置阻塞或非阻塞模式
sk.settimeout()                 # 设置阻塞套接字操作的超时时间
sk.gettimeout()                 # 得到()
sk.fileno()                     # 套接字的文件描述符
sk.makefile()                   # 创建一个与该套接字有关的文件
       
十、验证客户端连接的合法性


import hmac                             # 相当于md5

name = '光头'

pwd = b'taibai'

md5_v = hmac.new(name.encode('utf-8'),pwd)

r = md5_v.digest()                      # bytes类型

  - 将服务器加完盐的密文与客户端的密文进行一致性验证

十一、socketserver


- server端:

import socketserver
class Myserver(socketserver.BaseRequestHandler):
    def handlf(self):
        pass
        # 用self.request进行数据的收发

if __name__ == '__main__':
    HOST,PORT = '127.0.0.1',9999
    # 设置allow_reuse_address允许服务器重用地址

    socketserver.TCPServer.allow_reuse_address = True
    server = socketserver.TCPServer((HOST,PORT),Myserver)
    server.serve_forever()
    # 让server永远执行,除非强制停止

 
- client端:
 
import socket

HOST, PORT = "127.0.0.1", 9999
data = "hello"

# 创建一个socket链接,SOCK_STREAM代表使用TCP协议
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:

    sock.connect((HOST, PORT))                         # 链接到客户端
    sock.sendall(bytes(data + "
", "utf-8"))        # 向服务端发送数据
    received = str(sock.recv(1024), "utf-8")         # 从服务端接收数据

print("Sent:     {}".format(data))
print("Received: {}".format(received))

 
原文地址:https://www.cnblogs.com/-li926/p/9643685.html