Linux网络编程基础API

Linux网络编程基础API

socket地址API

socket地址:一个IP地址和端口对(ip,port),唯一表示了使用TCP通信的一端

字节序

大小端字节序

32位机的CPU累计器一次能装载(至少)4字节,即一个整数。这4个字节在内存中的排列顺序将影响其被累加器装载成的整数的值

  • 大端字节序(MSB):一个整数的高位字节(2331bit)存储在内存低地址处,低位字节(07bit)存储在内存高地址处

  • 小端字节序(LSB):一个整数的高位字节(2331bit)存储在内存高地址处,低位字节(07bit)存储在内存低地址处

    • 例如一个16bit的short型 x,在内存中的地址为0x0010,x 的值为0x1122,那么0x11为⾼字节,0x22为低字节 . 对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在⾼地址中,即0x0011中。小端模式,刚好相反。

    Linux网络编程基础API_第1张图片

判断大小端字节序

联合判断

void byteorder(){
     
    union {
     
        short value;
        char union_bytes[sizeof(short)];
    }test;

    test.value = 0x0102;
    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("littel endian\n");
    }
    else{
     
        printf("unknow...\n");
    }
}

Linux网络编程基础API_第2张图片

指针判断

void byteorder(){
     
    int a=0x12345678;
    char *p = NULL;
    p = (char*)&a;
    if(*p==0x78){
     
        printf("littel endian\n");
    }
    else if(*p==0x12){
     
        printf("big endian\n");
    }
}

网络字节序与主机字节序

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

问题:在两台使用不同字节序的主机之间(或同一台机器的两个进程之间,一个用C编写,一个用java编写,java虚拟机采用大端字节序)直接传递时,接收端会错误解释

解决:发送端将发送的数据转化成大端字节序数据后再发送,接收端根据自身采用的字节序决定是否对接收到的数据进行转换

大端字节序被称为网络字节序,对所有接收数据的主机提供一个正确解释收到的格式化数据的保证

转换函数
#include

unsigned long int htonl(unsigned long int hostlong);

unsigned short int htons(unsigned short int hostshort);

unsigned long int ntohl(unsigned long int netlong);

unsigned short int ntohs(unsigned short int netshort);

// h-->host  to-->to n-->network l-->long s-->short

//htonl表示host to network long 将长整型的主机字节序数据转化为网络字节序数据

//长整型函数通常用于转换IP地址,短整型函数用于转换端口号

通用socket地址

//socket网络编程接口中表示socket地址是结构体sockaddr

#include

struct sockaddr
{
     
    sa_family_t sa_family;//地址族类型
    char sa_data[14];
}

地址族类型通常与协议族类型(protocol familydomain)对应

协议族就是不同协议的集合

地址族就是一个协议族所使用的地址集合

地址族和协议族其实是一样的,值也一样,都是用来识别不同协议的,为什么要搞两套东西呢?这是因为之前UNIX有两种风格系统:BSD系统和POSIX系统,对于BSD系统,一直用的是AF,对于POSIX系统,一直用的是PF。Linux作为后起之秀,为了兼容,所以两种都支持,这样两种风格的UNIX下的软件就可以在Linux上运行了

Linux网络编程基础API_第3张图片

PF_*AF_*均定义在bits/socket.h头文件中

sa_date存放socket地址值,不同协议族的地址值具有不同含义和长度

Linux网络编程基础API_第4张图片

14字节的sa_data无法容纳多数协议族的地址值,Linux定义了一个新的通用socket地址结构体

#include

strcut sockaddr_storage{
     
    sa_family_t sa_family;
    unsigned long int __ss_align;
    char __ss_padding[128-sizeof(__ss_align)];
}

//该结构体内存对齐(__ss_align成员的作用)

专用socket地址

通用socket地址在设置与获取IP地址和端口号时需要执行烦琐的位操作,Linux为各个协议族提供专门的socket地址结构体

//UNIX本地域协议族

#include

struct sockaddr_un{
     
    sa_family_t sin_family;//地址族AF_UNIX
    char sun_path[108];//文件路径名
};
//TCP/IP协议族
struct sockaddr_in{
     
    sa_family_t sin_family;//AF_INET
    u_int16_t sin_port;//端口号,用网络字节序表示
    struct in_addr sin_addr;//IPv4地址结构体
};

struct in_addr{
     
    u_int32_t s_addr;//IPv4地址,网络字节序表示
};


struct sockaddr_in6{
     
    sa_family_t sin6_family;//AF_INET6
    u_int16_t sin6_port;//端口号,网络字节序表示
    u_int32_t sin6_flowinfo;//流信息,设置为0
    struct int6_addr sin6_addr;//IPv6地址结构体
    u_int32_t sin6_scope_id;
};

struct in6_addr{
     
    unsigned char sa_addr[16];//IPv6地址,网络字节序表示
}

所有专用socket地址(包括sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转换),因为所有socket编程接口使用的地址参数类型都是sockaddr

IP地址转换函数

人们习惯用可读性好的字符串来表示IP地址,比如用点分十进制字符串表示IPv4地址,以及用十 六进制字符串表示IPv6地址。但编程中需要先把它们转化为整数(二进制数)方能使用。而记录日志时则相反,要把整数表示的IP地址转化为可读的字符串

#include

in_addr_t inet_addr(const char*strptr);
//将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。失败时返回INADDR_NONE

int inet_aton(const char*cp,struct in_addr*inp);
//inet_aton函数完成和inet_addr同样的功能,但是将转化结果存储于参数inp指向的地址结构中。成功时返回1,失败则返回0

char* inet_ntoa(struct in_addr in);
//inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。但该函数内部用一个静态变量存储转化结果,函数的返回值指向该静态内存,因此inet_ntoa是不可重入的


//同时适用IPv4和IPv6

int inet_pton(int af,const char*src,void*dst); 
//inet_pton函数将用字符串表示的IP地址src(用点分十进制字符串表示的IPv4地址或用十六进制字符串表示的IPv6地址)转换成用网络字节序整数表示的IP地址,并把转换结果存储于dst指向的内存中。其中,af参 数指定地址族,可以是AF_INET或者AF_INET6。inet_pton成功时返回1,失败则返回0并设置errno

const char*inet_ntop(int af,const void*src,char*dst,socklen_t cnt);
//inet_ntop函数进行相反的转换,前三个参数的含义与inet_pton的参数相同,最后一个参数cnt指定目标存储单元的大小。成功时返回目标存储单元的地址,失败时返回NULL并设置errno

//下面的两个宏能帮助指定这个大小(分别用于IPv4和IPv6)

#include<netinet/in.h>
#define INET_ADDRSTRLEN 16 
#define INET6_ADDRSTRLEN 46
//inet_ntoa是不可重入
#include
#include

int main(){
     
    struct in_addr in1;
    in1.s_addr=127;
    struct in_addr in2;
    in2.s_addr=128;

    char* a = inet_ntoa(in1);
    printf("%s\n",a);//127.0.0.0
    char* b = inet_ntoa(in2);
    printf("%s\n",a);//128.0.0.0
    printf("%s\n",b);//128.0.0.0
    return 0;
}

socket基础API

创建socket

socket为可读、可写、可控制、可关闭的文件描述符

#include
#include

int socket(int domain,int type,int protocol);

//domain告诉系统使用哪个底层协议族

//type指定服务类型。服务类型主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报)服务。对TCP/IP协议族而言,其值取SOCK_STREAM表示传输层使用TCP协议,取SOCK_DGRAM表示传输层使用UDP协议
//自Linux内核版本2.6.17起,type参数可以接受上述服务类型与下面两个重要的标志相与的值:SOCK_NONBLOCK和SOCK_CLOEXEC。它们分别表示将新创建的socket设为非阻塞的,以及用fork调用创建子进程时在子进程中关闭该socket。在内核版本2.6.17之前的Linux中,文件描述符的这两个属性都 需要使用额外的系统调用(比如fcntl)来设置

//protocol是在前两个参数构成的协议集合下,再选择一个具体协议。不过该值通常都是唯一(前两个参数已经完全决定它的值)。一般设置为0表示使用默认协议

//socket调用成功时返回一个socket文件描述符,失败返回-1并设置errno

流服务:发送端send()只是将数据写到TCP发送缓冲区中,然后将发送缓冲区中的数据打包成报文段发送出去。接收端又将接收到的报文段写到缓冲区中,最后recv()直接取数据。数据没有明确分割(由底层做分割),不分一定的报文段,什么时候想发便可将写入缓冲区的数据,进行打包再发送,即send()与recv()的次数没有必然联系

数据报:发送端sendto()将数据直接打包成相对应的报文段发送。数据有明确分割,拿数据按报文段拿

命名socket

协议、IP、port

创建socket时指定了地址族,但未指定使用该地址族中的哪一个具体socket地址,将socket与socket地址绑定称为给socket命名

在服务器程序中,通常要命名socket,因为只有命名后客户端才能知道该如何连接它。客户端则通常不需要命名socket,而是采用匿名方式,即使用操作系统自动分配的socket地址

在一个pc上开启多个客户端进程, 如果指定固定端口, 必然会造成端口冲突, 影响通信。所以,客户端不指定端口,由操作系统自己分配。这样,实际上就是操作系统对客户端的socket进行了隐式的命名(名称中的端口是随机的)

#include 
#include 
int bind(int sockfd,const struct sockaddr*my_addr,socklen_t addrlen);

//bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址的长度
//bind成功时返回0,失败时返回-1并设置errno

//常见errno
//EACCES,被绑定的地址是受保护的地址,仅超级用户能够访问。比如普通用户将socket绑定到知名 服务端口(端口号为0~1023)上时,bind将返回EACCES错误
//EADDRINUSE,被绑定的地址正在使用,比如将socket绑定到一个处于TIME_WAIT状态的socket地址

监听socket

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

#include

int listen(int sockfd,int backlog);

//sockfd指定被监听的socket
//backlog提示内核监听队列的最大长度,监听队列长度如果超过backlog,服务器将不受新的客户连接,客户端也将收到ECONNREFUSED错误信息

//在内核版本2.2之前 的Linux中,backlog参数是指所有处于半连接状态(SYN_RCVD)和完全连接状态(ESTABLISHED)的 socket的上限。但自内核版本2.2之后,它只表示处于完全连接状态的socket的上限,处于半连接状态的 socket的上限则由/proc/sys/net/ipv4/tcp_max_syn_backlog内核参数定义。backlog参数的典型值是5

//listen成功时返回0,失败返回-1并设置errno

编写一个程序,其backlog为5,测试得ESTABLISHED状态的链接有6个

Linux网络编程基础API_第5张图片

多次修改backlog,最终完整链接的最多总是(backlog+1)个

在不同系统上,运行结果会有差别,但监听队列中完整连接的上限通常比backlog值略大

接受连接

从listen监听队列中接受一个连接

#include
#include

int accept(int sockfd,struct sockaddr* addr,socklen_t *addrlen);

//sockfd是执行过listen系统调用的监听socket
//addr用来获取被接受连接的远端socket地址,该socket地址长度由addrlen参数指出

//accept成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。accept失败时返回-1并设置 errno

若监听队列中处于ESTABLISHED状态的连接对应的客户端出现网络异常,那么服务器对该连接执行的accept调用是否成功

Linux网络编程基础API_第6张图片

accept只是从监听队列中取出连接,不管连接处于何种状态,也不关心网络状况的变化

发起连接

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

#include
#include

int connect(int sockfd,const struct sockaddr& serv_addr,socklen_t addrlen);

//sockfd参数由socket系统调用返回一个socket。serv_addr参数是服务器监听的socket地址,addrlen参数则指定这个地址的长度

//connect成功时返回0。一旦成功建立连接,sockfd就唯一地标识了这个连接,客户端就可以通过读写sockfd来与服务器通信。connect失败则返回-1并设置errno

//常见errno
//ECONNREFUSED,目标端口不存在,连接被拒绝
//ETIMEDOUT,连接超时

关闭连接

#include

int close(int fd);

//close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1。 只有当fd的引用计数为0时,才真正关闭连接

//无论如何都要立即终止连接(而不是将socket的引用计数减1)
#include
int shutdown(int sockfd,int howto);
//howto参数决定了shutdown行为

//shutdown成功时返回0,失败则返回-1并设置errno

Linux网络编程基础API_第7张图片

数据读写

TCP数据读写

#include<sys/types.h> 
#include<sys/socket.h> 

ssize_t recv(int sockfd,void*buf,size_t len,int flags); 
//recv读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小,flags参数通常设置为0
//recv成功时返回实际读取到的数据的长度,它可能小于期望的长度len。因此可能要多次调用recv,才能读取到完整的数据。recv可能返回0,这意味着通信对方已经关闭连接了。recv出错时返回-1并设置errno

ssize_t send(int sockfd,const void*buf,size_t len,int flags);
//send往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。send成功时返回实际写入的数据的长度,失败则返回-1并设置errno

//flags参数只对send和recv的当前调用生效

flags参数为数据收发提供了额外的控制

Linux网络编程基础API_第8张图片

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); 
//recvfrom读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小。因为UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址,即参数src_addr所指的内容,addrlen参数则指定该地址的长度

ssize_t sendto(int sockfd,const void*buf,size_t len,int flags,const struct sockaddr*dest_addr,socklen_t addrlen);
//sendto往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。dest_addr参数指定接收端的 socket地址,addrlen参数则指定该地址的长度

//recvfrom/sendto系统调用也可以用于面向连接(STREAM)的socket的数据读写,只需要把最后两个参数都设置为NULL以忽略发送端/接收端的socket地址(因为我们已经和对方建立了连接,所以已经知道其socket地址了)

通用数据读写函数

不仅可用于TCP流数据,也可用于UDP数据报

#include<sys/socket.h> 

ssize_t recvmsg(int sockfd,struct msghdr*msg,int flags); 

ssize_t sendmsg(int sockfd,struct msghdr*msg,int flags);

//sockfd参数指定被操作的目标socket。msg参数是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参数,并在调用过程中更新*/ 
};

//msg_iov成员是iovec结构体类型的指针
struct iovec {
     
    void*iov_base;/*内存起始地址*/ 
    size_t iov_len;/*这块内存的长度*/ 
};

//iovec结构体封装了一块内存的起始位置和长度。msg_iovlen指定这样的iovec结构对象有多少个。对于recvmsg而言,数据将被读取并存放在msg_iovlen块分散的内存中,这些内存的位置和长度则由 msg_iov指向的数组指定,这称为分散读(scatter read);对于sendmsg而言,msg_iovlen块分散内存中的数 据将被一并发送,这称为集中写(gather write)

//msg_control和msg_controllen成员用于辅助数据的传送

//msg_flags成员无须设定,它会复制recvmsg/sendmsg的flags参数的内容以影响数据读写过程。recvmsg还会在调用结束前,将某些更新后的标志设置到msg_flags中

带外标记

在Linux内核检测到TCP紧急标志时,将通知应用程序有带外数据需要接收。内核通知应用程序带外数据到达的两种常见方式是:I/O复用产生的异常事件和SIGURG信号。但是,即使应用程序得到了有带外数据需要接收的通知,还需要知道带外数据在数据流中的具体位置,才能准确接收带外数据

#include<sys/socket.h> 

int sockatmark(int sockfd);

//判断sockfd是否处于带外标记,即下一个被读取到的数据是否是带外数据。如果是sockatmark返回1,此时就可以利用带MSG_OOB标志的recv调用来接收带外数据。如果不是,则sockatmark返回0

地址信息函数

#include<sys/socket.h> 

int getsockname(int sockfd,struct sockaddr*address,socklen_t*address_len); 
//getsockname获取sockfd对应的本端socket地址,并将其存储于address参数指定的内存中,该socket地址 的长度则存储于address_len参数指向的变量中。如果实际socket地址的长度大于address所指内存区的大小, 那么该socket地址将被截断。getsockname成功时返回0,失败返回-1并设置errno

int getpeername(int sockfd,struct sockaddr*address,socklen_t*address_len);
//getpeername获取sockfd对应的远端socket地址,其参数及返回值的含义与getsockname的参数及返回值相同

socket选项

专门用来读取和设置socket文件描述符属性

#include<sys/socket.h> 

int getsockopt(int sockfd,int level,int option_name,void*option_value,socklen_t*restrict option_len); 

int setsockopt(int sockfd,int level,int option_name,const void*option_value,socklen_t option_len);

//level参数指定要操作哪个协议的选项(即属性),比如IPv4、 IPv6、TCP等。option_name参数则指定选项的名字。option_value和option_len参数分别是被操作选项的值和长度。不同的选项具有不同类型的值

Linux网络编程基础API_第9张图片

对服务器而言,有部分socket选项只能在调用listen系统调用前针对监听socket设置才有效。这是因为连接socket只能由accept调用返回,而accept从listen监听队列中接受的连接至少已经完成了 TCP三次握手的前两个步骤(因为listen监听队列中的连接至少已进入SYN_RCVD状态),这说明服务器已经往被接受连接上发送出了TCP同步报文段。但有的socket选项却应该在TCP同步报文段中设置,比如TCP最大报文段选项(该选项只能由同步报文段来发送)。对这种情 况,Linux给开发人员提供的解决方案是:对监听socket设置这些socket选项,那么accept返回的连接socket将自动继承这些选项。这些socket选项包括:SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWAT、SO_SNDBUF、SO_SNDLOWAT、 TCP_MAXSEG和TCP_NODELAY。而对客户端而言,这些socket选项则应该在调用connect函数之前设置,因为connect调用成功返回之后,TCP三次握手已完成

网络信息API

gethostbyname和gethostbyaddr

gethostbyname函数根据主机名称获取主机的完整信息,gethostbyaddr函数根据IP地址获取主机的完整信息。gethostbyname函数通常先在本地的/etc/hosts配置文件中查找主机,如果没有找到,再去访问DNS服务器

#include<netdb.h> 

struct hostent*gethostbyname(const char*name); 

struct hostent*gethostbyaddr(const void*addr,size_t len,int type);

//name参数指定目标主机的主机名,addr参数指定目标主机的IP地址,len参数指定addr所指IP地址的长 度,type参数指定addr所指IP地址的类型

#include<netdb.h> 
struct hostent {
     
    char*h_name;/*主机名*/ 
    char**h_aliases;/*主机别名列表,可能有多个*/ 
    int h_addrtype;/*地址类型(地址族)*/ 
    int h_length;/*地址长度*/ 
    char**h_addr_list/*按网络字节序列出的主机IP地址列表*/ 
};

getservbyname和getservbyport

通过读取/etc/services文件来获取服务的信息的

#include<netdb.h> 

struct servent*getservbyname(const char*name,const char*proto); 

struct servent*getservbyport(int port,const char*proto);

//name参数指定目标服务的名字,port参数指定目标服务对应的端口号。proto参数指定服务类型,给它传递“tcp”表示获取流服务,给它传递“udp”表示获取数据报服务,给它传递NULL则表示获取所有类型的服务

#include<netdb.h> 
struct servent {
     
    char*s_name;/*服务名称*/ 
    char**s_aliases;/*服务的别名列表,可能有多个*/ 
    int s_port;/*端口号*/ 
    char*s_proto;/*服务类型,通常是tcp或者udp*/
};

getaddrinfo

既能通过主机名获得IP地址(内部使用的gethostbyname函数),也能通过服务名获得 端口号(内部使用的是getservbyname函数)。它是否可重入取决于其内部调用的gethostbyname和 getservbyname函数是否是它们的可重入版本

#include<netdb.h> 

int getaddrinfo(const char*hostname,const char*service,const struct addrinfo*hints,struct addrinfo**result);

//hostname参数可以接收主机名,也可以接收字符串表示的IP地址(IPv4采用点分十进制字符串,IPv6则 采用十六进制字符串)。同样,service参数可以接收服务名,也可以接收字符串表示的十进制端口号。hints 参数是应用程序给getaddrinfo的一个提示,以对getaddrinfo的输出进行更精确的控制。hints参数可以被设置 为NULL,表示允许getaddrinfo反馈任何可用的结果。result参数指向一个链表,该链表用于存储getaddrinfo 反馈的结果

struct addrinfo {
     
    int ai_flags;/*见后文*/ 
    int ai_family;/*地址族*/ 
    int ai_socktype;/*服务类型,SOCK_STREAM或SOCK_DGRAM*/ 
    int ai_protocol;/*见后文*/ 
    socklen_t ai_addrlen;/*socket地址ai_addr的长度*/ 
    char*ai_canonname;/*主机的别名*/
	struct sockaddr*ai_addr;/*指向socket地址*/ 
    struct addrinfo*ai_next;/*指向下一个sockinfo结构的对象*/ 
};

//ai_protocol成员是指具体的网络协议,其含义和socket系统调用的第三个参数相同,通常被设置为0

//当我们使用hints参数的时候,可以设置其ai_flags,ai_family,ai_socktype和ai_protocol四个字段,其他字段则必须被设置为NULL

//getaddrinfo将隐式地分配堆内存(可以通过valgrind等工具查看),因 为res指针原本是没有指向一块合法内存的,所以,getaddrinfo调用结束后,我们必须使用如下配对函数来释放这块内存
#include<netdb.h> 
void freeaddrinfo(struct addrinfo*res);

getnameinfo

能通过socket地址同时获得以字符串表示的主机名(内部使用的是gethostbyaddr函数) 和服务名(内部使用的是getservbyport函数)。它是否可重入取决于其内部调用的gethostbyaddr和 getservbyport函数是否是它们的可重入版本

#include<netdb.h> 
int getnameinfo(const struct sockaddr*sockaddr,socklen_t addrlen,char*host,socklen_t hostlen,char*serv,socklen_t servlen,int flags);

//getnameinfo将返回的主机名存储在host参数指向的缓存中,将服务名存储在serv参数指向的缓存中, hostlen和servlen参数分别指定这两块缓存的长度。flags参数控制getnameinfo的行为

,因 为res指针原本是没有指向一块合法内存的,所以,getaddrinfo调用结束后,我们必须使用如下配对函数来释放这块内存
#include<netdb.h>
void freeaddrinfo(struct addrinfo*res);


### getnameinfo

能通过socket地址同时获得以字符串表示的主机名(内部使用的是gethostbyaddr函数) 和服务名(内部使用的是getservbyport函数)。它是否可重入取决于其内部调用的gethostbyaddr和 getservbyport函数是否是它们的可重入版本

```c
#include<netdb.h> 
int getnameinfo(const struct sockaddr*sockaddr,socklen_t addrlen,char*host,socklen_t hostlen,char*serv,socklen_t servlen,int flags);

//getnameinfo将返回的主机名存储在host参数指向的缓存中,将服务名存储在serv参数指向的缓存中, hostlen和servlen参数分别指定这两块缓存的长度。flags参数控制getnameinfo的行为

你可能感兴趣的:(服务器编程,编译器,c++,linux)