IP 地址格式转换函数
对于人来说,我们更容易阅读的是点分十进制的 IP 地址,譬如192.168. 1.110 、192.168.1.50,这其实是一种字符串的形式,但是计算机所需要理解的是二进制形式的 IP 地址,所以我们就需要在点分十进制字符
串和二进制地址之间进行转换。
点分十进制字符串和二进制地址之间的转换函数主要有:inet_aton、inet_addr、inet_ntoa、inet_ntop、inet_pton 这五个,在我们的应用程序中使用它们需要包含头文件<sys/socket.h>、<arpa/inet.h>以及<netinet/in.h>。
inet_aton、inet_addr、inet_ntoa 函数 这些函数可将一个 IP 地址在点分十进制表示形式和二进制表示形式之间进行转换,这些函数已经废弃了,基本不用这些函数了,但是在一些旧的代码中可能还会看到这些函数。完成此类转换工作我们应该使用下面介绍的这些函数。
inet_ntop、inet_pton 函数
inet_ntop()、inet_pton()与 inet_ntoa()、inet_aton()类似,但它们还支持 IPv6 地址。它们将二进制 Ipv4 或Ipv6 地址转换成以点分十进制表示的字符串形式,或将点分十进制表示的字符串形式转换成二进制 Ipv4 或Ipv6 地址。使用这两个函数只需包含<arpa/inet.h>头文件即可!
inet_pton()函数
inet_pton()函数原型如下所示:
int inet_pton(int af, const char *src, void *dst);
inet_pton()函数将点分十进制表示的字符串形式转换成二进制 Ipv4 或 Ipv6 地址。 将字符串 src 转换为二进制地址,参数 af 必须是 AF_INET 或 AF_INET6,AF_INET 表示待转换的 Ipv4地址,AF_INET6 表示待转换的是 Ipv6 地址;并将转换后得到的地址存放在参数 dst 所指向的对象中,如果参数 af 被指定为 AF_INET,则参数 dst 所指对象应该是一个 struct in_addr 结构体的对象;如果参数 af 被指定为 AF_INET6,则参数 dst 所指对象应该是一个 struct in6_addr 结构体的对象。
inet_pton()转换成功返回 1(已成功转换)。如果 src 不包含表示指定地址族中有效网络地址的字符串,则返回 0。如果 af 不包含有效的地址族,则返回-1 并将 errno 设置为EAFNOSUPPORT。
使用示例
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inct.h>
#define IPV4_ADDR "192.168.1.222"
int main(void)
{
struct in_addr addr;
inet_pton(AF_INET,IPV4_ADDR,&addr);
printf("ip addr:0x%x\n",add.s_addr);
exit(0);
}
测试结果:
inet_ntop()函数
inet_ntop()函数执行与 inet_pton()相反的操作,函数原型如下所示:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数 af 与 inet_pton()函数的 af 参数意义相同。
参数 src 应指向一个 struct in_addr 结构体对象或 struct in6_addr 结构体对象,依据参数 af 而定。函数inet_ntop()会将参数 src 指向的二进制 IP 地址转换为点分十进制形式的字符串,并将字符串存放在参数 dts所指的缓冲区中,参数 size 指定了该缓冲区的大小。
inet_ntop()在成功时会返回 dst 指针。如果 size 的值太小了,那么将会返回 NULL 并将 errno 设置为ENOSPC。
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
int main(void)
{
struct in_addr addr;
char buf[20]={0};
addr.s_addr = 0xde01a8c0;
inet_ntop(AF_INET,&addr,buf,sizeof(buf));
printf("ip addr: %s\n",buf);
exit(0);
}
测试结果:
socket 编程实战
经过上面的介绍,本小节我们将进行编程实战,实现一个简单地服务器和一个简单地客户端应用程序。
编写服务器程序
编写服务器应用程序的流程如下:
①、调用 socket()函数打开套接字,得到套接字描述符;
②、调用 bind()函数将套接字与 IP 地址、端口号进行绑定;
③、调用 listen()函数让服务器进程进入监听状态;
④、调用 accept()函数获取客户端的连接请求并建立连接;
⑤、调用 read/recv、write/send 与客户端进行通信;
⑥、调用 close()关闭套接字。
下面,我们就根据上面列举的步骤来编写一个简答地服务器应用程序,代码如下所示:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define SERVER_PORT 8888
int main(void)
{
struct sockaddr_in server_addr = {0};
struct sockaddr_in client_addr = {0};
char ip_str[20] = {0};
int sockfd,connfd;
int addrlen = sizeof(client_addr);
char recvbuf[512];
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(0>sockfd)
{
perror("socket error");
exit(EXIT_FAILURE);
}
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (0 > ret) {
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 使服务器进入监听状态 */
ret = listen(sockfd, 50);
if (0 > ret) {
perror("listen error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 阻塞等待客户端连接 */
connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
if (0 > connfd) {
perror("accept error");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("有客户端接入...\n");
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
printf("客户端主机的 IP 地址: %s\n", ip_str);
printf("客户端进程的端口号: %d\n", client_addr.sin_port);
/* 接收客户端发送过来的数据 */
for ( ; ; ) {
// 接收缓冲区清零
memset(recvbuf, 0x0, sizeof(recvbuf));
// 读数据
ret = recv(connfd, recvbuf, sizeof(recvbuf), 0);
if(0 >= ret) {
perror("recv error");
close(connfd);
break;
}
// 将读取到的数据以字符串形式打印出来
printf("from client: %s\n", recvbuf);
// 如果读取到"exit"则关闭套接字退出程序
if (0 == strncmp("exit", recvbuf, 4)) {
printf("server exit...\n");
close(connfd);
break;
}
}
/* 关闭套接字 */
close(sockfd);
exit(EXIT_SUCCESS);
}
}
以上我们实现了一个非常简单地服务器应用程序,根据上面列举的步骤完成了这个示例代码,最终的功能是,当客户端连接到服务器之后,客户端会向服务器(也就是本程序)发送数据,在我们服务器应用程序中会读取客户端发送的数据并将其打印出来,就是这么简单的一个功能。
SERVER_PORT 宏指定了本服务器绑定的端口号,这里我们将端口号设置为 8888,端口不能与其它服务器的端口号发生冲突,不常用的端口号通常大于 5000。