getsockname() 和 getpeername() 的原型如下:
[C++] 纯文本查看 复制代码 #include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
getsockname() 返回与某个套接字关联的本地协议地址,getpeername() 返回与某个套接字关联的外地协议地址。
下面用一段代码演示这两个函数的使用情况:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//显示错误信息
static void display_error(const char *err_msg)
{
perror (err_msg);
exit (EXIT_FAILURE);
}
//获取本地协议地址或外地协议地址
char *sock_addr(int s, char *buf, size_t buf_size, int type)
{
int z;
struct sockaddr_in addr_inet;
int len;
len = sizeof(addr_inet);
if (!type) {
z = getsockname(s, (struct sockaddr *)&addr_inet, &len);
if (z == -1) {
return NULL; // 获取失败
}
bzero(buf, buf_size);
snprintf(buf, buf_size, "%s:%u", //将地址信息存储到缓冲区
inet_ntoa(addr_inet.sin_addr),
(unsigned)ntohs(addr_inet.sin_port));
return buf;
} else {
z = getpeername(s, (struct sockaddr *)&addr_inet, &len);
if (z == -1) {
return NULL; // Failed
}
bzero(buf, buf_size);
snprintf(buf, buf_size, "%s:%u", //将地址信息存储到缓冲区
inet_ntoa(addr_inet.sin_addr),
(unsigned)ntohs(addr_inet.sin_port));
return buf;
}
}
int main(int argc, char **argv, char **envp)
{
int z;
int sockfd;
int len;
char buf[64];
struct sockaddr_in addr_inet, client_addr;
//创建一个套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if ( sockfd == -1 ) {
display_error("socket()");
}
memset (&addr_inet, 0, sizeof(addr_inet));
addr_inet.sin_family = AF_INET;
addr_inet.sin_port = htons(2012); //服务器绑定在 2012 端口
inet_aton ("192.168.1.108", &addr_inet.sin_addr); //服务器 IP 地址
len = sizeof(addr_inet);
//将地址绑定到套接字上
z = bind(sockfd, (struct sockaddr *)&addr_inet, len);
if ( z == -1) {
display_error("bind()");
}
//获取服务器端 IP 地址
if (!sock_addr(sockfd, buf, sizeof(buf), 0)) {
display_error("sock_addr()");
}
printf("Server address is '%s'\n", buf);
if (listen(sockfd, 5) == -1) {
display_error("listen()");
}
int new_fd;
if ((new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &len)) == -1) {
display_error("accept()");
}
if (!sock_addr(new_fd, buf, sizeof(buf), 1)) { //注意:如果应用 getsockname() 并通过 new_fd 来获取到地址仍然是服务器端地址
display_error("sock_addr()");
}
printf ("Client address is '%s'\n", buf);
close(new_fd);
close(sockfd);
return 0;
}
运行程序:beyes@beyes :~/c/getsockname> ./sockname
Server address is '192.168.1.108:2012' 运行上面程序后,程序打印出了服务器端的地址及端口号,然后进入监听阶段。这时候,我们不必要再写一个客户端连接程序进行测试,而是利用 nc 这个程序进行连接。这里使用一台 IP 为 192.168.1.110 的 Windows 主机上的 nc.exe 进行连接:C:\>nc.exe 192.168.1.108 2012 回车执行命令后,命令会马上退出,因为程序中的处理情况是,一旦连接发生,服务器也马上退出,此时可看到服务器端的输出:Client address is '192.168.1.110:10269' 这表示,客户机的 IP 是 192.168.1.110 ,并且从 10269 这个端口发出连接。
服务器端通过调用 accept() 接受了客户端的连接,此时对返回的 new_fd 应用 getpeername() 时可以获得客户端的的身份信息。需要注意的是,不能用 getsockname() 从 new_fd 上获得客户端的信息,因为 getsockname() 用于返回的是“本地协议地址”;如果这么做的话,得到的信息仍然是“Server address is '192.168.1.108:2012'。
一个实际的例子如 xinetd 中的 telnet 服务。当 xinetd 调用 accept() 响应连接请求时,它返回两个值:一个是已连接的套接字描述符(函数返回值);另一个是客户的 IP 地址和端口号(存放在 accept() 第 2 个参数所指向的套接字地址结构中)。然后 xinetd 调用 fork() 派生出一个子进程,由于子进程是父进程内存镜像的一个副本,因此父进程中的那个套接字地址结构在子进程中也可用,那个已连接的套接字描述符也是如此(如上面的 new_fd,因为描述符父子进程之间是共享的)。接着,子进程会调用 exec 执行真正的服务器程序 Telnet 时,子进程的内存映像就会被新的 Telnet 程序所替代,此时包含对端地址的那个套接字地址结构就此消失了(如上面的那个 client_addr),然而那个已经连接了的套接字描述符(new_fd)却可以跨 exec 继续保持开放。最后在进入 Telnet 的领空之后,它首先要调用的就是 getpeername() 用于获取客户的 IP 地址和端口号。
从上所述,在程序中,可用 getpeername() 作用于 new_fd 这个套接字描述符来获取对端的 IP 地址和端口号;在没有调用 exec 之前,也可以直接从套接字地质结构 client_addr 中直接提取。 |