网络编程

七层网络协议

1、物理层:
    数据与高低电信号相互转换的功能;
    发送到将数据转为电信号,接收端将电信号转为数据;
2、数据链路层:
    单纯的0/1电信号没有意义,必须按组进行划分,定义电信号的分组方式;
    以太网协议:统一的网络协议,一组电信号构成一个数据包;一个数据包称为帧,帧有报头head和数据data两部分;
        报头head:固定18个字节;
            》发送者/源地址:6个字节
            》接收者/尾地址:6个字节
            数据类型:6个字节
        数据data:最短46个字节,最长1500字节
            》数据包的具体内容
        报头head+数据data=最短64字节,最长1518字节,超过最大长度则分片发送
    Mac地址:
        接入Internet的设备必须具备网卡,发送端和接收端的地址是指网卡地址,即世界唯一的Mac地址。
        Mac地址由12位16进制数表示;前6:厂商编号、后6:流水编号。
    广播:
        有了Mac地址,同一网络内的两台主机就可以通信了(一台主机通过ARP协议获取另一台主机的Mac地址);
        Ethernet采用最原始的方式,广播的方式进行通信。
3、网络层:
    世界范围的互联网是有一个个局域网构成的,如果所有的通信都采用以太网的广播方式,那么一台机器发送的包全世界都会收到,这是一种灾难。
    必须通过一种方法来区分广播域,同一广播域采用广播;否则采用路由方式(向不同广播域/子网分发数据包),Mac地址是无法区分的。
    网络层功能:引入一套新的地址用来区分不同的广播域/子网,即网络地址。
    IP协议:
        网络地址协议称为IP协议,它定义的地址称为ip地址,广泛采用v4版本即ipv4,由32位2进制表示;
        范围0.0.0.0~255.255.255.255;也可用4为10进制数表示,172.168.12.10
    ip地址:
        网络部分:标识子网
        主机部分:标识主机
        注:单纯从两个ip(172.16.10.1/172.16.10.2)看,不能确定是否为同一网段,需要通过子网掩码来计算是否在同一网段;
    子网掩码:
        子网掩码是计算ip所在网络的参数,它在形式上等同于IP地址,它的网络部分全部为1,主机部分全部为0。
        如子网掩码255.255.255.0(11111111.11111111.11111111.00000000),网络部分为是24个1,主机部分是8个0,子网可容纳256台主机
        如子网掩码255.255.0.0(11111111.11111111.00000000.00000000),网络部分为是16个1,主机部分是16个0,子网可容纳65536台主机
        如:172.168.10.1和255.255.255.0进行And按位与运算,获得172.168.10.0,若其他ip计算也是这个值,则两个ip属于同网段。
    ip数据包:
        ip数据包分为head和data两部分,ip数据包存储在以太网的data部分。
        head:长度为20~60字节
        data:最长为65515字节
        以太网数据包的数据部分,最长只有1500字节。如果IP数据包超过了1500字节,它就需要分割成几个以太网数据包,分开发送了。
        以太网头+以太网数据包(ip报头+ip数据+其他数据)
    ARP协议:通信是基于Mac地址的广播方式实现的,计算机通信获取本机Mac简单,获取尾端Mac地址需要通过ARP协议。
        例如:主机172.16.10.10/24访问172.16.10.11/24
        首先通过ip地址和子网掩码区分出自己所处的子网,子网地址172.16.10.0,属于同一网络
            场景数        据包地址
            同一子网      目标主机mac,目标主机ip
            不同子网      网关mac,目标主机ip
        如果不是同一网络,那么下表中目标ip为172.16.10.1,通过arp获取的是网关的mac
                    源mac        目标mac                源ip            目标ip            数据部分
        发送端主机    发送端mac    FF:FF:FF:FF:FF:FF    172.16.10.10/24    172.16.10.11/24    数据
        如果目标Mac是FF:FF:FF:FF:FF:FF则表示广播发送数据,所有接收端判断如果目标ip是自己的ip则应答自己的Mac地址。
    RARP协议:

 

网络通信

=============== 1、创建和销毁socket ===============
socket 模块中的socket(family,type[,proto])函数创建一个新的socket对象。
    family取值:
        AF_INET,用于服务器间IPV4连接,默认值
        AF_INET6,用于服务器间IPV6连接
        AF_UNIX,用于Unix进程间通信
    type取值:
        SOCK_STREAM(TCP连接),默认值
        SOCK_DGRAM(UDP链接)

=============== 2、服务器端socket ===============
import socket

sk = socket.socket()
#创建socket对象
address = ('127.0.0.1',8000)
#本地链接ip和端口
sk.bind(address)
#socket对象绑定address
sk.listen(3)#配置最大连接数
while True:
    conn,addr = sk.accept()#开启监听连接
    while True:
        data =conn.recv(1024)#服务器接收客户端1024字节数据
        print(str(data,'utf8'))#将接收的数据从byte类型转为utf8
        if not data :
            conn.close()#关闭客户端连接
            break
        conn.send(bytes('serve:约啊','utf8'))#只能发送byte数据类型,需要将数据从utf8转byte

sk.close()#关闭服务器连接池

=============== 3、客户端socket ===============
import socket

sk = socket.socket()
#创建socket对象
address = ('127.0.0.1',8000)
#服务器端的链接ip和端口
sk.connect(address)
#向服务器端发起连接
while True:
    data = input('>>>')# 客户端接收服务器发送的1024字节数据
    print(data.decode('utf8'))# 将数据从byte类型转为utf8
    if data=='exit':
        break
    sk.send(bytes(data, 'utf8'))# 客户端向服务器端发送数据,从utf8转为byte类型
    data = sk.recv(1024)
    print(str(data, 'utf8'))  # 将接收的数据从byte类型转为utf8
sk.close()#关闭服务器连接池

 

网络远程

=============== 1、服务器端执行命令 ===============
import socket

sk = socket.socket()
#创建socket对象
address = ('127.0.0.1',8000)
#本地链接ip和端口
sk.bind(address)
#socket对象绑定address
sk.listen(3)#配置最大连接数
while True:
    conn,addr = sk.accept()#开启监听连接
    while True:
        data =conn.recv(1024)#服务器接收客户端1024字节数据
        print(str(data,'utf8'))#将接收的数据从byte类型转为utf8
        if not data :
            conn.close()#关闭客户端连接
            break
        obj = subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
        conn.send(byte(str(len(obj.stdout.read())),'utf8'))#先发送结果长度,方便客户的接收
        conn.recv(1024)#此处接收任意数据,防止两次发送出现粘包现象,影响数据接收方处理
        conn.send(obj.stdout.read())#只能发送byte数据类型,需要将数据从utf8转byte


sk.close()#关闭服务器连接池

=============== 2、客户端发送命令和读取结果 ===============
import socket

sk = socket.socket()
#创建socket对象
address = ('127.0.0.1',8000)
#服务器端的链接ip和端口
sk.connect(address)
#向服务器端发起连接
while True:
    data = input('>>>')# 客户端接收服务器发送的1024字节数据
    print(data)# 将数据从byte类型转为utf8
    if data=='exit':
        break
    sk.send(bytes(data, 'utf8'))# 客户端向服务器端发送数据,从utf8转为byte类型
    ret_len = int(str(sk.recv(1024)
,'utf8'))
    sk.send('ok')#此处发送任意数据,通知对方继续发送数据,防止出现粘包现象
    data = bytes()
    while len(data) != ret_len:
        recv = sk.recv(1024)
        data += recv
    print(str(data, 'gbk'))  # 将接收的数据从byte类型转为utf8
sk.close()#关闭服务器连接池

socketserver并发聊天

1、服务端
import socketserver

class MyServer(socketserver.BaseRequestHandler):#必须继承该父类

    def handle(self):#在此处理客户端逻辑
        print('服务端启动:...')
        while True:
            conn = self.request#获取客户端连接
            print(self.client_address)
            while True:
                client_data = conn.recv(1024)
                print(str(client_data,'utf8'))
                print('waiting....')
                server_data = input('>>>:')
                conn.sendall(bytes(server_data,'utf8'))
            conn.close()
server = socketserver.ThreadingTCPServer(('127.0.0.1',8908), MyServer)
#创建server对象,MyServer类是处理业务逻辑
print('start')
server.serve_forever()
#启动服务端

2、客户端
import socket

ip_port = ('127.0.0.1', 8893)

sk = socket.socket()
sk.connect(ip_port)

print('启动客户端:')

while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,'utf8'))
    if inp=='exit':
        break
    ret = sk.recv(1024)
    print(str(ret,'utf8'))
sk.close()

 

事件驱动模型

********************************************* 事件驱动模型 *********************************************
https://www.cnblogs.com/yuanchenqi/articles/5722574.html
目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
    1、有一个事件(消息)队列;
    2、鼠标按下时,往这个队列中增加一个点击事件(消息);
    3、有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
    4、事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数; 


=============== 1、非阻塞IO ===============

import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(('127.0.0.1',6667))
sk.listen(5)
sk.setblocking(False)
#非阻塞设置
while True:
    try:
#非阻塞状态下若监听不到客户端连接会报异常,所以可以循环处理异常
        print ('waiting client connection .......')
        connection,address = sk.accept()   # 进程主动轮询
监听
        print("+++",address)
        client_messge = connection.recv(1024)
        print(str(client_messge,'utf8'))
        connection.close()
    except Exception as e:
        print (e)
        time.sleep(4)

# select 模拟一个socket server,注意socket必须在非阻塞情况下才能实现IO多路复用。
# 接下来通过例子了解select 是如何通过单进程实现同时处理多个非阻塞的socket连接的。
#server端
import select
import socket
import queue

server = socket.socket()
server.bind(('localhost',9000))
server.listen(1000)

server.setblocking(False)  # 设置成非阻塞模式,accept和recv都非阻塞
# 这里如果直接 server.accept() ,如果没有连接会报错,所以有数据才调他们
# BlockIOError:[WinError 10035] 无法立即完成一个非阻塞性套接字操作。
msg_dic = {}
inputs = [server,]  # 交给内核、select检测的列表。
# 必须有一个值,让select检测,否则报错提供无效参数。
# 没有其他连接之前,自己就是个socket,自己就是个连接,检测自己。活动了说明有链接
outputs = []  # 你往里面放什么,下一次就出来了

while True:
    readable, writeable, exceptional = select.select(inputs, outputs, inputs)  # 定义检测
    #新来连接                                        检测列表         异常(断开)
    # 异常的也是inputs是: 检测那些连接的存在异常
    print(readable,writeable,exceptional)
    for r in readable:
        if r is server:  # 有数据,代表来了一个新连接
            conn, addr = server.accept()
            print("来了个新连接",addr)
            inputs.append(conn)  # 把连接加到检测列表里,如果这个连接活动了,就说明数据来了
            # inputs = [server.conn] # 【conn】只返回活动的连接,但怎么确定是谁活动了
            # 如果server活动,则来了新连接,conn活动则来数据
            msg_dic[conn] = queue.Queue()  # 初始化一个队列,后面存要返回给这个客户端的数据
        else:
            try :
                data = r.recv(1024)  # 注意这里是r,而不是conn,多个连接的情况
                print("收到数据",data)
                # r.send(data) # 不能直接发,如果客户端不收,数据就没了
                msg_dic[r].put(data)  # 往里面放数据
                outputs.append(r)  # 放入返回的连接队列里
            except ConnectionResetError as e:
                print("客户端断开了",r)
                if r in outputs:
                    outputs.remove(r) #清理已断开的连接
                inputs.remove(r) #清理已断开的连接
                del msg_dic[r] ##清理已断开的连接

    for w in writeable:  # 要返回给客户端的连接列表
        data_to_client = msg_dic[w].get()  # 在字典里取数据
        w.send(data_to_client)  # 返回给客户端
        outputs.remove(w)  # 删除这个数据,确保下次循环的时候不返回这个已经处理完的连接了。

    for e in exceptional:  # 如果连接断开,删除连接相关数据
        if e in outputs:
            outputs.remove(e)
        inputs.remove(e)
        del msg_dic[e]


#*************************client
import socket
client = socket.socket()

client.connect(('localhost', 9000))

while True:
    cmd = input('>>> ').strip()
    if len(cmd) == 0 : continue
    client.send(cmd.encode('utf-8'))
    data = client.recv(1024)
    print(data.decode())

client.close()

 

posted @ 2022-11-20 09:13  大碗麻辣烫  阅读(29)  评论(0编辑  收藏  举报