跳转至

UNIX net

1 套接字地址结构

title:套接字地址结构区别对比
1. sockaddr、sockaddr_in、sockaddr_in6首部都含2个字节长度的address family字段,通过此字段,可以判断类型是AF_INET、还是AF_INET6。
2. sockaddr内存占16字节,sockaddr_in占16字节,sockaddr_in6占28字节。
3. 通常在使用bind、connect函数绑定sockadd结构指针时,强制将sockaddr_in、sockaddr_in6转成sockadd结构指针,虽然它们内存大小不同,但内部实现会根据sockaddr::sa_family来判断到底是sockaddr_in6,还是sockaddr_in。
  • 示例
int bind(int socket_fd, sockaddr* p_addr, int add_size)
{
    if (p_addr->sa_family == AF_INET)
    {
        sockaddr_in* p_addr_in = (sockaddr_in*)p_addr;
        //...
    }
    else if (p_addr->sa_family == AF_INET6)
    {
        sockaddr_in6* p_addr_in = (sockaddr_in6*)p_addr;
        //...
    }
    else
    {
        //...
    }
}

1.1 通用套接字地址结构 sockaddr

  • 头文件:sys/socket.h
struct sockaddr
{
    __SOCKADDR_COMMON(sa_); /* Common data: address family and length.  */
    char sa_data[14];       /* Address data.  */
};

1.2 ipv4 套接字地址结构 sockaddr_in

  • 头文件:netinet/in.h
struct sockaddr_in
{
    __SOCKADDR_COMMON(sin_);
    in_port_t sin_port;      /* Port number.  */
    struct in_addr sin_addr; /* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
};

1.3 ipv6 套接字地址结构 sockaddr_in6

  • 头文件:netinet/in.h
struct sockaddr_in6
{
    __SOCKADDR_COMMON(sin6_);
    in_port_t sin6_port;       /* Transport layer port  */
    uint32_t sin6_flowinfo;    /* IPv6 flow information */
    struct in6_addr sin6_addr; /* IPv6 address */
    uint32_t sin6_scope_id;    /* IPv6 scope-id */
};

2 字节排序

- 网络字节序统一规定是大端;主机字节序可以是大端,也可以是小端,由操作系统决定。
  • 头文件:netinet/in.h
// h 表示host n 表示network s 表示short=16位 l 表示long=32位
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);//主机字节序转网络字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);//网络字节序转主机字节序

3 地址转换函数

应该尽量选择 ipv4和ipv6通用地址转换inet_pton、inet_ntop

3.1 ipv4 字符串和网络字节序地址转换

  • 头文件:arpa/inet.h
int inet_aton(const char *cp, struct in_addr *inp);//ipv4字符串(如127.0.0.1)转到in_addr结构

in_addr_t inet_addr(const char *cp);//ipv4字符串转32位的网络字节序地址

in_addr_t inet_network(const char *cp);

char *inet_ntoa(struct in_addr in);//32位的网络字节序地址转ipv4字符串

struct in_addr inet_makeaddr(int net, int host);

in_addr_t inet_lnaof(struct in_addr in);

in_addr_t inet_netof(struct in_addr in);

3.2 ipv4 和 ipv6 通用地址转换

  • 语法:
  • int inet_pton(int af, const char *src, void *dst);:文本格式ip地址转2进制形式
    • af:表示ip协议族 ,必须是AF_INET 或 AF_INET6
    • src:ip字符串表示,如ipv4 127.0.0.1; ipv6 0:0:0:0:0:0:0:1
    • dst:ip地址结构,ipv4是in_addr ;ipv6 是in6_addr
  • const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);:2进制形式ip地址转文本格式
    • af:表示ip协议族 ,必须是AF_INET 或 AF_INET6
    • src:ip地址结构,ipv4是in_addr ;ipv6 是in6_addr
    • dst:ip字符串表示,如ipv4 127.0.0.1; ipv6 0:0:0:0:0:0:0:1
    • size:dst参数字符串的大小,ipv4时=INET_ADDRSTRLEN ;ipv6时=INET6_ADDRSTRLEN
  • 示例
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    unsigned char buf[sizeof(struct in6_addr)];
    int domain, s;
    char str[INET6_ADDRSTRLEN];

    if (argc != 3)
    {
        fprintf(stderr, "Usage: %s {i4|i6|<num>} string\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    domain = (strcmp(argv[1], "i4") == 0) ? AF_INET : (strcmp(argv[1], "i6") == 0) ? AF_INET6
                                                                                   : atoi(argv[1]);

    s = inet_pton(domain, argv[2], buf);
    printf("inet_pton(%d, %s, %s) = %d\n", domain, argv[2], buf, s);
    if (s <= 0)
    {
        if (s == 0)
            fprintf(stderr, "Not in presentation format");
        else
            perror("inet_pton");
        exit(EXIT_FAILURE);
    }

    if (inet_ntop(domain, buf, str, INET6_ADDRSTRLEN) == NULL)
    {
        perror("inet_ntop");
        exit(EXIT_FAILURE);
    }
    printf("inet_ntop(%d, %s, %s, %d) = %s\n", domain, buf, str, INET6_ADDRSTRLEN, str);
    printf("%s\n", str);

    exit(EXIT_SUCCESS);
}

4 socket 接口

- 客户端示例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(){
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    //向服务器(特定的IP和端口)发起请求
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    //读取服务器传回的数据
    char buffer[40];
    read(sock, buffer, sizeof(buffer)-1);

    printf("Message form server: %s\n", buffer);

    //关闭套接字
    close(sock);

    return 0;
}
  • 服务端示例
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(){
    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //将套接字和IP、端口绑定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    //进入监听状态,等待用户发起请求
    listen(serv_sock, 20);

    //接收客户端请求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

    //向客户端发送数据
    char str[] = "Hello World!";
    write(clnt_sock, str, sizeof(str));

    //关闭套接字
    close(clnt_sock);
    close(serv_sock);

    return 0;
}

4.1 socket

  • 功能:创建套接字
  • 使用返回:客户端、服务端
  • 语法:int socket(int domain, int type, int protocol)
  • domain:通信域,如:AF_INET、AF_INET6、AF_UNIX
  • type:套接字类型,如:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW
  • protocal:通信协议,通常指定0就可以,这样系统会根据domain和type采用默认值
  • 返回值:成功返回套接字描述符,失败返回-1

4.2 connect

  • 功能:连接套接字
  • 使用范围:客户端
  • 语法:int connect(int socket, const struct sockaddr *address,socklen_t address_len);
  • socket:scoket接口创建的套接字描述符
  • address:需要连接的套接字地址
  • address_len:address结构的长度
  • 返回值:成功0;是失败-1

4.3 bind

  • 功能:绑定套接字
  • 使用范围:服务端
  • 语法:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
  • socket:scoket接口创建的套接字描述符
  • address:需要连接的套接字地址
  • address_len:address结构的长度
  • 返回值:成功0;是失败-1

4.4 listen

  • 功能:在套接字上的监听连接请求
  • 使用范围:服务端
  • 语法:int listen(int sockfd, int backlog);
  • sockfd:创建并绑定后的套接字描述符
  • backlog:队列中最大描述符数量
  • 返回值:成功0;是失败-1

4.5 accept

  • 功能:接收套接字连接请求
  • 使用范围:服务端
  • 语法:int accept(int socket, struct sockaddr *restrict address,socklen_t *restrict address_len);
  • socket:scoket接口创建的套接字描述符
  • address:存储对方套接字信息,可以为NULL
  • address_len:address结构的长度
  • 返回值:成功返回新的套接字,供读写;失败-1

5 消息读写接口

- `send`函数在本质上并不是向网络上发送数据,而是将**应用层发送缓冲区的数据拷贝到内核缓冲区**中,至于数据什么时候会从网卡缓冲区中真正地发到网络中,要根据TCP/IP协议栈的行为来确定。如果`socket`设置了`TCP_NODELAY`选项(即**禁用nagel算法**)。存放到内核缓冲区的数据就会被立即发送出去,反之,一次放入内核缓冲区的数据包如果太小,则系统会在多个小的数据包凑成一个足够大的数据包之后才会将数据发送出去。
- `recv`函数在本质上并不是从网络上收取数据,而是**将内核缓冲区中的数据拷贝到应用程序的缓冲区**中。在拷贝完成后会将内核缓冲区中的该部分数据移除。
  • recvsend通常用于TCP;recvfromsendto通常用于UDP
  • recv中flag参数为0时,等价于read;当send中flag参数为0时,等价于write;

5.1 recv 函数

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

5.2 send 函数

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

6 阻塞和非阻塞模式

非阻塞模式一般用于需要支持**高并发多QPS**的场景(如服务器程序)
返回值n 返回值的含义
大于0 成功发送send或接收recvn字节
等于0 对端关闭连接
小于0 出错,被信号中断,对端TCP窗口太小导致数据发送不出去或者当前网卡缓冲区已无数据可接收

返回值大于 0。当 send/recv 函数的返回值大于 0 时,表示发送或接收多少字节。需要注意的是,在这种情况下,我们一定要判断 send 函数的返回值是不是我们期望发送的字节数,而不是简单判断其返回值大于 0。 - 阻塞模式下正确的发送示例

//不推荐的方式
int n = send(socket, buf, buf_length, 0);
if(n == buf_length)
{
    printf("send data successfully\n");
}

//推荐的方式
bool SendData(const char* buf, int buf_length)
{
    //已经发送的字节数
    int sent_bytes = 0;
    int ret = 0;
    while(true)
    {
        ret = send(m_hSocket, buf + sent_bytes, buf_length - sent_bytes, 0);
        if(ret == -1)
        {
            if(errno == EWOULDBLOCK)
            {
                //严谨的做法:如果发送不出去,则应该缓存尚未发送出去的数据
                break;
            }
            else if(errno == EINTR)
                continue;
            else
                return false;
        }
        else if(ret == 0)
            return false;
        sent_bytes += ret;
        if(sent_bytes == buf_length)
            break;
    }
    return true;
}
  • 下表表示的是非阻塞模式下,socketsendrecv返回值,对于阻塞模式下的socket,如果返回值为-1,则一定表示出错。
返回值和错误码 send函数 recv函数
返回-1,错误码是EWOULDBLOCK或EAGAIN TCP窗口太小,数据暂时发送不出去 在当前缓冲区中无可读数据
返回-1,错误码是EINTR 被信号中断,需要重试 被信号中断,需要重试
返回-1,错误码不是以上3种 出错 出错

阻塞的 socket 函数在调用 sendrecvconnectaccept 等函数时,如果特定的条件不满足,就会阻塞其调用线程直至超时,非阻塞的 socket 恰恰相反。

7 其它

7.1 getsockopt

  • 功能:获取socket选项
  • 语法:int getsockopt(int socket, int level, int option_name,void *restrict option_value, socklen_t *restrict option_len);
  • socket:socket描述符
  • level:指定协议层,如操作socket选项,为SOL_SOCKET;操作TCP选项,为IPPROTO_TCP;
  • option_name:操作的选项名称
  • option_value:执行后获取的option_name选项的数据
  • option_len:执行后获取的option_value结构的实际大小
  • level的可选参数有如下
  • SOL_SOCKET:套接字协议
  • IPPROTO_TCP:Transmission Control Protocol
  • IPPROTO_UDP:User Datagram Protocol
  • IPPROTO_IP:Dummy protocol for TCP
  • IPPROTO_ICMP:Internet Control Message Protocol
  • ...
  • option_name的可选参数有如下
  • SOL_SOCKET协议如下
    • SO_RCVTIMEO:接受超时
    • SO_SNDTIMEO:发送超时
  • 示例
struct timeval  tv;
getsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, (socklen_t *)&sin_size);

7.2 setsockopt

  • 功能:设置socket选项
  • 语法:int setsockopt(int socket, int level, int option_name,const void *option_value, socklen_t option_len);
  • socket:socket描述符
  • level:指定协议层,如操作socket选项,为SOL_SOCKET;操作TCP选项,为IPPROTO_TCP;
  • option_name:操作的选项名称
  • option_value:设置option_name选项需要的数据
  • option_len:指定option_value结构的大小
  • 示例
struct timeval  tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));

7.3 getprotoent

7.4 getaddrinfo、freeaddrinfo、gethostbyname、gethostbyaddr

getaddrinfo函数将主机名、主机地址、服务名和端口的字符串表示转换成套接字地址结构体。它是已弃用的getgostbyname和getservbyname函数的新的替代品。

7.4.1 getaddrinfo

  • man 手册示例代码
  • 功能:通过域名或端口服务名得到一系列满足要求的 addrinfo 结构。
  • 语法结构
    • __name : 一个主机名或者地址串 (IPv4 的点分十进制串或者 IPv6 的 16 进制串)。
    • __service :服务名可以是十进制的端口号,也可以是已定义的服务名称,如 ftp、http 等。
    • __req :可以是一个空指针,也可以是一个指向某个 addrinfo 结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。
    • __pai :本函数通过 __pai 指针参数返回一个指向 addrinfo 结构体链表的指针。
int getaddrinfo (const char *__restrict __name,
         const char *__restrict __service,
         const struct addrinfo *__restrict __req,
         struct addrinfo **__restrict __pai);
  • __name 内容可以在 /etc/hosts 文件查看
  • __service 内容可用在 /etc/services 文件查看
  • 示例
  cat /etc/hosts 
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
140.82.113.4 github.com
➜  cat /etc/services |tail -2
cloudcheck      45514/tcp                ASSIA CloudCheck WiFi Management System
spremotetablet  46998/tcp                Capture handwritten signatures
  • addrinfo 结构
/* Structure to contain information about address of a service provider.  */
struct addrinfo
{
  int ai_flags;         /* Input flags.  */
  int ai_family;     /* Protocol family for socket.  */
  int ai_socktype;      /* Socket type.  */
  int ai_protocol;      /* Protocol for socket.  */
  socklen_t ai_addrlen;    /* Length of socket address.  */
  struct sockaddr *ai_addr;   /* Socket address for socket.  */
  char *ai_canonname;      /* Canonical name for service location.  */
  struct addrinfo *ai_next;   /* Pointer to next in list.  */
};

7.4.2 freeaddrinfo

  • 功能:释放 getaddrinfo 执行成功后第四个参数的内存。
  • 语法
void freeaddrinfo (struct addrinfo *__ai);

7.4.3 示例

7.4.3.1 根据主机名获取 IP 地址
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    if (argc != 2) {
        printf("Usag: ./a.out hostname|ip\n");
        exit(1);
    }
    struct addrinfo hints;
    struct addrinfo *res, *cur;
    int ret;
    struct sockaddr_in *addr;
    char ipbuf[16];
    int port;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET; /* Allow IPv4 */
    hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
    hints.ai_protocol = 0; /* Any protocol */
    hints.ai_socktype = SOCK_DGRAM;
    ret = getaddrinfo(argv[1], NULL,&hints,&res);
    if (ret < 0) {
        fprintf(stderr, "%s\n", gai_strerror(ret));
        exit(1);
    }

    for (cur = res; cur != NULL; cur = cur->ai_next) {
        addr = (struct sockaddr_in *)cur->ai_addr;
        printf("ip: %s\n", inet_ntop(AF_INET, &addr->sin_addr, ipbuf, 16));
        printf("port: %d\n", inet_ntop(AF_INET, &addr->sin_port, (void *)&port, 2));
        //printf("port: %d\n", ntohs(addr->sin_port));

    }
    freeaddrinfo(res);
    exit(0);
}
7.4.3.2 根据主机名和端口号获取地址信息
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <netdb.h>
#include <sys/socket.h>
void main()
{ 
    struct addrinfo *ailist, *aip; 
    struct addrinfo hint; 
    struct sockaddr_in *sinp; 
    char *hostname = "localhost";
    char buf[INET_ADDRSTRLEN]; 
    char *server = "6543"; /* 这是服务端口号 */
    const char *addr; 
    int ilRc;
    hint.ai_family = AF_UNSPEC; /* hint 的限定设置 */
    hint.ai_socktype = 0; /* 这里可是设置 socket type . 比如 SOCK——DGRAM */
    hint.ai_flags = AI_PASSIVE; /* flags 的标志很多 。常用的有AI_CANONNAME; */
    hint.ai_protocol = 0; /* 设置协议 一般为0,默认 */ 
    hint.ai_addrlen = 0; /* 下面不可以设置,为0,或者为NULL */
    hint.ai_canonname = NULL; 
    hint.ai_addr = NULL; 
    hint.ai_next = NULL;
    ilRc = getaddrinfo(hostname, server, &hint, &ailist); 
    if (ilRc < 0) 
    { 
        printf("str_error = %s\n", gai_strerror(errno)); 
        return; 
    }

    /* 显示获取的信息 */
    for (aip = ailist; aip != NULL; aip = aip->ai_next)
    { 
        sinp = (struct sockaddr_in *)aip->ai_addr;
        addr = inet_ntop(AF_INET, &sinp->sin_addr, buf, INET_ADDRSTRLEN); 
        printf(" addr = %s, port = %d\n", addr?addr:"unknow ", ntohs(sinp->sin_port)); 
    }
}
7.4.3.3 由内核分配随机端口(再也不担心端口被占了)
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

#define MAX_CONN_COUNT 10
 #define INVALID_SOCKET (~0) 

int main(int argc, char **argv)
{
    int motionListenPort = 0;
    int motion_sock = 0;
    int    err;
    int    maxconn;
    char familyDesc[32];
    struct sockaddr_storage motion_sock_addr;
    socklen_t alen;
    struct addrinfo *addrs = NULL, *addr, hints;
    int ret;
    int tries = 0;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET; /* Allow IPv4 */
    hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
    hints.ai_protocol = 0; /* Any protocol */
    hints.ai_socktype = SOCK_STREAM;
    ret = getaddrinfo(NULL, "0", &hints, &addrs);
    if (ret < 0)
    {
        fprintf(stderr, "%s\n", gai_strerror(ret));
        exit(1);
    }

    for (addr = addrs; addr != NULL; addr = addr->ai_next) {
        /* Create the socket. */
        if ((motion_sock = socket(addr->ai_family, SOCK_STREAM, 0)) == INVALID_SOCKET)
        {
            fprintf(stderr, "Error:could not create socket for the motion\n");
            continue;
        }

        /* Bind it to a kernel assigned port on localhost and get the assigned port via getsockname(). */
        if (bind(motion_sock, addr->ai_addr, addr->ai_addrlen) < 0)
        {
            fprintf(stderr, "Error: could not bind socket for the motion\n");
            close(motion_sock);
            motion_sock = INVALID_SOCKET;
            continue;
        }

        alen = sizeof(motion_sock_addr);
        if (getsockname(motion_sock, (struct sockaddr *) &(motion_sock_addr), &alen) < 0)
        {
            fprintf(stderr, "could not get address of socket for the motion\n");
            close(motion_sock);
            motion_sock = INVALID_SOCKET;
            continue;
        }

        /* Resolve the motion listen port. */
        switch(motion_sock_addr.ss_family)
        {
            case AF_INET:
                {
                    struct sockaddr_in *motion_addr = (struct sockaddr_in *) &motion_sock_addr;
                    motionListenPort = ntohs(motion_addr->sin_port);
                    strcpy(familyDesc, "IPv4");
                    fprintf(stdout, "motionListenPort=%d, familyDesc = %s\n", motionListenPort, familyDesc);
                    break;
                }
            case AF_INET6:
                {
                    struct sockaddr_in6 *motion_addr = (struct sockaddr_in6 *) &motion_sock_addr;
                    motionListenPort = ntohs(motion_addr->sin6_port);
                    strcpy(familyDesc, "IPv6");
                    fprintf(stdout, "motionListenPort=%d, familyDesc = %s\n", motionListenPort, familyDesc);
                    break;
                }
            default:
                {
                    fprintf(stderr, "Error:unrecognized address family \"%d\" for the motion\n", motion_sock_addr.ss_family);
                    continue;
                }
        }

        /* 监听 */
        maxconn = MAX_CONN_COUNT;
        err = listen(motion_sock, maxconn);
        if (err < 0)
        {
            fprintf(stderr, "could not listen on socket for the motion\n");
            close(motion_sock);
            motion_sock = INVALID_SOCKET;
            continue;
        }
    }

    if (motion_sock == INVALID_SOCKET)
        goto listen_failed;

    /* XXXXXX    socket通信过程 */


    freeaddrinfo(addrs);
    close(motion_sock);
    return 0;

listen_failed:
    fprintf(stderr, "Error: failed to listen for the motion\n");
    if (addrs)
        freeaddrinfo(addrs);

    if (motion_sock != INVALID_SOCKET)
        close(motion_sock);
    motion_sock = INVALID_SOCKET;
    return -1;
}
7.4.3.4 使用 ioctl 获取指定网卡 IP 地址
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#define ETH_NAME    "eth0"

int main()

{
    int sock;
    struct sockaddr_in sin;
    struct ifreq ifr;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
    {
        perror("socket");
        return -1;        
    }

    strncpy(ifr.ifr_name, ETH_NAME, IFNAMSIZ);
    ifr.ifr_name[IFNAMSIZ - 1] = 0;
    if (ioctl(sock, SIOCGIFADDR, &ifr) < 0)
    {
        perror("ioctl");
        return -1;
    }

    memcpy(&sin, &ifr.ifr_addr, sizeof(sin));
    fprintf(stdout, "eth0: %s\n", inet_ntoa(sin.sin_addr));

    return 0;
}
7.4.3.5 使用 ping 指令,根据 hostname 获取 ip 地址

本例未用 getaddrinfo,而是采用shell指令方法(不推荐)。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>


//使用ping指令,根据hostname获取ip地址
int getIpAddrByHostname(char *hostname, char* ip_addr, size_t ip_size)
{
    char command[256];
    FILE *f;
    char *ip_pos;

    snprintf(command, 256, "ping -c1 %s | head -n 1 | sed 's/^[^(]*(\\([^)]*\\).*$/\\1/'", hostname);
    fprintf(stdout, "%s\n", command);
    if ((f = popen(command, "r")) == NULL)
    {
        fprintf(stderr, "could not open the command, \"%s\", %s\n", command, strerror(errno));
        return -1;
    }

    fgets(ip_addr, ip_size, f);
    fclose(f);

    ip_pos = ip_addr;
    for (;*ip_pos && *ip_pos!= '\n'; ip_pos++);
    *ip_pos = 0;

    return 0;
}

int main()
{
    char addr[64] = {0};
    getIpAddrByHostname("localhost", addr, INET_ADDRSTRLEN);
    fprintf(stdout, "localhost: %s\n", addr);
    return 0;
}

7.4.4 gethostbyname

7.4.5 gethostbyaddr

8 专用名词解析

8.1 带外数据 (out—of—band data)

  • 带外数据(out—of—band data):有时也称为加速数据(expedited data),是指连接双方中的一方发生重要事情,想要迅速地通知对方。这种通知在已经排队等待发送的任何“普通”(有时称为“带内”)数据之前发送。带外数据设计为比普通数据有更高的优先级。带外数据是映射到现有的连接中的,而不是在客户机和服务器间再用一个连接。
  • 带外数据的发送和接收:send(sockfd,"a",1,MSG_OOB);recv(sockfd,&outofband,1,MSG_OOB);