C code for a simple HTTP client

本文介绍了一个用C语言编写的简易HTTP客户端实现。该客户端能够通过命令行参数接收目标主机名及页面路径,解析主机名,建立TCP连接,并发送HTTP GET请求获取指定网页的内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. Linux provide a high level socket API that will allow programmer to easily connect to any TCP or UDP services.

    In this tutorial, we will see how this works by implementing a simple HTTP client which will get request a web page given the hostname and the page name, then read the server answer and output the HTML content of the reply.

    To be able to connect to a service built on top of TCP, we first need to create a socket for the TCP protocol, fill in a network address structure representing our destination and the port to connect to and use the latter to connect to the remote server.

    From there, we will be able to send and receive data over the network. Once we are done, we will close the connection.

    Below is the C code for a simple HTTP client that will get the host and the page to request from the command line arguments, resolve the hostname name to an IP, connect to this IP on port 80, build the HTTP query, send it and the retrieve the page content.

  2. #include <stdio.h>
  3. #include <sys/socket.h>
  4. #include <arpa/inet.h>
  5. #include <stdlib.h>
  6. #include <netdb.h>
  7. #include <string.h>
  8. int create_tcp_socket();
  9. char *get_ip(char *host);
  10. char *build_get_query(char *host, char *page);
  11. void usage();
  12.  
  13. #define HOST "coding.debuntu.org"
  14. #define PAGE "/"
  15. #define PORT 80
  16. #define USERAGENT "HTMLGET 1.0"
  17.  
  18. int main(int argc, char **argv)
  19. {
  20.   struct sockaddr_in *remote;
  21.   int sock;
  22.   int tmpres;
  23.   char *ip;
  24.   char *get;
  25.   char buf[BUFSIZ+1];
  26.   char *host;
  27.   char *page;
  28.  
  29.   if(argc == 1){
  30.     usage();
  31.     exit(2);
  32.   }  
  33.   host = argv[1];
  34.   if(argc > 2){
  35.     page = argv[2];
  36.   }else{
  37.     page = PAGE;
  38.   }
  39.   sock = create_tcp_socket();
  40.   ip = get_ip(host);
  41.   fprintf(stderr, "IP is %s/n", ip);
  42.   remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *));
  43.   remote->sin_family = AF_INET;
  44.   tmpres = inet_pton(AF_INET, ip, (void *)(&(remote->sin_addr.s_addr)));
  45.   if( tmpres < 0)  
  46.   {
  47.     perror("Can't set remote->sin_addr.s_addr");
  48.     exit(1);
  49.   }else if(tmpres == 0)
  50.   {
  51.     fprintf(stderr, "%s is not a valid IP address/n", ip);
  52.     exit(1);
  53.   }
  54.   remote->sin_port = htons(PORT);
  55.  
  56.   if(connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0){
  57.     perror("Could not connect");
  58.     exit(1);
  59.   }
  60.   get = build_get_query(host, page);
  61.   fprintf(stderr, "Query is:/n<<START>>/n%s<<END>>/n", get);
  62.  
  63.   //Send the query to the server
  64.   int sent = 0;
  65.   while(sent < strlen(get))
  66.   {
  67.     tmpres = send(sock, get+sent, strlen(get)-sent, 0);
  68.     if(tmpres == -1){
  69.       perror("Can't send query");
  70.       exit(1);
  71.     }
  72.     sent += tmpres;
  73.   }
  74.   //now it is time to receive the page
  75.   memset(buf, 0, sizeof(buf));
  76.   int htmlstart = 0;
  77.   char * htmlcontent;
  78.   while((tmpres = recv(sock, buf, BUFSIZ, 0)) > 0){
  79.     if(htmlstart == 0)
  80.     {
  81.       /* Under certain conditions this will not work.
  82.       * If the /r/n/r/n part is splitted into two messages
  83.       * it will fail to detect the beginning of HTML content
  84.       */
  85.       htmlcontent = strstr(buf, "/r/n/r/n");
  86.       if(htmlcontent != NULL){
  87.         htmlstart = 1;
  88.         htmlcontent += 4;
  89.       }
  90.     }else{
  91.       htmlcontent = buf;
  92.     }
  93.     if(htmlstart){
  94.       fprintf(stdout, htmlcontent);
  95.     }
  96.  
  97.     memset(buf, 0, tmpres);
  98.   }
  99.   if(tmpres < 0)
  100.   {
  101.     perror("Error receiving data");
  102.   }
  103.   free(get);
  104.   free(remote);
  105.   free(ip);
  106.   close(sock);
  107.   return 0;
  108. }
  109.  
  110. void usage()
  111. {
  112.   fprintf(stderr, "USAGE: htmlget host [page]/n/
  113. /thost: the website hostname. ex: coding.debuntu.org/n/
  114. /tpage: the page to retrieve. ex: index.html, default: //n");
  115. }
  116.  
  117.  
  118. int create_tcp_socket()
  119. {
  120.   int sock;
  121.   if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
  122.     perror("Can't create TCP socket");
  123.     exit(1);
  124.   }
  125.   return sock;
  126. }
  127.  
  128.  
  129. char *get_ip(char *host)
  130. {
  131.   struct hostent *hent;
  132.   int iplen = 15; //XXX.XXX.XXX.XXX
  133.   char *ip = (char *)malloc(iplen+1);
  134.   memset(ip, 0, iplen+1);
  135.   if((hent = gethostbyname(host)) == NULL)
  136.   {
  137.     herror("Can't get IP");
  138.     exit(1);
  139.   }
  140.   if(inet_ntop(AF_INET, (void *)hent->h_addr_list[0], ip, iplen) == NULL)
  141.   {
  142.     perror("Can't resolve host");
  143.     exit(1);
  144.   }
  145.   return ip;
  146. }
  147.  
  148. char *build_get_query(char *host, char *page)
  149. {
  150.   char *query;
  151.   char *getpage = page;
  152.   char *tpl = "GET /%s HTTP/1.0/r/nHost: %s/r/nUser-Agent: %s/r/n/r/n";
  153.   if(getpage[0] == '/'){
  154.     getpage = getpage + 1;
  155.     fprintf(stderr,"Removing leading /"//", converting %s to %s/n", page, getpage);
  156.   }
  157.   // -5 is to consider the %s %s %s in tpl and the ending /0
  158.   query = (char *)malloc(strlen(host)+strlen(getpage)+strlen(USERAGENT)+strlen(tpl)-5);
  159.   sprintf(query, tpl, getpage, host, USERAGENT);
  160.   return query;
  161. }

 

To compile it, run:

$ gcc -o htmlget htmlget.c
$ ./htmlget 
USAGE: htmlget host [page]
	host: the website hostname. ex: coding.debuntu.org
	page: the page to retrieve. ex: index.html, default: /

Informative messages and errors are printed to stderr. The content of the page is printed to stdout. Thus, to save the HTML content of a page to a file, you will need to run:

$ ./htmlget coding.debuntu.org category > /tmp/page.html
<think>好的,我现在需要帮用户写一个基本的Web服务器代码,用来响应他们提供的HTTP客户端。首先,用户的要求是服务器要监听80端口,响应“200 OK”的文本消息,并且能够通过循环处理多个顺序请求。同时需要代码注释和解释语句的作用。 首先,我得确定用什么语言来写。客户端是用Java写的,所以可能服务器也用Java比较合适,这样用户更容易理解两者的交互。不过用户没有明确指定语言,但考虑到客户端是Java,使用Java服务器会更一致。所以决定用Java编写服务器端代码。 接下来,服务器需要监听端口80。在Java中,可以使用ServerSocket类来实现。创建ServerSocket实例时指定端口80。然后在一个循环中调用accept()方法等待客户端连接。每次accept()返回一个Socket对象,代表客户端的连接。 然后,处理客户端的请求。客户端发送的是GET请求,但用户提供的客户端代码中发送的是"GET index.htmtl",可能存在拼写错误(应该是index.html),不过服务器端可能不需要处理具体文件,只需要回复固定的响应。所以服务器读取请求后,不管请求内容如何,都返回“200 OK”的响应。 接下来,服务器需要读取客户端发送的请求。可以使用BufferedReader来读取输入流。但需要注意,HTTP协议中请求以空行结束(两个换行),所以可能需要读取直到空行。但为了简化,可能直接忽略请求内容,直接发送响应。 响应部分需要遵循HTTP协议格式。正确的HTTP响应应该以状态行开始,比如“HTTP/1.1 200 OK”,然后是头部字段,比如Content-Type和Content-Length,接着是空行,然后是响应体。用户要求返回简单的文本消息,所以响应体可以是“200 OK”。 然后,服务器需要将响应发送给客户端。使用OutputStream来写入响应。确保头部和内容正确,并关闭连接。 需要注意的是,服务器必须在一个循环中处理多个请求,即每次处理完一个客户端连接后,继续等待下一个连接。但不需要多线程,所以处理完一个请求后再处理下一个,按顺序处理。 现在考虑代码结构: 1. 创建ServerSocket并绑定到80端口。 2. 进入无限循环,接受连接。 3. 对于每个连接,获取输入输出流。 4. 读取客户端请求(可能不需要处理具体内容)。 5. 发送HTTP响应。 6. 关闭连接。 7. 继续等待下一个连接。 可能需要注意异常处理,但用户提供的客户端代码没有处理异常,所以服务器代码可能也需要抛出异常或者简单处理。 现在开始编写代码: 导入必要的包,比如java.net.*和java.io.*。 主类HTTPServer,main方法抛出Exception。 在main方法中: - 创建ServerSocket在端口80。 - 进入while(true)循环。 - 调用serverSocket.accept()获取客户端Socket。 - 获取输入流和输出流。 - 读取请求:可能需要读取所有输入直到空行,但这里可能简单读取一行或者忽略。 - 构建响应字符串,包含状态行、头部、空行和内容。 - 发送响应,关闭流和socket。 但需要注意,HTTP协议要求客户端可能保持连接,但用户客户端在发送请求后关闭连接,所以服务器处理完一个请求后应该关闭socket。 现在编写代码: public class HTTPServer { public static void main(String[] args) throws Exception { // 创建服务器Socket,监听80端口 ServerSocket serverSocket = new ServerSocket(80); while (true) { // 等待客户端连接 Socket clientSocket = serverSocket.accept(); // 获取输入流读取客户端请求 BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); // 获取输出流发送响应 DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream()); // 读取请求(简单读取一行,实际应处理完整请求) String request = in.readLine(); // 构建HTTP响应 String response = "HTTP/1.1 200 OK\r\n"; response += "Content-Type: text/plain\r\n"; response += "Content-Length: 7\r\n"; response += "\r\n"; response += "200 OK"; // 发送响应 out.writeBytes(response); // 关闭资源 out.close(); in.close(); clientSocket.close(); } } } 不过这里可能有错误,比如Content-Length应该是7,但“200 OK”是7个字符吗?数一下:2 0 0 O K → 6个字符?不,是7个,包括空格:2 0 0 空格 O K → 200 OK是7个字符。是的,正确。 或者,可能用户只需要返回状态行和消息,但HTTP协议需要头部。用户可能希望响应体是纯文本,所以正确设置Content-Type和Content-Length是必要的。 测试客户端是否会正确读取响应。客户端代码中使用readLine()读取响应,但服务器发送的是整个响应,包括多行。客户端可能只读取第一行,即“HTTP/1.1 200 OK”,所以回复中的第一行会被客户端捕获为reply变量,输出“Received HTTP Response! HTTP/1.1 200 OK”,这可能更符合用户示例中的期望。 但用户要求服务器返回“200 OK”文本消息,可能需要调整响应体。或者可能用户希望状态行是“200 OK”,而响应体是其他内容?可能需要明确。 根据题目描述,用户希望服务器响应一个简单的“200 OK”文本消息。所以正确的HTTP响应应该状态行是200 OK,然后响应体包含“200 OK”。 但根据HTTP标准,状态行是协议版本加状态码加原因短语,例如“HTTP/1.1 200 OK”。然后头部和内容。响应体中的内容可以是任何文本,但需要正确设置Content-Length。 所以上面的代码是正确的,但响应体中的“200 OK”是否正确?或者是否应该只在状态行有200 OK,而响应体为空?可能用户示例中的客户端代码发送的请求是“GET index.htmtl”,然后读取一行回复。服务器回复的如果是“HTTP/1.1 200 OK\r\n...”则客户端读取的第一行是“HTTP/1.1 200 OK”,然后后面的内容可能未被读取,但客户端代码只读了一行,所以reply变量会是“HTTP/1.1 200 OK”,而用户示例中的客户端输出会是“Received HTTP Response! HTTP/1.1 200 OK”,这可能更准确。 但用户问题中说明服务器应响应“200 OK”文本消息。所以可能需要将响应体设置为“200 OK”,而状态行同样是200 OK,这样客户端在读取响应时,第一行是状态行,后面的行是内容。但客户端代码中的inFromServer.readLine()只会读第一行,即状态行,所以回复的reply会是“HTTP/1.1 200 OK”,而响应体可能未被读取。但用户可能希望整个响应体是“200 OK”,所以可能需要调整响应内容。 或者,可能用户希望服务器直接发送“200 OK”作为响应,而不遵循HTTP协议格式。但客户端是按照HTTP协议发送请求的,所以服务器必须按照HTTP协议响应,否则客户端可能无法正确解析。 例如,用户提供的客户端发送的是“GET index.htmtl”加上两个换行,这可能不符合HTTP协议的正确格式(HTTP请求应该是“GET /index.html HTTP/1.1”等),但服务器需要处理这个请求。因此,服务器应该返回一个符合HTTP协议的响应,这样客户端才能正确解析。 因此,正确的响应应该以状态行开始,头部,空行,然后是响应体。例如: HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 7 200 OK 这样,客户端读取的第一行是状态行“HTTP/1.1 200 OK”,然后后续的头部,空行,然后响应体“200 OK”。如果客户端只读取第一行,那么reply变量会是“HTTP/1.1 200 OK”,而响应体可能未被读取。但如果客户端继续读取,会得到后面的内容。不过根据用户提供的客户端代码,它只调用了一次readLine(),所以可能只读取状态行。 但用户问题中的服务器需要响应一个简单的“200 OK”文本消息,因此可能需要将响应体设置为该消息,而状态行也是200 OK。这可能符合用户的要求,尽管客户端的处理方式可能不完整。 综上,服务器代码需要生成符合HTTP协议的响应,包含状态行、头部、空行和响应体。因此,代码中的响应字符串应该按照此格式构建。 可能存在的问题是,在代码中使用out.writeBytes(response)时,是否正确处理了换行符。HTTP要求换行是\r\n,所以在Java中,构建响应字符串时应该用\r\n作为换行符,如: String response = "HTTP/1.1 200 OK\r\n"; response += "Content-Type: text/plain\r\n"; response += "Content-Length: 7\r\n"; response += "\r\n"; // 空行 response += "200 OK"; 然后写入这个响应。这样客户端在读取时,会正确解析。 另外,客户端的请求可能包含多个行,比如在发送GET请求后有两个换行,所以服务器可能需要读取所有请求数据,但为了简化,服务器可以只读取直到空行,或者忽略输入,直接发送响应。但在代码中,可能只需要读取一行请求(如in.readLine()),然后发送响应,因为客户端发送的请求是“GET index.htmtl\n\n”,所以读取第一行是“GET index.htmtl”,之后可能还有空行,但服务器可以忽略后续输入,直接发送响应。 综上,最终的服务器代码应该包括: - 创建ServerSocket,监听80端口。 - 循环接受连接。 - 对于每个连接,读取请求,发送HTTP响应,关闭连接。 代码中的注释需要解释每个步骤的作用,比如创建ServerSocket、等待连接、读取请求、构建响应、发送响应、关闭资源等。 现在检查可能的错误,比如端口是否被占用,需要处理异常,但用户代码示例中抛出Exception,所以服务器代码也可以这样做。 总结,最终的Java服务器代码应如上述结构,并附带注释解释每个部分的功能。</think>以下是一个简单的Java Web服务器实现,能够响应客户端请求并返回"200 OK"消息。代码包含详细注释: ```java import java.net.*; // 网络编程相关类 import java.io.*; // 输入输出流类 public class HTTPServer { public static void main(String[] args) throws Exception { // 创建服务器Socket,监听80端口(标准HTTP端口) ServerSocket serverSocket = new ServerSocket(80); System.out.println("Server started on port 80"); // 永久循环,支持顺序处理多个请求 while (true) { // 等待客户端连接(阻塞方法,直到有连接请求) Socket clientSocket = serverSocket.accept(); System.out.println("New client connected"); try { // 创建输入流读取客户端请求 BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); // 创建输出流向客户端发送响应 DataOutputStream out = new DataOutputStream( clientSocket.getOutputStream()); // 读取客户端请求的第一行(例如:"GET index.htmtl") String request = in.readLine(); System.out.println("Received request: " + request); // 构建HTTP响应(符合HTTP协议格式) String response = "HTTP/1.1 200 OK\r\n"; // 状态行 response += "Content-Type: text/plain\r\n"; // 内容类型头 response += "Content-Length: 7\r\n\r\n"; // 内容长度头+空行 response += "200 OK"; // 响应正文 // 发送HTTP响应 out.writeBytes(response); System.out.println("Response sent"); } finally { // 关闭连接和相关资源 clientSocket.close(); } } } } ``` 代码结构解析: 1. **网络基础设置** ```java ServerSocket serverSocket = new ServerSocket(80); ``` - 创建`ServerSocket`监听80端口(标准HTTP端口) - 会抛出`IOException`(通过main方法的throws Exception统一处理) 2. **主循环** ```java while (true) { Socket clientSocket = serverSocket.accept(); // 处理请求... } ``` - `accept()`方法阻塞等待客户端连接 - 每个连接顺序处理,支持多个请求的串行处理 3. **流处理** ```java BufferedReader in = ... // 读取请求 DataOutputStream out = ... // 发送响应 ``` - 使用缓冲字符流读取请求(适合文本协议) - 使用数据输出流确保字节级精确控制 4. **HTTP响应构建** ```java "HTTP/1.1 200 OK\r\n" ``` - 状态行必须包含协议版本和状态码 - `\r\n`是HTTP协议要求的换行符 5. **响应头设置** ```java Content-Type: text/plain Content-Length: 7 ``` - 明确响应内容类型(纯文本) - 精确指定内容长度("200 OK"为7字节) 6. **资源清理** ```java clientSocket.close(); ``` - 在finally块中确保连接关闭 - 防止资源泄漏 客户端-服务器交互流程: 1. 客户端建立TCP连接(`Socket("localhost", 80)`) 2. 服务器接受连接(`accept()`) 3. 客户端发送请求(`GET index.htmtl`) 4. 服务器读取请求并生成响应 5. 服务器发送符合HTTP协议的响应 6. 客户端接收并显示响应 7. 连接关闭,等待下一个请求 注意事项: 1. 需要管理员权限才能绑定80端口(Linux/macOS使用sudo) 2. 实际HTTP协议要求更复杂的头处理,本示例做了简化 3. 没有实现真正的多线程,但通过循环支持顺序请求处理 4. 未处理请求正文(仅读取第一行请求头) 5. 客户端代码中的"index.htmtl"可能是拼写错误(应为index.html) 测试方法: 1. 先运行服务器程序 2. 然后运行客户端程序 3. 预期客户端输出: ``` Received HTTP Response! HTTP/1.1 200 OK ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值