inet_ntoa的一个典型误用

 

在做linux下面的网络编程时写了如下一段程序


执行之后打印结果总是源IP和目的IP一样,如下(程序名为pro):
#./pro -s 192.1.1.1 -d 192.2.2.2
#source[192.2.2.2] dest[192.2.2.2]

       检查了很久都没发现任何错误,最始还以为赋值时不小心把源IP赋成了目的IP,但是事实上编写的时候还是比较细心, 没犯这种错误,呵呵:)。
       最后一步步排查,将saddr.s_addr与daddr.s_addr的数值都打印出来了,发现二者的数值完全不一样,既然它们两不 一样,通过inet_ntoa()返回的字符串也应该不一样,所以,问题就出在了inet_ntoa()的应用上了。man了一下,发现了对inet_ntoa的这么一行说明:

The inet_ntoa() function converts the Internet host address in, given
in network byte order, to a string in IPv4 dotted-decimal notation.
The string is returned in a statically allocated buffer, which subse-
quent calls will overwrite.


       红色字体的意思是inet_ntoa()返回的字符串是临时装在一个静态分配的缓冲区里面,下一次调用此函数的时候缓冲区会被重写 。哈哈,终于发现为什么转化为字符串后,源地址老是与目的地址一样了,关键原因在于指针的使用,前面是将用来装载源地址和目的地址字符串的source与dest声明成了指针,那么,赋值之后二者指针就会都指向inet_ntoa()的临时缓冲区里,在给dest赋值时,缓冲区被重写了,所以source指针的值就变成了目的地址,二者自然就一样了。因此,改成下面的代码就没问题了:

习惯了用家用电器不看说明书-_-!,看来,以后在使用东西之前还是多看看说明,少走弯路

 

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> #define TRUE 1 #define BUF_SIZE 1024 #define SMALL_BUF 100 typedef struct sockaddr SA; void* request_handler(void* arg); void send_data(FILE* fp, char* ct, char* file_name); char* content_type(char* file); void send_error(FILE* fp); void error_handling(char* message); int main(int argc, char* argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; int clnt_adr_sz; char buf[BUF_SIZE] = { 0 }; pthread_t tid; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); //为serv_sock套接字文件描述符设置SO_REUSEADDR可选项 int option = TRUE; int optlen = sizeof(option); setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*)& option, optlen); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (SA*)& serv_adr, sizeof(serv_adr)) == -1) error_handling("bind() error!"); if (listen(serv_sock, 20) == -1) error_handling("listen() error!"); while (1) { clnt_adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (SA*)& clnt_adr, &clnt_adr_sz); if (clnt_sock == -1) { error_handling("accept() error!"); continue; } printf("Connection Request: %s:%d\n", inet_ntoa(clnt_adr.sin_addr), ntohs(clnt_adr.sin_port)); pthread_create(&tid, NULL, request_handler, &clnt_sock); //创建线程 pthread_detach(tid); //线程分离 } close(serv_sock); return 0; } void* request_handler(void* arg) { int clnt_sock = *(int*)arg; char req_line[SMALL_BUF] = { 0 }; FILE* clnt_read, * clnt_write; char method[10] = { 0 }; char ct[15] = { 0 }; char file_name[30] = { 0 }; clnt_read = fdopen(clnt_sock, "r"); clnt_write = fdopen(dup(clnt_sock), "w"); fgets(req_line, SMALL_BUF, clnt_read); if (strstr(req_line, "HTTP/") == NULL) //查看是否为HTTP提出的请求 { send_error(clnt_write); fclose(clnt_read); fclose(clnt_write); return NULL; } strcpy(method, strtok(req_line, " /")); //获取请求行中的方法 strcpy(file_name, strtok(NULL, " /")); //获取请求文件名 //printf("file_name: %s\n", file_name); strcpy(ct, content_type(file_name)); //获取Content-type if (strcmp(method, "GET") != 0) //查看是否为GET方式请求 { send_error(clnt_write); fclose(clnt_read); fclose(clnt_write); return NULL; } fclose(clnt_read); send_data(clnt_write, ct, file_name); //响应处理 return NULL; } //发送响应消息给客户端 void send_data(FILE* fp, char* ct, char* file_name) { char protocol[] = "HTTP/1.1 200 OK\r\n"; char server[] = "Server:Linux Web Server \r\n"; char cnt_len[] = "Content-length:2048\r\n"; char cnt_type[SMALL_BUF] = { 0 }; char buf[BUF_SIZE] = { 0 }; FILE* send_file; sprintf(cnt_type, "Content-type:%s\r\n\r\n", ct); send_file = fopen(file_name, "r"); if (send_file == NULL) { printf("open file [%s] error!\n", file_name); send_error(fp); return; } //传输HTTP的状态行+消息头信息 fputs(protocol, fp); fputs(server, fp); fputs(cnt_len, fp); fputs(cnt_type, fp); //传输HTTP的消息体信息,即HTML文件内容 while (fgets(buf, BUF_SIZE, send_file) != NULL) { fputs(buf, fp); fflush(fp); } fflush(fp); fclose(fp); } //区分Content-type char* content_type(char* file) { char extension[SMALL_BUF] = { 0 }; //存放文件扩展名 char file_name[SMALL_BUF] = { 0 }; //存放文件名 strcpy(file_name, file); strtok(file_name, "."); strcpy(extension, strtok(NULL, ".")); if (!strcmp(extension, "html") || !strcmp(extension, "htm")) return "text/html"; else return "text/plain"; } void send_error(FILE * fp) { char protocol[] = "HTTP/1.1 400 Bad Request\r\n"; char server[] = "Server:Linux Web Server \r\n"; char cnt_len[] = "Content-length:2048\r\n"; char cnt_type[] = "Content-type:text/html\r\n\r\n"; char content[] = "<html>\ <head><title>NETWORK</title></head>\ <body><font size=+5><br>\ 发生错误!请检查请求文件名和请求方式!\ </font></body>\ </html>"; //传输HTTP的状态行+消息头信息 fputs(protocol, fp); fputs(server, fp); fputs(cnt_len, fp); fputs(cnt_type, fp); //传输HTTP的消息体信息,即HTML文件内容 fputs(content, fp); fflush(fp); fclose(fp); } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
08-08
`inet_ntoa` 和 `inet_ntop` 是用于将 IP 地址从网络字节序的二进制形式(如 `struct in_addr` 或 `struct in6_addr`)转换为可读的字符串形式(如 `"192.168.1.1"` 或 `"2001:db8::1"`)的函数。它们的主要区别在于: | 特性 | `inet_ntoa` | `inet_ntop` | |------|-------------|-------------| | 支持的协议 | 仅 IPv4 | IPv4 和 IPv6 | | 线程安全性 | 不安全(使用静态缓冲区) | 安全(用户提供缓冲区) | | 函数原型 | `char *inet_ntoa(struct in_addr in);` | `const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);` | | 返回值类型 | 静态缓冲区指针 | 用户提供的缓冲区指针 | | 推荐使用 | 已不推荐使用 | 推荐使用 | --- ### ✅ `inet_ntoa` 示例(IPv4) ```cpp #include <iostream> #include <arpa/inet.h> // Linux // #include <winsock2.h> // Windows int main() { struct in_addr ip; inet_aton("192.168.1.1", &ip); // 将字符串 IP 转为网络结构体 char *ip_str = inet_ntoa(ip); // 转换为字符串 std::cout << "IP: " << ip_str << std::endl; return 0; } ``` #### 说明: - `inet_ntoa` 使用内部静态缓冲区,因此每次调用会覆盖上次的结果。 - **不是线程安全的**,不适合多线程程序或频繁调用的场景。 --- ### ✅ `inet_ntop` 示例(支持 IPv4 和 IPv6) ```cpp #include <iostream> #include <arpa/inet.h> int main() { struct in_addr ipv4; inet_aton("192.168.1.1", &ipv4); char ip4_str[INET_ADDRSTRLEN]; // 存放 IPv4 字符串 inet_ntop(AF_INET, &ipv4, ip4_str, sizeof(ip4_str)); std::cout << "IPv4: " << ip4_str << std::endl; // IPv6 示例 struct in6_addr ipv6; inet_pton(AF_INET6, "2001:db8::1", &ipv6); char ip6_str[INET6_ADDRSTRLEN]; // 存放 IPv6 字符串 inet_ntop(AF_INET6, &ipv6, ip6_str, sizeof(ip6_str)); std::cout << "IPv6: " << ip6_str << std::endl; return 0; } ``` #### 说明: - `inet_ntop` 是线程安全的,用户自己提供缓冲区。 - 支持 IPv4 和 IPv6,是现代网络编程中推荐使用的函数。 --- ### 🧠 总结对比 | 比较点 | `inet_ntoa` | `inet_ntop` | |--------|-------------|-------------| | 是否线程安全 | ❌ 否 | ✅ 是 | | 是否支持 IPv6 | ❌ 否 | ✅ 是 | | 是否推荐使用 | ❌ 否 | ✅ 是 | | 是否需要手动提供缓冲区 | ❌ 否 | ✅ 是 | ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值