0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
电子发烧友
开通电子发烧友VIP会员 尊享10大特权
海量资料免费下载
精品直播免费看
优质内容免费畅学
课程9折专享价
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何構(gòu)建MCP客戶端

OSC開源社區(qū) ? 來源:阿里云開發(fā)者 ? 2025-03-20 09:32 ? 次閱讀

以下文章來源于阿里云開發(fā)者,作者無棄

Anthropic開源了一套MCP協(xié)議,它為連接AI系統(tǒng)與數(shù)據(jù)源提供了一個(gè)通用的、開放的標(biāo)準(zhǔn),用單一協(xié)議取代了碎片化的集成方式。本文教你從零打造一個(gè)MCP客戶端。

一、背景

如何讓大語言模型與外部系統(tǒng)交互,一直是AI系統(tǒng)需要解決的問題:

Plugins:OpenAI推出ChatGPT Plugins,首次允許模型通過插件與外部應(yīng)用交互。插件功能包括實(shí)時(shí)信息檢索(如瀏覽器訪問)、代碼解釋器(Code Interpreter)執(zhí)行計(jì)算、第三方服務(wù)調(diào)用(如酒店預(yù)訂、外賣服務(wù)等)

113773ce-0196-11f0-9310-92fbcf53809c.jpg

Function Calling:Function Calling技術(shù)逐步成熟,成為大模型與外部系統(tǒng)交互的核心方案。

1151e664-0196-11f0-9310-92fbcf53809c.jpg

Agent框架 Tools: 模型作為代理(Agent),動(dòng)態(tài)選擇工具完成任務(wù),比如langchain的Tool。

1160d2fa-0196-11f0-9310-92fbcf53809c.jpg

一個(gè)企業(yè),面對(duì)不同的框架或系統(tǒng),可能都需要參考他們的協(xié)議,去開發(fā)對(duì)應(yīng)Tool,這其實(shí)是一個(gè)非常重復(fù)的工作。

面對(duì)這種問題,Anthropic開源了一套MCP協(xié)議(Model Context Protocol),

https://www.anthropic.com/news/model-context-protocol?

https://modelcontextprotocol.io/introduction?

它為連接AI系統(tǒng)與數(shù)據(jù)源提供了一個(gè)通用的、開放的標(biāo)準(zhǔn),用單一協(xié)議取代了碎片化的集成方式。其結(jié)果是,能以更簡單、更可靠的方式讓人工智能系統(tǒng)獲取所需數(shù)據(jù)。

二、架構(gòu)

117b07ce-0196-11f0-9310-92fbcf53809c.jpg

MCP Hosts:像 Claude Desktop、Cursor這樣的程序,它們通過MCP訪問數(shù)據(jù)。

MCP Clients:與服務(wù)器保持 1:1 連接的協(xié)議客戶端。

MCP Servers:輕量級(jí)程序,每個(gè)程序都通過標(biāo)準(zhǔn)化的模型上下文協(xié)議公開特定功能。

結(jié)合AI模型,以一個(gè)Java應(yīng)用為例,架構(gòu)是這樣:

1186beac-0196-11f0-9310-92fbcf53809c.jpg

可以看到傳輸層有兩類:

StdioTransport

HTTP SSE

11a07504-0196-11f0-9310-92fbcf53809c.jpg

三、實(shí)現(xiàn)MCP Server

首先看一個(gè)最簡單的MCP Server例子:

import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";


// Create an MCP server
const server = new McpServer({
 name: "Demo",
 version: "1.0.0"
});


// Add an addition tool
server.tool("add",
 'Add two numbers',
 { a: z.number(), b: z.number() },
 async ({ a, b }) => ({
  content: [{ type: "text", text: String(a + b) }]
 })
);


async function main() {
 // Start receiving messages on stdin and sending messages on stdout
 const transport = new StdioServerTransport();
 await server.connect(transport);
}


main()

代碼頭部和底部都是一些樣板代碼,主要變化的是在tool這塊,這個(gè)聲明了一個(gè)做加法的工具。這就是一個(gè)最簡單的可運(yùn)行的Server了。

同時(shí)也可以使用官方的腳手架,來創(chuàng)建一個(gè)完整復(fù)雜的Server:

npx @modelcontextprotocol/create-server my-server

3.1 使用SDK

從上面代碼可以看到很多模塊都是從@modelcontextprotocol/sdk 這個(gè)SDK里導(dǎo)出的。

11af1ca8-0196-11f0-9310-92fbcf53809c.jpg

SDK封裝好了協(xié)議內(nèi)部細(xì)節(jié)(JSON-RPC 2.0),包括架構(gòu)分層,開發(fā)者直接寫一些業(yè)務(wù)代碼就可以了。

https://github.com/modelcontextprotocol/typescript-sdk?

MCP服務(wù)器可以提供三種主要功能類型:

Resources:可以由客戶端讀取的類似文件的數(shù)據(jù)(例如API響應(yīng)或文件內(nèi)容)

Tools:LLM可以調(diào)用的功能(在用戶批準(zhǔn)下)

Prompts:可幫助用戶完成特定任務(wù)的預(yù)先編寫的模板

ResourcesPrompts可以讓客戶端喚起,供用戶選擇,比如用戶所有的筆記,或者最近訂單。

11bf91fa-0196-11f0-9310-92fbcf53809c.jpg

重點(diǎn)在Tools,其他很多客戶端都不支持。

11d2b91a-0196-11f0-9310-92fbcf53809c.jpg

3.2 調(diào)試

如果寫好了代碼,怎么調(diào)試這個(gè)Server呢?官方提供了一個(gè)調(diào)試器:

npx @modelcontextprotocol/inspector

1.連接Server

11f017da-0196-11f0-9310-92fbcf53809c.jpg

2.獲取工具

11fe107e-0196-11f0-9310-92fbcf53809c.jpg

3.執(zhí)行調(diào)試

1211b160-0196-11f0-9310-92fbcf53809c.jpg

3.3 在客戶端使用

如果運(yùn)行結(jié)果沒錯(cuò),就可以上架到支持MCP協(xié)議的客戶端使用了,比如Claude、Cursor,這里以Cursor為例:

12211024-0196-11f0-9310-92fbcf53809c.jpg

在Cursor Composer中對(duì)話,會(huì)自動(dòng)識(shí)別這個(gè)Tool,并尋求用戶是否調(diào)用

123903be-0196-11f0-9310-92fbcf53809c.jpg

點(diǎn)擊運(yùn)行,就可以調(diào)用執(zhí)行:

12450d4e-0196-11f0-9310-92fbcf53809c.jpg

3.4 HTTP SSE類型Server

import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { z } from "zod";


const server = new McpServer({
 name: "demo-sse",
 version: "1.0.0"
});


server.tool("exchange",
 '人民幣匯率換算',
 { rmb: z.number() },
 async ({ rmb }) => {
  // 使用固定匯率進(jìn)行演示,實(shí)際應(yīng)該調(diào)用匯率API
  const usdRate = 0.14; // 1人民幣約等于0.14美元
  const hkdRate = 1.09; // 1人民幣約等于1.09港幣
  
  const usd = (rmb * usdRate).toFixed(2);
  const hkd = (rmb * hkdRate).toFixed(2);
  
  return {
   content: [{ 
    type: "text", 
    text: `${rmb}人民幣等于:
${usd}美元
${hkd}港幣`
   }]
  }
 },
);
const app = express();
const sessions: Record = {}
app.get("/sse", async (req, res) => {
 console.log(`New SSE connection from ${req.ip}`);
 const sseTransport = new SSEServerTransport("/messages", res);
 const sessionId = sseTransport.sessionId;
 if (sessionId) {
  sessions[sessionId] = { transport: sseTransport, response: res }
 }
 await server.connect(sseTransport);
});


app.post("/messages", async (req, res) => {
 const sessionId = req.query.sessionId as string;
 const session = sessions[sessionId];
 if (!session) {
  res.status(404).send("Session not found");
  return;
 }


 await session.transport.handlePostMessage(req, res);
});


app.listen(3001);

核心的差別在于需要提供一個(gè)sse服務(wù),對(duì)于Tool基本一樣,但是sse類型就可以部署在服務(wù)端了。上架也和command類型相似:

125b951e-0196-11f0-9310-92fbcf53809c.jpg

126a73c2-0196-11f0-9310-92fbcf53809c.jpg

3.5 一個(gè)復(fù)雜一點(diǎn)的例子

操作瀏覽器執(zhí)行自動(dòng)化流程。

可以操作瀏覽器,Cursor秒變Devin。想象一下,寫完代碼,編輯器自動(dòng)打開瀏覽器預(yù)覽效果,然后截圖給視覺模型,發(fā)現(xiàn)樣式不對(duì),自動(dòng)修改。

如果對(duì)接好內(nèi)部系統(tǒng),貼一個(gè)需求地址,自動(dòng)連接瀏覽器,打開網(wǎng)頁,分析需求,分析視覺稿,然后自己寫代碼,對(duì)比視覺稿,你就喝杯咖啡,靜靜的看著它工作。

3.6 MCP Server資源

有很多寫好的Server,可以直接復(fù)用。

https://github.com/modelcontextprotocol/servers?

https://github.com/punkpeye/awesome-mcp-servers/blob/main/README-zh.md?

四、實(shí)現(xiàn)MCP Client

一般MCP Host以一個(gè)Chat box為入口,對(duì)話形式去調(diào)用。

12862f9a-0196-11f0-9310-92fbcf53809c.jpg

那我們怎么在自己的應(yīng)用里支持MCP協(xié)議呢?這里需要實(shí)現(xiàn)MCP Client。

4.1 配置文件

使用配置文件來標(biāo)明有哪些MCP Server,以及類型。

const config = [
 {
  name: 'demo-stdio',
  type: 'command',
  command: 'node ~/code-open/cursor-toolkits/mcp/build/demo-stdio.js',
  isOpen: true
 },
 {
  name: 'weather-stdio',
  type: 'command',
  command: 'node ~/code-open/cursor-toolkits/mcp/build/weather-stdio.js',
  isOpen: true
 },
 {
  name: 'demo-sse',
  type: 'sse',
  url: 'http://localhost:3001/sse',
  isOpen: false
 }
];
export default config;

4.2 確認(rèn)交互形態(tài)

MCP Client主要還是基于LLM,識(shí)別到需要調(diào)用外部系統(tǒng),調(diào)用MCP Server提供的Tool,所以還是以對(duì)話為入口,可以方便一點(diǎn),直接在terminal里對(duì)話,使用readline來讀取用戶輸入。大模型可以直接使用openai,Tool的路由直接使用function calling。

4.3 編寫Client

大致的邏輯:

1.讀取配置文件,運(yùn)行所有Server,獲取可用的Tools 2.用戶與LLM對(duì)話(附帶所有Tools名稱描述,參數(shù)定義) 3.LLM識(shí)別到要執(zhí)行某個(gè)Tool,返回名稱和參數(shù) 4.找到對(duì)應(yīng)Server的Tool,調(diào)用執(zhí)行,返回結(jié)果 5.把工具執(zhí)行結(jié)果提交給LLM 6.LLM返回分析結(jié)果給用戶

使用SDK編寫Client代碼

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport, StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import OpenAI from "openai";
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { ChatCompletionMessageParam } from "openai/resources/chat/completions.js";
import { createInterface } from "readline";
import { homedir } from 'os';
import config from "./mcp-server-config.js";
// 初始化環(huán)境變量
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
if (!OPENAI_API_KEY) {
  throw new Error("OPENAI_API_KEY environment variable is required");
}
interface MCPToolResult {
  content: string;
}
interface ServerConfig {
  name: string;
  type: 'command' | 'sse';
  command?: string;
  url?: string;
  isOpen?: boolean;
}
class MCPClient {
  static getOpenServers(): string[] {
    return config.filter(cfg => cfg.isOpen).map(cfg => cfg.name);
  }
  private sessions: Map = new Map();
  private transports: Map = new Map();
  private openai: OpenAI;
  constructor() {
    this.openai = new OpenAI({
      apiKey: OPENAI_API_KEY
    });
  }
  async connectToServer(serverName: string): Promise {
    const serverConfig = config.find(cfg => cfg.name === serverName) as ServerConfig;
    if (!serverConfig) {
      throw new Error(`Server configuration not found for: ${serverName}`);
    }
    let transport: StdioClientTransport | SSEClientTransport;
    if (serverConfig.type === 'command' && serverConfig.command) {
      transport = await this.createCommandTransport(serverConfig.command);
    } else if (serverConfig.type === 'sse' && serverConfig.url) {
      transport = await this.createSSETransport(serverConfig.url);
    } else {
      throw new Error(`Invalid server configuration for: ${serverName}`);
    }
    const client = new Client(
      {
        name: "mcp-client",
        version: "1.0.0"
      },
      {
        capabilities: {
          prompts: {},
          resources: {},
          tools: {}
        }
      }
    );
    await client.connect(transport);
    
    this.sessions.set(serverName, client);
    this.transports.set(serverName, transport);
    // 列出可用工具
    const response = await client.listTools();
    console.log(`
Connected to server '${serverName}' with tools:`, response.tools.map((tool: Tool) => tool.name));
  }
  private async createCommandTransport(shell: string): Promise {
    const [command, ...shellArgs] = shell.split(' ');
    if (!command) {
      throw new Error("Invalid shell command");
    }
    // 處理參數(shù)中的波浪號(hào)路徑
    const args = shellArgs.map(arg => {
      if (arg.startsWith('~/')) {
        return arg.replace('~', homedir());
      }
      return arg;
    });
    
    const serverParams: StdioServerParameters = {
      command,
      args,
      env: Object.fromEntries(
        Object.entries(process.env).filter(([_, v]) => v !== undefined)
      ) as Record
    };
    return new StdioClientTransport(serverParams);
  }
  private async createSSETransport(url: string): Promise {
    return new SSEClientTransport(new URL(url));
  }
  async processQuery(query: string): Promise {
    if (this.sessions.size === 0) {
      throw new Error("Not connected to any server");
    }
    const messages: ChatCompletionMessageParam[] = [
      {
        role: "user",
        content: query
      }
    ];
    // 獲取所有服務(wù)器的工具列表
    const availableTools: any[] = [];
    for (const [serverName, session] of this.sessions) {
      const response = await session.listTools();
      const tools = response.tools.map((tool: Tool) => ({
        type: "function" as const,
        function: {
          name: `${serverName}__${tool.name}`,
          description: `[${serverName}] ${tool.description}`,
          parameters: tool.inputSchema
        }
      }));
      availableTools.push(...tools);
    }
    // 調(diào)用OpenAI API
    const completion = await this.openai.chat.completions.create({
      model: "gpt-4-turbo-preview",
      messages,
      tools: availableTools,
      tool_choice: "auto"
    });
    const finalText: string[] = [];
    
    // 處理OpenAI的響應(yīng)
    for (const choice of completion.choices) {
      const message = choice.message;
      
      if (message.content) {
        finalText.push(message.content);
      }
      if (message.tool_calls) {
        for (const toolCall of message.tool_calls) {
          const [serverName, toolName] = toolCall.function.name.split('__');
          const session = this.sessions.get(serverName);
          
          if (!session) {
            finalText.push(`[Error: Server ${serverName} not found]`);
            continue;
          }
          const toolArgs = JSON.parse(toolCall.function.arguments);
          // 執(zhí)行工具調(diào)用
          const result = await session.callTool({
            name: toolName,
            arguments: toolArgs
          });
          const toolResult = result as unknown as MCPToolResult;
          finalText.push(`[Calling tool ${toolName} on server ${serverName} with args ${JSON.stringify(toolArgs)}]`);
          console.log(toolResult.content);
          finalText.push(toolResult.content);
          // 繼續(xù)與工具結(jié)果的對(duì)話
          messages.push({
            role: "assistant",
            content: "",
            tool_calls: [toolCall]
          });
          messages.push({
            role: "tool",
            tool_call_id: toolCall.id,
            content: toolResult.content
          });
          // 獲取下一個(gè)響應(yīng)
          const nextCompletion = await this.openai.chat.completions.create({
            model: "gpt-4-turbo-preview",
            messages,
            tools: availableTools,
            tool_choice: "auto"
          });
          if (nextCompletion.choices[0].message.content) {
            finalText.push(nextCompletion.choices[0].message.content);
          }
        }
      }
    }
    return finalText.join("
");
  }
  async chatLoop(): Promise {
    console.log("
MCP Client Started!");
    console.log("Type your queries or 'quit' to exit.");
    const readline = createInterface({
      input: process.stdin,
      output: process.stdout
    });
    const askQuestion = () => {
      return new Promise((resolve) => {
        readline.question("
Query: ", resolve);
      });
    };
    try {
      while (true) {
        const query = (await askQuestion()).trim();
        if (query.toLowerCase() === 'quit') {
          break;
        }
        try {
          const response = await this.processQuery(query);
          console.log("
" + response);
        } catch (error) {
          console.error("
Error:", error);
        }
      }
    } finally {
      readline.close();
    }
  }
  async cleanup(): Promise {
    for (const transport of this.transports.values()) {
      await transport.close();
    }
    this.transports.clear();
    this.sessions.clear();
  }
  hasActiveSessions(): boolean {
    return this.sessions.size > 0;
  }
}
// 主函數(shù)
async function main() {
  const openServers = MCPClient.getOpenServers();
  console.log("Connecting to servers:", openServers.join(", "));
  const client = new MCPClient();
  
  try {
    // 連接所有開啟的服務(wù)器
    for (const serverName of openServers) {
      try {
        await client.connectToServer(serverName);
      } catch (error) {
        console.error(`Failed to connect to server '${serverName}':`, error);
      }
    }
    if (!client.hasActiveSessions()) {
      throw new Error("Failed to connect to any server");
    }
    await client.chatLoop();
  } finally {
    await client.cleanup();
  }
}
// 運(yùn)行主函數(shù)
main().catch(console.error);?

4.4 運(yùn)行效果

NODE_TLS_REJECT_UNAUTHORIZED=0 node build/client.js

NODE_TLS_REJECT_UNAUTHORIZED=0 可以忽略(不校驗(yàn)證書)

4.5 時(shí)序圖?

129578a6-0196-11f0-9310-92fbcf53809c.png

五、總結(jié)

總體來說解決了Client和Server數(shù)據(jù)交互的問題,但是沒有解決LLM到Tool的對(duì)接:不同模型實(shí)現(xiàn)function call支持度不一樣,比如DeepSeek R1不支持,那么如何路由到工具就成了問題。

不足:

1.開源時(shí)間不長,目前還不是很完善,語言支持度不夠,示例代碼不多。

2.Server質(zhì)量良莠不齊,缺乏一個(gè)統(tǒng)一的質(zhì)量保障體系和包管理工具,很多Server運(yùn)行不起來,或者經(jīng)常崩。

3.本地的Server還是依賴Node.js或者Python環(huán)境,遠(yuǎn)程Server支持的很少。

如果未來都開始接入MCP協(xié)議,生態(tài)起來了,能力就會(huì)非常豐富了,使用的人多了,就會(huì)有更多的系統(tǒng)愿意來對(duì)接,寫一套代碼就可以真正所有地方運(yùn)行了。

個(gè)人認(rèn)為MCP還是有前途的,未來可期!

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 開源
    +關(guān)注

    關(guān)注

    3

    文章

    3486

    瀏覽量

    43016
  • 模型
    +關(guān)注

    關(guān)注

    1

    文章

    3428

    瀏覽量

    49529
  • 客戶端
    +關(guān)注

    關(guān)注

    1

    文章

    293

    瀏覽量

    16846
  • MCP
    MCP
    +關(guān)注

    關(guān)注

    0

    文章

    256

    瀏覽量

    14049

原文標(biāo)題:從零開始教你打造一個(gè)MCP客戶端

文章出處:【微信號(hào):OSC開源社區(qū),微信公眾號(hào):OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 0人收藏

    評(píng)論

    相關(guān)推薦

    一個(gè)服務(wù)器,多個(gè)客戶端,怎么向指定的客戶端發(fā)數(shù)據(jù)

    我用labview做服務(wù)器,單片機(jī)做客戶端,客戶端幾百個(gè),怎么區(qū)分客戶端,給指定的客戶發(fā)發(fā)數(shù)據(jù)
    發(fā)表于 06-01 09:26

    websocket客戶端性能很差是什么原因?

    我正在構(gòu)建一個(gè)解決方案,其中一個(gè)應(yīng)用程序運(yùn)行一個(gè) websocket 服務(wù)器,許多 esp32s3 設(shè)備充當(dāng)客戶端并通過 wifi 連接到該服務(wù)器。一般功能是客戶端在觸發(fā)時(shí)向應(yīng)用程序發(fā)送
    發(fā)表于 04-13 07:00

    用Delphi開發(fā)OPC客戶端工具的方法研究

    本文通過介紹OPC 技術(shù)的工作原理,結(jié)合OPC 客戶端的工作機(jī)制,給出OPC 客戶端的開發(fā)方法及在的Delphi 的具體實(shí)現(xiàn),提出了OPC 客戶端開發(fā)工具的設(shè)計(jì)方案,并實(shí)現(xiàn)了OPC 客戶端
    發(fā)表于 06-15 10:37 ?35次下載

    基于USB的加密視頻客戶端的設(shè)計(jì)與實(shí)現(xiàn)

    針對(duì)USB無線視頻實(shí)時(shí)接收裝置的開發(fā),論文介紹了在Windows視頻客戶端通過USB數(shù)據(jù)接口來接收數(shù)據(jù),并且通過在Linux服務(wù)器將采集的視頻和音頻數(shù)據(jù)加密,在客戶端進(jìn)行解密從而保
    發(fā)表于 08-31 16:04 ?23次下載

    CoolpyCould客戶端

    一款開源的物聯(lián)網(wǎng)服務(wù)器平臺(tái),利用nodejs寫成,此文件是CoolpyCould客戶端
    發(fā)表于 11-06 17:00 ?18次下載

    CSDN博客客戶端源碼

    CSDN博客客戶端源碼CSDN博客客戶端源碼CSDN博客客戶端源碼
    發(fā)表于 11-18 10:22 ?1次下載

    JAVA教程之UDP客戶端模型

    JAVA教程之UDP客戶端模型,很好的JAVA的資料,快來學(xué)習(xí)吧
    發(fā)表于 04-11 17:28 ?4次下載

    JAVA教程之Telnet客戶端

    JAVA教程之Telnet客戶端,很好的JAVA的資料,快來學(xué)習(xí)吧
    發(fā)表于 04-11 17:28 ?6次下載

    Android 仿QQ客戶端及服務(wù)源碼

    Android 仿QQ客戶端及服務(wù)源碼
    發(fā)表于 03-19 11:23 ?3次下載

    iOS淘寶客戶端應(yīng)用名稱發(fā)生變化 Android客戶端應(yīng)用名稱尚未更改

    iOS淘寶客戶端應(yīng)用名稱發(fā)生變化 Android客戶端應(yīng)用名稱尚未更改
    發(fā)表于 04-18 15:37 ?980次閱讀

    HTTP客戶端快速入門指南

    HTTP客戶端快速入門指南
    發(fā)表于 01-12 18:45 ?0次下載
    HTTP<b class='flag-5'>客戶端</b>快速入門指南

    HTTP客戶端快速入門指南

    HTTP客戶端快速入門指南
    發(fā)表于 07-03 18:38 ?0次下載
    HTTP<b class='flag-5'>客戶端</b>快速入門指南

    MQTT中服務(wù)客戶端

    MQTT 是一種基于客戶端-服務(wù)架構(gòu)(C/S)的消息傳輸協(xié)議,所以在 MQTT 協(xié)議通信中,有兩個(gè)最為重要的角色,它們便是服務(wù)客戶端。 1)服務(wù)
    的頭像 發(fā)表于 07-30 14:55 ?2871次閱讀

    ROS是如何設(shè)計(jì)的 ROS客戶端

    實(shí)現(xiàn)通信的代碼在ros_comm包中,如下。 其中clients文件夾一共有127個(gè)文件,看來是最大的包了。 現(xiàn)在我們來到了ROS最核心的地帶。 客戶端這個(gè)名詞出現(xiàn)的有些突然,一個(gè)機(jī)器人操作系統(tǒng)里
    的頭像 發(fā)表于 09-14 17:29 ?976次閱讀
    ROS是如何設(shè)計(jì)的 ROS<b class='flag-5'>客戶端</b>庫

    C#編寫socket客戶端案例

    C#編寫socketDemo,socket做服務(wù)器和做客戶端例子
    發(fā)表于 10-25 15:10 ?0次下載

    電子發(fā)燒友

    中國電子工程師最喜歡的網(wǎng)站

    • 2931785位工程師會(huì)員交流學(xué)習(xí)
    • 獲取您個(gè)性化的科技前沿技術(shù)信息
    • 參加活動(dòng)獲取豐厚的禮品