getsockname和getpeername函数
getsockname函数用于获取与某个套接字关联的本地协议地址
getpeername函数用于获取与某个套接字关联的外地协议地址
定义如下:
#include<sys/socket.h> int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen); int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);对于这两个函数,如果函数调用成功,则返回0,如果调用出错,则返回-1。
使用这两个函数,我们可以通过套接字描述符来获取自己的IP地址和连接对端的IP地址,如在未调用bind函数的TCP客户端程序上,可以通过调用getsockname()函数获取由内核赋予该连接的本地IP地址和本地端口号,还可以在TCP的服务器端accept成功后,通过getpeername()函数来获取当前连接的客户端的IP地址和端口号。
如下面的客户端-服务器程序:
服务器端代码
/*服务器端*/ #define MAXLINE 4096 #define PORT 6563 #define LISTENQ 1024 #include<stdio.h> #include<sys/socket.h> #include<netinet/in.h> #include<unistd.h> #include<string.h> #include<arpa/inet.h> int main() { int listenfd, connfd; struct sockaddr_in servaddr;//服务器绑定的地址 struct sockaddr_in listendAddr, connectedAddr, peerAddr;//分别表示监听的地址,连接的本地地址,连接的对端地址 int listendAddrLen, connectedAddrLen, peerLen; char ipAddr[INET_ADDRSTRLEN];//保存点分十进制的地址 listenfd = socket(AF_INET, SOCK_STREAM, 0); memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT); bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));//服务器端绑定地址 listen(listenfd, LISTENQ); listendAddrLen = sizeof(listendAddr); getsockname(listenfd, (struct sockaddr *)&listendAddr, &listendAddrLen);//获取监听的地址和端口 printf("listen address = %s:%d\n", inet_ntoa(listendAddr.sin_addr), ntohs(listendAddr.sin_port)); while(1) { connfd = accept(listenfd, (struct sockaddr *)NULL, NULL); connectedAddrLen = sizeof(connectedAddr); getsockname(connfd, (struct sockaddr *)&connectedAddr, &connectedAddrLen);//获取connfd表示的连接上的本地地址 printf("connected server address = %s:%d\n", inet_ntoa(connectedAddr.sin_addr), ntohs(connectedAddr.sin_port)); getpeername(connfd, (struct sockaddr *)&peerAddr, &peerLen); //获取connfd表示的连接上的对端地址 printf("connected peer address = %s:%d\n", inet_ntop(AF_INET, &peerAddr.sin_addr, ipAddr, sizeof(ipAddr)), ntohs(peerAddr.sin_port)); } return 0; }
上面的代码中,在调用listen函数之后就获取监听套接字描述符对应的本地地址,在accept()函数后,由于accept返回了一个套接字描述符connfd用于表示该连接,所以可以对这个connfd调用getsockname函数和getpeername函数,分别获取内核赋予该连接的本地IP地址和连接的对端地址。
客户端代码
/*客户端*/ #define PORT 6563 #include<stdio.h> #include<sys/socket.h> #include<netinet/in.h> #include<unistd.h> #include<string.h> #include<arpa/inet.h> int main(int argc, char **argv) { struct sockaddr_in servaddr;//服务器端地址 struct sockaddr_in clientAddr;//客户端地址 int sockfd; int clientAddrLen = sizeof(clientAddr); char ipAddress[INET_ADDRSTRLEN];//保存点分十进制的ip地址 if(argc < 2) { printf("parameter error"); } sockfd = socket(AF_INET, SOCK_STREAM, 0); memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { printf("server address error\n");//地址参数不合法 } connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//向服务器端发起连接请求 getsockname(sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen);//获取sockfd表示的连接上的本地地址 printf("client:client ddress = %s:%d\n", inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress)), ntohs(clientAddr.sin_port)); return 0; }
在客户端的代码中,调用connect函数后,即可调用getsockname来获连接上的本地地址。
代码的运行结果如下:
从上面的代码中可以看到,服务器端listenfd套接字描述符对应的地址即为绑定的通配IP地址和指定的端口,而connfd套接字描述符对应的连接的服务器端的地址为内核赋予的地址和用户指定的端口。
上面的客户端与服务器端的代码中使用了函数inet_ntoa,inet_pton对32位的地址进行转换,其中inet_ntoa是较老的函数,与它一起的还有函数inet_addr和inet_ntoa,这三个函数的定义如下:
#include<arpa/inet.h> int inet_aton(const char *strptr, struct in_addr *addrptr); in_addr_t inet_addr(const char *strptr); char *inet_ntoa(struct in_addr inaddr);
inet_aton与inet_addr函数的功能类似,都是将点分十进制的字符串表示的IP地址转换成32位的网络字节序的IPv4地址。
inet_ntoa函数将32位的网络字节序的IPv4地址转换成点分十进制的字符串表示的IP地址,inet_addr函数已被废弃,并且这三个函数只针对IPv4地址有效,在点分十进制数串和32位的网络字节序二进制值间进行转换,如果要对于IPv4和IPv6都适用,那么使用下面两个函数:
#include<arpa/inet.h> int inet_pton(int family, const char *strptr, void *addrptr); const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
函数中的p和n分别代表 表达式(presentation) 和 数值(numeric)
所以inet_pton函数将strptr指针所指的字符串转换为网络地址(IPv4和IPv6),再将地址保存到addrptr指向的结构体中,inet_ntop将网络地址转为字符串表示的地址,结果存放在strptr中,len参数是strptr的大小。
这两个函数支持IPv4和IPv6,所以需要通过参数family来指定,当前要转换的是IPv4地址还是IPv6地址。
Reference
《UNIX网络编程 卷1:套接字联网API(第3版)》