网站seo诊断优化方案,骨干校建设验收网站,wordpress关闭网站吗,高级网站开发文章目录 1、不同函数介绍1.1 recvfrom1.2 accept1.3 getsockname、getpeername 2、使用场景2.1、获取本地地址信息2.1.1 UDP客户端获取本地地址2.1.2 TCP客户端获取本地地址 2.2、获取对端地址信息2.2.1 UDP中获取对端地址2.2.2 TCP中获取对端地址 3、总结3.1 获取对端地址信息… 文章目录 1、不同函数介绍1.1 recvfrom1.2 accept1.3 getsockname、getpeername 2、使用场景2.1、获取本地地址信息2.1.1 UDP客户端获取本地地址2.1.2 TCP客户端获取本地地址 2.2、获取对端地址信息2.2.1 UDP中获取对端地址2.2.2 TCP中获取对端地址 3、总结3.1 获取对端地址信息3.2 获取本地地址信息3.3 解析地址信息 在UDP/TCP套接字编程因为业务需要知道本地客户端、对端服务端的地址信息。先将有关地址获取的函数进行说明再根据使用场景选择对应函数。 1、不同函数介绍
先介绍有关获取地址信息的函数 recvfrom、accept、getsockname、getpeername等其他函数这里暂不做说明。
1.1 recvfrom
#include sys/socket.h
ssize_t recvfrom(int sock, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);关注后两个参数from和fromlen用于获取对端的地址信息。指针from为NULL时表示不关心对端地址信息同时指针fromlen也赋值为NULL。
当需要获取对端信息时需要传递对象保存地址结构的指针from以及当前结构地址的大小fromlen。recvfrom函数正常执行时会将对端的地址信息写入from指向对象并且会重写fromlen值为实际对端地址结构大小。但是当传入的fromlen值小于对端地址结构大小会造获取信息截断。例如传入的是sockaddr_in而实际的对端地址结构是sockaddr_in6。
可以选择足够大的空间对象保存对端地址结构例如传入sockaddr_in6对象以满足对端是ipv4或ipv6地址再根据fromlen来解析不同地址结构的信息。
1.2 accept
#include sys/socket.h
int accept(int sockfd, struct sockaddr *clientaddr, socklen_t* addrlen); // 成功返回非负的已连接套接字出错返回-1使用参数完全同recvfrom函数获取地址结构也相同。注意返回值成功时会返回一个非负的已连接套接字描述符已经绑定对端地址信息。我们可以利用这个返回值调用getpeername获取对端客户端的地址信息。
1.3 getsockname、getpeername
#include sys/socket.h
int getpeername (int sockfd, struct sockaddr *localaddr, socklen_t * addrlen);
int getsockname (int sockfd, struct sockaddr *peeraddr, socklen_t * addrlen);
// 成功返回0出错返回-1两个函数放在一起说明后两个参数含义、使用方法同上述recvfrom要求参数sockfd是一个已连接的套接字。
当使用getsockname时sockfd 应该是一个已经绑定到本地地址的套接字这个本地地址可以是手动bind或者由内核分配的。 当使用getpeername时sockfd 应该是一个已经绑定对端地址的套接字例如服务端经accept后的返回值套接字客户端经过connect之后的本地套接字。
2、使用场景
用于udp和tcp客户端不同客户端继续可以划分。
2.1、获取本地地址信息
2.1.1 UDP客户端获取本地地址
客户端bind或者connect之后使用 getsockname。为操作方便先实现一个通用函数以打印输出本地地址信息。
(1) 输出本地地址函数
void print_getsockname(int socket_fd)
{sockaddr_storage storage; // 能够适应不同种类的地址协议结构socklen_t sock_len sizeof(storage); // 必须给初值int ret getsockname(socket_fd, (sockaddr*)storage, sock_len); if(ret 0){printf(getsockname error: %s\n, strerror(errno));return;}if (storage.ss_family AF_INET){sockaddr_in* addr (sockaddr_in* )storage;printf(local addr: %s:%d\n, inet_ntoa(addr-sin_addr), ntohs(addr-sin_port));}else if(storage.ss_family AF_INET6){sockaddr_in6* addr (sockaddr_in6* )storage;char ip[INET6_ADDRSTRLEN];inet_ntop(AF_INET6, addr-sin6_addr, ip, sizeof(addr));printf(local addr: %s:%d\n, ip, ntohs(addr-sin6_port));}
}2常规使用 对于未bind的套接字必须发送数据后内核会分配端口。 /// 创建socketint socket_fd socket(AF_INET,SOCK_DGRAM, 0); // udp/// 连接sockaddr_in servaddr;servaddr.sin_family AF_INET;inet_pton(AF_INET,127.0.0.1, servaddr.sin_addr);servaddr.sin_port htons(8080);// 对于未bind的套接字必须发送数据后内核会分配端口int ret sendto(socket_fd, ,0,0, (sockaddr*)servaddr, sizeof(servaddr));if(ret 0){printf(bind error: %s\n, strerror(errno));}/// 获取本地信息print_getsockname(socket_fd);/// 4、关闭连接::close(socket_fd);print_getsockname(socket_fd);函数输出的ip地址默认是”0.0.0.0”但是服务端是仍然能解析的。如下 3使用connect函数
将上面常规方式中发送数据的代码替换成connect效果一样。 /// 创建socketint socket_fd socket(AF_INET,SOCK_DGRAM, 0); // udp/// 连接sockaddr_in servaddr;servaddr.sin_family AF_INET;inet_pton(AF_INET,127.0.0.1, servaddr.sin_addr);servaddr.sin_port htons(8080);int ret ::connect(socket_fd, (sockaddr*)servaddr, sizeof(servaddr));if(ret 0){printf(bind error: %s\n, strerror(errno));return 0;}/// 获取本地信息print_getsockname(socket_fd);/// 4、关闭连接::close(socket_fd);4使用bind
首先替换常规方法中发送数据部分仅调用bind后执行print_getsockname函数。结果相对会存在一些问题。例如 ip指定为127.0.0.1, port指定9000bind成功后getsockname函数返回正常 ip指定127.0.0.1, port指定0bind成功后getsockname函数返回正常端口为内核分配 ip指定INADDR_ANY, port指定0bind成功后getsockname函数返回ip为”0.0.0.0” 端口为内核分配。 接着bind之后先发送任意数据到服务端再调用getsockname结果不变。 换句话说使用bind后端口要要么是指定的要么是内核分配的最终的端口getsockname都能正确获取当地址为通配INADDR_ANY时最终选择的地址getsockname没有办法知道的。
5不常见的使用方法
见2.2.1节。
2.1.2 TCP客户端获取本地地址
对于TCP客户端使用bind后再调用getsockname的结果也是跟UDP的情况一样。 TCP客户端获取本地地址常规使用connect函数之后调用getsockname。代码如下 /// 创建socketint socket_fd socket(AF_INET,SOCK_STREAM, 0); // tcp/// 连接sockaddr_in servaddr;servaddr.sin_family AF_INET;inet_pton(AF_INET,127.0.0.1, servaddr.sin_addr);servaddr.sin_port htons(8080);int ret ::connect(socket_fd, (sockaddr*)servaddr, sizeof(servaddr));if(ret 0){printf(bind error: %s\n, strerror(errno));return 0;}/// 获取本地信息print_getsockname(socket_fd);/// 4、关闭连接::close(socket_fd);结果如下 当先使用bind指定端口且指定地址为INADDR_ANY 再经过connect之后能够使用getsockname获取最终tcp选择的ip地址。 如bind部分代码 /// 创建socketint socket_fd socket(AF_INET,SOCK_STREAM, 0); // tcp/// 连接sockaddr_in servaddr;servaddr.sin_family AF_INET;inet_pton(AF_INET,127.0.0.1, servaddr.sin_addr);servaddr.sin_port htons(8080);/// bind 或 connectint ret;sockaddr_in localaddr;localaddr.sin_family AF_INET;localaddr.sin_addr.s_addr INADDR_ANY;localaddr.sin_port htons(9000);ret ::bind(socket_fd, (sockaddr*)localaddr, sizeof(localaddr));if(ret 0){printf(bind error: %s\n, strerror(errno));}ret ::connect(socket_fd, (sockaddr*)servaddr, sizeof(servaddr));if(ret 0){printf(bind error: %s\n, strerror(errno));return 0;}/// 获取本地信息print_getsockname(socket_fd);/// 4、关闭连接::close(socket_fd);2.2、获取对端地址信息
获取对端的地址UDP主要通过recvfrom函数 TCP主要通过getpeername函数。
2.2.1 UDP中获取对端地址
UPP服务端和客户端都可能使用recvfrom函数接收来自对端的数据同时也能获取对端的地址信息。以服务端为例如下 /// 创建socketint socket_fd socket(AF_INET,SOCK_DGRAM, 0); // udp/// bindint ret;sockaddr_in localaddr;localaddr.sin_family AF_INET;inet_pton(AF_INET, 127.0.0.1, localaddr.sin_addr);localaddr.sin_port htons(8080);ret ::bind(socket_fd, (sockaddr*)localaddr, sizeof(localaddr));if(ret 0){printf(bind error: %s\n, strerror(errno));}/// 接收char buf[1024];int len;while(true){sockaddr_storage storage;socklen_t sock_len sizeof(storage); // 必须给初值len ::recvfrom(socket_fd, buf, sizeof(buf), 0, (struct sockaddr *)storage, sock_len);if (len 0){printf(recv failed. err %s\n, strerror(errno));return;}buf[len] \0;/// 输出对端信息if (storage.ss_family AF_INET){sockaddr_in* addr (sockaddr_in* )storage;char ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, addr-sin_addr, ip, sock_len);printf(recv client [%s:%d] %2d: %s, ip, ntohs(addr-sin_port), len, buf);}else if(storage.ss_family AF_INET6){sockaddr_in6* addr (sockaddr_in6* )storage;char ip[INET6_ADDRSTRLEN];inet_ntop(AF_INET6, addr-sin6_addr, ip, sock_len);printf(recv client [%s:%d] %2d: %s, ip, ntohs(addr-sin6_port), len, buf);} }// 关闭连接::close(socket_fd);函数recvfrom返回成功后参数storage、sock_len存储了对端的地址信息同前面获取本地地址一样解析根据长度或协议类型进行解析即可。
在UDP客户端使用中有一个不常用的方法获取本地信息即通过connect后使用getsockname获取本地信息。 int socket_fd socket(AF_INET,SOCK_DGRAM, 0); // udp/// 连接sockaddr_in servaddr;servaddr.sin_family AF_INET;inet_pton(AF_INET,127.0.0.1, servaddr.sin_addr);servaddr.sin_port htons(8080);::connect(socket_fd, (sockaddr*)servaddr, sizeof(servaddr));/// 获取地址信息print_getpeername(socket_fd); // 函数见下一节 TCP中获取对端地址print_getsockname(socket_fd); // 当前socket_fd是一个已连接的UDP套接字, 理论上需要使用write或send函数//::sendto(socket_fd,123,3, 0, (sockaddr*)servaddr, sizeof(servaddr));::write(socket_fd,123,3); // 关闭连接::close(socket_fd);2.2.2 TCP中获取对端地址
TCP服务端直接使用accept能够接收客户端的连接并且能够获取客户端的地址信息其次accept返回值是当前客户端已连接的套接字可以使用getpeername获取客户端地址信息。
TCP客户端也可以在connect之后调用getpeername获取服务端信息。
1使用accept直接获取对端信息 /// 创建socketint socket_fd socket(AF_INET, SOCK_STREAM, 0); // tcp/// bindint ret;sockaddr_in localaddr;localaddr.sin_family AF_INET;inet_pton(AF_INET, 127.0.0.1, localaddr.sin_addr);localaddr.sin_port htons(8080);ret ::bind(socket_fd, (sockaddr *)localaddr, sizeof(localaddr));// 监听::listen(socket_fd, 5);sockaddr_storage storage;socklen_t sock_len sizeof(storage); // 必须给初值::accept(socket_fd, (sockaddr *)storage, sock_len);/// 输出对端信息if (storage.ss_family AF_INET){sockaddr_in *addr (sockaddr_in *)storage;char ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, addr-sin_addr, ip, sock_len);printf(client [%s:%d] \n, ip, ntohs(addr-sin_port));}else if (storage.ss_family AF_INET6){sockaddr_in6 *addr (sockaddr_in6 *)storage;char ip[INET6_ADDRSTRLEN];inet_ntop(AF_INET6, addr-sin6_addr, ip, sock_len);printf(client [%s:%d] \n, ip, ntohs(addr-sin6_port));}// 关闭连接::close(socket_fd);经过accept之后需要根据协议类型解析地址信息。测试代码反复运行结果如下 2使用accept返回值调用getpeername 注意传递给getpeername的是函数accept的返回值sock_id这个是已连接的客户端套接字不是服务端的套接字sock_fd。 其中包含了print_getpeername()的代码见下面注释。演示效果同使用accept直接获取对端地址信息方法。 /// 创建socketint socket_fd socket(AF_INET, SOCK_STREAM, 0); // tcp/// bindint ret;sockaddr_in localaddr;localaddr.sin_family AF_INET;inet_pton(AF_INET, 127.0.0.1, localaddr.sin_addr);localaddr.sin_port htons(8080);ret ::bind(socket_fd, (sockaddr *)localaddr, sizeof(localaddr));// 监听::listen(socket_fd, 5);// 等待连接int sock_id ::accept(socket_fd, NULL, NULL); /// 输出对端信息实际是 print_getsockname()函数;sockaddr_storage storage;socklen_t sock_len sizeof(storage); // 必须给初值ret ::getpeername(sock_id, (sockaddr *)storage, sock_len); // 注意是sock_idif (ret 0){printf(getpeername error: %s\n, strerror(errno));}else{if (storage.ss_family AF_INET){sockaddr_in *addr (sockaddr_in *)storage;char ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, addr-sin_addr, ip, sock_len);printf(client [%s:%d] \n, ip, ntohs(addr-sin_port));}else if (storage.ss_family AF_INET6){sockaddr_in6 *addr (sockaddr_in6 *)storage;char ip[INET6_ADDRSTRLEN];inet_ntop(AF_INET6, addr-sin6_addr, ip, sock_len);printf(client [%s:%d] \n, ip, ntohs(addr-sin6_port));}}// 关闭连接::close(sock_id);::close(socket_fd);3、总结
3.1 获取对端地址信息
recvfrom------------- 多用于udp服务端和客户端 accept --------------- 用于tcp服务端 getpeername ------ tcp服务端要在accept之后tcp/udp客户端要在connect之后
3.2 获取本地地址信息
getsockname ----- 可以直接在bind后获取准确的port在connect、accept之后可以获取准确的port和ip。
3.3 解析地址信息
接收地址新的对象空间足够大根据函数返回的地址信息长度或者协议类型进行解析。 以Ipv4和ipv6地址为例选择sockaddr_in6时根据长度解析选择sockaddr_storage时根据协议解析。