好的,我们继续按照您列表的顺序,从最后一个已介绍的函数 (listen
) 往前,来介绍下一个重要的系统调用。在您的列表中,listen
之前的一个函数是 bind
,但我们已经介绍过了。
让我们回顾一下列表的结尾部分:
… sendto
, recvfrom
, sendmsg
, recvmsg
, shutdown
, bind
, listen
, getsockname
, getpeername
, …
我们已经介绍了 getsockname
,所以接下来应该介绍 getpeername
。
1. 函数介绍 链接到标题
getpeername
是一个 Linux 系统调用,用于获取已连接套接字的对方(peer)的协议地址。这个地址包含了与本端套接字建立连接的那个远程主机的 IP 地址 和 端口号。
你可以把它想象成查看已接通电话的对方号码:
- 你和朋友正在通话(套接字已连接)。
- 你想知道现在和你通话的人的电话号码是多少(对方地址)。
getpeername
就是查看这个对方号码的功能。
这对于以下场景非常有用:
- 服务器: 服务器
accept
一个连接后,得到一个新的已连接套接字。服务器可以使用getpeername
来获取是哪个客户端(IP 和端口)连接了进来。 - 客户端: 客户端在
connect
成功后,可以使用getpeername
来确认它连接到了哪个服务器地址(IP 和端口),尤其是在连接时使用了域名,想确认解析后的具体 IP。 - 调试和日志: 在网络程序中记录连接信息时,
getpeername
是获取对端地址的标准方法。
2. 函数原型 链接到标题
#include <sys/socket.h> // 必需
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
3. 功能 链接到标题
- 获取对端地址: 检索与已连接套接字
sockfd
关联的对方协议地址。 - 填充结构体: 将获取到的地址信息填充到调用者提供的
struct sockaddr
结构体指针addr
所指向的内存中。 - 返回地址大小: 通过
addrlen
参数返回实际存储在addr
中的地址结构的大小。
4. 参数 链接到标题
int sockfd
: 这是一个有效的、已连接的套接字文件描述符。- 对于 TCP,这意味着已经成功调用了
connect
(客户端)或accept
(服务器返回的新套接字)。 - 对于 UDP,如果调用了
connect
,也可以使用getpeername
。 - 如果套接字未连接,调用会失败。
- 对于 TCP,这意味着已经成功调用了
struct sockaddr *addr
: 这是一个指向套接字地址结构的指针,用于接收对方的地址信息。- 调用者需要提供一个足够大的缓冲区(例如
struct sockaddr_in
或struct sockaddr_in6
)并将其地址传递给addr
。
- 调用者需要提供一个足够大的缓冲区(例如
socklen_t *addrlen
: 这是一个指向socklen_t
类型变量的指针。- 输入: 在调用
getpeername
时,这个变量必须被初始化为addr
指向的缓冲区的大小(以字节为单位)。例如,如果addr
指向struct sockaddr_in
,则*addrlen
应初始化为sizeof(struct sockaddr_in)
。 - 输出:
getpeername
返回时,这个变量会被更新为实际存储在addr
中的地址结构的大小。
- 输入: 在调用
5. 返回值 链接到标题
- 成功时: 返回 0。同时,
addr
指向的结构体被成功填充,*addrlen
被更新为实际地址大小。 - 失败时: 返回 -1,并设置全局变量
errno
来指示具体的错误原因(例如EBADF
sockfd
无效,EINVAL
addrlen
指针无效,ENOTSOCK
sockfd
不是一个套接字,ENOTCONN
套接字未连接等)。
6. 相似函数,或关联函数 链接到标题
getsockname
: 用于获取本地(本端)套接字的地址信息。connect
: 客户端使用此函数连接到服务器。连接成功后,可以使用getpeername
获取服务器地址。accept
: 服务器使用此函数接受客户端连接,返回一个已连接的新套接字,可以使用getpeername
获取客户端地址。socket
: 创建套接字。
7. 示例代码 链接到标题
示例 1:客户端使用 getpeername
确认服务器地址
链接到标题
这个例子演示了 TCP 客户端在连接到服务器后,如何使用 getpeername
来获取它所连接的服务器的地址信息。
// 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)
。sock
: 已连接的套接字。&peer_addr
: 指向用于存储对端地址的sockaddr_in
结构的指针。&peer_addr_len
: 指向socklen_t
变量的指针,初始化为sizeof(peer_addr)
。
getpeername
成功后,打印出服务器的地址(IP 和端口)。- 对比: 调用
getsockname
获取自己的本地地址。 - 最后关闭套接字。
示例 2:服务器使用 getpeername
获取客户端地址
链接到标题
这个例子演示了 TCP 服务器在 accept
一个连接后,如何使用 getpeername
来获取连接进来的客户端的地址信息。
// 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
,清晰地展示它们的区别。
// 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”)。 - 在该函数内部:
- 调用
getsockname
获取并打印本地地址。 - 调用
getpeername
获取并打印对端地址。
- 调用
- 在
main
函数中:- 客户端连接成功后,调用
print_socket_addresses(client_sock, "Client")
。 - 服务器
accept
连接后,调用print_socket_addresses(accepted_sock, "Server-Accepted")
。
- 客户端连接成功后,调用
- 通过打印的地址可以清楚地看到:
- 客户端的本地地址 == 服务器
accept
套接字的对端地址。 - 服务器的本地地址 == 客户端的对端地址。
- 客户端的本地地址 == 服务器
重要提示与注意事项: 链接到标题
- 仅对已连接套接字有效:
getpeername
只能用于已连接的套接字。对于未连接的套接字(如刚创建的套接字,或未connect
的 UDP 套接字),调用会失败,errno
通常为ENOTCONN
。 - 区分
getsockname
和getpeername
:getsockname
: 获取本地地址(我的地址)。getpeername
: 获取对端地址(对方的地址)。
addrlen
的初始化: 与getsockname
一样,在调用前,必须将*addrlen
初始化为目标缓冲区的大小。调用后,它会被更新为实际的地址结构大小。- 服务器常用: 服务器在
accept
后,经常使用getpeername
来记录或显示是哪个客户端连接了进来。 - 客户端确认: 客户端可以用它来确认连接的服务器地址,尤其是在使用域名时。
- 错误处理: 始终检查返回值。最常见的错误是
ENOTCONN
(套接字未连接)。
总结:
getpeername
是一个用于获取已连接套接字对端地址信息的系统调用。它与 getsockname
相辅相成,分别用于查询连接两端的地址。在服务器记录客户端信息和客户端确认服务器信息等场景中非常有用。理解其参数和使用条件对于进行有效的网络编程至关重要。