活动介绍
file-type

NClient:简化HTTP API调用的C# HTTP客户端

ZIP文件

下载需积分: 50 | 129KB | 更新于2025-02-10 | 36 浏览量 | 1 下载量 举报 收藏
download 立即下载
NClient 是一个在.NET环境中使用的HTTP客户端库,它提供了简单而灵活的方式来调用Web服务API。根据给定的文件信息,我们可以了解到NClient的一些关键特性和使用场景,这些知识点可以在.NET开发中非常实用。 ### 知识点一:NClient的功能和特点 1. **HTTP客户端功能**:NClient可以作为一个HTTP客户端使用,负责发起HTTP请求到Web服务,并接收相应的HTTP响应。它不是传统意义上的网络库,而是专为API调用而设计的工具。 2. **注释驱动的API调用**:通过使用C#中的注解(attributes),NClient允许开发者定义接口或控制器,从而以声明性的方式描述Web服务的方法调用。这种方式降低了与Web服务交互的复杂性,使得代码更加清晰。 3. **异步调用支持**:异步编程是现代Web应用开发的一个重要方面,NClient支持异步调用Web服务API。这意味着当发起远程调用时,应用程序可以继续执行其他任务而无需阻塞,直到请求完成。 4. **重试策略**:在网络请求中可能会遇到各种暂时性错误,NClient提供了重试策略来处理这些情况,增加了程序的健壮性。开发者可以根据需要设置重试次数、重试间隔等。 5. **日志记录**:为了跟踪API调用和调试,NClient支持日志记录。通过日志记录,开发者可以查看发起请求和接收响应的详细信息,这对于定位问题非常有帮助。 6. **与ASP.NET Core的集成**:NClient可以很好地与ASP.NET Core集成,这使得在ASP.NET Core应用中使用NClient变得非常方便。 ### 知识点二:使用NClient的基本步骤 1. **建立控制器**:在ASP.NET Core中,可以通过定义控制器类来处理HTTP请求。在控制器类中,可以使用NClient提供的特性(如[ApiController]和[Route])来标记类和方法。 - `ApiController`特性表示一个类是一个Web API控制器,它是ASP.NET Core中定义Web API控制器的标准方式。 - `Route`特性用于定义控制器或其方法的路由模板,这决定了客户端如何访问API。 2. **提取接口并添加NClient**:为了更好地管理API的消费,通常需要将控制器的方法抽象成接口。这不仅有助于测试,还能让客户端代码依赖于接口而非具体实现。在添加NClient时,需要引入NClient库,并配置NClient以使用这些接口调用对应的Web服务。 ### 知识点三:NClient与C#编程语言的关联 NClient主要是一个适用于C#语言的库,它提供了一系列面向对象的特性来简化.NET开发中对Web服务API的调用。由于NClient与C#语言紧密集成,因此它支持C#中的一些高级特性,例如异步编程模式(async/await)和面向对象的接口定义。 ### 知识点四:NClient在项目中的实际应用 在实际项目中使用NClient可以极大地简化HTTP调用的代码,尤其是在需要频繁调用外部Web服务时。以下是一个简单的例子,展示了如何使用NClient发起异步HTTP GET请求: ```csharp // 首先确保引入了NClient包 // 使用NClient的异步方法来调用Web服务 public async Task<WeatherForecast> GetWeatherForecastAsync(DateTime date) { var client = new NClient<WeatherForecast>.Client("http://example.com/api/") .WithHttpPolicy(builder => builder .WithAutomaticRetry() .WithLogging(logger => logger.LogAll())); return await client.GetAsync(date); } ``` 在上述代码中,我们创建了一个NClient实例,并配置了它的一些行为(例如重试策略和日志记录)。然后我们调用了`GetAsync`方法来异步获取天气预报信息。 总结来说,NClient作为一个专门用于.NET环境的HTTP客户端库,提供了简洁且强大的API调用能力,通过使用注释和灵活的配置,使得与Web服务的集成变得异常简单。通过结合异步编程和重试机制,它增强了Web服务请求的健壮性和效率。此外,它与ASP.NET Core的紧密集成进一步简化了.NET Web应用的开发。

相关推荐

filetype

任务描述 本关任务: 编写两个程序实现客户端与服务端的通信,clinet.c为客户端,server.c为服务器端。服务器端能够读取客户端发送的信息。 相关知识 为了完成本关任务,你需要掌握:1.socket编程,2.数据通信过程。 1、Socket 网络中进程可以通过socket通信,socket起源于Unix,满足“一切皆文件”原理,即操作为“打开open->读写write/read->关闭close”。 ①socket的调用函数主要有: socket()/*创建描述符,设定协议域和socket类型*/ bind()/*绑定地址*/ listen()/*监听*/ connect()/*连接*/ read()/*I/O读操作*/ write()/*I/O写操作*/ close()/*断开连接*/ shutdown()/*部分断连*/ ②socket操作主要有三类: 1)三次握手:让客户端与服务端双方都能明确自己和对方的收、发能力是正常的。 2)数据传输:socket使用的是TCP连接,为双向传输的对等模式,双方都可以同时向对方发送或接收数据。 3)四次挥手:由于TCP连接是全双工,每个方向都必须单独进行关闭。 2、socket函数 int socket(int domain,int type,int protocol) sockfd=socket(); socket()创建一个socket描述符,标识唯一一个socket domain:协议域->AF_INET、AF_INET6、AF_LOCAL(AF_UNIX)、AF_ROUTE。协议族决定socket地址类型,通信中必须采用对应的地址,AF_INET决定要用ipv4地址(32位)与端口号(16位)组合看,AF_UNIX决定用一个绝对路径作为地址。 type:socket->指定socket类型。常用SOCK_STREAM(TCP)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET、SOCK_DGRAM(UDP)等 protocol:协议->常用协议IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输、UDP传输、STCP传输、TIPC传输 注:type和protocol不可用随意组合,SOCKET_STREAM和IPPROTO_UDP不可用组合。当protocol为0,会自动选择type类型对应默认协议。 socket函数创建一个socket时,返回的socket描述符存在于协议族address family中,没有具体地址,赋值一个地址必须调用bind()或者调用connect()、listen()系统自动随机分配端口。 3、bind函数 bind()函数把一个地址族中特定地址赋值给socket,AF_INET、AF_INET6就是一个ipv4或ipv6地址和端口号组合,赋值给socket。 int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen); sockfd:socket描述符,通过socket()创建,唯一标识一个socket。bind()将这个描述符绑定一个名字。 addr:const struct sockaddr*指针,指向sockfd的协议地址。地址结构根据创建socket时的地址协议族不同而变化。 addrlen:地址长度 注:①服务器启动时会绑定地址(ip+端口号)。提供服务时,服务端通过地址连接服务器。客户端不用绑定,系统自动分配端口号和自身iP地址组合。所以通常服务端listen前会调用bind(),客户端不会调用,在connect()时系统随机生成一个。 ②主机字节序(大端小端模式):不同CPU有不同字节序类型,这些字节序指整数在内存中保存的顺序。 1)little-endian,低位字节放内存低地址段,高位字节排放内存高地址端。 2)big-endian,高位字节放内存低地址段,低位字节放内存高地址端。 网络字节序:4个字节的32bit值以下次序传输,0-7、8-15、16-23、24-31。大端字节序。由于TCP/IP首部中所有二进制整数在网络中传输时都要求以这种次序,因此又称作网络字节序。字节序,发育一个字节类型数据在内存中存放顺序,一个字节的数据没有顺序问题。 在绑定地址到socket时候,必须将主机字节序转换成网络字节序,不能让主机字节序跟网络字节序一样使用big-endian。 4、listen、connetct函数 服务器调用socket()、bind()后就会调用listen()监听socket,如果客户端这时候调用connect()发出连接请求,服务器就会接受这个请求。 int listen(int sockfd,int backlog); int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen); listen中sockfd为要监听的socket描述符,backlog为相应socket可以排队的最大连接个数。 socket()函数创建的socket是一个主动类型,listen()将socket变为被动类型等待客户连接请求。 connect中sockfd为客户端socket描述符,addr为服务器socket地址,addrlen为socket地址长度。客户端调用connect函数建立与TCP服务器的连接。 5、accept函数 TCP服务器调用socket()、bind()、listen()后,监听socket地址。TCP客户端调用socket()、connect()后,给TCP服务器发送一个连接请求。TCP服务器监听到请求,调用accept()接收请求,连接建立好开始I/O操作。 int accept(int sockfd,struct sockaddr *addr,socklen_t addrlen) sockfd服务器socket描述符 addr返回客户端协议地址 addrlen协议地址长度 accept成功,返回值由内核自动生成的全新描述符,表示已与返回客户的TCP连接。 注:sockfd是服务器的socket描述符,服务器开始调用socket()生成的,即监听socket描述符,accept返回的是已连接socket描述符,一个服务器通常只创建一个监听socket描述符,在服务器生命周期内会一直存在,内核为每个服务器进程接收的客户连接创建一个已连接socket描述符,当服务器完成客户服务,相应已连接socket描述符会被关闭。 6、read、write函数 服务器与客户建立好连接,调用网络I/O进行读写操作,网络中不同进程之间通信。 网络I/O操作 read()/write() recv()/send() readv()/writev() recvmsg()/sendmsg() recvfrom/sendto() 7、close、shutdown函数 服务器与客户端建立连接后,读写操作完,需要关闭相应的socket描述符。 #include<unistd.h> int close(int fd); sockfd不能在作为read或者write的第一个参数。 注:close操作只是将相应的sockfd描述字引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。 shutdown()函数,可以关闭socket一端或者全部。 int shutdown(int _fd,int _how) TCP连接是双向的(可读可写),使用close读写通道都关闭,使用shutdown有三种: ①howto=0,关闭读通道,可以继续写。 ②howto=1,关闭写通道,只可以读。 ③howto=2,关闭读写通道,和close一样,全部关闭。 8、三次握手 tcp建立连接进行“三次握手”: ①客户端向服务器发送一个SYN x。 ②服务器向客户端发送一个SYN y,并对SYN x进行ACK x+1。 ③客户端再向服务器发一个ACK y+1。 ①客户端调用connect触发连接请求,向服务器发送SYN x包,connect进入阻塞状态; ②服务器listen监听到连接请求(收到SYN x),调用accept接受请求,向客户端发送SYN y和ACK x+1,accept进入阻塞状态; ③客户端收到服务器的SYN y和ACK x+1,对SYN y确认,connect返回ACK y+1; ④服务器收到ACK y+1,accept返回,三次握手完毕,连接建立。 注:客户端的connect在三次握手的第二次握手返回,服务端的accept在三次握手的第三次握手返回。 9、数据传输 ①建立完连接后,进行数据传输,read和write。 ②客户端发起给服务端写入,并发送SYN x+1和ACK y+1给服务端(xy未发生变化)。 ③服务端发起给客户端读取,并发送ACK x+2。 10、四次挥手 ①客户端应用进程调用close主动关闭连接,客户端TCP发送FIN x+2和ACK y+1。 ②服务端接受到FIN x+2,执行被动关闭,对FIN确认。即当服务端收到FIN后停止数据的操作并发出一个ACK x+3客户端表示数据操作结束。 ③一段时间后,服务端应用接收到文件结束符的应用进程调用close关闭服务端socket,发送一个FIN。(表示socket已经关闭) ④客户端接收到FIN,关闭客户端的socket,发送ACK y+2高速服务端。 编程要求及注意事项 根据client.c代码及提示,在右侧编辑器中选中server.c注释处补充代码。 client.c如下: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLINE 4096 int main(int argc, char *argv[]) { int sockfd, n; // sockfd是客户端创建的socket描述字,客户端不需要区分监听listenfd和连接connfd。n为发送长度 char recvline[MAXLINE], sendline[MAXLINE]; // recvline暂时没用,sendline为即将发送的数据 struct sockaddr_in servaddr; //创建需要连接的服务端地址 if (argc != 2) { printf("usage: ./client <ipaddress>"); exit(0); } //设置运行时候需要输入的格式,执行文件+服务端ip地址 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("create socket error:%s(errno:%d)\n", strerror(errno), errno); exit(0); } //创建socket描述字,因为客户端,只需要一次连接 memset(&servaddr, 0, sizeof(servaddr)); //先把当前连接的服务端地址全填充0 servaddr.sin_family = AF_INET; //目的服务端地址协议簇为ipv4 servaddr.sin_port = htons(6666); //设置端口号6666 if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { printf("inet_pton error for %s\n", argv[1]); exit(0); } //将目的服务端的地址,转换成网络地址,必须转换成网络地址!!argv[0]是./client,argv[1]是服务端的地址 if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf("connect socket error:%s(errno:%d)\n", strerror(errno), errno); exit(0); } //创建连接,本机的socket通过服务端地址连接服务端,强转指针格式 printf("send msg to server:\n"); //连接上后输出发送信息到服务端 fgets(sendline, 4096, stdin); //标准输入流中获取需要发送的信息,存储到sendline字符数组中 if (send(sockfd, sendline, strlen(sendline), 0) < 0) { printf("send msg error:%s(errno:%d)\n", strerror(errno), errno); exit(0); } //发送信息,sendline buff中的信息 close(sockfd); //信息发送完关闭自己的sockfd,第一次挥手 exit(0); } server.c如下: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #define MAXLINE 4096 #define PORT 6666 // 与客户端端口一致 int main() { int listenfd, connfd; // 监听socket和连接socket struct sockaddr_in servaddr; // 服务端地址结构 char buffer[MAXLINE]; // 接收缓冲区 // 1. 创建监听socket if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("Create socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } // 2. 配置服务端地址 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网络接口 servaddr.sin_port = htons(PORT); // 端口号6666 // 3. 绑定socket到地址 if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { printf("Bind socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } // 4. 开始监听连接 if (listen(listenfd, 10) == -1) { // 等待队列最大10 printf("Listen socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } printf("====== Server running on port %d ======\n", PORT); printf("Waiting for client connection...\n"); // 5. 接受客户端连接 if ((connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1) { printf("Accept socket error: %s(errno: %d)\n", strerror(errno), errno); } else { printf("Client connected!\n"); } // 6. 读取客户端数据 int n = recv(connfd, buffer, MAXLINE, 0); if (n > 0) { buffer[n] = '\0'; // 确保字符串终止 printf("Received message from client: %s\n", buffer); } else if (n == 0) { printf("Client closed connection\n"); } else { printf("Recv error: %s(errno: %d)\n", strerror(errno), errno); } // 7. 关闭连接 close(connfd); close(listenfd); printf("Server shutdown\n"); return 0; }

filetype

我前段时间写了一个python程序,用于抓取自己闲鱼页面的商品信息,但是为了避免违规,我想重新编写一个程序,用于在阿奇索后台(开发指南:接入指南 1、接入流程 【开发者操作】登录后台申请AppIds。 入口:https://open.agiso.com/#/my/application/app-list 【开发者操作】申请到AppId后,开发者可以登录后台管理AppId,这里可以查看和更换AppSecret、更改推送url、更改授权回调url等。 入口:https://open.agiso.com/#/my/application/app-list 【商家操作】授权,方法有二: 1、输入开发者提供的AppId(相当于告诉Agiso,允许这个AppId通过Agiso开放平台获取或操作商家自己的订单数据),勾选相应要授权的权限。授权后会显示一个Token,将Token复制给开发者。 入口:https://aldsIdle.agiso.com/#/open/authorize 2、开发者如果有开发自动授权,则商家可以通过访问以下页面进行授权: 入口:https://aldsIdle.agiso.com/#/authorize?appId={$请替换为要授权的开发者的appId}&state=2233 【开发者操作】开发者得到各个商家授权给的Token,并使用Token调用接口。调用接口时,需要使用AppSecret进行签名,具体签名方法参见下文。 注意:开发者与商家,也可以是同一个人。 2、获取AccessToken详解 手动模式自动模式 将你的AppId告诉您的用户,用户通过在授权页面(https://aldsIdle.agiso.com/#/open/authorize) 进行授权。用户授权完成后,会获得一个AccessToken,让您的用户把该AccessToken发给你。 AccessToken的有效期和您的用户购买Agiso软件的使用时间一致。如果您的用户续费,那么AccessToken的有效期也会延长。 3、调用接口详解 调用任何一个API都必须把AccessToken 和 ApiVersion 添加到Header ,格式为"Authorization: Bearer access_token",其中Bearer后面有一个空格。同时还需传入以下公共参数: timestamp 是 Date 时间戳,例如:1468476350。API服务端允许客户端请求最大时间误差为10分钟。 sign 是 string API输入参数签名结果,签名算法参照下面的介绍。 注意:接口调用配额,20次/秒。 4、签名算法 【对所有API请求参数(包括公共参数和业务参数,但除去sign参数和byte[]类型的参数),根据参数名称的ASCII码表的顺序排序。如:foo=1, bar=2, foo_bar=3, foobar=4排序后的顺序是bar=2, foo=1, foo_bar=3, foobar=4。 将排序好的参数名和参数值拼装在一起,根据上面的示例得到的结果为:bar2foo1foo_bar3foobar4。 把拼装好的字符串采用utf-8编码,在拼装的字符串前后加上app的secret后,使用MD5算法进行摘要,如:md5(secret+bar2foo1foo_bar3foobar4+secret); 5、Header设置示例代码 JavaC#PHP HttpPost httpPost = new org.apache.http.client.methods.HttpPost(url); httpPost.addHeader("Authorization","Bearer "+ accessToken); httpPost.addHeader("ApiVersion", "1"); 6、签名算法示例代码 JavaC#PHP Map<String, String> data = new HashMap<String, String>(); data.put("modifyTimeStart", "2016-07-13 10:44:30"); data.put("pageNo", "1"); data.put("pageSize", "20"); //timestamp 为调用Api的公共参数,详细说明参考接入指南 data.put("timestamp", '1468476350');//假设当前时间为2016/7/14 14:5:50 //对键排序 String[] keys = data.keySet().toArray(new String[0]); Arrays.sort(keys); StringBuilder query = new StringBuilder(); //头加入AppSecret ,假设AppSecret值为****************** query.append(this.getClientSecret()); for (String key : keys) { String value = data.get(key); query.append(key).append(value); } //到这query的值为******************modifyTimeStart2016-07-13 10:44:30pageNo1pageSize20timestamp1468476350 //尾加入AppSecret query.append(this.getClientSecret()); //query=******************modifyTimeStart2016-07-13 10:44:30pageNo1pageSize20timestamp1468476350****************** byte[] md5byte = encryptMD5(query.toString()); //sign 为调用Api的公共参数,详细说明参考接入指南 data.put("sign", byte2hex(md5byte)); //byte2hex(md5byte) = 935671331572EBF7F419EBB55EA28558 // Md5摘要 public byte[] encryptMD5(String data) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5.digest(data.getBytes("UTF-8")); } public String byte2hex(byte[] bytes) { StringBuilder sign = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xFF); if (hex.length() == 1) { sign.append("0"); } sign.append(hex.toLowerCase()); } return sign.toString(); } 7、完整调用API示例代码 以下代码以调用LogisticsDummySend(更新发货状态)为例 JavaC#PHP public String LogisticsDummySend() { string accessToken = "*************"; string appSecret = "*************"; WebRequest apiRequest = WebRequest.Create("http://gw.api.agiso.com/aldsIdle/Trade/LogisticsDummySend"); apiRequest.Method = "POST"; apiRequest.ContentType = "application/x-www-form-urlencoded"; apiRequest.Headers.Add("Authorization", "Bearer " + accessToken); apiRequest.Headers.Add("ApiVersion", "1"); //业务参数 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); var args = new Dictionary<string,string>() { {"tids","123456789,987465123"}, 注意 tids是示例参数实际参数要以当前文档上的入参为准!!! {"timestamp",Convert.ToInt64(ts.TotalSeconds).ToString()} }; args.Add("sign", Sign(args, appSecret)); //拼装POST数据 string postData = ""; foreach (var p in args) { if (!String.IsNullOrEmpty(postData)) { postData += "&"; } string tmpStr = String.Format("{0}={1}", p.Key, HttpUtility.UrlEncode(p.Value)); postData += tmpStr; } using (var sw = new StreamWriter(apiRequest.GetRequestStream())) { sw.Write(postData); } WebResponse apiResponse = null; try { apiResponse = apiRequest.GetResponse(); } catch (WebException we) { if (we.Status == WebExceptionStatus.ProtocolError) { apiResponse = (we.Response as HttpWebResponse); } else{ //TODO:处理异常 return ""; } } using(Stream apiDataStream = apiResponse.GetResponseStream()){ using(StreamReader apiReader = new StreamReader(apiDataStream, Encoding.UTF8)){ string apiResult = apiReader.ReadToEnd(); apiReader.Close(); apiResponse.Close(); return apiResult; } } } //参数签名 public string Sign(IDictionary<string,string> args, string ClientSecret) { IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(args, StringComparer.Ordinal); string str = ""; foreach (var m in sortedParams) { str += (m.Key + m.Value); } //头尾加入AppSecret str = ClientSecret + str + ClientSecret; var encodeStr = MD5Encrypt(str); return encodeStr; } //Md5摘要 public static string MD5Encrypt(string text) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] fromData = System.Text.Encoding.UTF8.GetBytes(text); byte[] targetData = md5.ComputeHash(fromData); string byte2String = null; for (int i = 0; i < targetData.Length; i++) { byte2String += targetData[i].ToString("x2"); } return byte2String; })作为API接入商家后台,我想要获取我自己的商品数据:(# helps.py(仅仅作为功能参考,可以根据实际编写需要改写) import os def display_help(): """显示帮助信息""" print(""" 商品信息抓取工具 v1.0 ========================== 使用方法: python main.py [选项] 选项: -u, --url URL 商品URL -o, --output PATH 输出文件路径(.docx) -f, --file FILE 批量任务文件路径 -t, --threads N 并发线程数(默认:2) -d, --debug 启用调试模式 -v, --verbose 显示详细输出 -h, --help 显示帮助信息 批量任务文件格式: URL | 输出文件名称 以#开头的行默认为注释,将不会进行任何的处理 示例: https://2.taobao.com/item?id=123 | item1.docx https://2.taobao.com/item?id=456 | item2.docx 输出文件: 1. Word文档(.docx) - 包含商品标题、描述和图片 2. Excel报告(.xlsx) - 包含word文档标题和元数据(价格,商品标题等) 日志文件: xianyu_scraper.log - 包含详细运行日志 依赖库: pip~=25.1.1 requests==2.32.4 beautifulsoup4==4.12.3 python-docx==1.1.0 pandas==2.3.1 openpyxl==3.1.5 fake-useragent==2.2.0 htmldocx==0.0.6 pillow==11.3.0 selenium==4.34.2 webdriver-manager==4.0.2 tqdm==4.67.1(进度条) """) def show_banner(): """显示程序横幅""" print(r""" 商品信息抓取工具 v1.0 ============================================================================== """),现在请为我编写程序,并且给我一个详细的接入阿奇索后台的步骤

filetype

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/select.h> #include <errno.h> #include <signal.h> // for signal() #define PORT 8080 #define BUFFER_SIZE 4096 #define MAX_PATH 256 #define WEB_ROOT "./web" #define MAX_CLIENTS 100 // 服务器类型枚举 typedef enum { SERVER_MULTITHREAD, SERVER_MULTIPROCESS, SERVER_IO_MULTIPLEXING } server_type_t; // 线程参数结构体 (仅多线程模式使用) typedef struct { int client_socket; } thread_args_t; // 客户端连接信息 (仅IO多路复用模式使用) typedef struct { int socket; int active; } client_info_t; // --- 通用辅助函数 (所有模型共用) --- // 获取文件MIME类型 const char* get_mime_type(const char* filename) { const char* ext = strrchr(filename, '.'); if (!ext) return "text/plain"; if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) return "text/html"; if (strcmp(ext, ".css") == 0) return "text/css"; if (strcmp(ext, ".js") == 0) return "application/javascript"; if (strcmp(ext, ".png") == 0) return "image/png"; if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg"; if (strcmp(ext, ".gif") == 0) return "image/gif"; return "text/plain"; } // 发送HTTP响应头 void send_response_header(int client_socket, int status_code, const char* status_text, const char* content_type, long content_length) { char header[BUFFER_SIZE]; int header_len = snprintf(header, sizeof(header), "HTTP/1.1 %d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" "Connection: close\r\n" "\r\n", status_code, status_text, content_type, content_length); send(client_socket, header, header_len, 0); } // 发送错误响应 void send_error(int client_socket, int status_code, const char* status_text) { char body[100]; int body_len = snprintf(body, sizeof(body), "%d %s", status_code, status_text); send_response_header(client_socket, status_code, status_text, "text/plain", body_len); send(client_socket, body, body_len, 0); } // 发送文件内容 void send_file(int client_socket, const char* filepath) { struct stat file_stat; if (stat(filepath, &file_stat) == -1) { send_error(client_socket, 404, "Not Found"); return; } int file_fd = open(filepath, O_RDONLY); if (file_fd == -1) { send_error(client_socket, 500, "Internal Server Error"); return; } // 发送响应头 const char* mime_type = get_mime_type(filepath); send_response_header(client_socket, 200, "OK", mime_type, file_stat.st_size); // 发送文件内容 char buffer[BUFFER_SIZE]; int bytes_read; while ((bytes_read = read(file_fd, buffer, sizeof(buffer))) > 0) { if (send(client_socket, buffer, bytes_read, 0) < 0) { // 如果发送失败 (例如客户端关闭连接), 停止发送 break; } } close(file_fd); } // 处理HTTP请求 (所有模型共用的核心逻辑) void handle_request(int client_socket) { char buffer[BUFFER_SIZE]; // 只读取一次,对于简单的GET请求足够了 int bytes_received = recv(client_socket, buffer, sizeof(buffer) - 1, 0); if (bytes_received <= 0) { // 客户端已关闭连接或发生错误 return; } buffer[bytes_received] = '\0'; char method[16], path[MAX_PATH]; if (sscanf(buffer, "%s %s", method, path) != 2) { send_error(client_socket, 400, "Bad Request"); return; } if (strcmp(method, "GET") != 0) { send_error(client_socket, 405, "Method Not Allowed"); return; } char filepath[MAX_PATH * 2]; if (strcmp(path, "/") == 0) { snprintf(filepath, sizeof(filepath), "%s/index.html", WEB_ROOT); } else { snprintf(filepath, sizeof(filepath), "%s%s", WEB_ROOT, path); } send_file(client_socket, filepath); } // ===== 模型一:多线程服务器实现 ===== // 核心思想: 主线程负责监听和接受连接,每当有新连接到来,就创建一个新的子线程来专门处理这个连接。 // 优点: 线程间共享内存,通信方便,创建开销比进程小。 // 缺点: 需要处理线程同步问题(本例中不需要,因为线程间不共享数据),线程数量过多会消耗大量系统资源。 // 线程处理函数 void* thread_handler(void* arg) { thread_args_t* args = (thread_args_t*)arg; int client_socket = args->client_socket; free(args); // 释放参数结构体 // 让线程可以被系统自动回收资源,主线程无需调用 pthread_join pthread_detach(pthread_self()); printf("[线程 %lu] 开始处理客户端 socket %d\n", pthread_self(), client_socket); handle_request(client_socket); printf("[线程 %lu] 完成处理,关闭 socket %d\n", pthread_self(), client_socket); close(client_socket); return NULL; } void run_multithread_server() { int server_socket; struct sockaddr_in server_addr; // --- 标准的Socket服务器设置 --- server_socket = socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)); listen(server_socket, 10); printf("=== 多线程Web服务器启动,监听端口 %d ===\n", PORT); while (1) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); // 1. 主线程阻塞在 accept(),等待新连接 int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_len); if (client_socket < 0) continue; printf("接受新连接: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 2. 为新连接动态分配参数 thread_args_t* args = malloc(sizeof(thread_args_t)); args->client_socket = client_socket; // 3. 【关键】创建新线程,让新线程去执行 handle_request pthread_t thread_id; if (pthread_create(&thread_id, NULL, thread_handler, args) != 0) { perror("无法创建线程"); free(args); close(client_socket); } // 主线程不等待子线程结束,立即返回到 accept() 等待下一个连接 } close(server_socket); } // ===== 模型二:多进程服务器实现 ===== // 核心思想: 主进程(父进程)负责监听和接受连接,每当有新连接到来,就 fork() 一个子进程来专门处理这个连接。 // 优点: 进程间地址空间独立,一个进程崩溃不会影响其他进程,非常稳定。 // 缺点: 创建进程的开销远大于线程,进程间通信(IPC)复杂。 // 处理僵尸进程的信号处理器 void sigchld_handler(int sig) { // 使用 waitpid 循环回收所有已结束的子进程 while (waitpid(-1, NULL, WNOHANG) > 0); } void run_multiprocess_server() { int server_socket; struct sockaddr_in server_addr; // --- 标准的Socket服务器设置 --- server_socket = socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)); listen(server_socket, 10); // 【关键】设置信号处理函数来处理子进程结束信号,防止僵尸进程 // 你的原代码使用 SIG_IGN 是一种简便方法,这里使用更健壮的 waitpid 方式 signal(SIGCHLD, sigchld_handler); printf("=== 多进程Web服务器启动,监听端口 %d ===\n", PORT); while (1) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); // 1. 父进程阻塞在 accept(),等待新连接 int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_len); if (client_socket < 0) continue; printf("接受新连接: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 2. 【关键】创建子进程 pid_t pid = fork(); if (pid < 0) { perror("无法创建进程"); close(client_socket); } else if (pid == 0) { // --- 子进程代码 --- // a. 子进程不需要监听,关闭服务器socket close(server_socket); printf("[进程 %d] 开始处理客户端 socket %d\n", getpid(), client_socket); // b. 子进程调用 handle_request 处理业务 handle_request(client_socket); printf("[进程 %d] 完成处理,关闭 socket %d\n", getpid(), client_socket); // c. 关闭连接并退出子进程 close(client_socket); exit(0); } else { // --- 父进程代码 --- // a. 父进程不处理具体连接,关闭客户端socket close(client_socket); // b. 父进程立即返回到 accept() 等待下一个连接 } } close(server_socket); } // ===== 模型三:IO多路复用服务器实现 (select) ===== // 核心思想: 使用一个进程(或线程)来同时监视多个文件描述符(socket)。当任何一个socket准备好被读/写时,`select` 调用就会返回,然后程序可以去处理那个就绪的socket。 // 优点: 用最少的进程/线程处理大量的连接,系统资源开销小。 // 缺点: 编码比前两者复杂。`select` 本身有最大文件描述符数量的限制(通常是1024),并且每次调用都需要在内核和用户空间之间复制整个文件描述符集合,效率在连接数非常多时会下降(`poll` 和 `epoll` 是更优的替代方案)。 void run_io_multiplexing_server() { int server_socket, max_fd; struct sockaddr_in server_addr; fd_set master_fds, read_fds; // master_fds 保存所有要监控的fd,read_fds是每次传给select的临时集合 client_info_t clients[MAX_CLIENTS]; for (int i = 0; i < MAX_CLIENTS; i++) { clients[i].socket = -1; clients[i].active = 0; } // --- 标准的Socket服务器设置 --- server_socket = socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)); listen(server_socket, MAX_CLIENTS); // 监听队列可以大一点 printf("=== IO多路复用Web服务器启动,监听端口 %d ===\n", PORT); // 1. 【关键】初始化文件描述符集合 FD_ZERO(&master_fds); // 2. 将服务器监听socket加入监控集合 FD_SET(server_socket, &master_fds); max_fd = server_socket; while (1) { // 每次循环前,都从 master_fds 复制一份到 read_fds,因为 select 会修改传入的集合 read_fds = master_fds; // 3. 【关键】调用 select(),阻塞程序,直到有fd就绪 // select 会监控 read_fds 中的所有fd,当有fd可读时,它会返回 int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (activity < 0 && errno != EINTR) { perror("select 错误"); break; } // 4. 检查是哪个fd就绪了 // a. 检查是不是服务器监听socket就绪了 (意味着有新连接) if (FD_ISSET(server_socket, &read_fds)) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int new_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_len); printf("接受新连接: %s:%d on socket %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), new_socket); // 将新连接的socket也加入到监控集合中 int i; for (i = 0; i < MAX_CLIENTS; i++) { if (!clients[i].active) { clients[i].socket = new_socket; clients[i].active = 1; FD_SET(new_socket, &master_fds); if (new_socket > max_fd) max_fd = new_socket; printf("客户端 socket %d 添加到槽位 %d\n", new_socket, i); break; } } if (i == MAX_CLIENTS) { printf("连接数已满\n"); close(new_socket); } } // b. 检查是不是某个已连接的客户端socket就绪了 (意味着客户端发来了数据) for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].active && FD_ISSET(clients[i].socket, &read_fds)) { printf("处理客户端槽位 %d (socket %d) 的请求\n", i, clients[i].socket); // 在这个简单的模型中,我们直接处理完请求并关闭连接 // 一个更复杂的IO多路复用服务器会在这里进行非阻塞读写 handle_request(clients[i].socket); // 从监控集合中移除,并关闭socket close(clients[i].socket); FD_CLR(clients[i].socket, &master_fds); clients[i].active = 0; clients[i].socket = -1; printf("客户端槽位 %d (socket %d) 连接已关闭\n", i, clients[i].socket); } } } close(server_socket); } // 主函数,用于选择服务器模型 int main(int argc, char* argv[]) { server_type_t server_type = SERVER_MULTITHREAD; // 默认使用多线程 if (argc > 1) { if (strcmp(argv[1], "thread") == 0) { server_type = SERVER_MULTITHREAD; } else if (strcmp(argv[1], "process") == 0) { server_type = SERVER_MULTIPROCESS; } else if (strcmp(argv[1], "select") == 0) { server_type = SERVER_IO_MULTIPLEXING; } else { fprintf(stderr, "用法: %s [thread|process|select]\n", argv[0]); fprintf(stderr, " thread - 多线程模式 (默认)\n"); fprintf(stderr, " process - 多进程模式\n"); fprintf(stderr, " select - IO多路复用模式\n"); return 1; } } // 自动创建web根目录和index.html用于测试 system("mkdir -p " WEB_ROOT); FILE* index_file = fopen(WEB_ROOT "/index.html", "w"); if (index_file) { fprintf(index_file, "<!DOCTYPE html><html><head><title>Test Page</title><meta charset='UTF-8'></head>" "<body>

Web Server Test Page

Server is running!

</body></html>"); fclose(index_file); } // 根据选择启动对应的服务器 switch (server_type) { case SERVER_MULTITHREAD: run_multithread_server(); break; case SERVER_MULTIPROCESS: run_multiprocess_server(); break; case SERVER_IO_MULTIPLEXING: run_io_multiplexing_server(); break; } return 0; }
filetype

跟上面一样/* * @copyright: copyright (c) 2025 Chengdu TP-Link Technologies Co.Ltd. * @fileName: server.h * @description: function declaration file of server.c * @Author: Wang Zhiheng * @email: [email protected] * @version: 1.0.0 * @Date: 2025-08-07 * @history: \arg 1.0.0, 25Aug07, Wang Zhiheng, Create the file */ /**************************************************************************************************/ /* INCLUDE FILES */ /**************************************************************************************************/ #include <arpa/inet.h> #include <sys/epoll.h> #include <pthread.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #include <strings.h> #include <string.h> #include <sys/stat.h> #include <sys/sendfile.h> #include <dirent.h> #include <stdlib.h> #include <pthread.h> #include <ctype.h> #include <fcntl.h> #include <math.h> #include "server.h" /* 函数声明文件 */ /**************************************************************************************************/ /* LOCAL_PROTOTYPES */ /**************************************************************************************************/ /** * @function: void* recv_http_request(void* arg) * @description: 接收http请求, 读取数据 * @return * @note: conn_fd表示连接socket, epoll_fd表示epoll树 * @param {void*} arg */ void *recv_http_request(void *arg) { printf("进入recv\n"); int client_fd = *(int *)arg; int len = 0; /* 用于接收recv的返回值 */ char buf[4096] = {0}; /* 接收客户端请求数据 */ len = recv(client_fd, buf, sizeof(buf), 0); if (len <= 0) { printf("Client disconnected\n"); close(client_fd); free(arg); return NULL; } printf("len%d\n", len); parse_request(buf, (int *)arg); free(arg); printf("退出 recv\n"); close(client_fd); } /** * @function: int parse_request(const char *message, int *conn_fd) * @description: 解析HTTP请求并路由处理 * @param message: HTTP请求消息 * @param conn_fd: 客户端连接socket指针 * @return: 0成功, -1失败 */ int parse_request(const char *message, int *conn_fd) { char method[12], path[1024], version[1024]; sscanf(message, "%s %s %s", method, path, version); if (strcasecmp(method, "GET") == 0) { return handle_get_request(conn_fd, path); } else if (strcasecmp(method, "POST") == 0) { return handle_post_request(conn_fd, path, message); } return -1; } /** * @function: int handle_get_request(int *conn_fd, const char *path) * @description: 处理GET请求 * @param conn_fd: 客户端连接socket指针 * @param path: 请求路径 * @return: 0成功, -1失败 */ int handle_get_request(int *conn_fd, const char *path) { char *filename = NULL; /* 处理根路径请求 */ if (strcmp(path, "/") == 0) { filename = "About.html"; } /* 处理文件资源请求 */ else { filename = (char *)(path + 1); } send_http_response(*conn_fd, filename); return 0; } /** * @function: int handle_post_request(int *conn_fd, const char *path, const char *message) * @description: 处理POST请求 * @param conn_fd: 客户端连接socket指针 * @param path: 请求路径 * @param message: 完整HTTP请求消息 * @return: 0成功, -1失败 */ int handle_post_request(int *conn_fd, const char *path, const char *message) { /* 处理联系表单提交 */ if (strcmp(path, "/data/contact.json") == 0) { const char *body_start = strstr(message, "\r\n\r\n"); if (!body_start) { send_error_response(*conn_fd, 400, "请求体缺失"); return -1; } body_start += 4; /* 跳过空行 */ process_contact_form(*conn_fd, body_start); return 0; } /* 其他POST路径返回404 */ send_error_response(*conn_fd, 404, "不支持的路径"); return -1; } /** * @function: void process_contact_form(int conn_fd, const char *body) * @description: 处理联系表单数据 * @param conn_fd: 客户端连接socket * @param body: 请求体内容 */ void process_contact_form(int conn_fd, const char *body) { char *name = NULL, *email = NULL, *message_content = NULL; char *token = strtok((char *)body, "&"); /* 解析表单字段 */ while (token) { if (strstr(token, "name=") == token) { name = url_decode(token + 5); } else if (strstr(token, "email=") == token) { email = url_decode(token + 6); } else if (strstr(token, "message=") == token) { message_content = url_decode(token + 8); } token = strtok(NULL, "&"); } /* 打印表单数据 */ printf("收到联系表单数据:\n姓名: %s\n邮箱: %s\n留言: %s\n", name, email, message_content); /* 释放解码内存 */ if (name) free(name); if (email) free(email); if (message_content) free(message_content); /* 发送成功响应 */ const char *response = "HTTP/1.1 200 OK\r\n" "Content-Type: application/json\r\n" "Content-Length: 27\r\n" "Connection: close\r\n" "\r\n" "{\"callback\":\"提交成功\"}"; send(conn_fd, response, strlen(response), 0); } /** * @function: void send_http_response(int sockfd, const char* file_path) * @description: 发送http响应报文, 包括 响应头 响应文件数据 等 * @return * @note: * @param {int*} sockfd 与客户端连接的socket文件描述符 * @param {char*} file_path 需要发送的文件路径 */ void send_http_response(int sockfd, const char *file_path) { struct stat file_stat; /* stat函数的传出采纳数 */ char buffer[(int)pow(2, 17)]; /* 用于读取文件和发送数据的缓冲区 */ ssize_t bytes_read; /* 用于接收read返回的数据长度 */ if (stat(file_path, &file_stat) != 0) { /* 文件不存在或无法获取状态 */ const char *response_404 = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/html\r\n" "Content-Length: 133\r\n" "Connection: close\r\n" "\r\n" "<html><body>

404 Not Found

The requested file was not found on this server.

</body></html>"; send(sockfd, response_404, strlen(response_404), 0); close(sockfd); return; } int fd = open(file_path, O_RDONLY); if (fd == -1) { /* 打开文件失败 */ const char *response_500 = "HTTP/1.1 500 Internal Server Error\r\n" "Content-Type: text/html\r\n" "Content-Length: 151\r\n" "Connection: close\r\n" "\r\n" "<html><body>

500 Internal Server Error

Failed to open the requested file.

</body></html>"; send(sockfd, response_500, strlen(response_500), 0); close(sockfd); return; } printf("打开文件%s\n", file_path); const char *mime_type = get_mime_type(file_path); /* 发送HTTP响应头 */ char response_header[2048]; snprintf(response_header, sizeof(response_header), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" "Connection: keep-alive\r\n" "\r\n", mime_type, file_stat.st_size); send(sockfd, response_header, strlen(response_header), 0); /* 循环读取并发送文件内容 */ while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) { ssize_t sent = send(sockfd, buffer, bytes_read, 0); if (sent < 0) { perror("send failed"); break; } } printf("bytes_read: %zd\n", bytes_read); close(fd); /* 关闭文件描述符 */ } /** * @function: void send_error_response(int conn_fd, int code, const char *msg) * @description: 发送错误响应 * @param conn_fd: 客户端连接socket * @param code: HTTP状态码 * @param msg: 错误信息 */ void send_error_response(int conn_fd, int code, const char *msg) { char response[512]; const char *status = ""; /* 设置状态码描述 */ switch(code) { case 400: status = "400 Bad Request"; break; case 404: status = "404 Not Found"; break; case 500: status = "500 Internal Server Error"; break; } /* 构造JSON错误响应 */ snprintf(response, sizeof(response), "HTTP/1.1 %s\r\n" "Content-Type: application/json\r\n" "Content-Length: %d\r\n" "Connection: close\r\n" "\r\n" "{\"error\":\"%s\"}", status, (int)strlen(msg) + 10, msg); send(conn_fd, response, strlen(response), 0); } /** * @function: char *url_decode(const char *src) * @description: URL解码 * @param src: 编码字符串 * @return: 解码后字符串(需调用者释放) */ char *url_decode(const char *src) { if (!src) return NULL; size_t src_len = strlen(src); char *decoded = malloc(src_len + 1); char *dst = decoded; while (*src) { if (*src == '+') { *dst++ = ' '; src++; } else if (*src == '%' && isxdigit(src[1]) && isxdigit(src[2])) { char hex[3] = {src[1], src[2], '\0'}; *dst++ = (char)strtol(hex, NULL, 16); src += 3; } else { *dst++ = *src++; } } *dst = '\0'; return decoded; } /** * @function: const char* get_mime_type(const char* filename) * @description: MIME类型映射, 获取发送给客户端文件名称的后缀名, 以生成对应Content-Type参数 * @return 返回存储Content-Type参数的指针 * @note: 根据提供的web页面, 只涉及了其中三种Content-Type参数 * @param {char*} filename 要发送给客户端的路径名或文件名 */ const char *get_mime_type(const char *filename) { const char *dot = NULL; /* 接收strrchr返回参数,存储文件后缀名 */ dot = strrchr(filename, '.'); /* strrchr获取指定字符的最后一次出现 */ /* 根据文件后缀名生成对应Content-Type参数 */ if (dot == NULL) return "application/octet-stream"; if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0) return "text/html"; else if (strcmp(dot, ".css") == 0) return "text/css"; else if (strcmp(dot, ".png") == 0 || strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0) return "image/jpeg"; return "application/octet-stream"; } /**************************************************************************************************/ /* PUBLIC_FUNCTIONS */ /**************************************************************************************************/ /** * @function: int init_listen_fd(unsigned short port) * @description: 新建一个用于TCP监听的socket文件描述符,并返回 * @return 返回监听socket的文件描述符 * @note: * @param {unsigned short} port 监听端口号 */ int init_listen_fd(unsigned short port) { int listen_sockfd; /* 监听socket */ struct sockaddr_in listen_addr; /* 监听地址 */ memset(&listen_addr, 0, sizeof(listen_addr)); /* 初始化 */ int temp_result; /* 存储临时变量用于debug */ /* 创建监听socket */ listen_sockfd = socket(AF_INET, SOCK_STREAM, 0); handle_error("socket", listen_sockfd); /* 设置端口复用 */ int opt = -1; int ret = setsockopt(listen_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); handle_error("setsockopt", listen_sockfd); /* 绑定端口和IP */ listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = INADDR_ANY; listen_addr.sin_port = htons(port); /* 绑定地址 */ temp_result = bind(listen_sockfd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)); handle_error("bind", temp_result); printf("bind success\n"); /* 进入监听模式 */ if (listen(listen_sockfd, 128) < 0) { perror("listen failed"); close(listen_sockfd); exit(EXIT_FAILURE); } printf("listen success\n"); return listen_sockfd; } /** * @function: int epoll_run(int listen_sockfd) * @description: 启动epoll * @return 返回值无实际作用,因为会处于循环状态监听 * @note: * @param {int} listen_sockfd 监听socket的文件描述符 */ int threads_run(int listen_sockfd) { int temp_result; /* 存储临时变量用于debug */ struct sockaddr_in client_addr; /* 用于存储客户端地址 */ memset(&client_addr, 0, sizeof(client_addr)); /* 接收client连接 */ socklen_t client_addr_len = sizeof(client_addr); while (1) { pthread_t pid; /* 子线程, 用于从客户端读取数据*/ int *client_fd_ptr = (int *)malloc(sizeof(int)); int client_fd = accept(listen_sockfd, (struct sockaddr *)&client_addr, &client_addr_len); *client_fd_ptr = client_fd; if (client_fd < 0) { perror("accept failed"); close(listen_sockfd); exit(EXIT_FAILURE); } printf("a client from ip:%s at port %d, socket %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd); /* 调用子线程接收客户端数据, 并发送响应*/ if ((pthread_create(&pid, NULL, recv_http_request, client_fd_ptr)) != 0) { // perror("pthread_create"); // pthread_exit(&pid); perror("pthread_create failed"); close(client_fd); free(client_fd_ptr); continue; } /* 使子线程处于detached状态,使其终止时自动回收资源,同时不阻塞线程 */ // pthread_join(pid, NULL); pthread_detach(pid); } printf("close socket"); close(listen_sockfd); return 0; }
filetype

把代码中// 的注释修改为/* */ #include "common.h" // 客户端数据结构 typedef struct { int fd; // 客户端套接字描述符 char buffer[BUFFER_SIZE]; // 请求数据缓冲区 int buffer_len; // 当前缓冲区数据长度 int status; // 客户端状态(0:等待,1:读取中,2:写入中) } client_data; /** * 工作进程函数(处理客户端连接) */ void worker_process(int server_fd) { /* 入参检查*/ if( server_fd <= 2){ return; } while (1) { struct sockaddr_in client_addr; socklen_t addrlen = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addrlen); if (client_fd < 0) { if (errno == EINTR) continue; // 被信号中断,重试 perror("accept"); continue; } printf("[Worker %d] New connection: %s:%d\n", getpid(), inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 读取请求 char buffer[BUFFER_SIZE]; ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1); if (bytes_read <= 0) { close(client_fd); continue; } buffer[bytes_read] = '\0'; fprintf(stdout,"======buffer======\n%s============\n",buffer); // 处理请求 handle_request(client_fd, buffer); // 关闭连接 close(client_fd); } } /** * 主函数:设置服务器并运行事件循环 */ int main() { int server_fd, epoll_fd; struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); // 1. 创建服务器套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置套接字选项(允许地址重用) int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } // 2. 配置服务器地址 addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口 addr.sin_port = htons(PORT); // 监听端口 // 3. 绑定套接字 if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 4. 开始监听 if (listen(server_fd, SOMAXCONN) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Web server running on port %d...\n", PORT); printf("Root directory: %s\n", ROOT_DIR); // 5. 创建epoll实例 if ((epoll_fd = epoll_create1(0)) < 0) { perror("epoll_create1 failed"); close(server_fd); exit(EXIT_FAILURE); } // 6. 添加服务器套接字到epoll struct epoll_event ev; ev.events = EPOLLIN; // 监听读事件 ev.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) < 0) { perror("epoll_ctl failed"); close(server_fd); close(epoll_fd); exit(EXIT_FAILURE); } printf("Web server running on port %d...\n", PORT); printf("Root directory: %s\n", ROOT_DIR); // 7. 分配客户端数据存储空间 struct epoll_event events[MAX_EVENTS]; client_data *clients = calloc(MAX_EVENTS, sizeof(client_data)); // 8. 主事件循环 while (1) { // 等待事件发生 int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); // 一直等到事件发生 if (nfds < 0) { perror("epoll_wait"); continue; } // 处理所有就绪事件 for (int i = 0; i < nfds; i++) { int fd = events[i].data.fd; // 处理新连接 if (fd == server_fd) { // 接受新连接 int client_fd = accept(server_fd, (struct sockaddr*)&addr, &addrlen); if (client_fd < 0) { perror("accept"); continue; } // 设置非阻塞模式 fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL, 0) | O_NONBLOCK); // 添加新客户端到epoll ev.events = EPOLLIN | EPOLLET; // 边缘触发模式 ev.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) { perror("epoll_ctl client"); close(client_fd); } // 初始化客户端数据 clients[client_fd].fd = client_fd; clients[client_fd].buffer_len = 0; clients[client_fd].status = 0; printf("New connection: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); } else { // 处理现有客户端数据 client_data *client = &clients[fd]; // 读取客户端数据 int bytes_read = read(fd, client->buffer + client->buffer_len, BUFFER_SIZE - client->buffer_len); if (bytes_read <= 0) { // 连接关闭或错误 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); close(fd); memset(client, 0, sizeof(client_data)); continue; } // 更新缓冲区 client->buffer_len += bytes_read; client->buffer[client->buffer_len] = '\0'; // 检查是否收到完整HTTP请求(以空行结束) if (strstr(client->buffer, "\r\n\r\n") != NULL) { // 处理请求 handle_request(fd, client->buffer); // 重置缓冲区 client->buffer_len = 0; memset(client->buffer, 0, BUFFER_SIZE); } } } } // 清理资源(实际不会执行到这里) free(clients); close(server_fd); close(epoll_fd); return 0; }

filetype

#include <ESP8266WiFi.h> // ESP8266 WiFi功能库 #include <ESP8266HTTPClient.h> // HTTP客户端库(发送HTTP请求) #include <WiFiClientSecureBearSSL.h> // 安全WiFi客户端(支持HTTPS) #include <ArduinoJson.h> // JSON数据处理库 // ------------------- 配置参数(需用户替换) ------------------- const char* ssid = "ncroom"; // WiFi名称(英文) const char* password = "12121212"; // WiFi密码 const char* apiKey = "sk-f6250c3bc81041fb8f435ddb3134a835"; // DeepSeek提供的API密钥 const char* apiEndpoint = "https://api.deepseek.com"; // DeepSeek API端点 // ------------------- 全局对象 ------------------- BearSSL::WiFiClientSecure client; // 安全WiFi客户端(支持HTTPS) HTTPClient http; // HTTP客户端对象 // ------------------- setup函数(初始化) ------------------- void setup() { Serial.begin(115200); // 初始化串口(波特率115200,用于调试) connectToWiFi(); // 连接WiFi // 开发阶段跳过SSL证书验证(生产环境建议配置证书) client.setInsecure(); } // ------------------- loop函数(主循环) ------------------- void loop() { if (Serial.available() > 0) { // 检测串口是否有输入 String userQuestion = Serial.readStringUntil('\n'); // 读取用户输入(直到换行) userQuestion.trim(); // 去除首尾空格 if (userQuestion.length() > 0) { // 输入非空时处理请求 processRequest(userQuestion); // 调用请求处理函数 } } } // ------------------- WiFi连接函数 ------------------- void connectToWiFi() { WiFi.begin(ssid, password); // 启动WiFi连接 Serial.print("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { // 等待连接成功 delay(500); Serial.print("."); } Serial.println("\nWiFi Connected! IP: " + WiFi.localIP().toString()); } // ------------------- 核心请求处理函数 ------------------- void processRequest(const String& question) { // 1. 构建请求体(JSON格式,需符合DeepSeek API要求) DynamicJsonDocument requestDoc(2048); // 创建JSON文档(容量根据实际调整) requestDoc["model"] = "deepseek-chat"; // DeepSeek模型名称(以实际为准) requestDoc["stream"] = false; // 禁用流式响应 requestDoc["max_tokens"] = 512; // 最大生成令牌数 requestDoc["temperature"] = 0.7; // 温度参数(控制随机性,0~1) // 添加用户消息(必选字段,DeepSeek要求messages数组) JsonArray messages = requestDoc.createNestedArray("messages"); JsonObject userMsg = messages.createNestedObject(); userMsg["role"] = "user"; // 角色为"user"(用户) userMsg["content"] = question; // 用户输入的问题内容 // 2. 序列化JSON请求体为字符串 String requestBody; serializeJson(requestDoc, requestBody); // 3. 发送HTTP POST请求 if (http.begin(client, apiEndpoint)) { // 初始化HTTP客户端(绑定安全连接和API地址) // 设置请求头 http.addHeader("Content-Type", "application/json"); http.addHeader("Authorization", "Bearer " + String(apiKey)); // API密钥认证 // 发送POST请求并获取状态码 int httpCode = http.POST(requestBody); // 4. 处理响应 if (httpCode == HTTP_CODE_OK) { // HTTP 200(成功) String responsePayload = http.getString(); // 获取响应内容(JSON) // 解析响应JSON DynamicJsonDocument responseDoc(4096); // 响应可能较大,扩大缓冲区 DeserializationError error = deserializeJson(responseDoc, responsePayload); if (!error) { // JSON解析成功 // 提取AI回复内容(根据DeepSeek API实际返回结构调整) if (responseDoc.containsKey("choices")) { String aiResponse = responseDoc["choices"][0]["message"]["content"]; Serial.println("\nAI Response: " + aiResponse); } else if (responseDoc.containsKey("error")) { // API返回错误 Serial.println("API Error: " + responseDoc["error"]["message"].as<String>()); } } else { // JSON解析失败 Serial.println("JSON Parse Error: " + String(error.c_str())); } } else { // HTTP请求失败(如401未授权、404未找到) Serial.println("HTTP Request Failed. Code: " + String(httpCode)); Serial.println("Response: " + http.getString()); Serial.println("API Endpoint: " + String(apiEndpoint)); // 打印实际使用的API地址 } http.end(); // 关闭HTTP连接 } else { Serial.println("Failed to connect to API endpoint."); } }优化

长迦
  • 粉丝: 44
上传资源 快速赚钱