ISO(国际标准化组织)html
使网络通讯工程的工做流程标准化python
应用层:提供用户服务,具体功能由应用呈现;linux
表示层:数据的压缩、优化、加密;编程
会话层:创建用户级的链接,选择适当的传输服务(由软件开发商决定);windows
传输层:提供传输服务,进行流量监控;浏览器
网路层:路由选择,网络互联(路由的寻址);缓存
链路层:进行数据交换,控制具体数据发送;服务器
物理层:提供数据传输的物理保证、传输介质网络
即(TCP/IP协议)数据结构
背景:在实际工做当中,七层模型太过细致,难以实践,逐渐演化成实际工做中应用的四层。
应用层:集中了原来的应用层、表示层、会话层的功能
传输层
网络层
物理链路层
UDP传输数据不创建链接,直接发送消息
流式套接字(SOCK_STREAM):以字节流的方式进行数据传输,实现TCP网络传输方案。
数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现UDP网络传输方案。
socket() --> bind() --> listen() --> accept() --> recv()/send() --> close()
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
返回值:
套接字对象
sockfd.bind(addr)
参数:
元组(ip,port) ==> ('127.0.0.1',8080)
sockfd.listen(n)
功能:将套接字设置为监听套接字,建立监听队列
参数:监听队列大小(即)
connfd,addr = sockfd.accept()
功能:阻塞等待客户端处理请求
返回值:
connfd 客户端套接字
addr 链接的客户端地址
*阻塞函数:程序运行过程当中遇到阻塞函数暂停执行,直到某种条件下才继续执行。
data = connfd.recv(buffersize)
功能:接收客户端消息
参数: 每次接受消息最大的字符
返回值:收到的消息内容
n = connfd.send(data)
功能:发送消息
参数:要发送的消息(bytes格式)
返回值:发送了多少字节
字符串转字节串 str ==>bytes
str.encode()
字节串转字符串 bytes==>str
bytes.decode()
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()
socket() --> connect() --> send/recv --> close()
sockfd = socket.socket()
只有相同类型套接字才能够通讯。
sockfd.connect(addr)
功能:链接服务端
参数:元组
消息的收发须看本身的状况而定。
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()
当接收数据大小受限时,多余的数据分批接收,这样的状况为粘包
tcp链接中当一端退出,另外一端若是阻塞在recv,则recv会当即返回一个空字符串;
tcp连接中若是另外一端已经不存在,再试图使用send向其发送内容时会出现BrokenPipeError(管道破裂);
网络收发缓冲区
实现循环收发消息
#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()
TCP以字节流方式传输数据,没有消息边界,屡次发送的内容若是被一次性的接收就会造成粘包。
若是每次发送的内容是须要独立解析的含义,此时沾包会对消息的解析产生影响。
socketfd = AF_INET,SOCK_DGRAM
socketfd.bind(addr)
data,addr = sockfd.recvfrom (buffersize)
功能:接收UDP消息
参数:每次接收多少字节流
返回值:data-->接收到的消息 addr-->消息发送方地址
n = sockfd.sendto(data,addr)
功能:发送UDP消息
参数:data-->发送的消息(bytes格式) addr-->目标地址
socketfd.close()
做用
- 释放端口;
- 释放内存
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()
client = socket(AF_INET,SOCK_DGRAM)
client.sendto(data.encode(),ADDR)
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()
当接收内容大小受限的时候,多出的包直接丢失,并不会分批接收
常见问题
在收发消息的时候,会发现服务端收到的消息有时是前半部分,有时是后半部分,这是由于多个客户端发送消息的时候,若是正好同时发送,而其中一个包大,则服务端就会接收前边的部分。
listen accept
完成链接才能进行数据收发,UDP套接字不须要链接便可收发消息;send recv
收发消息,UDP使用sendto recvfrom
;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)
带*
号的为须要掌握的内容
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)
即超文本传输协议
网页的传输,数据传输
应用层协议;
传输层使用TCP服务;
简单、灵活、无状态;
请求类型多样;
数据格式支持全面
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
具体的请求类别和请求内容
格式:GET / HTTP/1.1
分别为(请求类别、请求内容、协议版本)
请求类别:
GET : 获取网络资源;
POST : 提交必定的信息,获得反馈;
HEAD : 只获取网络资源响应头;
PUT :更新服务器资源;
DELETE:删除服务器资源;
CONNECT:预留协议;
TRACE:测试;
OPTIONS:获取服务器性能信息
对请求的进一步描述和解释。
格式:键 : 值
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
声明浏览器标识等......
标准的规定,必须留空
请求参数或者提交内容
# 监听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()
响应行、响应头、空体、响应体
反馈基本的响应状况
HTTP/1.1 200 OK (协议版本、响应码、附加信息)
响应码(详细的响应码信息见跳转连接):
1xx 表示提示信息,请求被接受
2xx 表示响应成功
3xx 表示响应须要进一步处理
4xx 表示客户端错误
5xx 服务端错误
对响应内容的描述(以键值对做为描述)
Content-Type : text/html #声明网页类型
协议必须加,无内容
响应的主体内容信息
# 监听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
# 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()
pass
pass
即输入输出,在内存中发生数据交换的状况,程序不可缺乏的一部分。
在内存中存在数据交换的操做都认为是IO操做
和终端交互:
input
、和磁盘交互:
read
、write
和网络交互:
recv
、send
分类
IO密集型:在程序中存在大量的IO,而CPU运算较少,消耗CPU资源少,耗时长,效率不高
计算密集:在程序中存在大量的计算操做,IO行为较少,CPU消耗多,执行速度快
阻塞状况
默认形态
定义:在执行操做时,因为不足满某些条件造成的阻塞形态,阻塞IO时IO的默认形态。
效率:很是低
逻辑:简单
定义:经过修改IO的属性行为,使本来阻塞的IO变为非阻塞的状态。
sockfd.setblocking(bool)
功能:设置套接字为非阻塞套接字
参数:True表示套接字IO阻塞,False表示非阻塞
阻塞等待指定的时间,超时后再也不阻塞。
sockfd.settimeout(sec)
功能:设置套接字超时时间
参数:超时时间
设置非阻塞和设置超时不会同时出现。要设置超时必须是阻塞。
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语句,捕获异常
同时监听多个IO事件,当哪一个IO事件准备就绪就执行哪一个IO,以此造成能够同时处理多个IO的行为,避免一个IO阻塞形成的其余IO没法执行,提升IO执行效率。
select
(windows、linux、unix)
poll
(linux、unix)
epoll
(Linux专属)
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
(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()
使用方法:基本与poll相同
特色:
epoll触发方式
边缘触发:IO事件就绪以后不作处理,则会跳过该阻塞
水平触发:在某一个IO准备就绪以后,就会一直在该处阻塞,直到该IO处理完成
将一组简单的数据进行打包,转换为bytes格式发送,或者将一组bytes转换为python数据类型,实现不一样的语言之间的互动。
(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
模块直接调用pack
、unpack
,第一个参数直接传入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]:
需求:从客户端输入学生ID、姓名、年龄、成绩,打包发送给服务端,服务端将其存入一个数据表中
本地两个程序之间的通讯
对一个内存对象进行读写操做,完成两个程序之间的数据交互
(1) 建立本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
(2) 绑定套接字文件
sockfd.bind(file)
(3) 监听、链接、收发消息
listen
、accept
、recv/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()
原文出处:https://www.cnblogs.com/chancey/p/11246688.html