fastapi:相比于 flask,支持异步的 web 框架。Flask:轻量级 python web 应用框架,小到称为“微框架”。组件丰富,按需选择,我选择Flask 概述 Flask 的模板是 MVC 架构,Model-View-Controller,中文名“模型 - 视图 - 控制器”,将 Python 与 HTML 代码一定程度地分离了 MVC:为了解决直接用脚本语言嵌入 HTML 导致的可维护性差的问题,Web 应用也引入了 Model-View-Controller 的模式,来简化 Web 开发。 异步开发、新的 MVVM 前端技术层出不穷。 一组 python 包,便于开发者专注于业务逻辑,而不必深入网络应用底层协议、线程、进程等方面,提高开发效率与程序质量 说明 wsgi wsgi:Web Server Gateway Interface:web 服务器网关接口。Web 应用程序的入口,处理 web 请求、并响应。python 语言中定义的 web 服务器与 web 应用程序或框架之间的通用接口标准。 CS(Client/Server):客户端/服务器架构 BS 架构:Browser/Server 模式的架构:客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。从浏览器请求服务器,获取 web 页面,在浏览器中展示给用户
CGI:Common Gateway Interface,用 C/C++ 编写。 ASGI:Asynchronous Server Gateway Interface:异步通信服务规范 WSGI:Web Server Gateway Interface:同步通信服务规范
web 服务器网关接口。python 语言中定义的 web 服务器与 web 应用程序或框架之间的通用接口标准。 Python 应用程序或框架和 Web 服务器之间的一种接口,WSGI 的其中一个目的就是让用户可以用统一的语言 (Python) 编写前后端。 REST:Representational State Transfer:表现层状态转移:一种软件架构风格
设计风格,而不是标准。主要用于客户端和服务端的 API 交互,它的优势在于更简洁、清晰、可读性强。 选择 FastAPI:请求耗时较久时,请求所需时间大于处理时间,它会多开线程,然后疯狂吃资源,引发内存泄露 FastAPI fastapi:相比于 flask,支持异步的 web 框架。
优点:快速:异步:高性能,功能强大,说是可与 NodeJS 和 Go 相媲美 直观:易于使用,最小化代码重复,快速编码 自动:自动数据验证:使用 pydantic 模式和 python 类型提示的组合来允许数据验证 自动文档 特性:API 标准:OpenAPI (以前称为 Swagger)路径操作、请求参数、请求体、安全体等声明 HTTP 基本认证 OAuth2 API 密钥:请求头、查询参数、Cookies 等 数据模型文档:JSON Schema 。 Starlette 特性:WebSockt,GraphQL,CORS、GZip Session、Cookie 类型提示编辑器支持 类型检查 定义参数要求 :声明对请求路径参数、查询参数、请求头、请求体、依赖等的要求。转换数据 :将来自请求的数据转换为需要的类型。校验数据:对于每一个请求:当数据校验失败时自动生成错误信息 返回给客户端。 使用 OpenAPI 记录 API: 不用管 SQLAlchemy 系列 基本使用 1 2 3 pip install fastapi pip install uvicorn
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 from fastapi import FastAPIapp = FastAPI() @app.get("/" ) def read_root (): return {"Hello" : "World" } @app.get("/items/{item_id}" ) def read_item (item_id: int , q: str = None ): return {"item_id" : item_id, "q" : q} if __name__ == "__main__" : uvicorn.run("example:app" , host="127.0.0.1" , port=5000 , log_level="info" ) from fastapi import FastAPIapp = FastAPI(docs_url="/docs" , redoc_url=None ) if __name__ == "__main__" : pass
1 2 3 4 5 6 7 uvicorn main:app --reload uvicorn app:app --port 5000 uvicorn app:app --workers 2 uvicorn web_api:app --host 10.50 .63 .63 --port 5000 --reload --debug
相关工具 参考文档 官方文档
交互式文档
1 2 3 4 # 交互式 API 文档,Swagger UI,可交互式操作:调用、测试 API http://127.0.0.1:8000/docs # API 文档 http://127.0.0.1:8000/redoc
1 2 3 4 5 6 7 8 9 10 11 12 13 from fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI() class User (BaseModel ): name: str age: int @app.post('/users' ) def save_user (user: User ): return {'name' : user.name, 'age' : user.age}
1 2 3 4 5 6 7 8 9 10 { "name" : "Isaac" , "age" : 60 } from pydantic import BaseModelclass User (BaseModel ): name: str age: int
1 2 3 4 5 6 7 8 9 { "series" : ["GOT" , "Dark" , "Mr. Robot" ] } from pydantic import BaseModelfrom typing import List class Metadata (BaseModel ): series: List [str ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { "users" : [ { "name" : "xyz" , "age" : 25 }, { "name" : "abc" , "age" : 30 } ], "group" : "Group A" } from pydantic import BaseModelfrom typing import List class User (BaseModel ): name: str age: int class UserGroup (BaseModel ): users: List [User] group: str
使用指南 参数 url 路径参数 1 2 3 @app.get('/user/{user_id}' ) async def get_user_msg (user_id:str ): return {'id' :user_id}
param 查询参数 1 2 3 4 https://www.baidu.com/search?ie=UTF-8 &wd=fastapi 参数汇总 @app.get("/search/" ) async def getEchoApi (text:str ): return {"echo" :text}
请求体参数 post、PUT 等请求时 以 JSON 读取请求的正文 验证数据,不对时会有清晰的错误返回 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from fastapi import FastAPIfrom pydantic import BaseModel app=FastAPI() class EchoArgs (BaseModel ): text:str app.post("/echo/" ) async def postEchoApi (args:EchoArgs ): return {"echo" :item.text} from fastapi import FastAPIimport uvicornfrom pydantic import BaseModelapp = FastAPI() class CommonItem (BaseModel ): token: str message_id: str to_id: str from_info: str strategy: int or str = 0 type : str or int from_id: str to_info: str content: str = None @app.post("/test" ) async def tests (item: CommonItem ): return item
表单参数 1 2 3 4 5 from fastapi import FastAPI,Form app=FastAPI() app.post("/echo/" ) async def postEchoApi (text:str =Form(None ) ): return {"echo" :text}
cookie 参数 一般用于用户的标识,用户习惯的记录等.各大网站都在使用这种方式,后来的 session 和 token 也要用到 cookie 技术.cookie 本质是一种在客户端保存的数据,每次请求都会携带,你也可以在响应中进行设置.
1 2 3 4 5 from fastapi import FastAPI,Cookieapp=FastAPI() app.post("/echo/" ) async def postEchoApi (text:str =Cookie(None ) ): return {"echo" :text}
file 参数 可用 bytes 或 UploadFile,推荐使用 UploadFile 1 2 3 4 5 6 7 8 from fastapi import FastAPI,File,UploadFile app=FastAPI() app.post("/upload/2" ) async def postUploadFile1Api (file:bytes =File(None ) ): ... app.post("/upload/1" ) async def postUploadFile2Api (file:UploadFile=File(None ) ): ...
url 参数: param 参数: 请求主体参数 cookie 参数 file 参数: HTTP 方法 1 2 3 4 @app.get('/' ) @app.put('/' ) @app.patch('/' ) @app.delete('/' )
变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @app.get('/users/{user_id}' ) def get_user_details (user_id: int ): return {'user_id' : user_id} @app.get('/search' ) def search (q: str ): return {'query' : q} from typing import Dict @app.post('/lowercase' ) def lower_case (json_data: Dict ): text = json_data.get('text' ) return {'text' : text.lower()}
上传文件 1 2 3 4 5 6 7 8 from fastapi import FastAPI, UploadFile, Fileapp = FastAPI() @app.post('/upload' ) def upload_file (file: UploadFile = File(... ) ): return {'name' : file.filename}
表单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from fastapi import FastAPI, Formapp = FastAPI() @app.post('/submit' ) def echo (city: str = Form(... ) ): return {'city' : city} from typing import Optional @app.post('/submit' ) def echo (city: Optional [str ] = Form(None ) ): return {'city' : city} @app.post('/submit' ) def echo (city: Optional [str ] = Form('Paris' ) ): return {'city' : city}
cookie 1 2 3 4 5 6 7 from fastapi import FastAPI, Cookieapp = FastAPI() @app.get('/profile' ) def profile (name = Cookie(None ) ): return {'name' : name}
蓝图 1 2 3 4 5 6 7 8 from fastapi import FastAPIfrom routers import userfrom routers import indexapp = FastAPI() app.include_router(index.router) app.include_router(user1.router,prefix="/user" )
1 2 3 4 5 6 7 from fastapi import APIRouterrouter = APIRouter() @router.get('/users' ) def list_users (): return {'users' : ['a' , 'b' , 'c' ]}
生产环境 静态文档 -aiofiles 静态文档:提前编写到文档里,浏览器每次访问时,里面的内容不变。 动态文档:通过服务器上运行的程序动态产生的,每次访问时更新。
1 2 3 4 5 6 7 8 from fastapi import FastAPIfrom fastapi.staticfiles import StaticFilesapp = FastAPI() app.mount("/static" , StaticFiles(directory="/mnt/data/static" ), name="static" )
1 2 3 4 5 6 7 8 9 archieve_dict = get_archieve_dict() if archieve_path in archieve_path_set: return FileResponse(archieve_path) else : return '不是日志文件路径'
遇到的问题 Flask Flask:轻量级 python web 应用框架,小到称为“微框架”。组件丰富,按需选择,我选择Flask
Django:全家桶,包含大量组件,ORM、Session、Form、Admin、分页、中间件、信号、缓存、ContenType… tornado:异步非阻塞、原生支持 websocket 协议。专注性能(大并发)上 Go、Java,专注效率上 Flask、Django。故,Tornado,Out。 webpy: bottle:比较简单 cherrypy: sanic:代码质量不好 pyramid:与 Django 同为面向大型应用的 web 框架,但是更加灵活,且性能优于 Flask。
概述 基于 Python,依赖 jinja2 模权和 Werkzeug WSGI 服务jinja2:模板引擎。 Werkzeug:Socket 服务端,提供路由、调试和 web 服务器网关接口路由:使用一个简单但有力的模式语法,映射 URLs 到代码。 WSGI(Web Server Gateway Interface)web 服务器网关接口、HTTP 开发服务器。python 语言中定义的 web 服务器与 web 应用程序或框架之间的通用接口标准。 扩展一大堆,这个是优点也是缺点。 语法简单,易用,部署方便。 轻量、扩展丰富,写法自由,Pythonic 简单、易上手、第三方开源组件丰富, 路由:路径映射
模板
学习资料 基本结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from flask import app=Flask(__name__) @app.route("/" ) def index (): return "<h1>helloworld !</h1>" @app.route("/<str:username" ) def user (username ): return 'hello{}' .format (username) @app.route('/request' ) def hello_world (): user_agent =request.headers.get ('User-Agent' ) return '<h2>your browser is {}</h2>' .format (user_agent) if __name__=='__main__' : app.run(debug=True )
Flask 对象 1 2 3 4 5 6 7 8 9 10 def __init__ (self, import_name, static_path=None , static_url_path=None , static_folder='static' , template_folder='templates' , instance_path=None , instance_relative_config=False , root_path=None ):
变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # aa.html {{ }} 双大括号占位符,表示里面的数据需从渲染模板时的数据传入 <ht> hello,{{ name }}</ht> 变量, {{ dict['dd'],list[3],str.lower() }} - 可用过滤器,但不用,用 python 的方法 - {% ... %} Jinja 语句,例如判断、循环语句 {{ ... }} 变量,会显示在浏览器中 - {# ... #} 注释,不会输出到浏览器中 {{ "helloworld"|reverse|upper}} #链式调用 {{"<h1>" | safe }} #safe 禁用转义 upper title reverse format striptage:渲染前把值中所有的 html 标签都删掉 truncate:字符串截断 {{[1,2,23] | first}} 列表取第一个元素 last length sum sort
控制结构 1 2 3 4 5 6 7 8 9 10 # if 结构 {% if name %} Hello{{name}} {% else %} Hello world! {% endif %} #关闭控制语句 # for 结构 {% for i in names %} <li> {{ i }}</li> {% endfor %}
路由 路由:将 url 与视图函数封装成一个 Rule 对象,添加到 Flask 的 url_map 字段中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @app.route('/index.html' ,methods=['GET' ,'POST' ],endpoint='index' ) def index (): return 'Hello WOrld' app.route( rule, view_func, defaults=None , enpoint=None , methods=None , strict_slashes=None , redirect_to=None , subdomain=None , 子域名访问 )
route 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @app.route('/user/<username>' ) @app.route('/post/<int:post_id>' ) @app.route('/post/<float:post_id>' ) @app.route('/post/<path:path>' ) @app.route('/login' , methods=['GET' , 'POST' ] ) DEFAULT_CONVERTERS = { 'default' : UnicodeConverter, 'string' : UnicodeConverter, 'any' : AnyConverter, 'path' : PathConverter, 'int' : IntegerConverter, 'float' : FloatConverter, 'uuid' : UUIDConverter, }
请求 requestrequest.from:POST 请求的数据 request.args:GET 请示的数据,可通过 .to_dict() 转换为字典 request.querystring:bytes 形式的 GET 请求 responsereturn render_template() return redirect() return “ v=make_response(返回值),.set_cookie 绑定 cookie session存在浏览器,且为加密的 依赖于:secret_key Jinja2 Jinja2:一个广泛应用的模板引擎 模版引擎:渲染模板:用真实值替换占位变量,返回最终的响应字符串 模板语言:被设计来自动生成文档的简单文本格式,通常是把变量传给模板,替换模板的特定位置上预先定义好的占位变量名
渲染 templates:模版:在 HTML 文档中嵌入占位变量,动态传入数据。
1 {% extends 'base.html' %}
1 2 3 4 5 from flask import Flask, render_template@app.route('/user/<name>' def user(name ): return render_template('user.html' , name=name )
static:静态文件
url_for("static",filename="style.css")
1 2 3 4 5 6 7 8 app = Flask(__name__, static_url_path='' ) @app.route('/' ) def root (): return app.send_static_file('index.html' ) url_for('index' , page=2 ) = /?page=2 url_for("static" ,filename="style.css" )
基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def {{ function_name }}({{ arg_list }}): {{ function_body }} from jinja2 import Templatetemplate_str = """ def {{ function_name }}({{ arg_list }}): {{ function_body }} """ template = Template(template_str) code = template.render(function_name='my_func' , arg_list='x, y' , function_body='return x + y' ) print (code)
Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 str() 方法转换为一个字符串就可以,比如,可以通过下面的方式显示一个字典或者列表中的某个元素: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { {{ post.title }} {% if user %} {{ user }} {% else %} hello! {% for index in indexs %} {{ index }} {% endfor %}
重定向和错误 用 redirect() 函数把用户重定向到其它地方。 用 abort() 函数放弃请求并返回错误代码。例如: 1 2 3 4 5 6 7 8 9 10 @app.route('/redir/' ) def redir (): return redirect(url_for('login' ) @app.route('/login/' ) def login (): abort(401 ) from flask import render_template@app.errorhandler(404 ) def page_not_found (error ): return render_template('page_not_found.html' ), 404
用户会从/redir/重定向到一个不能访问的页面 (401 意味着禁止访问)。 request 全局对象 1 2 3 4 5 6 7 8 9 10 11 from flask imoprt request@app.route('/login' ,methods=['POST' ,'GET' ] ) def login (): if request.method=='POST' : if valid_login(request.form['username' ],request.form['password' ]): return login_ok(request.form['username' ]) else : error='incalid username/password' return render_template('login.html' ,error=error) searchword=request.args.get('key' ,'' )
文件上传:request 的 files 属性,werkzeug 的 secure_filename 获取文件名 Cookies 1 2 3 4 5 6 7 8 9 10 11 服务端获取 cookies @app.route('/' ) def index (): username=request.cookies.get('username' ) 发送 cookies 到客户端 from flask import make_response@app.route('/' ) def index (): resp = make_response(render_template(...)) resp.set_cookie('username' , 'the username' ) return resp
Cookies 访问 cookies 设置在响应对象上 若能够使用会话,不要直接使用 cookies,会话更安全 1 2 3 4 5 6 7 8 from flask import request@app.route('/' ) def index (): username = request.cookies.get('username' ) resp=make_response(render_template(...)) resp.set_cookie('username' ,'my_username' ) return resp
除了请求对象之外还有一种称为 session 的对象,允许你在不同请求 之间储存信息。这个对象相当于用密钥签名加密的 cookie ,即用户可以查看你的 cookie ,但是如果没有密钥就无法修改它。 扩展 Flask-SQLalchemy:操作数据库,ORM; Flask-script:终端脚本工具,脚手架; Flask-migrate:管理迁移数据库;(能回滚) Flask-Session:Session 存储方式指定; Flask-WTF:表单; Flask-Mail:邮件; Flask-Bable:提供国际化和本地化支持,翻译; Flask-Login:认证用户状态; Flask-OpenID:认证, OAuth; Flask-RESTful:开发 REST API 的工具; Flask JSON-RPC: 开发 rpc 远程服务 [过程] 调用 Flask-Bootstrap:集成前端 Twitter Bootstrap 框架 Flask-Moment:本地化日期和时间 Flask-Admin:简单而可扩展的管理接口的框架 Flask-OAuthlib:OAuth 1.0/a, 2.0 客户端实现,供 Flask 使用。 flask-msearch blueprint:蓝图、模块化 flask-moment:本地化日期和时间,依赖于 moment.js 与 jquery.js Flask-WTF:表单; Flask-Bable:提供国际化和本地化支持,翻译; Flask-script:插入脚本; Flask-Login:认证用户状态; Flask-OpenID:认证; Flask-RESTful:开发 REST API 的工具; Flask-Bootstrap:集成前端 Twitter Bootstrap 框架; Flask-Moment:本地化日期和时间; Flask-Admin:简单而可扩展的管理接口的框架 Flask-PageDown:for markdown Flask-Admin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from flask import Flaskfrom flask_sqlalchemy import SQLAlchemyapp=Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI' ] = 'mysql://root:@localhost:3306/sqlalchemy?charset=utf8' db = SQLAlchemy(app) class Stuent (db.Model): id =db.Column(db.Integer,primary_key=True ) studentname=db.Column(db.String(80 ),unique=True ) stuclass=db.Column(db.String(120 ),unique=True ) def __init__ (self,studentname,stuclass ): self .studentname=studentname self .stuclass=stuclass def __repr__ (self ): return '<Student {}>' .format (studentname)
Flask-WTF Flask-WTF:表单,封装了 WTForms,可验证表单数据
HTML 页面中负责数据采集的部件。 对应 HTML 标准字段StringField:文本字段 TextAreaField:多行文本字段 PasswordField:密码文本字段 HiddenField:隐藏文件字段 DateField:值为 datetime.date 的文本字段 DateTimeField:值为 datetime.datetime 的文本字段 IntegerField:整数的文本字段 DecimalField:值为 decimal.Decimal 的文本字段 FloatField:值为浮点数的文本字段 BooleanField:复选框,值为 True 或 False RadioField:一组单选框 SelectField SelectMutipleField:下拉列表,可选择多个值 FileField:文件上传字段 SubmitField:表单提交按钮 FormField:将表单作为字段嵌入另一个表单 FieldList:一组指定类型的字段 验证函数:DataRequired 确保字段中有数据 EqualTo 比较两个字段的值,常用于比较两次密码输入 Length 验证输入的字符串长度 NumberRange 验证输入的值在数字范围内 URL 验证 URL AnyOf 验证输入值在可选列表中 NoneOf 验证输入值不在可选列表中 Falsk-Restful Flask-Restful:开发 REST API 的工具
资源 资源:Resource:在资源之上定义各个 HTTP 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from flask import Flask, requestfrom flask.ext.restful import Resource, Apiapp = Flask(__name__) api = Api(app) todos = {} class TodoSimple (Resource ): def get (self, todo_id ): return {todo_id: todos[todo_id]} def put (self, todo_id ): todos[todo_id] = request.form['data' ] return {todo_id: todos[todo_id]} api.add_resource(TodoSimple, '/<string:todo_id>' ) if __name__ == '__main__' : app.run(debug=True )
端点 Endpoints:
1 2 3 api.add_resource(Todo,'/' ,'todo' ) api.add_resource(Todo, '/todo/<int:todo_id>' , endpoint='todo_ep' )
验证数据 reqparse:验证数据,如果不对,返加一个 400 错误请求及高亮的错误信息回应 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from flask.ext.restful import reqparseparser = reqparse.RequestParser() parser.add_argument('name' , type =int , location='form' ) parser.add_argument('PageSize' , type =int , location='args' ) parser.add_argument('User-Agent' , type =str , location='headers' ) parser.add_argument('session_id' , type =str , location='cookies' ) parser.add_argument('picture' , type =werkzeug.datastructures.FileStorage, location='files' ) parser.add_argument('rate' , type =int , help ='Rate to charge for this resource' ) parser.add_argument('name' , type =str ) parser.add_argument('name' , type =str , required=True ,help = "Name cannot be blank!" ) parser.add_argument('name' , type =str , action='append' ) parser.add_argument('text' ,location=['headers' ,'values' ]) args = parser.parse_args()
继承解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask.ext.restful import RequestParserparser = RequestParser() parser.add_argument('foo' , type =int ) parser_copy = parser.copy() parser_copy.add_argument('bar' , type =int ) parser_copy.replace_argument('foo' , type =str , required=True , location='json' ) parser_copy.remove_argument('foo' )
遇到的问题 flask 中的模型加载两次 ConnectionResetError: [Errno 104] Connection reset by peer 描述:这是在对一个对端 socket 已经关闭的连接调用 write 或 send 方法,在这种情况下,调用 write 或 send 方法后,对端 socket 便会向本端 socket 发送一个 RESET 信号,在此之后如果继续执行 write 或 send 操作,就会得到 errno 为 104,错误描述为 connection reset by peer。
具体的分析可以结合 TCP 的 “ 四次挥手 “ 关闭. TCP 是全双工的信道, 可以看作两条单工信道, TCP 连接两端的两个端点各负责一条. 当对端调用 close 时, 虽然本意是关闭整个两条信道, 但本端只是收到 FIN 包. 按照 TCP 协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为 TCP 协议的限制, 一个端点无法获知对端的 socket 是调用了 close 还是 shutdown。
对于一个 TCP 连接,如果对端执行 close 操作,则会向本端发送一个 FIN 分节,这时候读本端 socket 会返回 0,我们就知道对方已经关闭了连接,通常这时候我们会在本地调用 close 来主动关闭本端连接。但如果对方 socket 已经执行了 close 的操作,本端 socket 还继续在这个连接上写数据,就会触发对端 socket 发送 RST 报文,按照 TCP 的四次挥手原理,这时候本端 socket 应该也要开始执行 close 的操作流程了,而不是接着发数据.
基本是网络的问题或服务器的问题,可能的原因如下:
服务器的并发连接数超过了其承载量,服务器会将其中一些连接关闭; 客户关掉了浏览器,而服务器还在给客户端发送数据; 浏览器端按了 Stop ,如果 对异常信息没有特别处理,可能在服务器的日志文件中重复出现该异常,造成服务器日志文件过大,影响服务器的运行,可以对引起异常的部分,使用 try…catch 捕获该异常,然后不输出或者只输出一句提示信息,避免使用 e.printStackTrace(); 输出全部异常信息。 防火墙的问题;如果网络连接通过防火墙,而防火墙一般都会有超时的机制,在网络连接长时间不传输数据时,会关闭这个 TCP 的会话,关闭后在读写,就会导致异常。 如果关闭防火墙,解决了问题,需要重新配置防火墙,或者自己编写程序实现 TCP 的长连接。实现 TCP 的长连接,需要自己定义心跳协议,每隔一段时间,发送一次心跳协议,双方维持连接。