getpeername系统调用及示例
data-ad-format="fluid"
data-ad-layout-key="-7k+ex-4a-9w+4a">
我们已经介绍了 getsockname,所以接下来应该介绍 getpeername。
1. 函数介绍 getpeername 是一个 Linux 系统调用,用于获取已连接套接字的对方(peer)的协议地址。这个地址包含了与本端套接字建立连接的那个远程主机的 IP 地址 和 端口号。
你可以把它想象成查看已接通电话的对方号码:
这对于以下场景非常有用:
服务器: 服务器 accept 一个连接后,得到一个新的已连接套接字。服务器可以使用 getpeername 来获取是哪个客户端(IP 和端口)连接了进来。
客户端: 客户端在 connect 成功后,可以使用 getpeername 来确认它连接到了哪个服务器地址(IP 和端口),尤其是在连接时使用了域名,想确认解析后的具体 IP。
调试和日志: 在网络程序中记录连接信息时,getpeername 是获取对端地址的标准方法。
2. 函数原型 1 2 3 4 #include <sys/socket.h> // 必需 int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
3. 功能
获取对端地址: 检索与已连接套接字 sockfd 关联的对方协议地址。
填充结构体: 将获取到的地址信息填充到调用者提供的 struct sockaddr 结构体指针 addr 所指向的内存中。
返回地址大小: 通过 addrlen 参数返回实际存储在 addr 中的地址结构的大小。
4. 参数 int sockfd: 这是一个有效的、已连接的套接字文件描述符。
struct sockaddr *addr: 这是一个指向套接字地址结构的指针,用于接收对方的地址信息。
调用者需要提供一个足够大的缓冲区(例如 struct sockaddr_in 或 struct sockaddr_in6)并将其地址传递给 addr。
socklen_t *addrlen: 这是一个指向 socklen_t 类型变量的指针。
5. 返回值
6. 相似函数,或关联函数
getsockname: 用于获取本地(本端)套接字的地址信息。
connect: 客户端使用此函数连接到服务器。连接成功后,可以使用 getpeername 获取服务器地址。
accept: 服务器使用此函数接受客户端连接,返回一个已连接的新套接字,可以使用 getpeername 获取客户端地址。
socket: 创建套接字。
7. 示例代码 示例 1:客户端使用 getpeername 确认服务器地址 这个例子演示了 TCP 客户端在连接到服务器后,如何使用 getpeername 来获取它所连接的服务器的地址信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 // getpeername_client.c #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // inet_ntoa, ntohs #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define SERVER_PORT 8093 #define SERVER_IP "127.0.0.1" // 可以换成域名测试,如 "localhost" int main() { int sock; struct sockaddr_in server_addr, peer_addr; socklen_t peer_addr_len = sizeof(peer_addr); sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } printf("Client socket created (fd: %d)\n", sock); // 配置服务器地址并连接 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) { fprintf(stderr, "Invalid server address: %s\n", SERVER_IP); close(sock); exit(EXIT_FAILURE); } printf("Connecting to server %s:%d...\n", SERVER_IP, SERVER_PORT); if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect failed"); close(sock); exit(EXIT_FAILURE); } printf("Connected to server successfully.\n"); // --- 关键: 使用 getpeername 获取服务器地址 --- printf("\n--- Getting peer (server) name with getpeername ---\n"); // 重新初始化长度,以防被修改 peer_addr_len = sizeof(peer_addr); if (getpeername(sock, (struct sockaddr *)&peer_addr, &peer_addr_len) == 0) { printf("Connected to server at:\n"); printf(" Peer IP Address: %s\n", inet_ntoa(peer_addr.sin_addr)); printf(" Peer Port: %d\n", ntohs(peer_addr.sin_port)); } else { perror("getpeername failed"); // 常见错误:ENOTCONN (如果套接字未连接) } // (对比) 使用 getsockname 获取自己的本地地址 struct sockaddr_in local_addr; socklen_t local_addr_len = sizeof(local_addr); if (getsockname(sock, (struct sockaddr *)&local_addr, &local_addr_len) == 0) { printf("\nMy local address is:\n"); printf(" Local IP Address: %s\n", inet_ntoa(local_addr.sin_addr)); printf(" Local Port: %d\n", ntohs(local_addr.sin_port)); } else { perror("getsockname failed"); } printf("\nClient is connected and verified peer/local addresses.\n"); // ... 进行通信 ... close(sock); printf("Client socket closed.\n"); return 0; }
代码解释:
创建 TCP 套接字。
配置服务器地址并调用 connect 连接。
关键步骤: 连接成功后,调用 getpeername(sock, (struct sockaddr *)&peer_addr, &peer_addr_len)。
getpeername 成功后,打印出服务器的地址(IP 和端口)。
对比: 调用 getsockname 获取自己的本地地址。
最后关闭套接字。
示例 2:服务器使用 getpeername 获取客户端地址 这个例子演示了 TCP 服务器在 accept 一个连接后,如何使用 getpeername 来获取连接进来的客户端的地址信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 // getpeername_server.c #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define PORT 8093 #define BACKLOG 10 void handle_client(int client_fd) { struct sockaddr_in peer_addr; socklen_t peer_addr_len = sizeof(peer_addr); printf("Handling new client connection (fd: %d)\n", client_fd); // --- 关键: 使用 getpeername 获取客户端地址 --- if (getpeername(client_fd, (struct sockaddr *)&peer_addr, &peer_addr_len) == 0) { printf(" Client connected from: %s:%d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port)); } else { perror(" getpeername on client socket failed"); } // 为了演示,我们立即关闭连接 // 实际应用中,这里会进行数据读写 close(client_fd); printf(" Closed connection to client.\n"); } int main() { int server_fd, client_fd; struct sockaddr_in address; socklen_t client_addr_len; struct sockaddr_in client_address; int opt = 1; server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == 0) { perror("socket failed"); exit(EXIT_FAILURE); } if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } if (listen(server_fd, BACKLOG) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Server listening on port %d\n", PORT); // 演示接受几个连接 for (int i = 0; i < 3; ++i) { printf("\n--- Waiting for connection #%d ---\n", i + 1); client_addr_len = sizeof(client_address); client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len); if (client_fd < 0) { perror("accept failed"); continue; } printf("New connection accepted (fd: %d)\n", client_fd); // accept 返回的 client_fd 已经包含了客户端地址信息, // 我们也可以用 getpeername 再次确认 handle_client(client_fd); } close(server_fd); printf("Server socket closed.\n"); return 0; }
如何测试:
在一个终端编译并运行服务器:gcc -o getpeername_server getpeername_server.c ./getpeername_server
在另外几个终端运行客户端(可以多次运行):gcc -o getpeername_client getpeername_client.c ./getpeername_client
代码解释:
创建、绑定、监听标准的 TCP 服务器套接字。
进入一个循环,accept 三个客户端连接。
每次 accept 成功后,调用 handle_client(client_fd)。
在 handle_client 函数中:
关键步骤: 调用 getpeername(client_fd, …)。
client_fd 是 accept 返回的、与特定客户端关联的已连接套接字。
getpeername 会返回该客户端的 IP 地址和端口号。
打印出客户端的地址信息。
函数结束时关闭与该客户端的连接。
示例 3:对比 getsockname 和 getpeername 这个例子在一个已连接的套接字上同时使用 getsockname 和 getpeername,清晰地展示它们的区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 // compare_sock_peer_name.c #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define SERVER_PORT 8094 #define SERVER_IP "127.0.0.1" void print_socket_addresses(int sock, const char* role) { struct sockaddr_in local_addr, peer_addr; socklen_t addr_len; printf("--- Addresses for %s socket (fd: %d) ---\n", role, sock); // 获取本地地址 addr_len = sizeof(local_addr); if (getsockname(sock, (struct sockaddr *)&local_addr, &addr_len) == 0) { printf(" Local Address : %s:%d\n", inet_ntoa(local_addr.sin_addr), ntohs(local_addr.sin_port)); } else { perror(" getsockname failed"); } // 获取对端地址 addr_len = sizeof(peer_addr); if (getpeername(sock, (struct sockaddr *)&peer_addr, &addr_len) == 0) { printf(" Peer Address : %s:%d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port)); } else { perror(" getpeername failed"); // 如果套接字未连接,会打印错误,如 "Transport endpoint is not connected" } printf("----------------------------------------\n"); } int main() { int server_fd, client_sock; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len = sizeof(client_addr); int opt = 1; // --- 服务器端 --- server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == 0) { perror("server socket failed"); exit(EXIT_FAILURE); } if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("server setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } 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(SERVER_PORT); if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("server bind failed"); close(server_fd); exit(EXIT_FAILURE); } if (listen(server_fd, 5) < 0) { perror("server listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Server listening on port %d\n", PORT); // --- 客户端 --- client_sock = socket(AF_INET, SOCK_STREAM, 0); if (client_sock < 0) { perror("client socket failed"); close(server_fd); exit(EXIT_FAILURE); } memset(&client_addr, 0, sizeof(client_addr)); client_addr.sin_family = AF_INET; client_addr.sin_port = htons(SERVER_PORT); if (inet_pton(AF_INET, SERVER_IP, &client_addr.sin_addr) <= 0) { fprintf(stderr, "Invalid server IP\n"); close(server_fd); close(client_sock); exit(EXIT_FAILURE); } printf("\nClient connecting to server...\n"); if (connect(client_sock, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) { perror("client connect failed"); close(server_fd); close(client_sock); exit(EXIT_FAILURE); } // --- 连接建立后,检查客户端套接字 --- print_socket_addresses(client_sock, "Client"); // --- 服务器接受连接 --- int accepted_sock = accept(server_fd, NULL, NULL); // 忽略客户端地址 if (accepted_sock < 0) { perror("server accept failed"); close(server_fd); close(client_sock); exit(EXIT_FAILURE); } // --- 检查服务器端的已连接套接字 --- print_socket_addresses(accepted_sock, "Server-Accepted"); // 客户端和服务器的本地地址应该等于对方的对端地址 printf("\nNote:\n"); printf("- Client's Local Address should equal Server's Peer Address.\n"); printf("- Server's Local Address should equal Client's Peer Address.\n"); close(client_sock); close(accepted_sock); close(server_fd); return 0; }
代码解释:
程序同时扮演服务器和客户端的角色。
设置服务器套接字并开始监听。
创建客户端套接字并连接到服务器。
定义一个 print_socket_addresses 函数,它接受一个套接字和一个角色名称(“Client” 或 “Server-Accepted”)。
在该函数内部:
在 main 函数中:
客户端连接成功后,调用 print_socket_addresses(client_sock, “Client”)。
服务器 accept 连接后,调用 print_socket_addresses(accepted_sock, “Server-Accepted”)。
通过打印的地址可以清楚地看到:
重要提示与注意事项: 仅对已连接套接字有效: getpeername 只能用于已连接的套接字。对于未连接的套接字(如刚创建的套接字,或未 connect 的 UDP 套接字),调用会失败,errno 通常为 ENOTCONN。
区分 getsockname 和 getpeername:
addrlen 的初始化: 与 getsockname 一样,在调用前,必须将 *addrlen 初始化为目标缓冲区的大小。调用后,它会被更新为实际的地址结构大小。
服务器常用: 服务器在 accept 后,经常使用 getpeername 来记录或显示是哪个客户端连接了进来。
客户端确认: 客户端可以用它来确认连接的服务器地址,尤其是在使用域名时。
错误处理: 始终检查返回值。最常见的错误是 ENOTCONN(套接字未连接)。
总结:
getpeername 是一个用于获取已连接套接字对端地址信息的系统调用。它与 getsockname 相辅相成,分别用于查询连接两端的地址。在服务器记录客户端信息和客户端确认服务器信息等场景中非常有用。理解其参数和使用条件对于进行有效的网络编程至关重要。
https://blog.csdn.net/zidier215/article/details/151373575?sharetype=blogdetail&sharerId=151373575&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118