這兩個函數或者返回與某個套接字關聯的本地協議地址(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
getpeername只有在連接建立以後才調用,否則不能正確獲得對方地址和端口,所以它的參數描述字一般是已連接描述字而非監聽套接口描述字。
沒有連接的UDP不能調用getpeername,但是可以調用getsockname和TCP一樣,它的地址和端口不是在調用socket就指定了,而是在第一次調用sendto函數以後。
已經連接的UDP,在調用connect以後,這2個函數(getsockname,getpeername)都是可以用的。但是這時意義不大,因為已經連接(connect)的UDP已經知道對方的地址。
需要這兩個函數的理由如下:
在一個沒有調用bind的TCP客戶上,connect成功返回後,getsockname用於返回由內核賦予該連接的本地IP地址和本地端口號。
在以端口號為0調用bind(告知內核去選擇本地臨時端口號)後,getsockname用於返回由內核賦予的本地端口號。
在一個以通配IP地址調用bind的TCP服務器上,與某個客戶的連接一旦建立(accept成功返回),getsockname就可以用於返回由內核賦予該連接的本地IP地址。在這樣的調用中,套接字描述符參數必須是已連接套接字的描述符,而不是監聽套接字的描述符。
當一個服務器的是由調用過accept的某個進程通過調用exec執行程序時,它能夠獲取客戶身份的唯一途徑便是調用getpeername。
例如下面的,inetd調用accept(左上方的方框)返回兩個值:已連接套接字描述符connfd,這是函數的返回值;客戶的IP地址及端口號,如圖中標有“對端地址”的小方框所示(代表一個網際網套接字地址結構)。inetd隨後調用fork,派生出inetd的一個子進程。這樣父進程的那個套接字地址結構在子進程也可用,那個已連接套接字描述符也是如此。然而當子進程調用exec執行真正的服務器程序(譬如說Telent服務器程序)時,子進程的內存映像被替換成新的Telnet服務器的程序文件(也就是說包含對端地址的那個套接字地址結構就此丟棄),不過那個已連接套接字描述符跨exec繼續保持開放。Telnet服務器首先調用的函數之一便getpeername
,用於獲取客戶的IP地址和端口號。
顯然,最後一個例子中的Telnet服務器必須在啟動之後獲取connfd的值。獲取該值有兩個常用方法:
調用exec的進程可以把這個描述符格式化成一個字符串,再把它作為一個命令行參數傳遞給新程序。
約定在調用exec之前,總是把某個特定描述符置為所接受的已連接套接字的描述符。
inetd采用的是第二種方法,它總是把描述符0、1、2置為所接受的已連接套接字的描述符(即將已連接套接字描述符dup到描述符0、1、2,然後close原連接套接字)。
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/
服務器的代碼:
#include "unp.h" int main(int argc, char ** argv) { int listenfd,connfd; struct sockaddr_in servaddr; pid_t pid; char temp[20]; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(10010); Bind(listenfd, (SA *)&servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); for( ; ; ) { struct sockaddr_in local; connfd = Accept(listenfd, (SA *)NULL, NULL); if((pid = fork()) == 0) { Close(listenfd);struct sockaddr_in serv, guest; char serv_ip[20]; char guest_ip[20]; socklen_t serv_len = sizeof(serv); socklen_t guest_len = sizeof(guest); getsockname(connfd, (struct sockaddr *)&serv, &serv_len); getpeername(connfd, (struct sockaddr *)&guest, &guest_len); Inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip)); Inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip)); printf("host %s:%d guest %s:%d\n", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port)); char buf[] = "hello world"; Write(connfd, buf, strlen(buf)); Close(connfd); exit(0); } Close(connfd); } }
客戶端的代碼:
#include "unp.h" #define DEST_IP "127.0.0.1" int main(int argc, char ** argv) { int sockfd, n; char buf[100]; char serv_ip[20], guest_ip[20]; struct sockaddr_in servaddr; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(struct sockaddr_in)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(10010); Inet_pton(AF_INET, DEST_IP, &servaddr.sin_addr); Connect(sockfd, (SA *)&servaddr, sizeof(servaddr)); struct sockaddr_in serv, guest; socklen_t serv_len = sizeof(serv); socklen_t guest_len = sizeof(guest); getsockname(sockfd, (SA *)&guest, &guest_len); getpeername(sockfd, (SA *)&serv, &serv_len); Inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip)); Inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip)); printf("host %s:%d, guest %s:%d\n", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port)); n = Read(sockfd, buf, 100); buf[n] = '\0'; printf("%s\n", buf); Close(sockfd); exit(0); }
TCP
對於服務器來說,在bind以後就可以調用getsockname來獲取本地地址和端口,雖然這沒有什麼太多的意義。getpeername只有在鏈接建立以後才調用,否則不能正確獲得對方地址和端口,所以他的參數描述字一般是鏈接描述字而非監聽套接口描述字。
對於客戶端來說,在調用socket時候內核還不會分配IP和端口,此時調用getsockname不會獲得正確的端口和地址(當然鏈接沒建立更不可能調用getpeername),當然如果調用了bind 以後可以使用getsockname。想要正確的到對方地址(一般客戶端不需要這個功能),則必須在鏈接建立以後,同樣鏈接建立以後,此時客戶端地址和端口就已經被指定,此時是調用getsockname的時機。
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/