当前位置: 首页 > news >正文

Linux网络编程基础API

Linux网络编程基础API

1、socket地址API

主机字节序和网络字节序

字节序分大端字节序小端字节序

  • 大端字节序:一个整数的高位字节(23~31bit)存储在内存的低地址处;
  • 小端字节序:一个整数的高位字节存储在地址的高地址处;

可以使用下面程序判断自己电脑的字节序

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>void byteorder(int val) {union {short value;char union_bytes[sizeof(short)];} test;test.value = val;printf("%x\n", test.value);if(test.union_bytes[0] == 1 && test.union_bytes[1] == 2) {printf("big-endian\n");} else if(test.union_bytes[0] == 2 && test.union_bytes[1] == 1) {printf("little-endian\n"); } else {printf("unknown\n");}
}int main() {byteorder(0x0201);return 0;
}

现代PC多采用小端字节序,因此小端字节序又被称为主机字节序

如果数据在两台主机之间传递时采用不同的字节序,一定会发生错误,解决办法是:发送端总是先将要发送的数据转换成大端字节序再发送,接收端根据自己的需要决定是否需要转换。因此大端字节序也被称为网络字节序

下面四个函数可以进行主机字节序和网络字节序之间的转换

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

上面几个函数中,h代表host本地,n代表network网络,l代表long32位数字,s代表short16位数字。所以htonl就表示将32位本地字节序整数转网络字节序

socket地址

通用socket地址

socket网络编程接口中用到的socket地址是sockaddr结构体,它也被称为通用socket地址

#include <bits/socket.h>
struct sockaddr {sa_family_t sa_family;char sa_data[14];
};

sa_family_t是地址族类型的变量,地址族类型通常和协议族类型对应

协议族地址族描述
PF_UNIXAF_UNIXUnix本地域协议族
PF_INETAF_INETTCP/IPv4协议族
PF_INET6AF_INET6TCP/IPv6协议族

但是通用socket地址只是在网络编程接口中需要,实际填写socket信息时不使用它,而是使用下面的专用socket地址

专用socket地址

Unix本地域协议族专用socket地址结构体

#include <sys/un.h>
struct sockaddr_un {sa_family_t sin_family;	// 地址族:AF_UNIXchar sun_path[108];		// 文件路径名
};

sockaddr_insockaddr_in6分别用于IPv4和IPv6

/* struct sockaddr_in */
struct in_addr {u_int32_t s_addr;	// IPv4地址,要用网络字节序
};
struct sockaddr_in {sa_family_t sin_family;	// 地址族:AF_INETu_int16_t sin_port;		// 端口号,要用网络字节序struct in_addr sin_addr;// IPv4地址结构体
};/* struct sockaddr_in6 */
struct in6_addr {unsigned char sa_addr[6];	// IPv6地址
};
struct sockaddr_in6 {sa_family_t sin6_family;	// 地址族:AF_INET6u_int16_t sin6_port;		// 端口号u_int32_t sin6_flowinfo;	// 流信息,应设置为0struct in6_addr sin6_addr;	// IPv6地址结构u_int32_t sin6_scope_id;	// scope ID
};

所有专用地址结构在传参给函数的时候都需要强制类型转换成通用地址结构sockaddr

IP地址转换函数

人们习惯用点分十进制的字符串表示IPv4地址,以及用16进制字符串表示IPv6地址,但在编程中,我们需要将她们转换成整数才能使用。下面三个函数可用于点分十进制表示的IPv4地址和网络字节序整数表示的IPv4地址之间进行转换

#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
  1. inet_addr将点分十进制的IPv4地址字符串转换成整数并返回;

  2. inet_aton有一样的效果,只是将转换后的结果直接存入了inp指向的空间;

  3. inet_ntoa将整数的IPv4地址转换成字符串;
    注意:该函数内部使用一个静态变量存储转换结果,函数的返回值指向该静态变量

    struct in_addr in1, in2;
    in1.s_addr = "1.2.3.4";
    in2.s_addr = "192.168.88.132";
    char *val1 = inet_ntoa(in1);
    char *val2 = inet_ntoa(in2);
    printf("address1: %s\n", val1);
    printf("address2: %s\n", val2);
    

    上面执行结果为

    address1: 41.240.28.149
    address2: 41.240.28.149
    

下面这两个函数也可以实现上面三个函数的效果,并且同时适用于IPv4和IPv6

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
  1. af指定地址族,AF_INETAF_INET6

2、建立连接

创建socket

Unix/Linux有一个哲学:所有东西都是文件。socket也不例外,他就是一个可读、可写的文件描述符,下面函数用于创建一个socket

#include <sys/type.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
  1. domain是底层协议,对应PF_INET, PF_INET6, PF_UNIX
  2. type是服务类型,主要有SOCK_STREAM流服务和SOCK_UGRAM数据包服务,分别对应TCP和UDP;
  3. protocol一般设置为0;
  4. 函数执行成功返回一个socket;

命名socket

socket函数指定了地址族,但是没有指定具体的socket地址。将一个socket与socket地址绑定称为给socket命名,命名采用bind函数

#include <sys/type.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  1. sockfd是要命名的文件描述符;
  2. addr是前面讲的socket地址,使用专用socket地址,传参时需要强转成const struct sockaddr *

监听socket

socket被命名之后,还不能马上接受客户连接,还需要调用listen来创建一个监听队列存放待处理的客户连接

#include <sys/socket.h>
int listen(int sockfd, int backlog)
  1. backlog是监听队列的最大长度

接受连接

调用accept函数从listen监听队列中接受一个连接

#include <sys/type.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  1. addr用来获取被接受连接的远端socket地址;
  2. 函数执行成功返回一个新的连接socket,该socket唯一的表示了被接受的这个连接,后续通过这个sokcet与被连接端通信;

发起连接

客户端通过connect主动与服务器建立连接

#include <sys/type.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *ser_addr, socklen_t addrlen);

关闭连接

关闭连接就是关闭对应的socket文件描述符,所以调用close即可

#include <unistd.h>
int close(int fd);

示例

下面是一套服务端和客户端程序,客户端输入一个字符串,服务端返回将每个字母大写后的结果

服务端:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define SOCK_PORT 9999int main(int argc, char *argv[]) {// 服务器:socket() -> bind() -> listen() -> accept()int listenfd;listenfd = socket(AF_INET, SOCK_STREAM, 0);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));struct sockaddr_in ser_addr;bzero(&ser_addr, sizeof(ser_addr));ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(SOCK_PORT);ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);bind(listenfd, (const struct sockaddr *)&ser_addr, sizeof(ser_addr));listen(listenfd, 5);int clifd;struct sockaddr_in cli_addr;socklen_t cli_len = sizeof(cli_addr);clifd = accept(listenfd, (struct sockaddr *)&cli_addr, &cli_len);char buf[BUFSIZ];while(1) {int nread = read(clifd, buf, sizeof(buf));if(nread == 0) {printf("client exited\n");break;}printf("Received from client: %s\n", buf);for(int i=0; i<nread; ++i) buf[i] = toupper(buf[i]);write(clifd, buf, nread);}return 0;
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main(int argc, char *argv[]) {// 客户端:socket() -> connect()int listenfd;listenfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in ser_addr;bzero(&ser_addr, sizeof(ser_addr));ser_addr.sin_family = AF_INET;inet_pton(AF_INET, argv[1], (void *)&ser_addr.sin_addr.s_addr);ser_addr.sin_port = htons(atoi(argv[2]));connect(listenfd, (const struct sockaddr *)&ser_addr, sizeof(ser_addr));int nread;char buf[BUFSIZ];while(1) {nread = read(STDIN_FILENO, buf, sizeof(buf));write(listenfd, buf, nread);read(listenfd, buf, sizeof(buf));printf("Received from server: %s\n", buf);}return 0;
}

3、数据读写

TCP数据读写

下面两个系统调用专用于TCP数据读写

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
  1. 前几个参数都好理解,分别是套接字、缓冲区、缓冲区大小;
  2. flag参数为数据收发提供了额外的控制,一般就填0,下面是一些其他的常用的值:
  3. send常用的值
    • MSG_DONTROUTE:不查路由表,只能发送到本地子网中的主机;
    • MSG_DONTWAIT:非阻塞发送,如果套接字本身是阻塞的,这里也能让send立即返回;
    • MSG_NOSIGNAL:在对方关闭连接时,不触发SIGPIPE信号,而是返回EPIPE错误;
    • MSG_MORE:告诉内核后面还有数据要发,推迟实际的发送;
  4. recv常用值:
    • MSG_DONTWAIT:非阻塞发送,如果套接字本身是阻塞的,这里也能让send立即返回;
    • MSG_PEEK:窥探数据,不会把数据从接收缓冲区移除;
    • MSG_WAITALL:一直等到请求的 len 字节都收到才返回;

如果只是普通的读写,不设置flag参数,send/recvwrite/read基本没有区别,因为他们的底层实现记录相同,所以性能上也是借本没有区别的;

send/recv主要用于设置flag进行特殊的控制;

UDP数据读写

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
  1. 因为UDP通信不建立连接,所以每次读写数据都需要带上socket地址信息;
  2. 如果将recvfrom/sendto的socket地址设置为NULL,这两个函数也可以用于TCP数据读写;

通用数据读写

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

msghdr定义如下

struct msghdr {void *msg_name;				// socket地址socklen_t msg_namelen;		// socket地址的长度struct iovec* msg_iov;		// 分散的内存块int msg_iovlen;				// 分散内存块的数量void *msg_control;			// 指向辅助数据的起始地址socklen_t msg_controllen;	// 辅助数据的大小int msg_flags;				// 复制函数中的flags参数
};struct iovec {void *iov_base;size_t iov_len;
};
  1. iovec结构体封装了一块内存的起始地址和长度,msg_iovlen指定了这样的iovec有多少个;
    • 对于recvmsg而言,数据将被读取并存放在msg_iovlen块分散的内存中,这些内存的位置和长度由msg_iov指向的数组指定,这称为分散读
    • 对于sendmsg而言,msg_iov分散块的数据将被一起发送,这称为集中写

4、socket选项

fcntl系统调用是控制文件描述符属性的通用方法,下面两个系统调用则是专门用来读取和设置socket文件描述符的方法

#include <sys/types.h>   
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
  1. level参数指定要操作哪个协议的选项,如IPv4,IPv6,TCP等;
  2. opt_name指定选项的名字;

在这里插入图片描述

SO_REUSEADDR选项

在TCP四次挥手断开连接的时候,处于TIME_WAIT一方在2MSL时间内是不能再次使用当前端口的,这对于客户端来说没什么,但是对于想要立即重启的服务器来说就不好了

SO_REUSEADDR就是强制使处于TIME_WAIT状态的连接可以被重新使用;

int sock = socket(AF_INTE, SOCK_STREAM, 0);
int reuse = 1;
setsockopt(&sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

SO_REVBUF和SO_SNDBUF选项

这两个分别表示TCP接收缓冲区和发送缓冲区的大小,而且当我们使用这两个选项设置缓冲区大小时,系统都会将我们设置的值翻倍,并且不能小于最小值

示例

服务端

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main(int argc, char *argv[]) {/*** 服务器* socket() -> bind() -> listen() -> accept() -> recv()/send() -> close()*/int listenfd = socket(AF_INET, SOCK_STREAM, 0);int reuse = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));struct sockaddr_in ser_addr;bzero(&ser_addr, sizeof(ser_addr));ser_addr.sin_family = AF_INET;ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);ser_addr.sin_port = htons(9999);int recvbuf = atoi(argv[1]);int len = sizeof(recvbuf);setsockopt(listenfd, SOL_SOCKET, SO_RCVBUF, &recvbuf, len);getsockopt(listenfd, SOL_SOCKET, SO_RCVBUF, &recvbuf, (socklen_t *)&len);printf("the recv buf size after setting is %d\n", recvbuf);bind(listenfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));listen(listenfd, 128);struct sockaddr_in cli_addr;socklen_t cli_len = sizeof(cli_addr);int connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &len);char buf[BUFSIZ];memset(buf, '\0', sizeof(buf));while(recv(connfd, buf, sizeof(buf), 0) > 0) {fputs(buf, stdout);memset(buf, '\0', sizeof(buf));}close(connfd);return 0;
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main(int argc, char *argv[]) {/*** 客户端* socket() -> connect() -> send()/recv() -> close()*/int sockfd;sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in ser_addr;bzero(&ser_addr, sizeof(ser_addr));ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(atoi(argv[2]));inet_pton(AF_INET, argv[1], &ser_addr.sin_addr.s_addr);int sendbuf = atoi(argv[3]);int len = sizeof(sendbuf);setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuf, len);getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuf, (socklen_t *)&len);printf("the tcp send buffer size after setting is %d\n", sendbuf);connect(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));char buf[BUFSIZ];memset(buf, 0, sizeof(buf));snprintf(buf, sizeof(buf), "hello, this is a tcp client\n");send(sockfd, buf, sizeof(buf), 0);close(sockfd);return 0;
}

输入输出

$ ./server 4000
$ the recv buf size after setting is 8000
$ ./client 127.1 9999 4000
$ the tcp send buffer size after setting is 8000

网络API

gethostbyname/gethostbyaddr

这两个函数用于获取主机的完整信息,前者通过主机名,后者通过主机IP

#include <netdb.h>
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
struct hostent {char *h_name;		// 主机名char **h_aliases;	// 主机别名,可能有多个,数组int h_addrtype;		// 地址类型,地址族int h_length;		// 地址长度char **h_addr_list;	// 按网络字节序列出的主机IP地址列表
};
  1. type参数指定IP地址的类型,包括AF_INET, AF_INET6

示例

gethostbyname

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>int main() {const char *hostname = "www.baidu.com";struct hostent *host = gethostbyname(hostname);if (host == NULL) {herror("gethostbyname");return 1;}printf("Official name: %s\n", host->h_name);char **addr_list = host->h_addr_list;for (int i = 0; addr_list[i] != NULL; i++) {struct in_addr addr;memcpy(&addr, addr_list[i], sizeof(struct in_addr));printf("IP address: %s\n", inet_ntoa(addr));}return 0;
}

输出

Official name: www.baidu.com
IP address: 183.2.172.17
IP address: 183.2.172.177

gethostbyaddr

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>int main() {const char *ip_str = "8.8.8.8";struct in_addr addr;if (inet_aton(ip_str, &addr) == 0) {fprintf(stderr, "Invalid IP address\n");return 1;}struct hostent *host = gethostbyaddr(&addr, sizeof(addr), AF_INET);if (host == NULL) {herror("gethostbyaddr");return 1;}printf("Official name: %s\n", host->h_name);char **alias_list = host->h_aliases;for (int i = 0; alias_list[i] != NULL; i++) {printf("Alias: %s\n", alias_list[i]);}return 0;
}

输出

Official name: dns.google

getservbyname/getservbyport

前者根据名称获取某个服务的完整信息,后者根据端口号获取某个服务的完整信息

#include <netdb.h>
struct servent* getservbyname(const char *name, const char *proto);
struct servent* getservbyport(int port, const char* proto);
  1. proto指定服务类型,tcp或者udp
struct servent {char *s_name;		// 服务名称char **s_aliases;	// 服务的别名列表int s_port;			// 端口号char *s_proto;		// 服务类型
};

示例

getservbyname

#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>int main() {const char *service_name = "http";   // 服务名,例如 http、ftp、smtpconst char *protocol = "tcp";        // 协议名 tcp 或 udpstruct servent *service = getservbyname(service_name, protocol);if (service == NULL) {printf("Service not found.\n");return 1;}printf("Service name: %s\n", service->s_name);printf("Port (network byte order): %d\n", service->s_port);printf("Port (host byte order): %d\n", ntohs(service->s_port));printf("Protocol: %s\n", service->s_proto);return 0;
}

getservbyport

#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>int main() {int port = 80;                     // 端口号const char *protocol = "tcp";      // 协议名 tcp 或 udpstruct servent *service = getservbyport(htons(port), protocol); // 注意 htonsif (service == NULL) {printf("Service not found.\n");return 1;}printf("Service name: %s\n", service->s_name);printf("Port (network byte order): %d\n", service->s_port);printf("Port (host byte order): %d\n", ntohs(service->s_port));printf("Protocol: %s\n", service->s_proto);return 0;
}

他们的输出都是:

Service name: http
Port (network byte order): 20480
Port (host byte order): 80
Protocol: tcp

getaddrinfo

这个函数既能通过主机名获取IP地址,也能通过服务名获取端口号,也就是它内部是通过gethostbynamegetservbyname实现的

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>int getaddrinfo(const char *node,       // 主机名或 IP 地址(可为 NULL)const char *service,    // 服务名或端口号(字符串形式,可为 NULL)const struct addrinfo *hints, // 提供调用者的偏好设置(可为 NULL)struct addrinfo **res); // 输出结果链表
struct addrinfo {int              ai_flags;     // 特殊标志,如 AI_PASSIVEint              ai_family;    // 地址族 AF_INET 或 AF_INET6int              ai_socktype;  // 套接字类型 SOCK_STREAM / SOCK_DGRAMint              ai_protocol;  // 协议,0 表示自动选择socklen_t        ai_addrlen;   // 地址长度struct sockaddr *ai_addr;      // 指向实际地址的指针char            *ai_canonname; // 规范名称(可为 NULL)struct addrinfo *ai_next;      // 下一个节点指针
};

常用的hints设置

  • ai_family

    • AF_INET:IPv4
    • AF_INET6:IPv6
    • AF_UNSPEC:任意(IPv4 或 IPv6)

    ai_socktype

    • SOCK_STREAM:TCP
    • SOCK_DGRAM:UDP

    ai_flags

    • AI_PASSIVE:用于被动套接字(服务器端绑定)
    • AI_CANONNAME:获取规范主机名

示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>int main() {const char *hostname = "www.baidu.com";const char *service = "80"; // HTTP 默认端口struct addrinfo hints, *res, *p;memset(&hints, 0, sizeof(hints));hints.ai_family = AF_UNSPEC;      // 支持 IPv4 或 IPv6hints.ai_socktype = SOCK_STREAM;  // TCPint status = getaddrinfo(hostname, service, &hints, &res);if (status != 0) {fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));return 1;}// 遍历结果链表for (p = res; p != NULL; p = p->ai_next) {char ipstr[INET6_ADDRSTRLEN];void *addr;if (p->ai_family == AF_INET) { // IPv4struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;addr = &(ipv4->sin_addr);} else { // IPv6struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;addr = &(ipv6->sin6_addr);}inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));printf("IP: %s\n", ipstr);}freeaddrinfo(res); // 释放链表return 0;
}

输出

IP: 183.2.172.177
IP: 183.2.172.17
IP: 240e:ff:e020:99b:0:ff:b099:cff1
IP: 240e:ff:e020:98c:0:ff:b061:c306

getnameinfo

该函数可以通过socket地址同时获得以字符串表示的主机名和服务名,也就是函数内部由gethostbyaddrgetservbyport实现

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>int getnameinfo(const struct sockaddr *sa, socklen_t salen,char *host, size_t hostlen,char *serv, size_t servlen,int flags);
  1. sa:指向套接字地址结构,如 struct sockaddr_instruct sockaddr_in6
  2. host:输出主机名(或地址字符串)的缓冲区;
  3. serv:输出服务名(或端口号)的缓冲区;
  4. flags:控制输出格式:
    • NI_NUMERICHOST:以数字形式输出主机地址;
    • NI_NUMERICSERV:以数字形式输出端口号;
    • NI_NOFQDN:仅输出主机名,不包含域名;
    • NI_NAMEREQD:必须解析出名称,否则返回错误;

示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>int main() {// 假设这是一个 IPv4 客户端地址struct sockaddr_in client_addr;socklen_t addr_len = sizeof(client_addr);char host[NI_MAXHOST];char service[NI_MAXSERV];// 例子:IP = 192.168.1.100, port = 12345client_addr.sin_family = AF_INET;client_addr.sin_port = htons(12345);inet_pton(AF_INET, "192.168.1.100", &client_addr.sin_addr);int status = getnameinfo((struct sockaddr *)&client_addr, addr_len,host, sizeof(host),service, sizeof(service),NI_NUMERICHOST | NI_NUMERICSERV);if (status != 0) {fprintf(stderr, "getnameinfo: %s\n", gai_strerror(status));return 1;}printf("Host: %s, Port: %s\n", host, service);return 0;
}

输出

Host: 192.168.1.100, Port: 12345
http://www.dtcms.com/a/350843.html

相关文章:

  • [灵动微电子六步换向(方波控制)方案MM32BIN560C] 六步换向实现和规律
  • PostgreSQL诊断系列(2/6):锁问题排查全攻略——揪出“阻塞元凶”
  • RK3568 Linux驱动学习——pinctrl和gpio子系统
  • onnx入门教程(四)——ONNX 模型的修改与调试
  • Day24: NumPy 奥德赛:用科学计算的魔法征服数据宇宙!
  • 32.Ansible平台搭建
  • 2024年09月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • NFC线圈设计计算
  • 力扣热题——前K个高频元素
  • 记一次Arrays.asList集合删除的错误
  • Java vs Kotlin 在实际开发中的主要区别与面试题总结
  • 太阳光模拟器在国防军工中的应用
  • k8s-容器化部署论坛和商城服务(小白的“升级打怪”成长之路)
  • K8s Pod驱逐机制详解与实战
  • SpringBoot防重放攻击的5种实现方案
  • 什么是数据库?现代数据库类型、示例与应用(2025)
  • 深入理解 iptables:Linux 防火墙从入门到精通
  • Vue3使用 DAG 图(AntV X6)
  • 2024年12月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • Spring Boot 3.5 新特性
  • C++ namespace
  • 国内外大模型体验与评测:洞察智能时代的核心驱动力一、引言
  • DataX HdfsWriter 插件文档
  • 实现自己的AI视频监控系统-第二章-AI分析模块2
  • Java全栈开发面试实战:从基础到微服务的完整技术解析
  • Oracle数据库如何修改字段中的两个字符
  • CF2133C 下界(The Nether)
  • 敏捷价值实证:亚马逊如何用敏捷破解技术项目的“价值迷雾”?
  • 学习做动画3.八方移动
  • SW - 增加导出STL数据中的三角面数,增加别人逆向建模的难度