首页
最新活动
服务器租用
香港服务器租用
台湾服务器租用
美国服务器租用
日本服务器租用
新加坡服务器租用
高防服务器
香港高防服务器
台湾高防服务器
美国高防服务器
裸金属
香港裸金属服务器
台湾裸金属服务器
美国裸金属服务器
日本裸金属服务器
新加坡裸金属服务器
云服务器
香港云服务器
台湾云服务器
美国云服务器
日本云服务器
CDN
CDN节点
CDN带宽
CDN防御
CDN定制
行业新闻
官方公告
香港服务器资讯
帮助文档
wp博客
zb博客
服务器资讯
联系我们
关于我们
机房介绍
机房托管
登入
注册
帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
联系客服
服务器资讯
/
香港服务器租用
/
香港VPS租用
/
香港云服务器
/
美国服务器租用
/
台湾服务器租用
/
日本服务器租用
/
官方公告
/
帮助文档
C语言通过IXMLHttpRequest以get或post方式发送http请求获取服务器文本或xml数据
发布时间:2024-03-06 02:13:10 分类:帮助文档
C语言通过IXMLHttpRequest以get或post方式发送http请求获取服务器文本或xml数据 做过网页设计的人应该都知道ajax。 Ajax即Asynchronous Javascript And XML(异步的JavaScript和XML)。使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。 在IE浏览器中,Ajax技术就是基于JavaScript里面的XMLHttpRequest。AJAX通过XMLHttpRequest对象发出HTTP 请求,得到服务器返回的数据后,再进行处理。现在,服务器返回的都是JSON格式的数据,XML格式已经过时了,但是AJAX这个名字已经成了一个通用名词,字面含义已经消失了。 XMLHttpRequest对象是AJAX的主要接口,用于浏览器与服务器之间的通信。尽管名字里面有XML和Http,它实际上可以使用多种协议(比如file或ftp),发送任何格式的数据(包括字符串和二进制)。 其实,不仅在网页上能用JavaScript语言调用XMLHttpRequest组件,在桌面窗口程序里面也可以用C语言或C++调用XMLHttpRequest组件。 XMLHttpRequest是微软msxml6.0里面的组件。msxml6.0可直接解析服务器返回的xml文档,但json数据需要在网上找cJSON库来解析。 第一节 准备Web服务器页面 首先我们要在自己的服务器上准备好处理ajax请求的页面,本文准备了三个示例页面:str_test.php、json_test.php和xml_test.php,分别用来产生文本回应、json回应和xml回应。xml_test.php页面支持get和post两种ajax请求方式。 PHP文件用UTF-8编码保存,但C文件要用GB2312编码保存。 这是因为在微软的简体中文Windows系统里面,以A结尾的API函数(如CreateWindowA)采用的是GB2312编码的char *字符串,以W结尾的API函数(如CreateWindowW)采用的是UTF-16编码的wchar_t *字符串,COM组件使用的BSTR字符串是在wchar_t *字符串的基础上增加了4字节的字符串长度前缀。 IXMLHttpRequest组件(一种COM组件)工作时使用的是UTF-16编码的BSTR字符串。 我们在收到IXMLHttpRequest组件提供的UTF-16编码的BSTR字符串后,如果想要用printf在控制台上打印出来,就需要用convert_bstr_to_string函数将BSTR转换为GB2312编码的char *字符串。想要直接打印BSTR字符串不能用printf函数,要用WriteConsoleW函数才行。如果是显示在窗口的文本框或者窗口的标题栏上那就简单了,直接用SetWindowTextW或SetDlgItemTextW函数,把BSTR字符串传进去就行了。 同样,在给IXMLHttpRequest组件传递字符串参数时,需要先用convert_string_to_bstr函数将GB2312编码的char *字符串转换为UTF-16编码的带长度前缀的BSTR字符串。 如果是写入txt文本文件的话,我们就可以采用UTF-8编码,用MultiByteToWideChar函数(CP_UTF8)将BSTR转换成UTF-8编码的char *字符串,再用fprintf或fwrite写入txt文件。 str_test.php: 当前时间为=date("Y年n月j日 H:i:s")?>。 json_test.php: xml_test.php: '; $timestr = strftime("%Y%m%d%H%M%S"); echo "
"; foreach ($_COOKIE as $name => $value) { $_name = htmlspecialchars($name); $_value = htmlspecialchars($value); echo "
$_value
"; } foreach ($_GET as $name => $value) { $_name = htmlspecialchars($name); $_value = htmlspecialchars($value); echo "
$_value"; } foreach ($_POST as $name => $value) { $_name = htmlspecialchars($name); $_value = htmlspecialchars($value); echo "
$_value"; } echo '
'; ?> 第二节 以同步方式发送Ajax请求 下面我们来看看C语言如何像网页里面那样用XMLHttpRequest发送ajax请求。 请注意IXMLHttpRequest和IXMLHTTPRequest是两个名字不同但内容完全相同的接口,可以互换使用。 /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include
#include
#pragma comment(lib, "msxml6.lib") // char *转BSTR // 用完后调用SysFreeString释放 BSTR convert_string_to_bstr(const char *s) { int n; wchar_t *ws; BSTR bstr = NULL; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); bstr = SysAllocString(ws); free(ws); } return bstr; } // BSTR转char * // 用完后调用free释放 char *convert_bstr_to_string(BSTR bstr) { char *s; int n; n = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL); s = malloc(n); if (s != NULL) WideCharToMultiByte(CP_ACP, 0, bstr, -1, s, n, NULL, NULL); return s; } // 去掉字符串末尾的\r\n void remove_last_crlf(char *str) { int len; len = (int)strlen(str); if (len >= 2 && str[len - 2] == '\r' && str[len - 1] == '\n') str[len - 2] = '\0'; } // 示例: 读取纯文本内容 (同步方式) // 请注意有两个名称不同但内容完全一样的接口: IXMLHttpRequest和IXMLHTTPRequest, 随便用哪个都可以 // 但class id只有一个: CLSID_XMLHTTPRequest void str_test(const char *url) { char *response; long status; BSTR method_bstr, url_bstr, response_bstr; HRESULT hr; IXMLHttpRequest *xhr; VARIANT async; // VARIANT代表一个弱类型的变量 VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { // 打开连接 // get参数内容是放到url字符串里面的 method_bstr = convert_string_to_bstr("GET"); url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_FALSE; // 选择非异步方式 (也就是同步方式) null.vt = VT_NULL; hr = IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); // 最后两个参数是用户名和密码, 不用填 SysFreeString(method_bstr); SysFreeString(url_bstr); if (SUCCEEDED(hr)) printf("open() succeeded\n"); // 发送请求 // 由于我们选择的是同步方式, 所以send函数要等到所有数据都接收完了才返回 hr = IXMLHttpRequest_send(xhr, null); // 第二个参数是post数据内容, 不用管 if (SUCCEEDED(hr)) printf("send() succeeded\n"); // 获取请求状态 // 0: open函数还没调用 // 1: send函数还没调用 // 2: send函数调用了, get_status()函数已经可以使用了, 也可以读http headers了, 但是还读不了response // 3: 已收到了部分数据, 可以读responseBody和responseText // 4: 所有数据都已经接收完了, responseBody和responseText已经是完整数据了 hr = IXMLHttpRequest_get_readyState(xhr, &status); if (SUCCEEDED(hr)) printf("ready state: %d\n", status); // 获取http返回的response code hr = IXMLHttpRequest_get_status(xhr, &status); if (SUCCEEDED(hr)) printf("http status code: %d\n", status); // 读取并显示文本内容 hr = IXMLHttpRequest_get_responseText(xhr, &response_bstr); if (SUCCEEDED(hr)) { // response_bstr是utf16编码, 转换后的response是gb2312编码 // 把函数里面的CP_ACP改成CP_UTF8就可以转成utf8编码, 但是utf8就没办法用printf打印, 只能用fwrite写入txt文件再用记事本打开查看 response = convert_bstr_to_string(response_bstr); remove_last_crlf(response); printf("response: %s\n", response); free(response); SysFreeString(response_bstr); } IXMLHttpRequest_Release(xhr); } printf("\n"); } // 直接显示xml代码 void display_xmlstr(IXMLDOMDocument *xmldoc) { char *str; BSTR bstr; HRESULT hr; hr = IXMLDOMDocument_get_xml(xmldoc, &bstr); if (SUCCEEDED(hr)) { str = convert_bstr_to_string(bstr); remove_last_crlf(str); printf("xmlstr: %s\n", str); free(str); SysFreeString(bstr); } } // 显示xml属性值 void display_attr(IXMLDOMElement *elem, const char *name) { char *value; BSTR bstr; // 表示一个带长度前缀的utf16编码的字符串 VARIANT variant; // 表示一个弱类型的变量 bstr = convert_string_to_bstr(name); IXMLDOMElement_getAttribute(elem, bstr, &variant); SysFreeString(bstr); // 使用完字符串后必须释放 if (variant.vt == VT_BSTR) { value = convert_bstr_to_string(variant.bstrVal); printf("%s: %s\n", name, value); free(value); } else if (variant.vt == VT_NULL) printf("%s: (null)\n", name); VariantClear(&variant); } // 检查xml节点名称是否为指定名称 int check_node_name(IXMLDOMNode *node, const char *expected_name) { char *name; int ret; BSTR bstr; IXMLDOMNode_get_nodeName(node, &bstr); name = convert_bstr_to_string(bstr); SysFreeString(bstr); ret = (strcmp(name, expected_name) == 0); free(name); return ret; } // 检查xml属性值是否为指定值 int check_node_attr(IXMLDOMNode *node, const char *attr, const char *expected_value) { char *value; int ret = 0; BSTR bstr; HRESULT hr; IXMLDOMElement *elem; VARIANT variant; IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, &elem); // 先把node转成element bstr = convert_string_to_bstr(attr); hr = IXMLDOMElement_getAttribute(elem, bstr, &variant); // 再通过element获取属性值 SysFreeString(bstr); if (SUCCEEDED(hr)) { if (variant.vt == VT_BSTR) { value = convert_bstr_to_string(variant.bstrVal); ret = (strcmp(value, expected_value) == 0); free(value); } else if (variant.vt == VT_NULL) ret = (expected_value == NULL || expected_value[0] == '\0'); VariantClear(&variant); } IXMLDOMElement_Release(elem); return ret; } void display_xml(IXMLDOMDocument *xmldoc) { char *text; long i, len; BSTR text_bstr; HRESULT hr; IXMLDOMElement *root; IXMLDOMNode *node; IXMLDOMNodeList *list; IXMLDOMDocument_get_documentElement(xmldoc, &root); display_attr(root, "timestr"); display_attr(root, "example"); display_attr(root, "title"); hr = IXMLDOMElement_get_childNodes(root, &list); if (SUCCEEDED(hr)) { IXMLDOMNodeList_get_length(list, &len); for (i = 0; i < len; i++) { IXMLDOMNodeList_get_item(list, i, &node); if (check_node_name(node, "param") && check_node_attr(node, "method", "get") && check_node_attr(node, "name", "txt")) { IXMLDOMNode_get_text(node, &text_bstr); text = convert_bstr_to_string(text_bstr); printf("txt: %s\n", text); free(text); SysFreeString(text_bstr); } else if (check_node_name(node, "param") && check_node_attr(node, "method", "post") && check_node_attr(node, "name", "hello")) { IXMLDOMNode_get_text(node, &text_bstr); text = convert_bstr_to_string(text_bstr); printf("hello: %s\n", text); free(text); SysFreeString(text_bstr); } IXMLDOMNode_Release(node); } IXMLDOMNodeList_Release(list); } IXMLDOMElement_Release(root); } // 示例: 读取xml内容 (同步方式) void xml_test(const char *url, const char *post_data) { long status; BSTR method_bstr, url_bstr, header_bstr, value_bstr; HRESULT hr; IDispatch *disp; IXMLDOMDocument *xmldoc; IXMLHttpRequest *xhr; VARIANT async; VARIANT body; VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { if (post_data == NULL) method_bstr = convert_string_to_bstr("GET"); else method_bstr = convert_string_to_bstr("POST"); printf("method: %ls\n", method_bstr); // 不带中文的BSTR可直接用%ls输出, 带中文就不行了 url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_FALSE; null.vt = VT_NULL; hr = IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); SysFreeString(method_bstr); SysFreeString(url_bstr); if (SUCCEEDED(hr)) printf("open() succeeded\n"); if (post_data == NULL) body = null; else { header_bstr = convert_string_to_bstr("Content-Type"); value_bstr = convert_string_to_bstr("application/x-www-form-urlencoded"); IXMLHttpRequest_setRequestHeader(xhr, header_bstr, value_bstr); SysFreeString(header_bstr); SysFreeString(value_bstr); body.vt = VT_BSTR; body.bstrVal = convert_string_to_bstr(post_data); } hr = IXMLHttpRequest_send(xhr, body); if (SUCCEEDED(hr)) printf("send() succeeded\n"); VariantClear(&body); hr = IXMLHttpRequest_get_readyState(xhr, &status); if (SUCCEEDED(hr)) printf("ready state: %d\n", status); hr = IXMLHttpRequest_get_status(xhr, &status); if (SUCCEEDED(hr)) printf("http status code: %d\n", status); hr = IXMLHttpRequest_get_responseXML(xhr, &disp); if (SUCCEEDED(hr)) { printf("get_responseXML() succeeded\n"); hr = IDispatch_QueryInterface(disp, &IID_IXMLDOMDocument, &xmldoc); if (SUCCEEDED(hr)) { printf("IDispatch_QueryInterface() succeeded\n"); display_xmlstr(xmldoc); display_xml(xmldoc); IXMLDOMDocument_Release(xmldoc); } else printf("IDispatch_QueryInterface() failed\n"); IDispatch_Release(disp); } else printf("get_responseXML() failed"); IXMLHttpRequest_Release(xhr); } printf("\n"); } int main() { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); // 初始化COM组件对象模型 str_test("http://adv.purasbar.com/mcu/test/str_test.php"); str_test("http://adv.purasbar.com/mcu/test/json_test.php"); xml_test("http://adv.purasbar.com/mcu/test/xml_test.php?txt=xp%E7%B3%BB%E7%BB%9F%E4%B8%8B%E8%BD%BD&yaya=%E5%96%9D%E5%AE%8C%E8%8D%AF%E6%84%9F%E8%A7%89%E6%B8%85%E7%88%BD%E4%BA%86%E8%AE%B8%E5%A4%9A", NULL); // get方式 xml_test("http://adv.purasbar.com/mcu/test/xml_test.php?txt=xp%E7%B3%BB%E7%BB%9F%E4%B8%8B%E8%BD%BD", "hello=%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F&yaya=%E9%9B%BE%E9%9B%A8%20%E8%BF%9E%E9%98%B4%E5%A4%A9%20%E7%BE%BD%E7%BE%BD%20%E6%B5%85%E6%B5%85%E5%81%A5%E5%81%A5%E5%BA%B7%E5%BA%B7%20%E9%BB%9B%E8%A1%8D%20%E9%9F%B3%E9%9F%B3%E9%94%A6%E9%B2%A4%E9%99%84"); // post方式 CoUninitialize(); return 0; } get参数的提交方法: 将参数直接附加到URL网址的后面。如http://XXX//xml_test.php?A=B&C=D&E=F。汉字和符号必须用urlencode函数编码成%XX的形式。 post数据的提交方法: 通过VARIANT弱类型变量传入IXMLHttpRequest_send函数的第二个参数。没有post数据时要传入VT_NULL值。 汉字和符号同样也必须用urlencode函数编码成%XX的形式。只是最前面不需要以问号开头。 关于urlencode/urldecode、htmlspecialchars、trim和str_replace函数的C语言实现,请参考这篇文章: https://blog..net/ZLK1214/article/details/131748124https://blog..net/ZLK1214/article/details/131748124 程序运行结果: open() succeeded send() succeeded ready state: 4 http status code: 200 response: 当前时间为2024年1月24日 20:37:39。 open() succeeded send() succeeded ready state: 4 http status code: 200 response: {"date":"2024-1-24 20:37:40","time":1706099860,"desc":"abcd\u7b80\u4f5 3\u4e2d\u6587"} method: GET open() succeeded send() succeeded ready state: 4 http status code: 200 get_responseXML() succeeded IDispatch_QueryInterface() succeeded xmlstr:
xp系统下载
喝完药感觉清爽了许多< /test> timestr: 20240124203740 example: 简体中文 title: (null) txt: xp系统下载 method: POST open() succeeded send() succeeded ready state: 4 http status code: 200 get_responseXML() succeeded IDispatch_QueryInterface() succeeded xmlstr:
ktvth t5ebhkssu0k5srknmpic3
xp系统下载< param method="post" name="hello">测试环境后台管理系统
雾雨 连阴天 羽羽 浅浅健健康康 黛衍 音音锦鲤附
timestr: 20240124203740 example: 简体中文 title: (null) txt: xp系统下载 hello: 测试环境后台管理系统 请按任意键继续. . . 第三节 以异步方式发送Ajax请求 IXMLHttpRequest_open函数的六个参数分别是XMLHttpRequest对象、提交方式、URL网址、是否为异步方式、用户名和密码。 其中第四个参数若为VARIANT_FALSE,则是同步方式;若为VARIANT_TRUE,则是异步方式。 如果选择同步方式,则IXMLHttpRequest_send会阻塞,直到收到完整的服务器回应后,函数才返回。 如果选择异步方式,则IXMLHttpRequest_send不会阻塞,会立即返回。后面程序需要用一个while循环轮询IXMLHttpRequest_get_readyState,当ready_state=4时说明收到了完整的数据内容。 IXMLHttpRequest_get_readyState的值定义如下: 0: open函数还没调用 1: send函数还没调用 2: send函数调用了, get_status()函数已经可以使用了, 也可以读http headers了, 但是还读不了response 3: 已收到了部分数据, 可以读responseBody和responseText 4: 所有数据都已经接收完了, responseBody和responseText已经是完整数据了 IXMLHttpRequest_get_status函数返回的是HTTP的回应码,通常为200。如果找不到指定网页,就是400。如果遇到服务器端程序错误,就是500。 /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include
#include
#pragma comment(lib, "msxml6.lib") // char *转BSTR // 用完后调用SysFreeString释放 BSTR convert_string_to_bstr(const char *s) { int n; wchar_t *ws; BSTR bstr = NULL; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); bstr = SysAllocString(ws); free(ws); } return bstr; } // BSTR转char * // 用完后调用free释放 char *convert_bstr_to_string(BSTR bstr) { char *s; int n; n = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL); s = malloc(n); if (s != NULL) WideCharToMultiByte(CP_ACP, 0, bstr, -1, s, n, NULL, NULL); return s; } // 去掉字符串末尾的\r\n void remove_last_crlf(char *str) { int len; len = (int)strlen(str); if (len >= 2 && str[len - 2] == '\r' && str[len - 1] == '\n') str[len - 2] = '\0'; } // 示例: 读取纯文本内容 (异步方式) // 请注意有两个名称不同但内容完全一样的接口: IXMLHttpRequest和IXMLHTTPRequest, 随便用哪个都可以 // 但class id只有一个: CLSID_XMLHTTPRequest void str_test(const char *url) { char *response; long ready_state, status; BSTR method_bstr, url_bstr, response_bstr; HRESULT hr; IXMLHttpRequest *xhr; VARIANT async; // VARIANT代表一个弱类型的变量 VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { // 打开连接 // get参数内容是放到url字符串里面的 method_bstr = convert_string_to_bstr("GET"); url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_TRUE; // 选择异步方式 (选择这种方式后, main里面CoInitializeEx必须用COINIT_MULTITHREADED) null.vt = VT_NULL; hr = IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); // 最后两个参数是用户名和密码, 不用填 SysFreeString(method_bstr); SysFreeString(url_bstr); if (SUCCEEDED(hr)) printf("open() succeeded\n"); // 发送请求 // 由于我们选择的是异步方式, 所以send函数会直接返回 hr = IXMLHttpRequest_send(xhr, null); // 第二个参数是post数据内容, 不用管 if (SUCCEEDED(hr)) printf("send() succeeded\n"); while (1) { // 获取请求状态 // 0: open函数还没调用 // 1: send函数还没调用 // 2: send函数调用了, get_status()函数已经可以使用了, 也可以读http headers了, 但是还读不了response // 3: 已收到了部分数据, 可以读responseBody和responseText // 4: 所有数据都已经接收完了, responseBody和responseText已经是完整数据了 hr = IXMLHttpRequest_get_readyState(xhr, &ready_state); if (SUCCEEDED(hr)) { printf("ready state: %d\n", ready_state); if (ready_state == 4) break; } } // 获取http返回的response code hr = IXMLHttpRequest_get_status(xhr, &status); if (SUCCEEDED(hr)) printf("http status code: %d\n", status); // 读取并显示文本内容 hr = IXMLHttpRequest_get_responseText(xhr, &response_bstr); if (SUCCEEDED(hr)) { // response_bstr是utf16编码, 转换后的response是gb2312编码 // 把函数里面的CP_ACP改成CP_UTF8就可以转成utf8编码, 但是utf8就没办法用printf打印, 只能用fwrite写入txt文件再用记事本打开查看 response = convert_bstr_to_string(response_bstr); remove_last_crlf(response); printf("response: %s\n", response); free(response); SysFreeString(response_bstr); } IXMLHttpRequest_Release(xhr); } printf("\n"); } int main() { CoInitializeEx(NULL, COINIT_MULTITHREADED); // 初始化COM组件对象模型 str_test("http://adv.purasbar.com/mcu/test/str_test.php"); CoUninitialize(); return 0; } 程序运行后,会不断地打印ready state=1,直到收到完整数据时ready state=4才退出循环,打印出收到的回应内容。 请注意,使用异步模式的前提是main函数里面CoInitializeEx的参数为COINIT_MULTITHREADED,不能是COINIT_APARTMENTTHREADED,否则readyState将一直为1,程序陷入死循环。 还有就是,COINIT_MULTITHREADED这个选项是不能在UI(图形界面)线程里面使用的,UI线程里面只能用COINIT_APARTMENTTHREADED。一般在主线程里面处理UI,单独新开一个线程处理网络通信。 第四节 直接通过IDispatch接口读取XML数据 IXMLHttpRequest_get_responseXML函数返回的是IDispatch接口,可以用IDispatch_QueryInterface函数将IDispatch转换成IXMLDOMDocument接口,然后读取XML数据。 IXMLDOMDocument其实是IDispatch的子类。实际上我们直接用IDispatch接口也可以读写xml数据。只不过要用wchar_t *字符串指定要调用的函数名称,用IDispatch_GetIDsOfNames函数查出函数名称对应的函数ID,用VARIANT数组(VARIANTARG是VARIANT的别名)指定函数参数后,再用IDispatch_Invoke函数通过函数ID和函数参数调用函数,并得到函数的返回值。 /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include
#include
#pragma comment(lib, "msxml6.lib") // char *转BSTR // 用完后调用SysFreeString释放 BSTR convert_string_to_bstr(const char *s) { int n; wchar_t *ws; BSTR bstr = NULL; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); bstr = SysAllocString(ws); free(ws); } return bstr; } // BSTR转char * // 用完后调用free释放 char *convert_bstr_to_string(BSTR bstr) { char *s; int n; n = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL); s = malloc(n); if (s != NULL) WideCharToMultiByte(CP_ACP, 0, bstr, -1, s, n, NULL, NULL); return s; } void read_attribute_from_disp(IDispatch *disp, const char *attr) { char *s; wchar_t *func_name; DISPID func_id; DISPPARAMS params = {0}; HRESULT hr; VARIANT result; VARIANTARG arg; // 调用getAttribute方法 (带1个参数) func_name = L"getAttribute"; hr = IDispatch_GetIDsOfNames(disp, &IID_NULL, &func_name, 1, LOCALE_SYSTEM_DEFAULT, &func_id); if (SUCCEEDED(hr)) { arg.vt = VT_BSTR; arg.bstrVal = convert_string_to_bstr(attr); params.rgvarg = &arg; params.cArgs = 1; hr = IDispatch_Invoke(disp, func_id, &IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶ms, &result, NULL, NULL); VariantClear(&arg); if (SUCCEEDED(hr)) { if (result.vt == VT_BSTR) { s = convert_bstr_to_string(result.bstrVal); printf("%ls: %s\n", func_name, s); free(s); } VariantClear(&result); } } } void read_xml_from_disp(IDispatch *disp) { wchar_t *func_name; DISPID func_id; DISPPARAMS params = {0}; HRESULT hr; VARIANT result; // 调用hasChildNodes方法 func_name = L"hasChildNodes"; hr = IDispatch_GetIDsOfNames(disp, &IID_NULL, &func_name, 1, LOCALE_SYSTEM_DEFAULT, &func_id); if (SUCCEEDED(hr)) { hr = IDispatch_Invoke(disp, func_id, &IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶ms, &result, NULL, NULL); // DISPATCH_METHOD指明是调用方法 if (SUCCEEDED(hr)) { if (result.vt == VT_BOOL) { if (result.boolVal == VARIANT_TRUE) printf("%ls: true\n", func_name); else if (result.boolVal == VARIANT_FALSE) printf("%ls: false\n", func_name); } VariantClear(&result); // VARIANT类型的变量统一用这个函数释放 } } // 读取documentElement属性 func_name = L"documentElement"; hr = IDispatch_GetIDsOfNames(disp, &IID_NULL, &func_name, 1, LOCALE_SYSTEM_DEFAULT, &func_id); if (SUCCEEDED(hr)) { hr = IDispatch_Invoke(disp, func_id, &IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &result, NULL, NULL); // DISPATCH_PROPERTYGET指明是读取属性 if (SUCCEEDED(hr)) { if (result.vt == VT_DISPATCH) { read_attribute_from_disp(result.pdispVal, "timestr"); read_attribute_from_disp(result.pdispVal, "example"); } VariantClear(&result); } } } // 示例: 用IDispatch接口直接读取XML数据 (异步方式) void xml_test(const char *url) { long ready_state; BSTR method_bstr, url_bstr; HRESULT hr; IDispatch *disp; IXMLHttpRequest *xhr; VARIANT async; VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { method_bstr = convert_string_to_bstr("GET"); url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_TRUE; // 选择异步方式 null.vt = VT_NULL; IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); SysFreeString(method_bstr); SysFreeString(url_bstr); IXMLHttpRequest_send(xhr, null); while (1) { hr = IXMLHttpRequest_get_readyState(xhr, &ready_state); if (SUCCEEDED(hr)) { printf("ready state: %d\n", ready_state); if (ready_state == 4) break; } } hr = IXMLHttpRequest_get_responseXML(xhr, &disp); if (SUCCEEDED(hr)) { read_xml_from_disp(disp); IDispatch_Release(disp); } IXMLHttpRequest_Release(xhr); } } int main() { CoInitializeEx(NULL, COINIT_MULTITHREADED); // 初始化COM组件对象模型 xml_test("http://adv.purasbar.com/mcu/test/xml_test.php"); CoUninitialize(); return 0; } 在上面的程序中,我们直接用IDispatch_Invoke函数调用了hasChildNode函数判断了XML根节点下是否有子节点,获取了documentElement属性,然后还执行了documentElement.getAttribute()函数读取XML根节点上的两个属性值,属性名通过arg变量传入,属性值通过result变量传出,VARIANT弱类型变量使用完成后用VariantClear函数释放。 第五节 通过urlencode函数打包GET和POST数据 GET数据和POST数据采用的是键值对的形式,其中的汉字和特殊符号要用%XX编码,其中XX是UTF-8编码对应的十六进制数,通常一个汉字占三个字节。 当然也可以采用其他编码,如GB2312,但是我们的PHP服务器上用的是UTF-8编码。这个跟PHP代码有关。 C语言里面char *字符串是GB2312编码,先用MultiByteToWideChar转成UTF-16编码,再用WideCharToMultiByte转成UTF-8编码,最后用urlencode函数编码成%XX的形式。 键值对最终要写成A=B&C=D&E=F的形式,其中A、C、E是键名,B、D、F是它们对应的值,我们用create_param_string函数来完成这项工作。 #include
#include
#include
#include
struct param { char *name; char *value; char *reserved[4]; }; int urlencode_buf(char *buf, int *bufsize) { char x; int i, j, len; if (buf == NULL) { *bufsize = 1; return -1; } len = 0; for (i = 0; buf[i] != '\0'; i++) { if (isalnum((unsigned char)buf[i]) || strchr(" -_.", buf[i]) != NULL) len++; else len += 3; } if (*bufsize < len + 1) { if (len >= i) *bufsize = len + 1; else *bufsize = i + 1; return -1; } j = len - 1; for (i--; i >= 0; i--) { if (isalnum((unsigned char)buf[i]) || strchr("-_.", buf[i]) != NULL) { buf[j] = buf[i]; j--; } else if (buf[i] == ' ') { buf[j] = '+'; j--; } else { x = buf[i] & 15; buf[j] = (x < 10) ? (x + '0') : (x - 10 + 'A'); x = (buf[i] >> 4) & 15; buf[j - 1] = (x < 10) ? (x + '0') : (x - 10 + 'A'); buf[j - 2] = '%'; j -= 3; } } buf[len] = '\0'; return len; } char *urlencode(const char *s) { char *p; int size = 0; urlencode_buf((char *)s, &size); p = malloc(size); if (p != NULL) { if (s != NULL) strcpy_s(p, size, s); else *p = '\0'; urlencode_buf(p, &size); } return p; } char *gb2312_to_utf8(const char *s) { char *us = NULL; int n; wchar_t *ws; if (s == NULL) s = ""; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); us = malloc(n); if (us != NULL) WideCharToMultiByte(CP_UTF8, 0, ws, -1, us, n, NULL, NULL); free(ws); } return us; } char *create_param_string(const char *url, struct param params[], int count) { char *s = NULL; int i, j, n; int len = 0; int total = 0; // 计算字符串总长度 if (url != NULL) total = (int)strlen(url) + 1; // +'?' for (i = 0; i < count; i++) { if (i != 0) total++; // +'&' params[i].reserved[0] = gb2312_to_utf8(params[i].name); if (params[i].reserved[0] == NULL) goto cleanup; params[i].reserved[1] = urlencode(params[i].reserved[0]); if (params[i].reserved[1] == NULL) goto cleanup; total += (int)strlen(params[i].reserved[1]); if (params[i].value != NULL) { params[i].reserved[2] = gb2312_to_utf8(params[i].value); if (params[i].reserved[2] == NULL) goto cleanup; params[i].reserved[3] = urlencode(params[i].reserved[2]); if (params[i].reserved[3] == NULL) goto cleanup; total += 1 + (int)strlen(params[i].reserved[3]); // +'=' } else { params[i].reserved[2] = NULL; params[i].reserved[3] = NULL; } } // 分配内存 s = malloc(total + 1); if (s == NULL) goto cleanup; // 合成字符串 if (url != NULL) { n = (int)strlen(url); memcpy(s, url, n); len = n; if (count > 0) { s[n] = '?'; len++; } } for (i = 0; i < count; i++) { if (i != 0) { s[len] = '&'; len++; } n = (int)strlen(params[i].reserved[1]); memcpy(s + len, params[i].reserved[1], n); len += n; if (params[i].reserved[3] != NULL) { s[len] = '='; len++; n = (int)strlen(params[i].reserved[3]); memcpy(s + len, params[i].reserved[3], n); len += n; } } s[len] = '\0'; assert(len == total); cleanup: for (i = 0; i < count; i++) { for (j = 0; j < _countof(params[i].reserved); j++) { if (params[i].reserved[j] != NULL) { free(params[i].reserved[j]); params[i].reserved[j] = NULL; } } } return s; } int main() { char *str; char timestr[50]; struct param params[4]; struct tm tm; time_t t; params[0].name = "hello"; params[0].value = "world"; params[1].name = "yaya"; params[1].value = "喝完药感觉清爽了许多"; params[2].name = "test"; params[2].value = NULL; params[3].name = "msg"; params[3].value = timestr; time(&t); localtime_s(&tm, &t); strftime(timestr, sizeof(timestr), "当前时间: %Y-%m-%d %H:%M:%S", &tm); str = create_param_string("http://www.example.com/hello.php", params, _countof(params)); if (str != NULL) { printf("GET参数示例: %s\n", str); free(str); } str = create_param_string(NULL, params, _countof(params)); if (str != NULL) { printf("POST参数示例: %s\n", str); free(str); } return 0; } 程序运行结果如下。GET参数是附加到URL网址后面的,用问号隔开。POST数据是通过IXMLHttpRequest_send函数的第二个参数发给服务器的。 第六节 使用IXMLHttpRequest_put_onreadystatechange回调函数处理Ajax异步请求 当IXMLHttpRequest设置成异步模式后,数据还没有接收完,IXMLHttpRequest_send函数就会提前返回。这样我们就需要用一个while循环来轮询IXMLHttpRequest_get_readyState的值,当ready_state=4时才表明数据接收完了,这时才能读取response回应内容。 在刚才的程序运行截图里面可以看得出来,“ready state: 1”打印了很长一串,这期间CPU一直在空转,这说明while循环的执行时间还是很长的。 为了解决这个问题,我们可以使用IXMLHttpRequest_put_onreadystatechange函数设置一个回调函数,每当ready_state的值发生变化时IXMLHttpRequest自动帮我们调用这个回调函数,我们在这个回调函数里面去读取response回应。 IXMLHttpRequest_put_onreadystatechange函数有两个参数,第一个参数是IXMLHttpRequest对象,第二个参数是我们自己定义的包含回调函数的IDispatch对象。也就是说我们要自己实现一个IDispatch对象。 刚才在第四节里面已经解释了IDispatch到底是个什么东西。IDispatch是一个根据函数名字符串调用函数的工具。IXMLHttpRequest_get_responseXML函数是IXMLHttpRequest给我们提供IDispatch对象,而现在我们使用IXMLHttpRequest_put_onreadystatechange函数,就要自己实现一个IDispatch对象传递给IXMLHttpRequest。 C语言实现COM接口的方法是建立一个自定义的结构体,这个结构体的第一个成员变量是要实现的接口的函数指针表的指针,其他成员可随便自定义。 函数指针表的类型名称是在接口名称后面加上Vtbl。我们要实现的是IDispatch接口,所以函数指针表的类型名为IDispatchVtbl。 struct xxx { IDispatchVtbl *pvtbl; …… (其他自定义成员) }; pvtbl成员变量是一个指针,一定要指向一个变量,通常把指向的这个变量也放到结构体里面,或者单独malloc出来也行。 (方法1) struct xxx { IDispatchVtbl *pvtbl; IDispatchVtbl vtbl; …… (其他自定义成员) }; struct xxx x; x.pvtbl = &x.vtbl; (方法2) struct xxx { IDispatchVtbl *pvtbl; …… (其他自定义成员) }; struct xxx x; x.pvtbl = malloc(sizeof(IDispatchVtbl)); IDispatchVtbl也是一个结构体,里面一共有7个成员变量,全部为函数指针,这就是我们要实现的7个函数,如下表所示。 函数名作用QueryInterface切换父类或子类AddRef引用计数加1Release引用计数减1GetTypeInfoCount(本例程用不到这个函数)GetTypeInfo(本例程用不到这个函数)GetIDsOfNames获取函数名对应的函数ID (本例程用不到这个函数)Invoke执行指定ID号的函数 IDispatch接口继承IUnknown接口,QueryInterface、AddRef和Release这三个函数是父类IUnknown里面的函数,分别用于切换父子类、增加和减少引用计数。 通常我们定义的struct xxx结构体变量是由malloc动态分配出来的,引用计数的值什么时候为0决定了什么时候释放结构体变量所占用的内存。 struct xxx *p = malloc(sizeof(struct xxx)); 然后就可以转换成IDispatch指针,并使用系统提供的IDispatch_XXX系列函数操作IDispatch对象。 IDispatch *disp = (IDispatch *)p; IDispatch_XXX(disp, ...); 当onreadystatechange事件发生时,IXMLHttpRequest会去调用我们的IDispatch对象的Invoke函数,传入的函数ID(dispIdMember)为0,参数个数(pDispParams->cArgs)也为0,函数也不需要返回值(pVarResult=NULL)。 我们来看一下例程。 main.c: /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include
#include
#include "xhr_callback.h" #pragma comment(lib, "msxml6.lib") // char *转BSTR // 用完后调用SysFreeString释放 BSTR convert_string_to_bstr(const char *s) { int n; wchar_t *ws; BSTR bstr = NULL; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); bstr = SysAllocString(ws); free(ws); } return bstr; } // BSTR转char * // 用完后调用free释放 char *convert_bstr_to_string(BSTR bstr) { char *s; int n; n = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL); s = malloc(n); if (s != NULL) WideCharToMultiByte(CP_ACP, 0, bstr, -1, s, n, NULL, NULL); return s; } // 去掉字符串末尾的\r\n void remove_last_crlf(char *str) { int len; len = (int)strlen(str); if (len >= 2 && str[len - 2] == '\r' && str[len - 1] == '\n') str[len - 2] = '\0'; } void test_callback(IXMLHttpRequest *xhr) { char *response; long ready_state, status; BSTR response_bstr; HRESULT hr; // 获取请求状态 hr = IXMLHttpRequest_get_readyState(xhr, &ready_state); if (SUCCEEDED(hr)) { printf("ready_state: %d\n", ready_state); if (ready_state == 4) { // 获取http返回的response code hr = IXMLHttpRequest_get_status(xhr, &status); if (SUCCEEDED(hr)) printf("status: %d\n", status); // 读取并显示文本内容 hr = IXMLHttpRequest_get_responseText(xhr, &response_bstr); if (SUCCEEDED(hr)) { // response_bstr是utf16编码, 转换后的response是gb2312编码 // 把函数里面的CP_ACP改成CP_UTF8就可以转成utf8编码, 但是utf8就没办法用printf打印, 只能用fwrite写入txt文件再用记事本打开查看 response = convert_bstr_to_string(response_bstr); remove_last_crlf(response); printf("response text: %s\n", response); free(response); SysFreeString(response_bstr); } } } } void test(const char *url) { BSTR method_bstr, url_bstr; HRESULT hr; IDispatch *xhr_callback_disp; IXMLHttpRequest *xhr; VARIANT async; VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { // 打开连接 method_bstr = convert_string_to_bstr("GET"); url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_TRUE; // 选择异步方式 null.vt = VT_NULL; IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); SysFreeString(method_bstr); SysFreeString(url_bstr); // 设置回调函数 xhr_callback_disp = create_xhr_callback(xhr, test_callback); if (xhr_callback_disp != NULL) { IXMLHttpRequest_put_onreadystatechange(xhr, xhr_callback_disp); IDispatch_Release(xhr_callback_disp); } // 发送请求 IXMLHttpRequest_send(xhr, null); IXMLHttpRequest_Release(xhr); } } int main() { CoInitializeEx(NULL, COINIT_MULTITHREADED); // 初始化COM组件对象模型 test("http://adv.purasbar.com/mcu/test/str_test.php"); Sleep(5000); CoUninitialize(); return 0; } xhr_callback.h: #pragma once typedef void (*xhr_callback)(IXMLHttpRequest *xhr); IDispatch *create_xhr_callback(IXMLHttpRequest *xhr, xhr_callback callback); xhr_callback.c: /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include
#include
#include "xhr_callback.h" // 实现IDispatch接口 // IDispatch接口继承IUnknown接口 struct xhr_callback { IDispatchVtbl *pvtbl; IDispatchVtbl vtbl; ULONG refcnt; xhr_callback callback; IXMLHttpRequest *xhr; }; static HRESULT STDMETHODCALLTYPE xhr_callback_query_interface(IDispatch *This, REFIID riid, void ppvObject) { wchar_t *ws; HRESULT hr; hr = StringFromIID(riid, &ws); if (SUCCEEDED(hr)) { printf("xhr_callback_query_interface: rrid=%ls\n", ws); CoTaskMemFree(ws); } else printf("xhr_callback_query_interface\n"); if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDispatch)) { IDispatch_AddRef(This); *ppvObject = This; return S_OK; } else { *ppvObject = NULL; return E_NOINTERFACE; } } static ULONG STDMETHODCALLTYPE xhr_callback_addref(IDispatch *This) { struct xhr_callback *p = (struct xhr_callback *)This; p->refcnt++; printf("xhr_callback_addref: %u\n", p->refcnt); return p->refcnt; } static ULONG STDMETHODCALLTYPE xhr_callback_release(IDispatch *This) { struct xhr_callback *p = (struct xhr_callback *)This; p->refcnt--; printf("xhr_callback_release: %u\n", p->refcnt); if (p->refcnt == 0) { IXMLHttpRequest_Release(p->xhr); memset(p, 0, sizeof(struct xhr_callback)); free(p); return 0; } return p->refcnt; } static HRESULT STDMETHODCALLTYPE xhr_callback_get_type_info_count(IDispatch *This, UINT *pctinfo) { printf("xhr_callback_get_type_info_count\n"); *pctinfo = 0; // 不提供类型信息 return S_OK; } static HRESULT STDMETHODCALLTYPE xhr_callback_get_type_info(IDispatch *This, UINT iTInfo, LCID lcid, ITypeInfo ppTInfo) { printf("xhr_callback_get_type_info\n"); *ppTInfo = NULL; return S_OK; } static HRESULT STDMETHODCALLTYPE xhr_callback_get_ids_of_names(IDispatch *This, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { UINT i; printf("xhr_callback_get_ids_of_names\n"); for (i = 0; i < cNames; i++) rgDispId[i] = DISPID_UNKNOWN; return DISP_E_UNKNOWNNAME; } static HRESULT STDMETHODCALLTYPE xhr_callback_invoke(IDispatch *This, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { struct xhr_callback *p = (struct xhr_callback *)This; printf("xhr_callback_invoke: dispIdMember=%d, cArgs=%u, pVarResult=0x%p\n", dispIdMember, pDispParams->cArgs, pVarResult); if (dispIdMember == 0 && pDispParams->cArgs == 0) { if (p->callback != NULL) p->callback(p->xhr); } if (pVarResult != NULL) VariantInit(pVarResult); return S_OK; } IDispatch *create_xhr_callback(IXMLHttpRequest *xhr, xhr_callback callback) { struct xhr_callback *p; IDispatch *disp; p = malloc(sizeof(struct xhr_callback)); if (p == NULL) return NULL; memset(p, 0, sizeof(struct xhr_callback)); p->pvtbl = &p->vtbl; p->pvtbl->QueryInterface = xhr_callback_query_interface; p->pvtbl->AddRef = xhr_callback_addref; p->pvtbl->Release = xhr_callback_release; p->pvtbl->GetTypeInfoCount = xhr_callback_get_type_info_count; p->pvtbl->GetTypeInfo = xhr_callback_get_type_info; p->pvtbl->GetIDsOfNames = xhr_callback_get_ids_of_names; p->pvtbl->Invoke = xhr_callback_invoke; p->callback = callback; IXMLHttpRequest_AddRef(xhr); p->xhr = xhr; disp = (IDispatch *)p; IDispatch_AddRef(disp); return disp; } 程序运行结果如下。 可以看到,只有当ready_state的值发生了变化,回调函数才会执行。回调函数总共就执行了四次,效率一下子提升了许多。在第四次执行回调函数的时候,ready_state=4,我们在test_callback回调函数中读取了IXMLHttpRequest_get_responseText回应内容并显示。
上一篇
菏泽移动服务器租用多少钱
下一篇
湖北服务器租用需要多少钱
相关文章
vps防火墙怎么开端口
百度云怎么选择代理商
电脑服务器离线安装.net framework 3.5解决方案(错误-0x8024402c )(如何确定当前系统是否安装NET Framework 3.5)
用通俗易懂的方式讲解大模型:在 CPU 服务器上部署 ChatGLM3-6B 模型
qq空间怎么直接进入主页
无需公网IP,使用MCSM面板一键搭建我的世界Minecraft服务器联机游戏
怎么看官网的域名在哪买的
开黑啦kook 机器人开发 PHP swoole Liunx 服务器(宝塔)
二手域名怎么样
香港云服务器租用推荐
服务器租用资讯
·租用美国服务器配置
·怎样使用美国服务器(新的服务器怎样使用)
·怎么联系美国服务器(本服务器在美国受到法律)
·云服务器美国电影(美国高防云服务器)
·源服务器在美国(美国服务器ip)
·邮箱搭建美国服务器(群晖搭建邮箱服务器)
·微信美国服务器(微信小程序要服务器吗)
·受美国服务器保护(此服务器受美国保护)
·手机vpn美国服务器
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价
7*24H在线售后
高可用资源,安全稳定
1v1专属客服对接
无忧退款试用保障
德讯电讯股份有限公司
电话:00886-982-263-666
台湾总部:台北市中山区建国北路一段29号3楼
香港分公司:九龙弥敦道625号雅兰商业二期906室
服务器租用
香港服务器
日本服务器
台湾服务器
美国服务器
高防服务器购买
香港高防服务器出租
台湾高防服务器租赁
美国高防服务器DDos
云服务器
香港云服务器
台湾云服务器
美国云服务器
日本云服务器
行业新闻
香港服务器租用
服务器资讯
香港云服务器
台湾服务器租用
zblog博客
香港VPS
关于我们
机房介绍
联系我们
Copyright © 1997-2024 www.hkstack.com All rights reserved.