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
代表long
32位数字,s
代表short
16位数字。所以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_UNIX AF_UNIX Unix本地域协议族 PF_INET AF_INET TCP/IPv4协议族 PF_INET6 AF_INET6 TCP/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_in
和sockaddr_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);
inet_addr
将点分十进制的IPv4地址字符串转换成整数并返回;
inet_aton
有一样的效果,只是将转换后的结果直接存入了inp
指向的空间;
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);
af
指定地址族,AF_INET
或AF_INET6
;
2、建立连接
创建socket
Unix/Linux有一个哲学:所有东西都是文件。socket也不例外,他就是一个可读、可写的文件描述符,下面函数用于创建一个socket
#include <sys/type.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);
domain
是底层协议,对应PF_INET, PF_INET6, PF_UNIX
;type
是服务类型,主要有SOCK_STREAM
流服务和SOCK_UGRAM
数据包服务,分别对应TCP和UDP;protocol
一般设置为0;- 函数执行成功返回一个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);
sockfd
是要命名的文件描述符;addr
是前面讲的socket地址,使用专用socket地址,传参时需要强转成const struct sockaddr *
;
监听socket
socket被命名之后,还不能马上接受客户连接,还需要调用
listen
来创建一个监听队列存放待处理的客户连接#include <sys/socket.h> int listen(int sockfd, int backlog)
backlog
是监听队列的最大长度
接受连接
调用
accept
函数从listen监听队列中接受一个连接#include <sys/type.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr
用来获取被接受连接的远端socket地址;- 函数执行成功返回一个新的连接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)
- 前几个参数都好理解,分别是套接字、缓冲区、缓冲区大小;
- flag参数为数据收发提供了额外的控制,一般就填0,下面是一些其他的常用的值:
send
常用的值
MSG_DONTROUTE
:不查路由表,只能发送到本地子网中的主机;MSG_DONTWAIT
:非阻塞发送,如果套接字本身是阻塞的,这里也能让send
立即返回;MSG_NOSIGNAL
:在对方关闭连接时,不触发SIGPIPE
信号,而是返回EPIPE
错误;MSG_MORE
:告诉内核后面还有数据要发,推迟实际的发送;recv
常用值:
MSG_DONTWAIT
:非阻塞发送,如果套接字本身是阻塞的,这里也能让send
立即返回;MSG_PEEK
:窥探数据,不会把数据从接收缓冲区移除;MSG_WAITALL
:一直等到请求的len
字节都收到才返回;
如果只是普通的读写,不设置
flag
参数,send/recv
和write/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);
- 因为UDP通信不建立连接,所以每次读写数据都需要带上socket地址信息;
- 如果将
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; };
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);
level
参数指定要操作哪个协议的选项,如IPv4,IPv6,TCP等;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地址列表 };
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);
proto
指定服务类型,tcp或者udpstruct 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地址,也能通过服务名获取端口号,也就是它内部是通过
gethostbyname
和getservbyname
实现的#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
:IPv4AF_INET6
:IPv6AF_UNSPEC
:任意(IPv4 或 IPv6)
ai_socktype
:
SOCK_STREAM
:TCPSOCK_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地址同时获得以字符串表示的主机名和服务名,也就是函数内部由
gethostbyaddr
和getservbyport
实现#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);
sa
:指向套接字地址结构,如struct sockaddr_in
或struct sockaddr_in6
;host
:输出主机名(或地址字符串)的缓冲区;serv
:输出服务名(或端口号)的缓冲区;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