首页 文章 精选 留言 我的

精选列表

搜索[高性能rpc框架],共10000篇文章
优秀的个人博客,低调大师

(进阶篇)Python web框架FastAPI——一个比Flask和Tornada更高性能的API 框架

前言 上一篇已经初步了解了 FastAPI 的基本使用,但是如果想要真正把 FastAPI 部署上线到服务器,那么你需要了解更多,学习更多。所以本篇内容将注重于 FastAPI 的项目生产环境,诸如 数据库,路由蓝图,数据验证等问题在 FastAPI 中的具体操作和一些自己碰到的坑,分享给正在进攻 FastAPI 的各位小伙伴。 蓝图 事实上,FastAPI 并没有关于蓝图 (Blueprint) 的定义,在 FastAPI 中使用 Include_route 方法来添加路由,也就是我们所熟知的蓝图了。 import timefrom typing import Listfrom starlette.templating import Jinja2Templatesfrom fastapi import Depends, FastAPI, HTTPExceptionfrom starlette.staticfiles import StaticFilesfrom starlette.templating import Jinja2Templatesfrom app import modelsfrom app.database.database import SessionLocal, enginefrom app.home import user,index app = FastAPI() app.mount("/static", StaticFiles(directory="app/static"), name="static") # 挂载静态文件,指定目录templates = Jinja2Templates(directory="templates") # 模板目录 app.include_router(index.userRouter)app.include_router(user.userRouter,prefix="/user") 可以看到在 home 目录引入了 user.py 和 index.py 文件,注意必须要在文件中初始化一个 APIRouter() 类对象 (当然如果需要,可以选择继承),prefix 指明子路由的路径,更多的参数使用请参考官方文档。 user.py from starlette.templating import Jinja2Templatesfrom app import schemas, modelsfrom app.database.database import get_dbfrom app.home import crudfrom fastapi import Depends, HTTPException, Formfrom sqlalchemy.orm import Sessionfrom app.models import Userfrom sqlalchemy.orm import Sessionfrom fastapi import APIRouter, HTTPException,Requestfrom fastapi.responses import RedirectResponse userRouter = APIRouter()templates = Jinja2Templates(directory="app/templates") # 模板目录 @userRouter.post("/login/", response_model=schemas.UserOut)async def login(*,request: Request,db: Session = Depends(get_db), username: str = Form(None), password: str = Form(None),): if request.method == "POST": db_user = db.query(models.User).filter(User.username == username).first() if not db_user: raise HTTPException(status_code=400, detail="用户不存在") print("验证通过 !!!") return RedirectResponse('/index') return templates.TemplateResponse("user/login.html", {"request": request}) 看起来比 Flask 添加蓝图要轻松许多。 同时支持多种请求方式 在上面的 login 例子可以发现,我在上下文 request 中通过判断路由的请求方式来进行响应的逻辑处理,比如如果不是 Post请求 就把它重定向到 login 页面等等。那么就需要同时支持多种请求方式了,巧合的是,我在 FastAPI 文档中找不到相应的说明,刚开始的时候我也迷糊了一阵。所以,只能干源码了。 直接进入 APIRouter 类所在的文件,发现新大陆。 在 APIRouter 下有个叫 add_api_route 的方法,支持 http方法 以列表的形式作为参数传入,所以就换成了下面这种写法: async def login(*,request: Request,db: Session = Depends(get_db), username: str = Form(None), password: str = Form(None),): if request.method == "POST": db_user = db.query(models.User).filter(User.username == username).first() if not db_user: raise HTTPException(status_code=400, detail="用户不存在") print("验证通过 !!!") return RedirectResponse('/index') return templates.TemplateResponse("user/login.html", {"request": request}) async def userList(*,request: Request,db: Session = Depends(get_db)): userList = db.query(models.User).all() return templates.TemplateResponse("user/user-index.html", {"request": request,'userList':userList}) userRouter.add_api_route(methods=['GET','POST'],path="/login",endpoint=login)userRouter.add_api_route(methods=['GET','POST'],path="/list",endpoint=userList) 其中,methods 是非常熟悉的字眼,写入你想要的 http请求方式,path 指访问时的路径,endpoint 就是后端方法了。 这样就解决了同时存在于多个 http请求方式 的问题啦,编码也更为直观简洁。 数据库 在 FastAPI 中,我们一如既往的使用了 SQLAlchemy 初始化数据库文件: from sqlalchemy import create_enginefrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import sessionmaker 创建数据库连接URI SQLALCHEMY_DATABASE_URL = "mysql+pymysql://root:123456@127.0.0.1:3306/blog" 初始化 engine = create_engine( SQLALCHEMY_DATABASE_URL ) 创建DBSession类型 SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 创建基类 用于继承 也可以放到初始化文件中 Base = declarative_base() 获取数据库会话,用于数据库的各种操作 def get_db(): db = SessionLocal() 数据库模型文件: from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, Textfrom sqlalchemy.orm import relationshipfrom datetime import datetimefrom flask_login import UserMixinimport uuidfrom app.database.database import Base class User(UserMixin,Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) email = Column(String(64),) username = Column(String(64), ) role = Column(String(64), ) password_hash = Column(String(128)) head_img = Column(String(128), ) create_time = Column(DateTime,default=datetime.now) def __repr__(self): return '<User %r>' % self.username 文章表 class Article(Base): __tablename__ = 'article' id = Column(Integer, primary_key=True) title=Column(String(32)) author =Column(String(32)) img_url = Column(Text,nullable=False) content=Column(Text,nullable=False) tag=Column(String(64),nullable=True) uuid = Column(Text,default=uuid.uuid4()) desc = Column(String(100), nullable=False) create_time = Column(DateTime,default=datetime.now) articleDetail = relationship('Article_Detail', backref='article') def __repr__(self): return '<Article %r>' % self.title 增 async def articleDetailAdd(*,request: Request,db: Session = Depends(get_db),d_content:str,uid:int): if request.method == "POST": addArticleDetail= Article_Detail(d_content=d_content,uid=uid) db.add(addArticleDetail) db.commit() db.refresh(addArticleDetail) print("添加成功 !!!") return "添加成功" return "缺少参数" 删 async def articleDetailDel(*,request: Request,db: Session = Depends(get_db),aid:int): if request.method == "POST": db.query(Article_Detail).filter(Article_Detail.id == aid).delete() db.commit() print("删除成功 !!!") return "删除成功" return "缺少参数" 改 async def articleDetailUpdate(*,request: Request,db: Session = Depends(get_db),aid:int,d_content:str): if request.method == "POST": articleInfo= db.query(Article_Detail).filter(Article_Detail.id == aid).first() print(articleInfo) if not articleInfo: raise HTTPException(status_code=400, detail="no no no !!") articleInfo.d_content = d_content db.commit() print("提交成功 !!!") return "更新成功" return "缺少参数" 查 async def articleDetailIndex(*,request: Request,db: Session = Depends(get_db),): articleDetailList = db.query(models.Article_Detail).all() return templates.TemplateResponse("articleDetail/articleDetail-index.html", {"request": request,"articleDetailList":articleDetailList}) 这里是一些示例的 crud,真正部署的时候可不能这么鲁莽哇,错误的捕捉,数据库的回滚,语句必须严谨。 数据验证 在路由方法中,有个叫 response_model 的参数,用于限制路由方法的返回字段。 官方文档实例: from fastapi import FastAPIfrom pydantic import BaseModel, EmailStr app = FastAPI() class UserIn(BaseModel): username: str password: str email: EmailStr full_name: str = None class UserOut(BaseModel): username: str email: EmailStr full_name: str = None @app.post("/user/", response_model=UserOut)async def create_user(*, user: UserIn): return user 意思是 UserIn 作为请求体参数传入,返回时必须满足 UserOut 模型。 场景的话,可以想象用户登陆时需要传入用户名和密码,用户登陆成功之后在首页上展示用户名的邮件,不展示密码。嗯,这样就合理了。 所以在数据库操作的时候,可以自己定义传入和返回的模型字段来做有效的限制,你只需要继承 pydantic 中的 BaseModel 基类即可,看起来是那么的简单合理。 异常处理 在各种 http资源 不存在或者访问异常的时候都需要有 http状态码 和 异常说明,例如, 404 Not Found 错误,Post请求出现的 422,服务端的 500 错误,所以如何在程序中合理的引发异常,就变得格外重要了。 看看 FastAPI 中如何使用异常处理 from fastapi import FastAPI, HTTPException app = FastAPI() items = {"foo": "The Foo Wrestlers"} @app.get("/items/{item_id}")async def read_item(item_id: str): if item_id not in items: raise HTTPException(status_code=404, detail="Item not found") return {"item": items[item_id]} 使用 HTTPException,传入状态码 和 详细说明,在出现逻辑错误时抛出异常。 改写 HTTPException from fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponse class UnicornException(Exception): def __init__(self, name: str): self.name = name app = FastAPI() @app.exception_handler(UnicornException)async def unicorn_exception_handler(request: Request, exc: UnicornException): return JSONResponse( status_code=418, content={"message": f"我家热得快炸了..."}, ) @app.get("/unicorns/{name}")async def read_unicorn(name: str): if name == "yolo": raise UnicornException(name=name) return {"unicorn_name": name} UnicornException 继承自 Python 自带的 Exception 类,在出现服务端错误时抛出 418 错误,并附上错误说明。 自定义自己的异常处理代码 from fastapi import FastAPI, HTTPExceptionfrom fastapi.exceptions import RequestValidationErrorfrom fastapi.responses import PlainTextResponsefrom starlette.exceptions import HTTPException as StarletteHTTPException app = FastAPI() @app.exception_handler(StarletteHTTPException)async def http_exception_handler(request, exc): return PlainTextResponse(str(exc.detail), status_code=exc.status_code) @app.exception_handler(RequestValidationError)async def validation_exception_handler(request, exc): return PlainTextResponse(str(exc), status_code=400) @app.get("/items/{item_id}")async def read_item(item_id: int): if item_id == 3: raise HTTPException(status_code=418, detail="开空调啊") return {"item_id": item_id} 合理的使用异常处理机制,能让项目代码更健壮,客户端更友好,也易于维护。 还有吗? 在茫茫的 FastAPI 文档中我尽可能摸索出一些易用,实用,好用的功能来和大家分享,并尝试投入到实际的生产环境中,在这个过程中去学习更多的东西,体验更好的服务性能。 FastAPI 官方文档十分的庞大,有非常多的地方还没有普及和深入,比如 FastAPI 的安全加密,中间件的使用,应用部署等等。哈,来日方长 !!! 需要学习更多关于FastAPI 知识的话,可以戳阅读全部,获取详情: 参考文档:https://fastapi.tiangolo.com/tutorial

优秀的个人博客,低调大师

高性能IO框架Netty:手把手教你解决 “粘包/半包” 问题!

2 --> 前言:demo演示 首先,我们来看个demo 1、EchoServer public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public static void main(String[] args) throws InterruptedException { EchoServer echoServer = new EchoServer(9999); System.out.println("服务器即将启动"); echoServer.start(); System.out.println("服务器关闭"); } public void start() throws InterruptedException { final EchoServerHandler serverHandler = new EchoServerHandler(); EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/ .localAddress(new InetSocketAddress(port))/*指定服务器监听端口*/ /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel, 所以下面这段代码的作用就是为这个子channel增加handle*/ .childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(serverHandler);/*添加到该子channel的pipeline的尾部*/ } }); ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/ System.out.println("服务器启动完成,等待客户端的连接和数据....."); f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/ } finally { group.shutdownGracefully().sync();/*优雅关闭线程组*/ } } } 2、EchoServerHandler @ChannelHandler.Sharable public class EchoServerHandler extends ChannelInboundHandlerAdapter { private AtomicInteger counter = new AtomicInteger(0); /*** 服务端读取到网络数据后的处理*/ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf in = (ByteBuf) msg; String request = in.toString(CharsetUtil.UTF_8); System.out.println("Server Accept[" + request + "] and the counter is:" + counter.incrementAndGet()); String resp = "Hello," + request + ". Welcome to Netty World!" + System.getProperty("line.separator"); ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes())); } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 使用netty实现了个服务端,当接收到客户端的消息是,打印出来请求的内容,并统计接收请求的次数。 3、EchoClient public class EchoClient { private final int port; private final String host; public EchoClient(int port, String host) { this.port = port; this.host = host; } public void start() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { final Bootstrap b = new Bootstrap(); /*客户端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/ .remoteAddress(new InetSocketAddress(host, port))/*配置要连接服务器的ip地址和端口*/ .handler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoClientHandler()); } }); ChannelFuture f = b.connect().sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } public static void main(String[] args) throws InterruptedException { new EchoClient(9999, "127.0.0.1").start(); } } 4、EchoClientHandler import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; import java.util.concurrent.atomic.AtomicInteger; public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { private AtomicInteger counter = new AtomicInteger(0); /*** 客户端读取到网络数据后的处理*/ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client Accept[" + msg.toString(CharsetUtil.UTF_8) + "] and the counter is:" + counter.incrementAndGet()); } /*** 客户端被通知channel活跃后,做事*/ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf msg = null; String request = "test1,test2,test3,test4" + System.getProperty("line.separator"); for (int i = 0; i < 100; i++) { msg = Unpooled.buffer(request.length()); msg.writeBytes(request.getBytes()); ctx.writeAndFlush(msg); } } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 使用netty实现了个客户端,链接建立完成之后向服务端发送消息。循环100次。并且打印服务端返回的消息。并统计返回次数。 执行结果 服务端输出 客户端打印 结果发现,我们客户单发送了100次数据,但实际上只接收了30次。而且每次消息发送的是test1,test2,test3,test4,test5,但实际接受的却有很多相链接起来的。这是为什么呢?为什么不是100次test1,test2,test3,test4,test5呢?这就是TCP传输的粘包/半包问题。 一、什么是TCP粘包半包? 假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包; 服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包; 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包; 服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。 如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。 二、TCP粘包/半包发生的原因 由于TCP协议本身的机制(面向连接的可靠地协议-三次握手机制)客户端与服务器会维持一个连接(Channel),数据在连接不断开的情况下,可以持续不断地将多个数据包发往服务器,但是如果发送的网络数据包太小,那么他本身会启用Nagle算法(可配置是否启用)对较小的数据包进行合并(基于此,TCP的网络延迟要UDP的高些)然后再发送(超时或者包大小足够)。那么这样的话,服务器在接收到消息(数据流)的时候就无法区分哪些数据包是客户端自己分开发送的,这样产生了粘包;服务器在接收到数据库后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,造成粘包现象 UDP:本身作为无连接的不可靠的传输协议(适合频繁发送较小的数据包),他不会对数据包进行合并发送(也就没有Nagle算法之说了),他直接是一端发送什么数据,直接就发出去了,既然他不会对数据合并,每一个数据包都是完整的(数据+UDP头+IP头等等发一次数据封装一次)也就没有粘包一说了。 分包产生的原因就简单的多:可能是IP分片传输导致的,也可能是传输过程中丢失部分包导致出现的半包,还有可能就是一个包可能被分成了两次传输,在取数据的时候,先取到了一部分(还可能与接收的缓冲区大小有关系),总之就是一个数据包被分成了多次接收。 更具体的原因有三个,分别如下。 应用程序写入数据的字节大小大于套接字发送缓冲区的大小 进行MSS大小的TCP分段。MSS是最大报文段长度的缩写。MSS是TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是TCP报文段的最大长度,而是:MSS=TCP报文段长度-TCP首部长 以太网的payload大于MTU进行IP分片。MTU指:一种通信协议的某一层上面所能通过的最大数据包大小。如果IP层有一个数据包要传,而且数据的长度比链路层的MTU大,那么IP层就会进行分片,把数据包分成托干片,让每一片都不超过MTU。注意,IP分片可以发生在原始发送端主机上,也可以发生在中间路由器上。 三、解决粘包半包问题 由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下。 1、在包尾增加分割符 在包尾增加分割符,比如回车换行符进行分割,例如FTP协议; demo如下: LineBaseEchoServer public class LineBaseEchoServer { public static final int PORT = 9998; public static void main(String[] args) throws InterruptedException { LineBaseEchoServer lineBaseEchoServer = new LineBaseEchoServer(); System.out.println("服务器即将启动"); lineBaseEchoServer.start(); } public void start() throws InterruptedException { final LineBaseServerHandler serverHandler = new LineBaseServerHandler(); EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/ .localAddress(new InetSocketAddress(PORT))/*指定服务器监听端口*/ /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel, 所以下面这段代码的作用就是为这个子channel增加handle*/ .childHandler(new ChannelInitializerImp()); ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/ System.out.println("服务器启动完成,等待客户端的连接和数据....."); f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/ } finally { group.shutdownGracefully().sync();/*优雅关闭线程组*/ } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { //添加换行解码器 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new LineBaseServerHandler()); } } } LineBaseEchoServer public class LineBaseEchoServer { public static final int PORT = 9998; public static void main(String[] args) throws InterruptedException { LineBaseEchoServer lineBaseEchoServer = new LineBaseEchoServer(); System.out.println("服务器即将启动"); lineBaseEchoServer.start(); } public void start() throws InterruptedException { final LineBaseServerHandler serverHandler = new LineBaseServerHandler(); EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/ .localAddress(new InetSocketAddress(PORT))/*指定服务器监听端口*/ /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel, 所以下面这段代码的作用就是为这个子channel增加handle*/ .childHandler(new ChannelInitializerImp()); ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/ System.out.println("服务器启动完成,等待客户端的连接和数据....."); f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/ } finally { group.shutdownGracefully().sync();/*优雅关闭线程组*/ } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { //添加换行解码器 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new LineBaseServerHandler()); } } } LineBaseEchoClient public class LineBaseEchoClient { private final String host; public LineBaseEchoClient(String host) { this.host = host; } public void start() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { final Bootstrap b = new Bootstrap(); b.group(group)/*将线程组传入*/ .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/ .remoteAddress(new InetSocketAddress(host, LineBaseEchoServer.PORT))/*配置要连接服务器的ip地址和端口*/ .handler(new ChannelInitializerImp()); ChannelFuture f = b.connect().sync(); System.out.println("已连接到服务器....."); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { //回车符做了分割 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new LineBaseClientHandler()); } } public static void main(String[] args) throws InterruptedException { new LineBaseEchoClient("127.0.0.1").start(); } } LineBaseClientHandler public class LineBaseClientHandler extends SimpleChannelInboundHandler<ByteBuf> { private AtomicInteger counter = new AtomicInteger(0); /*** 客户端读取到网络数据后的处理*/ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client Accept[" + msg.toString(CharsetUtil.UTF_8) + "] and the counter is:" + counter.incrementAndGet()); ctx.close(); } /*** 客户端被通知channel活跃后,做事*/ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf msg = null; String request = "test1,test2,test3,test4,test5" + System.getProperty("line.separator"); for (int i = 0; i < 10; i++) { Thread.sleep(500); System.out.println(System.currentTimeMillis() + ":即将发送数据:" + request); msg = Unpooled.buffer(request.length()); msg.writeBytes(request.getBytes()); ctx.writeAndFlush(msg); } } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 执行效果 2、消息定长 例如每个报文的大小为固定长度200字节,如果不够,空位补空格; 服务端只需将服务端的ChannelInitializerImp 解码器new LineBasedFrameDecoder(1024)替换为new FixedLengthFrameDecoder( FixedLengthEchoClient.REQUEST.length())即可。 private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { //添加定长报文长度解码器,长度问请求的长度 ch.pipeline().addLast( new FixedLengthFrameDecoder( FixedLengthEchoClient.REQUEST.length())); ch.pipeline().addLast(new FixedLengthServerHandler()); } } 3、将消息分为消息头和消息体 消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度。类似与第二条,只是我们按照头部的content-length长度进行定长解码。

优秀的个人博客,低调大师

【新年呈献】高性能网络通信框架 HP-Socket v5.7.1

项目主页:http://www.oschina.net/p/hp-socket 开发文档: https://www.docin.com/p-2287339564.html 下载地址: https://github.com/ldcsaa/HP-Socket 本次更新增加 UDP Node 组件,新增大量 API ! v5.7.1 更新 一、新增组件:UDP Node UDP Node 没有“连接”的概念,可以与任意地址相互收发数据 UDP Node 支持三种运行模式:单播、组播和广播 增加 UDP Node 组件示例 Demo ‘TestUDPNode’ UDP Node 工作流程 二、API 更新 所有组件增加 Wait() 方法,用于等待组件关闭 所有通信组件增加 SetReuseAddressPolicy() 方法,支持设置地址重用策略 所有 SSL 通信组件增加 SetSSLCipherList() 方法,支持设置 SSL 加密套件 大量 API 更新,代码重构、优化 HP-Socket 组件列表 基础组件 SSL 组件 HTTP 组件

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册