当前位置: 首页 > news >正文

【MCP】了解远程MCP调用背后使用的SSE协议

本文介绍了远程MCP使用的SSE协议,通过wireshark抓包的方式了解MCP客户端和服务端之间通过SSE协议交互涉及到的请求与响应。

1. 什么是SSE协议?

参考:https://zhuanlan.zhihu.com/p/1894024642395619635和https://blog.csdn.net/aerror/article/details/146208818

MCP的远程服务是通过SSE(Server-Sent Events)启动的,SSE是一个基于HTTP的长连接协议。SSE在逻辑上是一个由客户端发起、由服务器同意而建立的从服务器向客户端发消息的单向管道。这个管道建立之后,客户端给服务器发消息时用传统方式发,服务器给客户端发消息时用这个管道发,双方就可以灵活地进行通信了。

MCP SSE客户端会发起多个请求,第一个请求是/sse路径,这是建立SSE长连接的第一步。服务端会使用chunked方式来回传数据,每次不告诉客户端数据量有多少,让客户端保持连接始终联通,即维护了一个长连接。后续每一次服务端与客户端的通讯,都会采用事件id、事件名称event、data三个字段来通信(服务端发送给客户端)。

事件id
event: 事件名称
data: 事件数据
<这里一定会有一个空行,作为单个事件结束标志>

2. 实际测试:运行MCP服务端和QwenAgent

因为使用sse远程方式启动mcp服务端时是在本地回环地址启动的, 所以可以通过wireshark工具监听到我们本地客户端与服务端之间传输的请求与响应,通过这种方式来进一步了解mcp每一步都请求了什么,响应了什么。

首先是用sse模式启动我们的mcp服务端demo,也就是官方python sdk中的mcp-python-sdk/examples/servers/simple-tool,设置端口为8000。注意修改命令中--directory之后的路径为你电脑上simple-tool的正确路径。

uv run \--directory mcp-python-sdk/examples/servers/simple-tool \mcp-simple-tool \--transport sse --port 8000

然后再在wireshark里面监听本地回环地址,使用过滤器tcp.port==8000筛选出所有和8000端口有关的请求。

image.png

使用如下代码,运行一次QwenAgent,调用mcp工具。

from qwen_agent.agents import Assistant
# DEBUG模式,列出详细处理日志
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s', filename='app.log', filemode='a') def init_agent_service():llm_cfg = {'model': 'Qwen/Qwen2.5-32B-Instruct','model_server': 'https://api.siliconflow.cn/v1','api_key': 'xxxx'}system = ('你是一个强大的助手,可以帮用户处理问题。')tools = [{"mcpServers": {"exmaple-server": {"url": "http://127.0.0.1:8000/sse","disabled": False,"timeout": 30}}}]bot = Assistant(llm=llm_cfg,name='网页查看助手',description='网页查看',system_message=system,function_list=tools,)return botdef main(text='这个网站是什么?https://blog.musnow.top/'):bot = init_agent_service()messages = [{'role': 'user', 'content': text}]# 请求for response in bot.run(messages):logging.info('bot response: ' + str(response)) # 这里也用logging打印if __name__ == "__main__":main()

MCP服务端服务端的日志中会出现下面五条请求记录

❯ uv run --directory /Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool mcp-simple-tool --transport sse --port 8000
start server sse
INFO:     Started server process [87086]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:49652 - "GET /sse HTTP/1.1" 200 OK
INFO:     127.0.0.1:49654 - "POST /?session_id=b53301ca408f4da4a12562ce2fde23de HTTP/1.1" 202 Accepted
INFO:     127.0.0.1:49656 - "POST /?session_id=b53301ca408f4da4a12562ce2fde23de HTTP/1.1" 202 Accepted
INFO:     127.0.0.1:49658 - "POST /?session_id=b53301ca408f4da4a12562ce2fde23de HTTP/1.1" 202 Accepted
INFO:     127.0.0.1:49668 - "POST /?session_id=b53301ca408f4da4a12562ce2fde23de HTTP/1.1" 202 Accepted

3. 分析wireshark抓包结果

3.1. 第一条请求:/SSE

首先在wireshark中找到第一条sse请求,在wireshark中能清晰的看到客户端从49652端口向8000端口发起TCP三次握手的记录。

image.png

客户端发起的/sse接口的请求报文如下,没有什么特别的

GET /sse HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: python-httpx/0.28.1
Accept: text/event-stream
Accept-Encoding: gzip, deflate
Cache-Control: no-store

服务端的响应如下,这一串响应是在两个tcp报文中发出的,下图中用紫色荧光笔标注len不为0的就是服务端发出的两个报文。

image.png

注意:这里的HTTP响应报文是一个chunked类型的,也就是这一条HTTP响应报文后续还一直会有其他内容(服务端和客户端之间的管道),直到客户端和服务端的交互结束了,这一条HTTP响应报文才算完整结束!

这两个报文的内容拼接起来如下,为了更直观的展示HTTP报文格式,这里将HTTP协议的\r\n换行符也人工标识出来。

HTTP/1.1 200 OK\r\n
date: Sun, 20 Apr 2025 06:00:38 GMT\r\n
server: uvicorn\r\n
cache-control: no-cache\r\n
connection: keep-alive\r\n
x-accel-buffering: no\r\n
content-type: text/event-stream; charset=utf-8\r\n
Transfer-Encoding: chunked\r\n
\r\n
51\r\n
event: endpoint\r\n
data: /messages/?session_id=b53301ca408f4da4a12562ce2fde23de\r\n
\r\n\r\n

这里便是服务端发出的第一个SSE协议事件数据了。其中,事件id是51(这个51是固定的事件编号,每次请求/sse接口返回的事件编号都是这个),事件名称是endpoint(告诉客户端后续需要请求的接口路径是啥),事件内容就是endpoint的具体值了。在data之后还额外出现了两个\r\n,这便是单个事件的结束标志。

51
event: endpoint
data: /messages/?session_id=b53301ca408f4da4a12562ce2fde23de
<空行>

这个响应就是告诉客户端,后续的请求全都要使用/messages/?session_id=b53301ca408f4da4a12562ce2fde23de这个路径来发起,这个路径中包含本次会话的session id,客户端使用这个路径,服务端就能够知道要在哪一个管道里面向客户端发回结果。

在QwenAgent的debug日志中(底层mcp交互用的是httpx库)也能观察到这个事件,客户端收到了服务端提供的endpoint URL。

2025-04-20 14:32:01,570 - INFO - HTTP Request: GET http://127.0.0.1:8000/sse "HTTP/1.1 200 OK"
2025-04-20 14:32:01,570 - DEBUG - SSE connection established
2025-04-20 14:32:01,570 - DEBUG - receive_response_body.started request=<Request [b'GET']>
2025-04-20 14:32:01,570 - DEBUG - Received SSE event: endpoint
2025-04-20 14:32:01,570 - INFO - Received endpoint URL: http://127.0.0.1:8000/messages/?session_id=b53301ca408f4da4a12562ce2fde23de

3.2. 第二条请求:初始化

第二条客户端的请求如下,这里已经开始使用服务端刚刚返回的endpoint了。请求体部分是json格式的内容,initialize代表是初始化MCP客户端,告诉服务端当前客户端使用的协议版本protocolVersion、支持的能力capabilities、jsonrpc版本等等信息

POST /messages/?session_id=b53301ca408f4da4a12562ce2fde23de HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: python-httpx/0.28.1
Content-Length: 194
Accept: text/event-stream
Accept-Encoding: gzip, deflate
Content-Type: application/json{"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"sampling":{},"roots":{"listChanged":true}},"clientInfo":{"name":"mcp","version":"0.1.0"}},"jsonrpc":"2.0","id":0}

针对这次请求,服务端发回的响应就比较简单了,一个Accepted告诉客户端他的请求已经被接受了,并没有返回实际性的内容。

HTTP/1.1 202 Accepted
date: Sun, 20 Apr 2025 06:00:38 GMT
server: uvicorn
content-length: 8Accepted

这正是前文提到过的SSE协议的特性,服务端传回的数据不会使用HTTP响应直接传回,而是会在第一次/sse请求后建立的长连接管道里面传回!上述响应只是针对客户端的POST请求,依照HTTP协议的要求发出的而已(HTTP要求每一个req都需要有一个res)

如下图所示,在服务端返回Accepted响应之后,就能观察到一个服务端向客户端发出的len不为0的TCP报文,这个报文中就包含了服务端针对客户端这次发起的初始化请求的实际事件响应。

image.png

这个报文的内容如下,e9是初始化事件响应的id,event事件名称是一个message,data中就包含了服务端对这次初始化请求的响应,返回了服务端的jsonrpc版本、支持的协议版本protocolVersion、支持的能力capabilities、服务端的信息serverInfo。

同样的,这里也是额外出现了两个\r\n作为事件结束标志。

e9\r\n
event: message\r\n
data: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-website-fetcher","version":"1.6.1.dev14+babb477"}}}\r\n
\r\n\r\n

3.3. 第三条请求:初始化成功告知

第三条请求就是客户端告诉服务端自己已经准备好了,初始化成功initialized。同样会有一对POST和Accepted的HTTP请求,这里不再赘述

POST /messages/?session_id=b53301ca408f4da4a12562ce2fde23de HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: python-httpx/0.28.1
Content-Length: 54
Accept: text/event-stream
Accept-Encoding: gzip, deflate
Content-Type: application/json{"method":"notifications/initialized","jsonrpc":"2.0"}
HTTP/1.1 202 Accepted
date: Sun, 20 Apr 2025 06:00:38 GMT
server: uvicorn
content-length: 8Accepted

从抓包结果中可以看到,这一条请求到下一条请求之间没有服务端向客户端发出len不为0的TCP报文,因为这一次请求只是客户端告知服务端自己已经准备好了,服务端没必要额外返回任何信息。

image.png

3.4. 第四条请求:请求工具列表

第四条请求就是客户端向服务端请求服务端提供的工具列表了

POST /messages/?session_id=b53301ca408f4da4a12562ce2fde23de HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: python-httpx/0.28.1
Content-Length: 46
Accept: text/event-stream
Accept-Encoding: gzip, deflate
Content-Type: application/json{"method":"tools/list","jsonrpc":"2.0","id":1}

服务端照常进行了Accepted响应

HTTP/1.1 202 Accepted
date: Sun, 20 Apr 2025 06:00:38 GMT
server: uvicorn
content-length: 8Accepted

随后在管道里面发出的TCP报文中,就包含了服务端当前支持的工具,以及工具的参数和参数的类型与释义。

109\r\n
event: message\r\n
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"fetch","description":"Fetches a website and returns its content","inputSchema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"URL to fetch"}}}}]}}\r\n
\r\n\r\n

从日志中看,QwenAgent会把这部分内容转换为prompt发送给AI,让AI来调用这个工具。

2025-04-20 14:32:01,637 - DEBUG - Request options: {'method': 'post', 'url': '/chat/completions', 'files': None, 'json_data': {'messages': [{'role': 'system', 'content': '你是一个强大的助手,可以帮用户处理问题。\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{"type": "function", "function": {"name": "exmaple-server-fetch", "description": "Fetches a website and returns its content", "parameters": {"type": "object", "properties": {"url": {"type": "string", "description": "URL to fetch"}}, "required": ["url"]}}}\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>'}, {'role': 'user', 'content': '这个网站是什么?https://blog.musnow.top/'}], 'model': 'Qwen/Qwen2.5-32B-Instruct', 'seed': 44742000, 'stream': True}}

其中上下文信息如下,可以看到这里并没有使用function call的请求格式,而是直接在system的prompt里把工具相关信息以XML格式发送给AI了。

'messages': [{'role':'system','content':'你是一个强大的助手,可以帮用户处理问题。\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{"type": "function", "function": {"name": "exmaple-server-fetch", "description": "Fetches a website and returns its content", "parameters": {"type": "object", "properties": {"url": {"type": "string", "description": "URL to fetch"}}, "required": ["url"]}}}\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>'}, {'role': 'user','content': '这个网站是什么?https://blog.musnow.top/'}]

在输出的bot response中,能看到AI针对这个tools生成了请求参数,url参数的值也是正确的,和我们提出的问题保持了一致。

t = [{'role': 'assistant','content': '','reasoning_content': '','name': '网页查看助手','function_call': {'name': 'exmaple-server-fetch','arguments': '{"url": "https://blog.musnow.top/"}'}
}]

3.5. 第五条请求:调用工具

在日志中能观察到,在AI生成了包含function_call的响应之后,QwenAgent的SDK就开始准备调用远程MCP工具了。

2025-04-20 15:32:49,370 - INFO - bot response: [{'role': 'assistant', 'content': '', 'reasoning_content': '', 'name': '网页查看助手', 'function_call': {'name': 'exmaple-server-fetch', 'arguments': '{"url": "https://blog.musnow.top/"}'}}]
2025-04-20 15:32:49,370 - DEBUG - receive_response_body.complete
2025-04-20 15:32:49,371 - DEBUG - response_closed.started
2025-04-20 15:32:49,371 - DEBUG - response_closed.complete
2025-04-20 15:32:49,373 - DEBUG - Sending client message: root=JSONRPCRequest(method='tools/call', params={'name': 'fetch', 'arguments': {'url': 'https://blog.musnow.top/'}}, jsonrpc='2.0', id=2)

此时发起的请求如下,请求体中包含了需要请求的工具名称fetch,以及传输过来的参数arguments

POST /messages/?session_id=b53301ca408f4da4a12562ce2fde23de HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: python-httpx/0.28.1
Content-Length: 119
Accept: text/event-stream
Accept-Encoding: gzip, deflate
Content-Type: application/json{"method":"tools/call","params":{"name":"fetch","arguments":{"url":"https://blog.musnow.top/"}},"jsonrpc":"2.0","id":2}

服务端还是会返回一个accpet响应

HTTP/1.1 202 Accepted
date: Sun, 20 Apr 2025 06:00:39 GMT
server: uvicorn
content-length: 8Accepted

随后,MCP服务端会根据这个请求,调用实际的工具,并最终返回结果。由于这个请求结果的content是慕雪个人博客首页的html源码,所以内容非常之大,这里就不贴出来完整的事件data了。

f0bd
event: message
data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"<!DOCTYPE html><html lang=\"zh-CN\" data-theme=\"light\"><head><meta charset=\"UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0,viewport-fit=cover\"><title>慕雪的寒舍 - 雪下了一夜</title>

可以看到,服务端通过三次TCP报文才把整个首页的html完整传输给客户端。

image.png

到这里,针对/sse接口的HTTP响应就完整结束了,MCP服务端以tools调用结果返回为标志来结束HTTP响应。

在wireshark拼接出来的完整HTTP响应中可以观察到,tools调用结果的json完整结束了,这个HTTP响应就是结束了,随后便出现了TCP四次挥手的报文。其中调用工具的响应json末尾会包含一个字段"isErr",应该是用于标识本次mcp工具调用是否成功的,为false代表调用成功。

image.png

3.6. 工具调用结果交付AI处理

在收到工具调用结果之后,日志中就能够观察到QwenAgent将这个工具调用结果拼接在prompt里面发送给AI了。这里我把html文档的内容全部删掉了,改成了“首页HTML内容”,保留了其他字段。

首先这里能看到完整的MCP服务端工具调用结果的响应,包含jsonrpc字段、id字段、result字段、isError字段。其中工具调用结果是在result/content里面返回的。

QwenAgent的SDK依旧是在消息上下文里面将MCP工具的响应结果通过<tool_response>\n首页HTML内容\n</tool_response>的拼接了起来,以user身份发送给了AI。

2025-04-20 14:33:18,289 - DEBUG - Received SSE event: message
2025-04-20 14:33:18,291 - DEBUG - Received server message: root=JSONRPCResponse(jsonrpc='2.0', id=2, result={'content': [{'type': 'text', 'text': '首页HTML内容'}], 'isError': False})
2025-04-20 14:33:18,330 - DEBUG - Request options: {'method': 'post', 'url': '/chat/completions', 'files': None, 'json_data': {'messages': [{'role': 'system', 'content': '你是一个强大的助手,可以帮用户处理问题。\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{"type": "function", "function": {"name": "exmaple-server-fetch", "description": "Fetches a website and returns its content", "parameters": {"type": "object", "properties": {"url": {"type": "string", "description": "URL to fetch"}}, "required": ["url"]}}}\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>'}, {'role': 'user', 'content': '这个网站是什么?https://blog.musnow.top/'}, {'role': 'assistant', 'content': '<tool_call>\n{"name": "exmaple-server-fetch", "arguments": {"url": "https://blog.musnow.top/"}}\n</tool_call>'}, {'role': 'user', 'content': '<tool_response>\n首页HTML内容\n</tool_response>'}], 'model': 'Qwen/Qwen2.5-32B-Instruct', 'seed': 181793637, 'stream': True}}
2025-04-20 14:33:18,330 - DEBUG - Sending HTTP Request: POST https://api.siliconflow.cn/v1/chat/completions

最终,AI理解并处理“首页HTML内容”,输出了回答

{'role': 'assistant', 'content': '该网站名为"慕雪的寒舍 - 雪下了一夜",由作者"慕雪年华"创建。该网站看起来是一个个人博客,包含了各种类型的文章,例如编程学习、博客建站等。网站中的文章包含了不同的主题,如使用Python管理虚拟环境、MCP协议的理解和使用等。此外,网站顶部和底部列出了作者的一些社交链接和其他网站。', 'reasoning_content': '', 'name': '网页查看助手'}

不过,这里日志中出现了一个奇怪的地方,那就是代码里面打印的bot response上下文中的工具调用格式又变成了function_call这里应该是QwenAgent SDK针对mcp工具在对外输出的response里面做的额外解析处理,并没有把内部通过prompt让AI调用MCP工具的格式输出出来,在最终输出的时候还是会使用function_call的格式来标识AI和MCP工具的调用,方便用户解析。

2025-04-20 15:32:57,549 - INFO - bot response: [{'role': 'assistant', 'content': '', 'reasoning_content': '', 'name': '网页查看助手', 'function_call': {'name': 'exmaple-server-fetch', 'arguments': '{"url": "https://blog.musnow.top/"}'}}, {'role': 'function', 'content': '首页HTML内容', 'reasoning_content': '', 'name': 'exmaple-server-fetch'}, {'role': 'assistant', 'content': '该网站名为"慕雪的寒舍 - 雪下了一夜",由作者"慕雪年华"创建。该网站看起来是一个个人博客,包含了各种类型的文章,例如编程学习、博客建站等。网站中的文章包含了不同的主题,如使用Python管理虚拟环境、MCP协议的理解和使用等。此外,网站顶部和底部列出了作者的一些社交链接和其他网站。', 'reasoning_content': '', 'name': '网页查看助手'}]

从前文的日志分析中我们已经能够确定QwenAgent在调用工具的时候是直接通过prompt的方式让AI识别mcp工具的。在之前的博客中也提到过,这是MCP工具集成在Agent中的两种方式之一(另外一个方式就是直接使用AI的function call功能来调用),两种方式并没有好坏之分,只是将MCP集成到Agent中的不同的实现方式而已。


我顺带测试了一下QwenAgent的自定义工具是否也是用prompt方式的,果不其然,通过QwenAgent提供的@register_tool注册的自定义工具也是通过prompt方式让AI来调用的。

以下是运行Qwen-Agent/examples/assistant_add_custom_tool.py时DEBUG日志中prompt内容,这里也是通过prompt让AI了解了自定义工具my_image_gen的调用方式。

2025-04-20 16:17:52,865 - DEBUG - Request options: {'method': 'post', 'url': '/chat/completions', 'files': None, 'json_data': {'messages': [{'role': 'system', 'content': 'According to the user\'s request, you first draw a picture and then automatically run code to download the picture and select an image operation from the given document to process the image\n\n# 知识库\n\n## 来自 [文件](doc.pdf) 的内容:\n\n```\n# Python Image Processing Tutorial: Downloading Images and Performing Flip Operations \n\nIn  this  tutorial,  we  will  learn  how  to  download  images  using  Python  and  perform  basic  image \noperations such as flipping and rotating using the Pillow library. \n ## Prerequisites \n Before we begin, make sure you have the following libraries installed in your Python environment: \n\n- `requests`: for downloading images \n- `Pillow`: for image processing \n If you haven\'t installed these libraries yet, you can install them using pip: \n\n```bash \npip install requests Pillow \n```\n ## Step 1: Downloading an Image \n First, we need to download an image. We will use the `requests` library to accomplish this task. \n\n```\nimport requests \n\ndef download_image(url, filename): \n\tresponse = requests.get(url) \n\tif response.status_code == 200: \n\twith open(filename, \'wb\') as file: \n\tfile.write(response.content) \n\telse: \n\tprint(f"Error: Failed to download image from {url}") \n\n# Example usage \nimage_url = "https://example.com/image.jpg"    # Replace with the URL of the image you want to \ndownload \nfilename = "downloaded_image.jpg" \ndownload_image(image_url, filename) \n```\n ## Step 2: Opening and Displaying the Image \n Next, we will use the `Pillow` library to open and display the image we just downloaded. \n\n```\nfrom PIL import Image \n\ndef open_and_show_image(filename): \n\timage = Image.open(filename) \n\timage.show() \n\n# Example usage \nopen_and_show_image(filename) \n```\n ## Step 3: Flipping and Rotating the Image \n\nNow we can perform flip and rotate operations on the image. The `Pillow` library provides several \nmethods for image manipulation. \n\n```\ndef flip_image(filename, mode=\'horizontal\'): \n\timage = Image.open(filename) \n\tif mode == \'horizontal\': \n\tflipped_image = image.transpose(Image.FLIP_LEFT_RIGHT) \n\telif mode == \'vertical\': \n\tflipped_image = image.transpose(Image.FLIP_TOP_BOTTOM) \n\telse: \n\tprint("Error: Mode should be \'horizontal\' or \'vertical\'") \n\treturn \n\tflipped_image.show() \n\treturn flipped_image \n\ndef rotate_image(filename, degrees): \n\timage = Image.open(filename) \n\trotated_image = image.rotate(degrees) \n\trotated_image.show() \n\treturn rotated_image \n\n# Example usage \nflipped_image = flip_image(filename, mode=\'horizontal\')    # Horizontally flip \nflipped_image.save("flipped_horizontal.jpg")    # Save the horizontally flipped image \n\nflipped_image = flip_image(filename, mode=\'vertical\')    # Vertically flip \nflipped_image.save("flipped_vertical.jpg")    # Save the vertically flipped image \n\nrotated_image = rotate_image(filename, 90)    # Rotate by 90 degrees \nrotated_image.save("rotated_90.jpg")    # Save the rotated image \n\n```\n ## Step 4: Saving the Modified Image \n\nIn the examples above, we have seen how to save flipped and rotated images. You can use the \n`save` method to save any modified image. \n\n```\n# Save the image \ndef save_image(image, filename): \n\timage.save(filename) \n\n# Example usage \nsave_image(flipped_image, "flipped_image.jpg") \nsave_image(rotated_image, "rotated_image.jpg") \n```\n\nBy  now,  you  have  learned  how  to  download  images  using  Python  and  perform  basic  image \noperations using the Pillow library. You can extend these basics to implement more complex image \nprocessing functions as needed. \n\n```\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{"type": "function", "function": {"name": "my_image_gen", "description": "AI painting (image generation) service, input text description, and return the image URL drawn based on text information.", "parameters": [{"name": "prompt", "type": "string", "description": "Detailed description of the desired image content, in English", "required": true}]}}\n{"type": "function", "function": {"name": "code_interpreter", "description": "Python code sandbox, which can be used to execute Python code.", "parameters": [{"name": "code", "type": "string", "description": "The python code.", "required": true}]}}\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>'}, {'role': 'user', 'content': '画一只猫的图片'}], 'model': 'Qwen/Qwen2.5-32B-Instruct', 'seed': 652077296, 'stream': True}}
2025-04-20 16:17:52,865 - DEBUG - Sending HTTP Request: POST https://api.siliconflow.cn/v1/chat/completions

4. The end

今天心血来潮,通过抓包看了一下MCP客户端和服务端到底是怎么交互的,也算是学到了不少新知识,SSE协议也是第一次听说。如果对MCP或SSE有任何问题,欢迎评论讨论。

这次测试也借机了解了QwenAgent SDK底层是如何让Qwen大模型去处理tools的,采用的是prompt方案。所以QwenAgent在对接其他非Qwen大模型的时候基本不可用,比如我尝试了硅基流动的THUDM/GLM-4-9B-0414deepseek-ai/DeepSeek-V3,都没办法正常处理QwenAgent提供的prompt,其中GLM-4-9B-0414尝试输出工具调用参数,但并不符合QwenAgent的需要,没有办法被SDK识别,程序直接停止运行了。

# THUDM/chatglm3-6b 输出,工具调用方式不正确
[{'role': 'assistant', 'content': 'mcp-simple-tool-fetch\n{"url": "https://blog.musnow.top/"}', 'reasoning_content': '', 'name': '网页查看助手'}] 

DeepSeek-V3尝试了多次,也是会出现无法正常输出工具调用参数的问题,勉强能成功一两次。这里给出DeepSeek成功处理的上下文,如下:

# 请求体,包含工具prompt
{'method': 'post', 'url': '/chat/completions', 'files': None, 'json_data': {'messages': [{'role': 'system', 'content': '你是一个强大的助手,可以帮用户处理问题。\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{"type": "function", "function": {"name": "mcp-simple-tool-fetch", "description": "Fetches a website and returns its content", "parameters": {"type": "object", "properties": {"url": {"type": "string", "description": "URL to fetch"}}, "required": ["url"]}}}\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>'}, {'role': 'user', 'content': '这个网站是什么?https://blog.musnow.top/'}], 'model': 'deepseek-ai/DeepSeek-V3', 'seed': 832221696, 'stream': True}}
# deepseek 成功输出工具调用参数
[{'role': 'assistant', 'content': '', 'reasoning_content': '', 'name': '网页查看助手', 'function_call': {'name': 'mcp-simple-tool-fetch', 'arguments': '{"url": "https://blog.musnow.top/"}'}}]
# deepseek 也正常识别出了工具返回结果,并输出了总结
{'role': 'assistant', 'content': '这个网站是“慕雪的寒舍”,是一个个人博客网站,由“慕雪年华”维护。博客主要涵盖了编程学习、技术教程、项目开发记录等内容,包括Python、SLAM(实时定位与地图构建)、图床工具、ROS(机器人操作系统)等技术方向的文章。此外,博客还展示了作者的书影评价、个人生活记录等内容。\n\n以下是一些关键信息:\n- **博客名称**:慕雪的寒舍  \n- **作者**:慕雪年华  \n- **简介**:爱折腾的代码初学者  \n- **内容分类**:编程学习、博客建站、工具推荐等  \n- **文章数量**:441篇  \n- **标签**:Hexo、Linux、Python、C语言等  \n\n此外,博客还提供了友链、关于页面、留言功能等社交互动模块。如果你对技术类内容感兴趣,可以进一步探索该博客的详细内容。', 'reasoning_content': '', 'name': '网页查看助手'}

总而言之,QwenAgent的这个prompt只有在使用Qwen自家模型的时候识别度才好,使用其他家模型的时候非常容易出现tools无法正常识别的情况。不过这也是意料之中了,毕竟人家都叫QwenAgent SDK了,本来就不是面向所有大模型的通用SDK。

本文首发地址:【MCP】了解远程MCP调用背后使用的SSE协议,首发于 2025-04-20 14:47:57

点我查看更多精彩内容:慕雪的寒舍

相关文章:

【MCP】了解远程MCP调用背后使用的SSE协议

本文介绍了远程MCP使用的SSE协议&#xff0c;通过wireshark抓包的方式了解MCP客户端和服务端之间通过SSE协议交互涉及到的请求与响应。 1. 什么是SSE协议&#xff1f; 参考&#xff1a;https://zhuanlan.zhihu.com/p/1894024642395619635和https://blog.csdn.net/aerror/artic…...

Log4j Properties 配置项详细说明

Log4j Properties 配置项详细说明 1. 核心配置项说明 根日志记录器&#xff1a;定义全局日志级别和输出目标 log4j.rootLogger [级别], appender1, appender2,...Appender 定义&#xff1a;指定日志输出目标&#xff08;控制台、文件等&#xff09; log4j.appender.[名称].[属…...

哪些物联网框架支持多协议接入?选型指南与核心能力解析

在物联网&#xff08;IoT&#xff09;领域&#xff0c;设备通信协议的多样性&#xff08;如MQTT、CoAP、Modbus、Zigbee等&#xff09;是开发者面临的核心挑战之一。选择支持多协议接入的物联网框架&#xff0c;可以显著降低异构设备连接的复杂度&#xff0c;提升系统的兼容性和…...

第三方测试机构如何保障软件质量并节省企业成本?

在软件行业&#xff0c;第三方测试机构扮演着极其重要的角色。他们提供独立且专业的测试服务&#xff0c;目的是为了保障软件的质量以及提升用户的使用体验。 专业独立 测试机构拥有经验丰富的测试员和严谨的测试流程。他们会对软件各项功能进行细致检验&#xff0c;力求不放…...

Eigen迭代求解器类

1. 迭代求解器核心类概览 Eigen 提供多种迭代法求解稀疏线性方程组 AxbAxb&#xff0c;适用于大规模稀疏矩阵&#xff1a; 求解器类适用矩阵类型算法关键特性ConjugateGradient对称正定&#xff08;SPD&#xff09;共轭梯度法&#xff08;CG&#xff09;高精度&#xff0c;内…...

AI 与高性能计算的深度融合:开启科技新纪元

在当今科技迅猛发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;与高性能计算&#xff08;HPC&#xff09;正以前所未有的态势深度融合&#xff0c;这种融合宛如一场强大的风暴&#xff0c;席卷并重塑着众多领域的格局。从科学研究的突破到商业应用的革新&#xff0c…...

写入cache时数据格式错误产生的ERRO导致整个测试框架无法运行

背景 在yaml文件里面提取request放入缓存时&#xff0c;request是form-data&#xff0c;错用jsonpath提取并写入缓存&#xff0c;导致后面的所有运行都异常 原因 起因是我想引用请求体的Uid&#xff0c;提取方式用错了&#xff0c;所以可以看到最后一段current_request_set_…...

3:QT联合HALCON编程—海康相机SDK二次程序开发

思路&#xff1a; 1.定义带UI界面的主函数类 1.1在主函数中包含其它所有类头文件&#xff0c;进行声明和实例化&#xff1b;使用相机时&#xff0c;是用公共相机的接口在某一个具体函数中去实例化具体的海康相机对象。 1.2设计界面&#xff1a;连接相机&#xff0c;单次采集&a…...

图论---LCA(倍增法)

预处理 O( n logn )&#xff0c;查询O( log n ) #include<bits/stdc.h> using namespace std; typedef pair<int,int> pii; const int N40010,M2*N;//是无向边&#xff0c;边需要见两边int n,m; vector<int> g[N]; //2的幂次范围 0~15 int depth[N],fa[N][1…...

Bento4的安装和简单转码

1.下载Bento4 2解压复制到安装位置 3配置环境变量 在path下配置 5.视频转码为Dash 视频分片化 mp4fragment --track video --fragment-duration 4000 C:\Users\zcc\Downloads\Video\gg.mp4 C:\Users\zcc\Downloads\Video\out3\input_fragmented.mp4分片化的视频转码为dash…...

用python写一个相机选型的简易程序

最近有点忙&#xff0c;上来写的时间不多。 今天就把之前写的一个选型的简易程序&#xff0c;供大家参考。 代码&#xff1a; import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QLabel, QLineEdit, QPushButton, QGro…...

论人际关系发展的阶段

朋友关系的建立和发展是一个渐进的过程&#xff0c;通常需要经历情感积累、信任磨合和价值观融合等阶段。以下是朋友关系发展的详细阶段划分及核心特征&#xff1a; 一、表层接触阶段&#xff08;社交试探期&#xff09; 核心特征&#xff1a;以信息交换为主&#xff0c;关系停…...

2软考系统架构设计师:第一章系统架构概述 - 练习题附答案及超详细解析

第一章系统架构概述综合知识单选题 每道题均附有答案解析&#xff1a; 1. 系统架构的核心定义是什么&#xff1f; A. 系统代码的实现细节 B. 系统组件、组件关系及与环境交互的高层次设计蓝图 C. 用户界面的设计规范 D. 数据库表结构的详细设计 答案&#xff1a;B 解析&…...

华为OD机试真题——素数之积RSA加密算法(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C、GO六种语言的最佳实现方式&#xff1b; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析&#xff1b; 本文收录于专栏&#xff1a;《2025华为OD真题目录…...

k8s中资源的介绍及标准资源namespaces实践

文章目录 第1章 k8s中的资源(resources)介绍1.1 k8s中资源(resouces)的分类1.2 k8s中资源(resources)的级别1.3 k8s中资源(resources)的API规范1.4 k8s中资源(resources)的manifests 第2章 k8s中的标准资源之namespaces的实践2.1 基本介绍2.2 编写相关ns资源对象的manifests2.3…...

k8s学习记录(四):节点亲和性

一、前言 在上一篇文章里&#xff0c;我们了解了 Pod 中的nodeName和nodeSelector这两个属性&#xff0c;通过它们能够指定 Pod 调度到哪个 Node 上。今天&#xff0c;我们将进一步深入探索 Pod 相关知识。这部分内容不仅信息量较大&#xff0c;理解起来也有一定难度&#xff0…...

联想笔记本电脑在Windows下通过联想驱动实现风扇控制

概述 本文旨在解决部分联想笔记本电脑无法使用主流的风扇控制工具&#xff08;如Fan Control, SpeedFan&#xff09;控制风扇的问题。主流的风扇控制工具在这些电脑上会因无法找到控制风扇的EC寄存器而无法发挥作用。但这是不是就意味着没办法控制风扇了呢&#xff1f;答案是否…...

Java单链表题目

Java链表题目练习 移除链表元素反转单链表链表的中间节点返回倒数第K个节点合并两个有序列表判断链表是否回文 学习了知识&#xff0c;就要进行其检验自己是否真正学会&#xff0c;练习题目来加强对知识的理解&#xff0c;今天就来练习一下链表题目 移除链表元素 目的&#xff…...

springboot入门-controller层

在 Spring Boot 中&#xff0c;Controller 层是处理 HTTP 请求的核心组件&#xff0c;负责接收客户端请求、调用业务逻辑&#xff08;Service 层&#xff09;并返回响应。其核心原理基于 Spring MVC 框架&#xff0c;通过注解驱动的方式实现请求的路由和参数绑定。以下是 Contr…...

游戏引擎学习第245天:wglChoosePixelFormatARB

Blackboard: PBO&#xff08;像素缓冲对象&#xff09; 我们将一起编写一个完整的游戏。老实说&#xff0c;我原本以为我们会花更长时间来实现异步纹理上传&#xff0c;结果我们只用了两天时间&#xff0c;主要原因是我们没有设置标志来真正告诉程序下载纹理&#xff0c;所以这…...

中国大陆DNS服务选择指南:阿里云VS AWS,合规性与最佳实践

导语 在中国大陆开展互联网业务时,DNS服务的选择不仅关乎性能,更涉及合规性问题。本文将深入探讨DNS服务商选择的自由度、阿里云与AWS DNS服务的优劣势,以及如何在确保合规的同时优化您的域名解析策略。无论您是初创公司还是跨国企业,这份指南都将助您在复杂的中国互联网环境中…...

LLaMa Factory大模型微调

LLaMa Factory大模型微调 大模型微调平台&硬件LLaMA-Factory安装hfd下载hugging face模型自我认知微调Alpaca数据集指令监督微调断点续训 大模型微调 微调自我认知微调特定领域数据集。 平台&硬件 Ubuntu20.04显卡&#xff1a;M40 24G 2080TI 22G微调框架&#xff…...

git和github的使用指南

目录 1.git初始化本地仓库 2.远程仓库 3.如何将自己的代码上传到远程仓库的某一个分支 1.git初始化本地仓库 在项目目录中初始化 Git 仓库&#xff1a; cd your-project-directory git init 将文件添加到暂存区&#xff1a; git add . //添加所有文件 git add <fi…...

如何快速轻松地恢复未保存的 Word 文档:简短指南

文字处理器已经存在了几十年&#xff0c;其中许多已经变得非常擅长防止问题。丢失未保存的数据是一个常见问题&#xff0c;因此办公软件通常带有恢复文件的方法。在本文中&#xff0c;我们将介绍如何恢复 Word 文档&#xff0c;即使您尚未保存它。 确保数据安全的最佳方法是保…...

【Linux网络】打造初级网络计算器 - 从协议设计到服务实现

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

基于STM32定时器中断讲解(HAL库)

基于STM32定时器中断讲解&#xff08;HAL库&#xff09; 1、定时器简单介绍 以STM32F103C8T6中几个定时器为例&#xff1a; TIM1&#xff1a;这是一个高级定时器&#xff0c;不仅具备基本的定时中断功能&#xff0c;还拥有内外时钟源选择、输入捕获、输出比较、编码器接口以…...

《Vue3学习手记5》

pinia 共享的数据交给集中状态管理 引入与使用 //main.ts // 引入Pinia import {createPinia} from "pinia"const piniacreatePinia() app.use(pinia)案例&#xff1a; <template><div class"count"><h2>当前和为&#xff1a;{{ sum…...

MySQL多查询条件下深度分页性能优化技巧及示例总结

深度分页(Deep Pagination)是MySQL中常见的性能瓶颈问题,特别是在多查询条件下,当offset值很大时,查询性能会急剧下降。本文将总结多种优化技巧,并提供实际示例。 一、深度分页的性能问题分析 当执行类似SELECT * FROM table WHERE condition1 AND condition2 LIMIT 1000…...

3、初识RabbitMQ

界面上的导航栏共分6部分&#xff0c;分别代表不同的意思 一、Producer和Consumer Producer: 生产者, 是RabbitMQ Server的客户端, 向RabbitMQ发送消息 Consumer: 消费者, 也是RabbitMQ Server的客⼾端, 从RabbitMQ接收消息 Broker&#xff1a;其实就是RabbitMQ Server, 主要…...

量子计算与GPU的异构加速:基于CUDA Quantum的混合编程实践

一、量子模拟的算力困境与GPU破局 量子计算模拟面临‌指数级增长的资源需求‌&#xff1a;n个量子比特的态向量需要存储2^n个复数。当n>30时&#xff0c;单机内存已无法承载&#xff08;1TB需求&#xff09;。传统CPU模拟器&#xff08;如Qiskit Aer&#xff09;在n28时计算…...

在Spring Boot项目中实现Word转PDF并预览

在Spring Boot项目中实现Word转PDF并进行前端网页预览&#xff0c;你可以使用Apache POI来读取Word文件&#xff0c;iText或Apache PDFBox来生成PDF文件&#xff0c;然后通过Spring Boot控制器提供文件下载或预览链接。以下是一个示例实现步骤和代码&#xff1a; 1. 添加依赖 …...

Windows怎样使用curl下载文件

安装curl 从官网下载&#xff1a;访问curl官方网站&#xff0c;根据系统位数&#xff08;32 位或 64 位&#xff09;选择相应的版本进行下载。下载完成后&#xff0c;双击安装程序并按照提示进行安装。也可以选择自定义安装路径&#xff0c;记住安装路径&#xff0c;后续配置环…...

priority_queue的学习

priority_queue的介绍 优先级队列是一种容器适配器&#xff0c;根据严格的弱排序标准&#xff0c;它的第一个元素总是它所包含的元素中最大的。此上下文类似于堆&#xff0c;在堆中可以随时插入元素&#xff0c;并且只能检索最大堆元素(优先队列中位于顶部的元素)。优先队列被…...

浅谈Java 内存管理:栈与堆,垃圾回收

在Java编程世界里&#xff0c;内存管理是一项极为关键的技能&#xff0c;它就像程序运行背后的“隐形守护者”&#xff0c;默默影响着程序的性能与稳定性。今天&#xff0c;咱们就来简单学习一下Java内存管理中的两大核心要点&#xff1a;栈与堆的内存分配机制&#xff0c;以及…...

windows下查看idea运行的进程占的JVM情况工具

jconsole 查看JVM 查看线程数 自己测试时&#xff0c;可以先不把线程关闭查效果。 也可以用这工具查下是不是有线程一直在增加。...

【新技术】微软 Azure Test Impact Analyzer (TIA) 全面解析

目录 一、什么是 Azure Test Impact Analyzer&#xff1f;二、核心功能与优势三、如何掌握 Azure TIA&#xff1f;四、工作中的典型应用场景五、最佳实践与注意事项六、总结 一、什么是 Azure Test Impact Analyzer&#xff1f; Azure Test Impact Analyzer (TIA) 是微软 Azur…...

JAVA服务内存缓慢上涨,年轻代GC正常但Full GC频繁,如何定位?

1. 分析 &#xff1a; 年轻代GC正常&#xff0c;说明年轻代的对象回收没有问题&#xff0c;可能大部分对象都是朝生夕死的&#xff0c;所以Minor GC能有效清理。但Full GC频繁&#xff0c;通常意味着老年代空间不足&#xff0c;导致频繁进行Full GC来回收老年代。而内存缓慢上…...

浏览器界面无显示,提示“代理服务器可能有问题”,这是怎么回事呢?

前言 &#x1f31f;&#x1f31f;本期讲解浏览器代理服务器解决办法介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话不…...

C#中的弱引用使用

弱引用&#xff08;Weak Reference&#xff09;是一种特殊的引用类型&#xff0c;它允许你引用一个对象&#xff0c;但不会阻止该对象被垃圾回收器&#xff08;GC&#xff09;回收。弱引用通常用于需要缓存或跟踪对象&#xff0c;但又不希望因保留引用而导致内存泄漏的场景。弱…...

在Linux虚拟机下使用vscode,#include无法跳转问题

总结&#xff1a;需要通过Linux指令来添加编译器和压缩文件&#xff0c;解压&#xff0c;这样获得的编译器会具有可执行权限类似于 -rwxr-xr-x 1 user user 12345 Apr 26 14:22 myscript.sh 如果你直接从window中拖入文件到Linux文件下&#xff0c;你需要自己来再度开启可编译…...

MIL、SIL、HIL与Back-to-Back测试详解:从模型到硬件的完整验证链

1. 引言 在嵌入式系统和控制算法开发中&#xff0c;MIL、SIL、HIL和Back-to-Back测试构成了从模型设计到硬件部署的完整验证流程。它们覆盖不同开发阶段&#xff0c;确保系统功能正确性、实时性和可靠性。 本文将清晰解析这四种测试方法的核心概念、应用场景及差异。 2. 四种测…...

【Android Compose】焦点管理

官方文档链接&#xff1a; https://developer.android.google.cn/develop/ui/compose/touch-input/focus?hlzh-cn 1、更改焦点遍历顺序 1.1、替换一维遍历顺序 &#xff08;1&#xff09;创建焦点引用对象&#xff1a; /// 创建4个引用对象&#xff08;二选一&#xff09…...

启动命令汇总(Redis / Kafka / Flume / Spark)

本文总结了本地开发环境&#xff08;Windows系统&#xff09;中启动推荐系统所需的所有组件命令&#xff0c;包括 Redis、Kafka、Flume 及 SparkStreaming 程序的启动流程。 1. 启动 Redis 进入 Redis 安装目录&#xff0c;执行&#xff1a; redis-server.exe测试连接&#x…...

python 画折线统计图

Python 画折线统计图&#xff08;line chart&#xff09;最常用的是 matplotlib。 最基本的折线图代码如下&#xff1a; import matplotlib.pyplot as plt# 假设这是你的数据 x [1, 2, 3, 4, 5] y [2, 3, 5, 7, 11]# 创建折线图 plt.plot(x, y, markero) # markero 是在点…...

java面向对象编程【高级篇】之继承

目录 &#x1f680;前言&#x1f914;什么是继承&#xff1f;&#x1f31f;权限修饰符&#x1f4af;private 修饰符&#x1f4af;默认&#xff08;无修饰符&#xff09;&#x1f4af;protected 修饰符&#x1f4af;public 修饰符&#x1f4af;归纳 &#x1f99c;继承的特点&…...

【数论分块】数论分块算法模板及真题

1.数论分块的含义 数论分块算法&#xff0c;就是枚举出使得取整函数发生变化的地方。 例如&#xff0c;对表达式 ⌊ n i ⌋ \lfloor \frac{n}{i} \rfloor ⌊in​⌋使用数论分块算法&#xff0c;就可以在 O ( n ) O(\sqrt n) O(n ​)的时间复杂度下枚举所有满足 ⌊ n i − 1 ⌋…...

DIY 3D打印机 原理及步骤概况

一、3D打印机的基本原理 硬件组成&#xff1a; 运动系统&#xff1a;控制X/Y/Z轴的步进电机&#xff08;或直线电机&#xff09;&#xff0c;决定打印头的移动精度。 热端&#xff08;挤出机&#xff09;&#xff1a;加热并挤出材料&#xff08;如PLA、ABS塑料&#xff09;。 …...

深度探索:DeepSeek赋能WPS图表绘制

一、研究背景 在当今数字化信息爆炸的时代&#xff0c;数据处理与可视化分析已成为众多领域研究和决策的关键环节。随着数据量的急剧增长和数据维度的不断丰富&#xff0c;传统的数据可视化工具在应对复杂数据时逐渐显露出局限性。Excel作为广泛应用的电子表格软件&#xff0c;…...

内存四区(栈)

今天我再次学到了有趣的知识&#xff0c;内存四区&#xff01; 内存四区分为代码区&#xff0c;全局区&#xff0c;栈区&#xff0c;堆区&#xff0c;今天我们详细来讲讲栈区&#xff01; 内存四区和栈区都是用来存放数据的&#xff0c;而栈区存放的数据具体有两类 1.形参数…...

Nginx性能优化:从配置到缓存,全面提升Web服务器性能

一、基础配置优化&#xff1a;释放硬件潜能 进程与连接调优 worker_processes: 推荐设置为 auto&#xff08;自动匹配CPU核心数&#xff09;&#xff0c;但在特殊场景下需手动优化&#xff1a;worker_processes 8; # 8核CPU手动指定 worker_cpu_affinity 000…...