网络编程

妖精的绣舞 提交于 2019-11-28 04:03:42

网络架构:
C/S B/S :

C:客户端,B:游览器(rowser),S:服务端

C/S:客户端与服务器之间的架构:QQ,微信,游戏,App都属于cs架构

优点:安全,响应速度快,个性化功能多

缺点:开发和维护成本高,面向客户固定

B/S:游览器与服务器之间的架构:

优点:开发维护成本低,维护成本低,面向的用户广泛
缺点:相对不安全,响应速度相对慢,个性化设计单一

互联网通讯原理:
打电话示例:
互联网通信:
1,一堆物理介质将两个电话连接起来
2,拨号
3,统一的通信标准,一揽子协议,这些互联网协议就是一个个标准,最终可以通信

网络五层:
osi五层:
OSI(Open System Interconnect)开放式系统互联
osi是一个标准,是一个规范,五层协议都是操作系统帮我们封装各种head
应用层 python (应用层包括会话层,表示层) 会话:保证多次网络传输准确性,表示:保证数据安全

​ 应用层常见协议:HTTP、HTTPS、FTP、SMTP、TELNET、POP3、DNS、DHCP

表示层 数据的表示,安全,压缩,格式有JPEG,ASCII 加密格式等

会话成 建立、管理、终止会话,对应主机进程,指本地主机与远程主机正在进行的会话

传输层 tcp udp 四层路由器 四层交换机

网络层 路由器 三层交换机 ipv4 ipv6 (国际协议)

数据链路层 二层傻瓜交换机,以太网协议,mac arp 网卡

物理层 物理连接介质,网线,光纤,发送的是010101比特数据流

1.物理层:
物理连接介质:网线,光纤:发送的数据就是0101010 1比特数据流

2.数据链路层:
交换机

以太网协议:

以太网协议是局域网中的一种协议

将比特流数据分组,一组叫做一帧,每一数据帧分成:报头head和数据data两部分

数据头(head)|data数据:

数据头:固定长度18个字节(标准)(源地址,目的地址,端口,数据类型)

data数据:最短46字节,<=data <=1500字节

以太网问题:
1,数据头为什么要固定?
固定就是一个标准,为了提取源地址以及目标地址

2,以太网协议中源目标地址如何设置唯一?
网线直接接触的硬件就是网卡,网卡上有一个mac地址,确定计算机的唯一性的物理地址

网卡mac地址:12位,16进制组成的一串数字,前六位厂商编号:后六位:流水线号

head(源地址,目的地址,端口,数据类型)|data("俊丽")

广播/单播:

计算机最原始的通信方式

数据经过以太网协议封装后(head)|data,先要从局域网内广播,

广播:

一对多连接,有了mac地址,两台主机通信,一台主机通过arp协议获取到另一台主机的mac地址
广播只存在局域网内,广播通信基本靠吼
单播:

一对一连接(第一次是广播访问,第二次交换机有学习功能,将mac写入表中,下次进行单播)

优点:提升效率

交换机学习功能:

交换机对照(网口与MAC地址的)表:
1: 1C-1B-0D-DA-E8-8F
2: FF-FF-FF-FF-FF-FF
3: FF-FF-FF-FF-FF-FF
4: FF-FF-FF-FF-FF-FF
5: 1C-1B-0F-4A-E8-8F

网口1: 出来一条信息:

第一次广播的形式发出去

网口1:源地址: 1C-1B-0D-DA-E8-8F 目标地址: 1C-1B-0F-4A-E8-8F | 明天放假

2,3,4,5口接收到次消息,查看目标mac地址是否是自己的,5口确定是自己的.

每个网口都广播发送消息一遍之后,对照表就构建好,下次在任意的网口在发消息,就直接以单播的形式发送.
目的: 避免局域网内每一次都广播的形式通信.以后就可以单播,提升效率.

mac地址:head报头中的mac地址 ,都会替换,了解
arp协议:

通过ip获取到mac地址

ARP协议:就是将对方的ip地址获取到对方的MAC地址
如果两个用户进行第一次通信的时候,你不可能知道对象的mac地址么? 但是你必须要知道对方的IP地址.

IP + ARP协议 = 获取对方的MAC地址

源mac 目标mac 源ip 目标ip 数据部分
发送端mac: 1C-1B-0D-DA-E8-8F FF:FF:FF:FF:FF:FF 172.16.10.10/24 172.16.10.11/34 今晚一起吃饭

目标计算机
(源mac:1C-1B-0F-4A-E8-8F 目标mac 1C-1B-0D-DA-E8-8F 源ip: 172.16.10.11/34 目标ip:172.16.10.10/24)

3.网络层:
路由器

网络层的作用是解决客户端到服务端连接中的各个节点问题

IP协议:

ipv4 ipv6 国际互联网协议第四版

广播,mac地址+IP == 可以找到世界上任意一台计算机

IP:

IP地址:
ip地址+子网掩码确定计算机所在网段,子网,局域网位置
ipv4地址:四点分十进制,范围 0-255.0-255.0-255.0-255
子网掩码:
一般都是c类
255.255.255.0
ip地址+子网掩码可以计算出IP网段,可以分254个ip地址,同一局域网内的ip地址不能重复

传输层:(tcp,udp)
端口协议:传输层建立了点到点的连接

端口号:0-65535 系统占用端口:1-1023 软件端口:1024-8000

广播,mac地址+IP +端口 == 可以找到世界上任意一台计算机对应的软件

TCP:
tcp是一种面向连接,可靠的,基于IP的传输层协议

使用的tcp的应用:Web浏览器,文件传输

优点:

可靠的面向连接协议,稳定安全 (面向连接就是双方通信)
缺点

效率相对低,传输速度慢些
UDP:
面向数据报协议,无连接协议

使用UDP应用:域名系统,视频流,IP语言,微信,QQ

优点:

效率高,速度快,发送数据前不需要建立连接
缺点:

不安全,不可靠,容易丢包

应用层:
应用层存储的都是自己开发的程序,HTTP

软件自己定义的协议:

qq:发送数据:“海洋“ ------> [”海洋“]

将数据按照自己定义的协议进行封装(http ftp协议等等)

物理层 --> 数据链路层(以太网协议,mac) --> 网络层 --> 传输层( tcp,udp) --> 应用层

mac地址 + 广播形式 +ip地址 + 端口 ==可以找到任意一台机器

握手状态:
seq 序号 用来表示tcp源端像目标端发送字节流,发送方发送数据对此进行标记 32位
ACK 确认请求 只有ACK标志位等于1事,确认序号才有效,ack = seq + 1
SYN 发送请求
FIN 释放一个连接
注意:确认方=发起方req + 1 ,两端配对

三次握手:
客户端与服务端第一次需要建立通信联系,需要三次握手:

第一次握手:

客户端向服务端发送syn=1的请求和一个随机seq等于x的报文,进入发送状态
第二次握手:

两条请求合并成一条请求
服务端收到客户端发送的syn报文,给客户端回应一个ack=x+1(我收到你的报文,进行+1)的确认请求和同样还会返回syn=1的请求一个随机seq等于y的报文 ,进入接收状态
第三次握手:

客户端收到服务器syn报文和确认请求,在回应服务器一个ack = y+1报文,客户端和服务端进入建立连接状态,完成三次握手
三次握手意义:

简述:主要防止客户端已经失效的连接请求报文突然传到了服务端,而产生的错误
客户端向服务端发送一个连接请求,这个请求因为网络节点问题滞留了,请求在到达服务器已经是一个失效的连接,如果没有三次握手,服务器确认请求,建立连接,但是请求是失效的,客户端不会理会服务器的确认信息,也不会发送消息,服务端一直等待客户端发送数据,这样很多资源就浪费了,这就是三次握手的作用

为什么要进行三次握手:

TCP协议在建立连接时,需要确认通信的双方收发信息的功能都是正常的,因此要进行三次握手

四次挥手:
建立的连接不能一直连接着

第一次挥手:

客户端向服务器发送FIN=1,seq序号=x 客户端停止发送数据
第二次挥手:

服务端收到FIN,回复确认请求 ack=x+1

客户端到服务端这条路断开

第三次挥手:

服务端向客户端发送FIN=1 ,seq=y
第四次挥手:

客户端发送ack=1 + y

服务端到客户端这条路断开

为什么要进行四次挥手:

tcp是一个面向连接,全双工模式,两条通道要进行两次确认才断开

客户端向服务端发送Fin关闭连接,服务端确认后,服务端确认后就表示服务端没有数据在发送给客户端,

客户端到服务端通道关闭

但是服务器的数据不一定都完整传给了客户端,服务器不会直接关闭,他可能还会发送数据给客户端,

之后,再次发送FIN报文,给客户端,客户端在同意后在关闭连接

服务端到客户端通道关闭

Socket:
socket 套接字就是一个网络通信的工具,建立客户端与服务端的通信 ,他存在于应用层和传输层之间的抽象层,这个通信工具封装好了所有的底层接口,可以直接调用这些接口,提高开发效率

TCP_Socket:

服务端

import socket

phone = socket.socket() #创建socket对象,默认tcp协议
phone.bind(("127.0.0.1",666)) #绑定IP地址和端口
phone.listen(5) #监听,开机状态,5同时可以有多少个请求连接

conn,addr = phone.accept() #建立连接,等待客户端,conn两条管道,addr客户端地址

while 1:
from_client_data = conn.recv(1024) #接受消息和多少字节
print(f'来自客户端的{addr}消息{from_client_data.decode("utf-8")}')

to_server = input(">>>>")
conn.send(to_server.encode("utf-8"))  #发送

conn.close() #关闭通道连接
phone.close() #关闭socket套接字

客户端

import socket

phone = socket.socket()
phone.connect(("127.0.0.1",666)) #连接server服务端信息

while 1:
to_client = input(">>>")
phone.send(to_client.encode("utf-8")) #send发送

from_server_data = phone.recv(1024)      #接受消息和多少字节
print(from_server_data.decode("utf-8"))

phone.close()

通讯循环-一个对多个客户端,客户端需要排队
import socket

phone = socket.socket() #创建socket对象,默认tcp协议
phone.bind(("127.0.0.1",666)) #绑定IP地址和端口
phone.listen(5) #监听,开机状态,5同时可以有多少个请求连接

while 1:
print("开启")
conn,addr = phone.accept() #建立连接,等待客户端,conn连接管道,addr客户端地址
print(conn,addr)

while 1:
    try:  #客户端异常退出报错进行异常处理
        from_client_data = conn.recv(1024)    #recv:接受消息和多少字节,夯住
        if from_client_data == "q".encode("utf-8"):
            break
        print(f'来自客户端的{addr}消息{from_client_data.decode("utf-8")}')

        to_server = input(">>>>")
        conn.send(to_server.encode("utf-8"))  #send:发送
    except ConnectionResetError:
        break

conn.close()    #关闭通道连接

phone.close() #关闭socket套接字

客户端还引用上面那个

执行win端命令:

服务端:

import subprocess
import socket

phone = socket.socket() #创建socket对象,默认tcp协议
phone.bind(("127.0.0.1",1666)) #绑定IP地址和端口
phone.listen(5) #监听,开机状态,5同时可以有多少个请求连接

while 1:
print("开启")
conn,addr = phone.accept() #建立连接,等待客户端,conn连接管道,addr客户端地址
while 1:
try:
cmd = conn.recv(1024) #接受消息和多少字节,夯住
obj = subprocess.Popen(cmd.decode("utf-8"),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
result = obj.stdout.read() + obj.stderr.read()
conn.send(result)

    except ConnectionResetError:
        break

conn.close()    #关闭通道连接

phone.close() #关闭socket套接字

客户端:

import socket
phone = socket.socket()
phone.connect(("127.0.0.1",1666)) #连接server服务端信息

while 1:
cmd = input(">>>").strip()
phone.send(cmd.encode("utf-8")) #将命令转为utf-8发送

result = phone.recv(1024)          #接受的消息为windows系统编码转为gbk
print(result.decode("gbk"))

phone.close()

粘包:
缓冲区作用:
Socket进行send和recv的时候,都要通过输出缓冲区和输入缓冲区,缓冲区可以暂时解决网络不稳定问题,提高稳定性

缓存区一般为8K

产生粘包的现象(只有TCP):
粘包发生在tcp协议中
产生粘包的现象有两种recv和send:

recv产生粘包:

如果你的recv最大接受值为1024字节,当你接受一个1500的字节,recv只会接受1024剩下的476个字节会停留在输入缓存区,当你第二次在发送数据,造成了数据的粘包。
send产生粘包:

当你连续send发送少量的数据,tcp会根据优化算法将数据合并成一个TCP段发送出去

解决粘包方式:

1、可以扩大recv的上限,但是会对服务器内存占用

2、客户端sleep睡眠,这样会非常影响效率

正确思路:

当第二次接受数据前,循环recv将所有的数据取完

recv的工作原理:

send多次,recv一次(不是一发一收制)

当缓存区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭,关闭远程端并读取所有数据后,返回空字符

low版:
问题1:非常大的数据使用struct会报错
问题2:报头信息不可能只包含数据总大小

服务端

import subprocess
import socket
import struct

phone = socket.socket() # 创建socket对象,默认tcp协议
phone.bind(("127.0.0.1", 1666)) # 绑定IP地址和端口
phone.listen(5) # 监听,开机状态,5同时可以有多少个请求连接

while 1:
print("开启")
conn, addr = phone.accept() # 建立连接,等待客户端,conn连接管道,addr客户端地址
while 1:
try:
cmd = conn.recv(1024) # 接受消息和多少字节,夯住
obj = subprocess.Popen(cmd.decode("utf-8"),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
result = obj.stdout.read() + obj.stderr.read()

        total_size = len(result)  # 制作报头(371)
        # 将不固定长度的int类型报头,转化成固定4字节(b's\x01\x00\x00')
        total_size_bytes = struct.pack("i", total_size)

        conn.send(total_size_bytes)  # 发送字节长度和数据信息
        conn.send(result)

    except ConnectionResetError:
        break

conn.close()

phone.close()

客户端:

import socket
import struct

phone = socket.socket()
phone.connect(("127.0.0.1", 1666)) # 连接server服务端信息

while 1:
cmd = input(">>>").strip()
phone.send(cmd.encode("utf-8"))

head_bytes = phone.recv(4)  # 接受报头四个字节(b's\x01\x00\x00')
total_size = struct.unpack("i", head_bytes)[0]  # 将报头反解回int类型(371)

total_data = b''  # 循环接受原数据
while len(total_data) < total_size:
    total_data += phone.recv(1024)

print(total_data.decode("gbk"))

phone.close()

旗舰版-自定义报头版:
解决粘包思路:

1,当第二次接受数据前,循环recv将所有的数据取完

result 3000bytes recv 3次

result 5000bytes recv 5次

result 30000bytes recv ?次 ---> 循环次数相关

2,接受总数据bytes,进行while循环,当接受数据大于总数据,循环结束

3,怎么获取到总bytes个数:数据转成bytes之后len() =总bytes数

dict报头-->“dict报头”---->json("dict报头") = 字典数据
len(json("dict报头")) --->len()转成四个字节 = 字典报头长度
先发送字典报头长度,在发送字典数据

服务端:

import socket
import subprocess
import struct
import json

phone = socket.socket()
phone.bind(('127.0.0.1', 8888))
phone.listen(5)

while 1:
print('start')
conn, addr = phone.accept()
while 1:
try:
cmd = conn.recv(1024)
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

        result = obj.stdout.read() + obj.stderr.read()
        result = result.decode('gbk').encode('utf-8')

        head_dict = {
            'MD5': 'fdsaf2345544324dfs',
            'file_name': '视频文件',
            'file_size': len(result),
        }                                                 #制作报头

        head_dict_json = json.dumps(head_dict)            #将报头字典转化成json序列化
        head_dict_json_bytes = head_dict_json.encode('utf-8')   #将json字符串 转化成bytes

        head_len = len(head_dict_json_bytes)         #获取字典的长度(88)
        head_len_bytes = struct.pack('i',head_len)  #将长度转化成固定的4个字节 (b'X\x00\x00\x00')


        conn.send(head_len_bytes)      #字典报头长度--->4个字节(b'X\x00\x00\x00')
        conn.send(head_dict_json_bytes)  # 字典数据bytes
        conn.send(result)                #发送原数据bytes

    except ConnectionResetError:
        break
conn.close()

phone.close()

客户端:

import socket
import struct
import json

phone = socket.socket()
phone.connect(('127.0.0.1', 8888))

while 1:
cmd = input('>>>').strip()
phone.send(cmd.encode('utf-8'))

jie_bytes = phone.recv(4)
total_size = struct.unpack('i', jie_bytes)[0]     #接收字典报头长度,struct转化为int

head_bytes = phone.recv(total_size)               #接受字典数据,total_size字典报头长度
head_dict  = json.loads(head_bytes.decode("utf-8"))  #将json字典转为字典

total_size = head_dict["file_size"]         #通过字典取出要发送过来数据的总长度

recv_data = b''                             #循环累加接受原数据
while len(recv_data) < total_size:          #不等于,当等于还会进去取值,这时值已经没有了
    recv_data += phone.recv(1024)

print(recv_data.decode('utf-8'))

phone.close()

UDP_Socket:
udp协议:不可靠,相对来说不安全的协议,面向数据报无连接协议(可靠不可靠是指数据是否能到达)效率高,速度快

基础通信:
UDP通信没有tcp连接通道,服务端和客户端每次发送都需要带着IP地址和端口号

服务端:

import socket

udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建udp协议对象
udp_server.bind(("127.0.0.1",666))

while 1:
from_client_data = udp_server.recvfrom(1024) #(b'niha', ('127.0.0.1', 64180))
print(f'来自{from_client_data[1]}{from_client_data[0].decode("utf-8")}')

to_client_data = input("输入").strip()
udp_server.sendto(to_client_data.encode("utf-8"),from_client_data[1])  

客户端:

import socket

udp_client= socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建udp协议对象

while 1:
to_client_data = input("输入").strip()
udp_client.sendto(to_client_data.encode("utf-8"),("127.0.0.1",666))

from_server_data = udp_client.recvfrom(1024)
print(f'来自{from_server_data[1]}的消息:{from_server_data[0].decode("utf-8")}')

Socket_Server多线程通信:

服务端:

import socketserver

class MyServer(socketserver.BaseRequestHandler): # 继承的类固定的

def handle(self):  # 必须是这个handle名字,类的约束
    while 1:
        from_client_data = self.request.recv(1024).decode('utf-8') #self.request == conn管道
        print(from_client_data)

        to_client_data = input('>>>').strip()
        self.request.send(to_client_data.encode('utf-8'))

if name == 'main':

ip_port = ('127.0.0.1',8848)
server = socketserver.ThreadingTCPServer(ip_port,MyServer)
# server.allow_reuse_address = True
# print(socketserver.ThreadingTCPServer.mro())
# [ThreadingTCPServer, ThreadingMixIn,TCPServer, BaseServer]
server.serve_forever()

客户端:

import socket

client = socket.socket()
client.connect(('127.0.0.1', 8848))

while 1:
content = input('>>>').strip()
client.send(f'海洋:{content}'.encode('utf-8'))

from_server_data = client.recv(1024)  # 夯住,等待服务端的数据传过来
print(f'来自服务端消息:{from_server_data.decode("utf-8")}')

phone.close()

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!