inet_pton和inetntop函数。字母p和n代表presentation和numeric。地址的表达presentation格式通常是ASCIL串,数值(numeric)格式则是存在于套接字地址结构中的二进制值。
inet_pton和inet_ntop函数是比较新的函数,它们能够处理ipv4和ipv6的地址转换。
1. inet_pton
int inet_pton(int af, const char *src, void *dst);将src所指的网络地址字符串(如"192.168.0.1")转换成网络使用的二进制数(unsigned long),存放在dst所指的in_addr结构中。使用基本与inet_aton一致,不同的是多了一个参数af(地址族:AF_INET或AF_INET6,分别对应ipv4和ipv6,对应的地址结构为sockaddr_in和sockaddr_in6)。
使用:
sockaddr_in server_addr; inet_pton(AF_INET, "192.168.0.1", (void *)&server_addr.sin_addr);
2. inet_ntop
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);将src所指的网络二进制数转换成网络地址字符串(如"192.168.0.1"),存放在dst所指字符串和返回值中。相比inet_pton多了个参数cnt,定义缓存区dst的大小,防止溢出。如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。
使用:
sockaddr_in server_addr; char IPdotdec[20]; //存放点分十进制IP地址 inet_pton(AF_INET, "192.168.0.1", (void *)&server_addr.sin_addr); printf(inet_ntop(AF_INET, (void *)&server_addr.sin_addr, IPdotdec, 16)); // 输出"192.168.0.1" printf("%s", IPdotdec); // 输出"192.168.0.1"
readn、writen和readline函数
字节流套接字上的read和write函数所表现的行为不同于通常的文件I/O.字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少,然而这不是出错状态.这个现象的原因在于内核中用于套接字的缓冲区可能已经达到了极限.此时所需的是调用者再次调用read个write函数,以输入或输出剩余的字节. 我们提供的以下三个函数是每当我们读或写一个字节流套接字时要使用的函数.
//从一个描述符读取n个字节 ssize_t readn(int fd, void* vptr, size_t n) { size_t nleft = n; //记录还剩下多少字节数没读取 ssize_t nread; //记录已经读取的字节数 char* ptr = vptr; //指向要读取数据的指针 while(nleft > 0) //还有数据要读取 { if(nread = read(fd,ptr,nleft) < 0) if(erron == EINTR)//系统被一个捕获的信号中断 nread = 0; //再次读取 else return -1; //返回 else if(nread == 0)//没有出错但是也没有读取到数据 break; //再次读取 nleft -= nread; //计算剩下未读取的字节数 ptr += nread; //移动指针到以读取数据的下一个位置 } return (n-nleft); //返回读取的字节数 } /**************************************************************************************************/ //从一个描述符读文本行,一次一个字节 ssize_t readline(int fd, void* vptr, size_t maxlen)//一个字节一个字节地读取 { ssize_t rc; //每次读取的字符 ssize_t n; //读取的次数也即读取字符串的长度 char c; // char* ptr = vptr; //指向要读取的数据的指针 for(n = 1;n < maxlen; n++) { again: if((rc = read(fd,&c,1)) == 1) { *ptr++ = c; //移动指针 if(c == ‘\n‘) //换行符 break; //跳出循环 else if(rc == 0) //结束 *ptr = 0; //字符串以0结尾 return (n-1); //返回读取的字节数 末尾的0不算 } else { if(erron == EINTR) goto again; //重新读取 return (-1) } } *ptr=0; return n; } /**************************************************************************************************/ //往一个描述符写n个字节 ssize_t writen(ind fd, const void* vptr, size_t n) { size_t nleft = n; //还需要写入的字节数 ssize_t nwritten; //每次写入的字节数 const char* ptr = vptr; //指向要写入的数据的指针 while(nleft > 0) { if((nwritten = write(fd,ptr,nleft)) <= 0) { if(nwritten < 0 && erron == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; //计算还需要写入的字节数 ptr += nwritten; //移动数据指针 } return n; }
值-结果参数
一个套接字函数传递一个套接字地址结构时候,该结构总以引用形式来传递,也就是说传递的指向该结构的一个指针,该结构的长度也作为一个参数来传递,不过其传递方式取决于该结构的传递方向:是从进程到内核,还是从内核到进程.如下图所示:
(1)、从进程到内核传递套接字结构函数:bind、connect和sendto.这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构体的整数大小.例如:
[cpp] view plaincopy
- struct sockaddr_in serv; //定义一个套接字地址结构体变量
- connect (sockfd, (struct sockaddr *) &serv, sizeof(serv));
(2)、从内核到进程传递套接字地址结构的函数:accept、recvfrom、getsockname和getpeername.这4个函数的其中两个参数指向某个套接字结构体的指针和指向表示该结构体大小的整数变量的指针.例如
[cpp] view plaincopy
- struct sockaddr_un cli; //定义一个套接字地址结构体变量
- socklen_t len = sizeof(cli); //该结构体的大小
- getpeername(unixfd,(struct sockaddr *)&cli,&len);//len可能会被改变
把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当函数被调用时,结构大小是一个值,他告诉内核该结构的大小,这样内核在写该结构时不至于越界;当函数返回时,结构大小又是一个结果,它告诉进程内核在该结构中究竟存储了多少信息.
fork和exec函数:
fork最困难的部分是它调用一次却返回两次。在调用进程(称为父进程),它返回一次,返回值是新派生进程(称为子进程)的进程ID,在子进程中它还返回一次,返回0。可通过返回值来判断当前进程是子进程还是父进程。
fork在子进程返回0而不是父进程ID,原因是:子进程只有一个父进程,他总可以调用getppid来得到;而父进程有许多子进程,他没有办法得到各子进程ID。如果父进程想跟踪所有子进程ID,他必须记住fork的返回值。
getsockname与getpeername
是返回套接口关联的本地协议地址和远程协议地址。
int getsockname(int sockfd, struct sockaddr * localaddr, socken_t * addrlen);
int getpeername(int sockfd, struct sockaddr * peeraddr, socken_t * addrlen);
返回0表示成功,返回1表示出错
参数sockfd表示你要获取的套接口的描述字。
localaddr返回本地协议地址描述结构, peeraddr返回远程协议地址描述结构,addrlen分别是上述2个结构的长度。
注意,2个函数的最后一个参数是值-结果参数。
需要这两个函数的理由如下:
- 在一个没有调用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原连接套接字)。
服务器的代码:
#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:%dn", 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:%dn", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port)); n = Read(sockfd, buf, 100); buf[n] = ‘?‘; printf("%sn", buf); Close(sockfd); exit(0); }
TCP
对于服务器来说,在bind以后就可以调用getsockname来获取本地地址和端口,虽然这没有什么太多的意义。getpeername只有在链接建立以后才调用,否则不能正确获得对方地址和端口,所以他的参数描述字一般是链接描述字而非监听套接口描述字。
对于客户端来说,在调用socket时候内核还不会分配IP和端口,此时调用getsockname不会获得正确的端口和地址(当然链接没建立更不可能调用getpeername),当然如果调用了bind 以后可以使用getsockname。想要正确的到对方地址(一般客户端不需要这个功能),则必须在链接建立以后,同样链接建立以后,此时客户端地址和端口就已经被指定,此时是调用getsockname的时机。
http://blog.chinaunix.net/zt/1016/unixjian_1016285.shtml
http://blog.csdn.net/yirancpp/article/details/8446879
http://tech.ddvip.com/2013-07/1374769636199661.html