01. 网络编程

一、网络编程

1. 模型

1.1 OSI七层模型

制定组织

ISO(国际标准化组织)

作用

使网络通信工程的工作流程标准化

内容

应用层:提供用户服务,具体功能由应用呈现;

表示层:数据的压缩、优化、加密;

会话层:建立用户级的连接,选择适当的传输服务(由软件开发商决定);

传输层:提供传输服务,进行流量监控;

网路层:路由选择,网络互联(路由的寻址);

链路层:进行数据交换,控制具体数据发送;

物理层:提供数据传输的物理保证、传输介质

优点

  1. 建立了统一的工作流程;
  2. 各部分功能清晰,各司其职;
  3. 降低耦合度,方便开发

1.2 四层模型

即(TCP/IP协议)

背景:在实际工作当中,七层模型太过细致,难以实践,逐渐演化成实际工作中应用的四层。

应用层:集中了原来的应用层、表示层、会话层的功能

传输层

网络层

物理链路层

1.2.1 TCP三次握手

1.2.2 TCP 四次挥手

1.2.3 UDP

UDP传输数据不建立连接,直接发送消息

1.3 套接字类型

流式套接字(SOCK_STREAM):以字节流的方式进行数据传输,实现TCP网络传输方案。

数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现UDP网络传输方案。

2. TCP套接字编程

2.1 服务端流程

socket() --> bind() --> listen() --> accept() --> recv()/send() --> close()

(1) 创建套接字

sockfd = socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)

参数:

socket_family : 网络地址类型(AF_INET ==> ipv4)

socket_type : 套接字类型(SOCK_STREAM> 流式套接字; SOCK_DGRAM> 数据报)

proto : 子协议类型,通常为0

返回值:

套接字对象

(2) 绑定地址

sockfd.bind(addr)

参数:

元组(ip,port) ==> ('127.0.0.1',8080)

(3) 设置监听端口

sockfd.listen(n)

功能:将套接字设置为监听套接字,创建监听队列

参数:监听队列大小(即)

(4) 等待处理客户端请求

connfd,addr = sockfd.accept()

功能:阻塞等待客户端处理请求

返回值:

connfd 客户端套接字

addr 连接的客户端地址

*阻塞函数:程序运行过程中遇到阻塞函数暂停执行,直到某种条件下才继续执行。

(5) 收发消息

data = connfd.recv(buffersize)

功能:接收客户端消息

参数: 每次接受消息最大的字符

返回值:收到的消息内容

n = connfd.send(data)

功能:发送消息

参数:要发送的消息(bytes格式)

返回值:发送了多少字节

字符串转字节串 str ==>bytes str.encode()

字节串转字符串 bytes==>str bytes.decode()

(5)关闭套接字

socket.close()

功能:关闭套接字

整体代码

import socket

# 创建套接字
sock_fd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定地址
sock_fd.bind(('0.0.0.0',8080))
# 设置监听端口
sock_fd.listen(5)
# 阻塞等待处理客户端的连接
print("Waiting ...")
conn,addr = sock_fd.accept()
print("Connect from ",addr[0]) # 客户地址
# 消息收发
data = conn.recv(1024)
print("Receive message:",data.decode())

n = conn.send(b"Hello World !")
print("Send %d bytes"%n)
# 关闭连接
conn.close()
sock_fd.close()

2.2 客户端流程

socket() --> connect() --> send/recv --> close()

(1) 创建套接字

sockfd = socket.socket()

只有相同类型套接字才可以通信。

(2) 请求连接

sockfd.connect(addr)

功能:连接服务端

参数:元组

(3) 消息收发

消息的收发须看自己的情况而定。

(4) 关闭套接字

sockfd.close()

整体代码

from socket import *

# 创建套接字
socket_fd = socket()
# 发起连接
server_addr = ('127.0.0.1',8080)
socket_fd.connect(server_addr)

# 收发消息
data = input(">>")
socket_fd.send(data.encode())
data = socket_fd.recv(1024)
print("From server : ",data.decode())
# 关闭套接字
socket_fd.close()

当接收数据大小受限时,多余的数据分批接收,这样的情况为粘包

2.3 TCP套接字传输特点

  1. tcp连接中当一端退出,另一端如果阻塞在recv,则recv会立即返回一个空字符串;

  2. tcp链接中如果另一端已经不存在,再试图使用send向其发送内容时会出现BrokenPipeError(管道破裂);

  3. 网络收发缓冲区

  • 缓冲区有效的协调了消息的收发速度;
  • send、recv实际是向缓冲区发送接收消息,当缓冲区不为空的时候recv就不会阻塞

实现循环收发消息

#server.py
import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
print("正在等待客户端连接...")
#waiting client connect ...
conn,addr = server.accept()
print('Connect from ',addr[0])
while True:
 data = conn.recv(1024)
 if not data:
     break
 print('收到%s的消息:%s'%(addr[0],data.decode()))
 n = conn.send("已经收到您的消息".encode())
 print('发送%s字节'%n)

conn.close()
server.close()
import socket

client = socket.socket() #创建套接字
server_addr = ('127.0.0.1',8080) #绑定IP及端口
client.connect(server_addr) # 连接服务端
while True:
    data = input(">>>")
    client.send(data.encode()) #发送消息
    if not data:
        break
    data = client.recv(1024) #接收数据
    print("来自服务器的消息",data.decode())

client.close()

实现循环连接客户端

#server.py
import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    print("正在等待客户端连接...")
    #waiting client connect ...
    conn,addr = server.accept()
    print('Connect from ',addr[0])

    while True:
        data = conn.recv(1024)
        if not data:
            break
        print('收到%s的消息:%s'%(addr[0],data.decode()))
        n = conn.send("已经收到您的消息".encode())
        print('发送%s字节'%n)
    conn.close()
#关闭套接字
server.close()
#client.py
import socket

client = socket.socket() #创建套接字
server_addr = ('127.0.0.1',8080) #绑定IP及端口
client.connect(server_addr) # 连接服务端
while True:
    data = input(">>>")
    client.send(data.encode()) #发送消息
    if not data:
        break
    data = client.recv(1024) #接收数据
    print("来自服务器的消息",data.decode())

client.close()

2.4 粘包(zhan bao)

原因

TCP以字节流方式传输数据,没有消息边界,多次发送的内容如果被一次性的接收就会形成粘包。

影响

如果每次发送的内容是需要独立解析的含义,此时沾包会对消息的解析产生影响。

处理

  1. 人为添加消息边界;
  2. 控制发送速度(发送少量消息适用)

3. UDP套接字编程

3.1 服务端流程

(1) 创建套接字

socketfd = AF_INET,SOCK_DGRAM

(2) 绑定地址

socketfd.bind(addr)

(3) 收发消息

data,addr = sockfd.recvfrom (buffersize)

功能:接收UDP消息

参数:每次接收多少字节流

返回值:data-->接收到的消息 addr-->消息发送方地址

n = sockfd.sendto(data,addr)

功能:发送UDP消息

参数:data-->发送的消息(bytes格式) addr-->目标地址

(4) 关闭套接字

socketfd.close()

作用

  1. 释放端口;
  2. 释放内存

整体代码

from socket import *

#创建数据报套接字,即创建UDP套接字
server = socket(AF_INET,SOCK_DGRAM)
#绑定地址
server.bind(('127.0.0.1',8080))
#收发消息
while True:
    data,addr = server.recvfrom(1024)
    print("收到消息来自%s的消息:%s"%(addr,data.decode()))
    server.sendto('谢谢你的消息'.encode(),addr)
#关闭套接字
serve.close()

3.2 客户端流程

(1) 创建UDP套接字

client = socket(AF_INET,SOCK_DGRAM)

(2) 发送接收套接字

client.sendto(data.encode(),ADDR)

(3) 关闭套接字

client.close()

整体代码

from socket import *
#服务端地址
HOST = '127.0.0.1'
PORT = 8080
ADDR = (HOST,PORT)
#创建套接字
client = socket(AF_INET,SOCK_DGRAM)
# 收发消息
while True:
    data = input(">>")
    if data == 'bye':
        break
    client.sendto(data.encode(),ADDR)
    msg,addr = client.recvfrom(2014)
    print("来自服务器的消息:",msg.decode())
# 关闭套接字
client.close()

当接收内容大小受限的时候,多出的包直接丢失,并不会分批接收

常见问题

在收发消息的时候,会发现服务端收到的消息有时是前半部分,有时是后半部分,这是因为多个客户端发送消息的时候,如果正好同时发送,而其中一个包大,则服务端就会接收前边的部分。

2. TCP与UDP的区别

  1. 流式套接字以字节流方式传输数据,数据报套接字以数据报形式传输数据;
  2. TCP套接字会有粘包问题存在,UDP套接字因为有边界而无粘包问题;
  3. TCP套接字保证了消息的完整性,UDP无法保证,会丢包;
  4. TCP套接字依赖listen accept完成连接才能进行数据收发,UDP套接字不需要连接即可收发消息;
  5. TCP 使用send recv收发消息,UDP使用sendto recvfrom;

二、SOCKET模块

1. 部分socket模块方法

import socket

socket.gethostname() #本机获取主机名
socket.gethostbyname() #通过主机名获取IP地址
socket.getservbyname() #获取指定服务的端口
socket.getservbyport() #获取指定端口的服务

socket.inet_aton('192.168.1.1') #将IP转换为字节串,也就是转换为16进制
socket.inet_ntoa(b'\xc0\xa8\x01\x01') #将字节串转换为IP

socket.htons(5) #网络制式,不同计算机之间通信的一种方式
socket.ntohs(1280) 

2. 套接字属性

*号的为需要掌握的内容

from socket import *
s = socket()
s.bind(('127.0.0.1',8888))

print(s.family) #地址类型
print(s.type) #套接字类型

print(s.getsockname()) #绑定地址

* print(s.getpeername()) #获取连接客户端的IP地址(需要在套接字连接之后使用)

* print(s.fileno()) #获取文件描述符

* s.setsockopt(level,option,value) # 设置套接字选项(参数:level设置选项类别,option具体选项内容,value具体的值)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,True) #设置端口立即重用



描述符

调用计算机的某个接口完成自己的实际需求,而这种操作是基于操作系统的,并非python,所有IO操作都会分配一个证书作为编号,该整数即为这个IO的文件描述符

特点

每个IO文件的描述符不会发生重复

三、广播

定义

一端接收,多端发送。

# server.py
from socket import *
client = socket(AF_INET,SOCK_DGRAM)
client.setsockopt(SOL_SOCKET,SO_BROADCAST,True)

#选择接收地址
client.bind(('0.0.0.0',8080))

while True:
    msg,addr = client.recvfrom(1024)
    print(msg.decode())
# client.py
from socket import *
from time import sleep

# 目标地址
dest = ('127.0.0.1',8080)
s = socket(AF_INET,SOCK_DGRAM)

# 设置可以发生和接收广播
s.setsockopt(SOL_SOCKET,SO_BROADCAST,True)

data = """
****************************************
****************清明********************
******清明时节雨纷纷,路上行人欲断魂******
******借问酒家何处有,牧童遥指杏花村******
****************************************
"""
while True:
    sleep(2)
    s.sendto(data.encode(),dest)

四、 HTTP协议

1. HTTP协议

即超文本传输协议

1.1 用途

网页的传输,数据传输

1.2 特点

  • 应用层协议;

  • 传输层使用TCP服务;

  • 简单、灵活、无状态;

  • 请求类型多样;

  • 数据格式支持全面

点击查看HTTP状态码详解

1.3 请求格式

GET /sample.jsp HTTP/1.1
Accept : image/gif.image/jpeg,*/*
Accept-Language : zh-cn
Connection : Keep-Alive
Host : localhost
User-Agent : Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding : gzip,deflate
username = jinqiao&password=1234

(1) 请求行

具体的请求类别和请求内容

格式:GET / HTTP/1.1分别为(请求类别、请求内容、协议版本)

请求类别:

  1. GET : 获取网络资源;

  2. POST : 提交一定的信息,得到反馈;

  3. HEAD : 只获取网络资源响应头;

  4. PUT :更新服务器资源;

  5. DELETE:删除服务器资源;

  6. CONNECT:预留协议;

  7. TRACE:测试;

  8. OPTIONS:获取服务器性能信息

(2) 请求头

对请求的进一步描述和解释。

格式:键 : 值

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36

声明浏览器标识等......

(3) 空体

标准的规定,必须留空

(4) 请求体

请求参数或者提交内容

# 监听8080端口
from socket import *

s = socket()
s.bind(('0.0.0.0',8080))
s.listen(5)
c,addr = s.accept()
print("连接到".encode(),addr)
data = c.recv(4096)
print(data)

c.close()
s.close()

1.4 响应格式

响应行、响应头、空体、响应体

(1) 响应行

反馈基本的响应情况

HTTP/1.1 200 OK (协议版本、响应码、附加信息)

响应码(详细的响应码信息见跳转链接):

1xx 表示提示信息,请求被接受

2xx 表示响应成功

3xx 表示响应需要进一步处理

4xx 表示客户端错误

5xx 服务端错误

(2) 响应头

对响应内容的描述(以键值对作为描述)

Content-Type : text/html #声明网页类型

(3) 响应体

协议必须加,无内容

(4)响应体

响应的主体内容信息

1.5 应用

1.5.1 简单的网页

# 监听8080端口
from socket import *

s = socket()
s.bind(('0.0.0.0',8080))
s.listen(5)
c,addr = s.accept()
print("连接到",addr)
data = c.recv(4096)
print(data)
data = '''
HTTP/1.1 200 OK
Content-Type : text/html

Hello Chancey !
'''
c.send(data.encode())
print(data)

c.close()
s.close()

打开浏览器,打开URL127.0.0.1:8080

1.5.2 上传文件

# recv.py
from socket import *

s = socket()
s.bind(('0.0.0.0',8809))
s.listen(5)
c,addr = s.accept()
print("来自",addr[0],'的连接')

f = open('demo.png','wb')

while True:
    data = c.recv(1024)
    if not data:
        break
    f.write(data)

f.close()
c.close()
s.close()
# send.py
from socket import *

s = socket()
s.connect(('127.0.0.1',8809))

f = open('pic.png','rb')
while True:
    data = f.read(1024)
    if not data:
        break
    s.send(data)

f.close()
s.close()

1.5.3 前置网页文件

pass

2. HTTPS协议

pass

五、IO

即输入输出,在内存中发生数据交换的情况,程序不可缺少的一部分。

1. 定义

在内存中存在数据交换的操作都认为是IO操作

和终端交互:inputprint

和磁盘交互:readwrite

和网络交互:recvsend

分类

  1. IO密集型:在程序中存在大量的IO,而CPU运算较少,消耗CPU资源少,耗时长,效率不高

  2. 计算密集:在程序中存在大量的计算操作,IO行为较少,CPU消耗多,执行速度快

2. IO模型

阻塞情况

  1. 因为某种条件没有达到而形成阻塞(accept、input、recv);
  2. 处理IO的时间太长产生的阻塞情况(网络传输、大文件的读写过程);

2.1 阻塞IO

默认形态

定义:在执行操作时,由于不足满某些条件形成的阻塞形态,阻塞IO时IO的默认形态。

效率:非常低

逻辑:简单

2.2 非阻塞IO

定义:通过修改IO的属性行为,使原本阻塞的IO变为非阻塞的状态。

2.2.1 设置方法

  1. 设置socket为非阻塞套接字

    sockfd.setblocking(bool)

    功能:设置套接字为非阻塞套接字

    参数:True表示套接字IO阻塞,False表示非阻塞

  2. 设置超时检测

    阻塞等待指定的时间,超时后不再阻塞。

    sockfd.settimeout(sec)

    功能:设置套接字超时时间

    参数:超时时间

设置非阻塞和设置超时不会同时出现。要设置超时必须是阻塞。

2.2.1 示例

from socket import *
from time import sleep,ctime

#创建一个tcp套接字
sockfd = socket()
sockfd.bind(('127.0.0.1',8809))
sockfd.listen(5)
#设置非阻塞(True为阻塞、False为非阻塞)
sockfd.setblocking(False)
while True:
    print("等待连接......")
    conn,addr = sockfd.accept()

#运行结果
>>>等待连接......
Traceback (most recent call last):
  File "D:/project/demo/网络编程/IO/block_io.py", line 12, in <module>
    connfd,addr = sockfd.accept()
  File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\socket.py", line 205, in accept
    fd, addr = self._accept()
BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。
    
    
    
    
 
from socket import *
from time import sleep,ctime

#创建一个tcp套接字
sockfd = socket()
sockfd.bind(('127.0.0.1',8809))
sockfd.listen(5)
#设置超时时间
sockfd.settimeout(3)
while True:
    print("等待连接......")
    try: #捕获异常,设置异常
        conn,addr = sockfd.accept()
    except BlockingIOError:
        sleep(2)
        print(ctime(),"连接错误")
        continue
    except timeout:
        print("超时连接")
    else:
        print("已连接至",addr[0])
        data = conn.recv(1024)
#运行之后,在有连接的情况下将不会再等待连接

报错是IO模块错误,所以使用try语句,捕获异常

2.3 IO多路复用

定义

同时监听多个IO事件,当哪个IO事件准备就绪就执行哪个IO,以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成的其他IO无法执行,提高IO执行效率。

具体方案

  1. select(windows、linux、unix)

  2. poll(linux、unix)

  3. epoll(Linux专属)

2.3.1 SELECT

概念

ra,ws,xs = select(rlist, wlist, xlist[, timeout])

功能

监控多个IO时间,阻塞等待IO的发生

参数

rlist : 列表(存放关注的等待发生的事件)

wlist : 列表(存放要主动处理的IO事件)

xlist : 列表(存放发生异常要处理的事件)

timeout : 超时时间

返回值

rs : 列表 rlist 中准备就绪的IO

ws : 列表 wlist 中准备就绪的IO

xs : 列表 xlist 中准备就绪的IO

select(...)
    select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)
    Wait until one or more file descriptors are ready for some kind of I/O.
    等待一个或多个文件描述符准备好进行某种I/O。
The first three arguments are sequences of file descriptors to be waited for:
    前三个参数是等待的文件描述符序列:
rlist -- wait until ready for reading
rlist——等待阅读准备就绪
wlist -- wait until ready for writing
wlist——等到准备好写作
xlist -- wait for an ``exceptional condition''
xlist——等待“异常情况”
If only one kind of condition is required, pass [] for the other lists.
如果只需要一种条件,则为其他列表传递[]。
A file descriptor is either a socket or file object, or a small integer gotten from a fileno() method call on one of those.
文件描述符可以是套接字或文件对象,也可以是一个小整数。从其中一个的fileno()方法调用中获取。
The optional 4th argument specifies a timeout in seconds; it may be a floating point number to specify  ractions of seconds.  If it is absent or None, the call will never time out.
可选的第4个参数指定以秒为单位的超时;它可能是用于指定秒分数的浮点数。如果它不在或者没有,电话永远不会超时。
The return value is a tuple of three lists corresponding to the first three arguments; each contains the  ubset of the corresponding file descriptors that are ready.
返回值是与前三个列表对应的三个列表的元组参数;每个包含相应文件描述符的子集准备好了。
*** IMPORTANT NOTICE ***
***重要通知***
On Windows, only sockets are supported; on Unix, all file descriptors can be used.
在Windows上,只支持套接字;在Unix上,所有文件可以使用描述符。

简单使用

from select import select
from socket import *

# 创建套接字作为关注的IO
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)

#添加到关注列表
rlist = [s]
wlist = []
xlist = []

while True:
    # 监控IO
    rs,ws,xs = select(rlist,wlist,xlist)
    for r in rlist:
        # s 就绪说明有客户端连接
        if r is rs:
            c,addr = r.accept()
            print("连接来自",addr[0])
            # 将客户端加入到关注列表里面
            rlist.append(c)
        # 如果是c就绪则表示对应的客户端发送消息
        else:
            data = r.recv(1024)
            if not data:
                rlist.remove(r)
                r.close()
                continue
            print(data.decode())
            # r.send(b'OK')
            # 当r放进wlist中时希望主动去处理
            wlist.append(r)
    for w in wlist:
        w.send(b'OK')
        wlist.remove(w)
    for x in xlist:
        pass

2.3.2 POLL

步骤

(1) p = select.poll()

功能:创建poll对象,该函数返回一个实例对象

返回值:poll对象

(2) p.register(fd,event)

功能:注册关注的IO

返回值:

参数

fd : 关注的IO

event : 关注IO事件的类型(读事件、写事件、异常事件)

# 常用IO事件类型的划分及写法
POLLIN # 读IO(rlist)
POLLOUT # 写IO(wlist)
POLLERR # 异常IO(xlist)
POLLHUP # 断开连接

# 多个事件类型的写法
p.register = sockfd(POLLIN | POLLOUT)

(3) p.unregister(fd)

功能:取消对IO的关注

参数: IO对象或者IO对象的fileno(文件描述符)

(4) events = p.pull()

功能:阻塞等待监控IO事件的发生

返回值: 就绪的IO事件

events格式:[ ( files , event ) , ( ) , ( ) , ......]

需要通过fileno寻找对应的IO对象,以操作IO事件,建立字典作为查找地图。{ fileno : io_obj }

实例 ①

from socket import *
from select import *

# 创建套接字作为监控的IO事件
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)

# 创建poll对象
p = poll()

# 建立地图(即创建字典)
fdmap = {s.fileno():s}

# 关注IO事件的维护,取关和关注
p.register(s,POLLIN | POLLERR) # 关注IO事件

# 循环监控IO
while True:
    events = p.poll()
    for fd,event in events: # 遍历events,处理IO
        if fd == s.fileno():
            c,addr = fdmap[fd].accept() # 通过键找到值,该值则为IO事件
            print("来自%s的连接"%addr[0])
            # 添加新的关注
            p.register(c,POLLIN | POLLHUP)
            fdmap[c.fileno()] = c
        elif event & POLLHUP:
            print("客户端退出")
            p.unregister(fd)
            fdmap[fd].close()
            del fdmap[fd]
        elif event & POLLIN:
            data = fdmap[fd].recv(1024)
            print(data.decode())
            fdmap[fd].send(b'OK')

实例 ②

'''
需求说明:
创建一个服务端、一个客户端 ,在client连接server时记录日志,并且保留所有的连接记录和消息记录
'''
from select import select
from socket import *
import sys
from time import ctime

# 日志文件
f = open('log.txt','a')

s = socket()
s.bind(('',8888)) # 该处如果为空,则为0.0.0.0
s.listen(5)

rlist = [s,sys.stdin]
wlist = []
xlist = []

while True:
    rs,ws,xs = select(rlist,wlist,xlist)
    for r in rs:
        if r is s:
            c,addr = r.accept()
            rlist.append(c)
        elif r is sys.stdin:
            name = 'Server'
            time = ctime()
            msg = r.readline()
            f.write('%s %s %s \n'%(name,time,msg))
            f.flush() # 清除缓存
        else:
            addr = r.getpeername()
            time = ctime()
            msg = r.recv(1024).decode()
            f.write('%s %s %s \n' % (addr, time, msg))
            f.flush()

f.close()
s.close()

2.3.3 EPOLL

使用方法:基本与poll相同

  • 生成对象改为epoll()
  • 将所有事件类型改为epoll事件

特点:

  1. 效率比poll高;
  2. 可以同时监控io的数量比poll多;
  3. 触发方式比poll更多;
  4. 只能 在Linux下使用

epoll触发方式

边缘触发:IO事件就绪之后不做处理,则会跳过该阻塞

水平触发:在某一个IO准备就绪之后,就会一直在该处阻塞,直到该IO处理完成

3. STRUTE

3.1 原理

将一组简单的数据进行打包,转换为bytes格式发送,或者将一组bytes转换为python数据类型,实现不同的语言之间的互动。

3.2 接口使用

(1) st = Struct(fmt)

功能:生成结构化数据

参数:定制的数据结构

要组织的数据:1 b'chancey' 1.75

fmt : 'i4sf'

解释:一个整型、四个字节型、一个浮点型

(2) st.pack(v1,...)

不定参,可以传多个参数

功能:将一组数据按照一定格式打包转换

返回值:bytes字节串

(3) st.unpack(bytes_data)

功能:将bytes按照格式解析

返回值:解析后的数据元组

In [2]: import struct                    
                                         
In [3]: st = struct.Struct('i4sf')       
                                         
In [4]: data = st.pack(1,b'chancey',1.68)
                                         
In [5]: data                             
Out[5]: b'\x01\x00\x00\x00chan=\n\xd7?'  
                                         
In [6]: st.unpack(data)                  
Out[6]: (1, b'chan', 1.6799999475479126) 
                                         
In [7]:                                  

(4) struct.pack( fmt , v1 , ... )

struct.unpack(fmt,bytes)

说明:使用struct模块直接调用packunpack,第一个参数直接传入fmt

In [1]: import struct

In [3]: data = struct.pack('7si',b'chancey',18) # 打包

In [5]: struct.unpack('7si',data) # 解包
Out[5]: (b'chancey', 18)

In [6]:

3.3 实例

需求:从客户端输入学生ID、姓名、年龄、成绩,打包发送给服务端,服务端将其存入一个数据表中


4. 本地套接字

1. 功能

本地两个程序之间的通信

2. 原理

对一个内存对象进行读写操作,完成两个程序之间的数据交互

3. 步骤

(1) 创建本地套接字

sockfd = socket(AF_UNIX,SOCK_STREAM)

(2) 绑定套接字文件

sockfd.bind(file)

(3) 监听、连接、收发消息

listenacceptrecv/send

实例

# server.py
from socket import *
import os

# 本地套接字文件
sock_file = './sock'

# 判断文件是否存在,存在返回True,反之亦然
if os.path.exists(sock_file):
    os.remove(sock_file) # 删除文件

# 创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
# 绑定文件
sockfd.bind(sock_file)
sockfd.listen(5)

while True:
    c,addr = sockfd.accept()
    while True:
        data = c.recv(1024)
        if not data:
            break
        print(data.decode())
    c.close()
sockfd.close()

# client.py

from socket import *

# 两边必须使用同一个套接字
sock_file = './sock'

sockfd = socket(AF_UNIX,SOCK_STREAM)
sockfd.connect(sock_file)

while True:
    msg = input('>>>')
    if not msg:
        break
    sockfd.send(msg.encode())
sockfd.close()
posted @ 2019-07-25 20:16  ChanceySolo  阅读(467)  评论(0编辑  收藏  举报