socket编程

socket与计算机网络

两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

在Unix一切皆文件哲学的思想下,socket是一种”打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个”文件”,在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

Socket抽象层
Socket抽象层

socket通信流程

socket连接过程
socket连接过程

流程如上图所示;

联想三次握手:

三次握手
三次握手
socket建立连接
socket建立连接

经过对比会发现socket的流程中建立连接的部分就是三次握手;

socket API与编程

服务端

  1. 创建套接字对象

    1
    2
    3
    4
    5
    6
    /*
    * _domain 套接字使用的协议族信息
    * _type 套接字的传输类型
    * __protocol 通信协议
    * */
    int socket (int __domain, int __type, int __protocol) __THROW;

    参数:

    1. 第一个参数,协议族信息可选如下:

      (在Linux系统中AF_PF_是等价的。在内核源码中net目录下面有Af_开头的一系列文件(:Af_inet.cAf_inet6.cAf_unix.c),每一个文件分别代表了一种协议族。)

地址族 含义
AF_INET IPv4网络协议中采用的地址族
AF_INET6 IPv6网络协议中采用的地址族
AF_UNIX, AF_LOCAL 本地通信中采用的UNIX协议的地址族(用的少)
AF_PACKET 链路层通信

  1. 第二个参数:套接字类型:

    参考内核源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * enum sock_type - Socket types
    * @SOCK_STREAM: stream (connection) socket
    * @SOCK_DGRAM: datagram (conn.less) socket
    * @SOCK_RAW: raw socket
    * @SOCK_RDM: reliably-delivered message
    * @SOCK_SEQPACKET: sequential packet socket
    * @SOCK_DCCP: Datagram Congestion Control Protocol socket
    * @SOCK_PACKET: linux specific way of getting packets at the dev level.
    * For writing rarp and other similar things on the user level.
    *
    * When adding some new socket type please
    * grep ARCH_HAS_SOCKET_TYPE include/asm-* /socket.h, at least MIPS
    * overrides this enum for binary compat reasons.
    */
    enum sock_type {
    SOCK_STREAM = 1,
    SOCK_DGRAM = 2,
    SOCK_RAW = 3,
    SOCK_RDM = 4,
    SOCK_SEQPACKET = 5,
    SOCK_DCCP = 6,
    SOCK_PACKET = 10,
    };
套接字类型 含义
SOCKET_RAW 原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议。
SOCK_STREAM SOCK_STREAM是数据流,一般为TCP/IP协议的编程。
SOCK_DGRAM SOCK_DGRAM是数据报,一般为UDP协议的网络编程;
  1. 第三个参数:最终采用的协议;常见的协议有IPPROTO_TCP、IPPTOTO_UDP。如果第二个参数选择了SOCK_STREAM,那么采用的协议就只能是IPPROTO_TCP;如果第二个参数选择的是SOCK_DGRAM,则采用的协议就只能是IPPTOTO_UDP。
  1. 向套接字分配网络地址

    1
    2
    3
    4
    5
    6
    /* 
    * __fd:socket描述字,也就是socket引用
    * myaddr:要绑定给sockfd的协议地址
    * __len:地址的长度
    */
    int bind (int __fd, const struct sockaddr* myaddr, socklen_t __len) __THROW;
    1. 第一个参数:socket文件描述符__fd即套接字创建时返回的对象;

    2. 第二个参数:myaddr则是填充了一些网络地址信息,包含通信所需要的相关信息,其结构体具体如下:

      1
      2
      3
      4
      5
      struct sockaddr
      {
      sa_family_t sin_family; /* Common data: address family and length. */
      char sa_data[14]; /* 地址数据 */
      };

      这里根据socket源码:typedef unsigned short sa_family_t;表示地址族,可选参数如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      /* Supported address families. */
      #define AF_UNSPEC 0
      #define AF_UNIX 1 /* Unix domain sockets */
      #define AF_LOCAL 1 /* POSIX name for AF_UNIX */
      #define AF_INET 2 /* Internet IP Protocol */
      #define AF_AX25 3 /* Amateur Radio AX.25 */
      #define AF_IPX 4 /* Novell IPX */
      #define AF_APPLETALK 5 /* AppleTalk DDP */
      #define AF_NETROM 6 /* Amateur Radio NET/ROM */
      #define AF_BRIDGE 7 /* Multiprotocol bridge */
      #define AF_ATMPVC 8 /* ATM PVCs */
      #define AF_X25 9 /* Reserved for X.25 project */
      #define AF_INET6 10 /* IP version 6 */
      #define AF_ROSE 11 /* Amateur Radio X.25 PLP */
      #define AF_DECnet 12 /* Reserved for DECnet project */
      #define AF_NETBEUI 13 /* Reserved for 802.2LLC project*/
      #define AF_SECURITY 14 /* Security callback pseudo AF */
      #define AF_KEY 15 /* PF_KEY key management API */
      #define AF_NETLINK 16
      #define AF_ROUTE AF_NETLINK /* Alias to emulate 4.4BSD */
      #define AF_PACKET 17 /* Packet family */
      #define AF_ASH 18 /* Ash */
      #define AF_ECONET 19 /* Acorn Econet */
      #define AF_ATMSVC 20 /* ATM SVCs */
      #define AF_RDS 21 /* RDS sockets */
      #define AF_SNA 22 /* Linux SNA Project (nutters!) */
      #define AF_IRDA 23 /* IRDA sockets */
      #define AF_PPPOX 24 /* PPPoX sockets */
      #define AF_WANPIPE 25 /* Wanpipe API Sockets */
      #define AF_LLC 26 /* Linux LLC */
      #define AF_CAN 29 /* Controller Area Network */
      #define AF_TIPC 30 /* TIPC sockets */
      #define AF_BLUETOOTH 31 /* Bluetooth sockets */
      #define AF_IUCV 32 /* IUCV sockets */
      #define AF_RXRPC 33 /* RxRPC sockets */
      #define AF_ISDN 34 /* mISDN sockets */
      #define AF_PHONET 35 /* Phonet sockets */
      #define AF_IEEE802154 36 /* IEEE802154 sockets */
      #define AF_MAX 37 /* For now.. */

      一般情况下会用这个sockaddr的变体sockaddr_in进行字段的初始化:

      1
      2
      3
      4
      5
      6
      struct sockaddr_in{
      sa_family_t sin_family; //前面介绍的地址族
      uint16_t sin_port; //16位的TCP/UDP端口号
      struct in_addr sin_addr; //32位的IP地址
      char sin_zero[8]; //不使用
      }
      1. 其中in_addr 结构定义如下:

        1
        2
        3
        4
        5
        6
        /* Internet address.  */
        typedef uint32_t in_addr_t;
        struct in_addr
        {
        in_addr_t s_addr;
        };

        其中s_addr是一种uint32_t类型的数据,而且在网络传输时,统一都是以大端序的网络字节序方式传输数据;但我们通常习惯的IP地址是点分十进制,比如“219.228.148.169”;可以使用如下函数转化把IP地址转换成32位的整数并且进行网络字节转换:

        1
        2
        3
        in_addr_t inet_addr (const char *__cp) __THROW;
        //或者
        int inet_aton (const char *__cp, struct in_addr *__inp) __THROW; //windows无此函数

        如果单纯要进行网络字节序地址的转换,可以采用如下函数:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        /*Functions to convert between host and network byte order.

        Please note that these functions normally take `unsigned long int' or
        `unsigned short int' values as arguments and also return them. But
        this was a short-sighted decision since on different systems the types
        may have different representations but the values are always the same. */

        // h代表主机字节序
        // n代表网络字节序
        // s代表short(4字节)
        // l代表long(8字节)
        extern uint32_t ntohl (uint32_t __netlong) __THROW __attribute__ ((__const__));
        extern uint16_t ntohs (uint16_t __netshort)
        __THROW __attribute__ ((__const__));
        extern uint32_t htonl (uint32_t __hostlong)
        __THROW __attribute__ ((__const__));
        extern uint16_t htons (uint16_t __hostshort)
      2. sin_zero 无特殊的含义,只是为了与下面介绍的sockaddr结构体一致而插入的成员。

      在具体调用bind的函数的时候,用强制类型转换把sockaddr_in转化为sockaddr即可;

      1
      2
      3
      struct sockaddr_in serv_addr;
      ...
      bind(serv_socket, (struct sockaddr*)&serv_addr, sizeof(serv_addr);
  2. 进入等待连接请求状态

    1
    2
    3
    4
    /* Prepare to accept connections on socket FD.
    N connection requests will be queued before further requests are refused.
    Returns 0 on success, -1 for errors. */
    extern int listen (int __fd, int __n) __THROW;

    给套接字分配了所需的信息后,就可以调用listen()函数对来自客户端的连接请求进行监听(客户端此时要调用connect()函数进行连接)

    1. 第一个参数:socket文件描述符__fd,分配所需的信息后的套接字。
    2. 第二个参数:连接请求的队列长度,如果为6,表示队列中最多同时有6个连接请求。

    这个函数的fd(socket套接字对象)就相当于一个门卫,对连接请求做处理,决定是否把连接请求放入到server端维护的一个队列中去。

  3. 受理客户端的连接请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* Await a connection on socket FD.
    When a connection arrives, open a new socket to communicate with it,
    set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
    peer and *ADDR_LEN to the address's actual length, and return the
    new socket's descriptor, or -1 for errors.

    This function is a cancellation point and therefore not marked with
    __THROW. */
    extern int accept (int __fd, struct sockaddr *addr, socklen_t *addr_len);

    listen()中的sock(__fd : socket对象)发挥了服务器端接受请求的门卫作用,此时为了按序受理请求,给客户端做相应的回馈,连接到发起请求的客户端,此时就需要用accept再次创建另一个套接字;

    函数成功执行时返回socket文件描述符,失败时返回-1。

    1. 第一个参数:socket文件描述符__fd,要注意的是这个套接字文件描述符与前面几步的套接字文件描述符不同。
    2. 第二个参数:保存发起连接的客户端的地址信息。
    3. 第三个参数: 保存该结构体的长度。
  4. send/write发送信息

    linux下面的发送函数:

    1
    2
    3
    4
    /* Write N bytes of BUF to FD.  Return the number written, or -1.(往__fd里面写N个__buf里面的bytes;返回写入的数字或者-1)
    This function is a cancellation point and therefore not marked with
    __THROW. */
    ssize_t write (int __fd, const void *__buf, size_t __n) ;

    windows下面的发送函数:

    1
    ssize_t send (int sockfd, const void *buf, size_t nbytes, int flag) ;
  5. recv/read接受信息

    linux下的接收函数为

    1
    2
    3
    4
    5
    6
    /* Read NBYTES into BUF from FD.  Return the
    number read, -1 for errors or 0 for EOF.

    This function is a cancellation point and therefore not marked with
    __THROW. */
    ssize_t read (int __fd, void *__buf, size_t __nbytes);

    而在windows下的接收函数为

    1
    ssize_t recv(int sockfd, void *buf, size_t nbytes, int flag) ;
  6. 关闭连接

    1
    2
    3
    4
    5
    /* Close the file descriptor FD.

    This function is a cancellation point and therefore not marked with
    __THROW. */
    int close (int __fd);

    退出连接,此时要注意的是:调用close()函数即表示向对方发送了EOF结束标志信息

客户端

服务端的socket套接字在绑定自身的IP即 及端口号后这些信息后,就开始监听端口(listen)等待客户端的连接(connect)请求,此时客户端在创建套接字后就可以按照如下步骤与server端通信:

  1. 创建套接字对象(如上)

  2. 请求连接

    1
    2
    3
    4
    5
    6
    7
    8
    /* Open a connection on socket FD to peer at ADDR (which LEN bytes long).
    For connectionless socket types, just set the default address to send to
    and the only address from which to accept transmissions.
    Return 0 on success, -1 for errors.

    This function is a cancellation point and therefore not marked with
    __THROW. */
    int connect (int socket, struct sockaddr* servaddr, socklen_t addrlen);

    几个参数的意义和前面的accept函数意义一样。要注意的是服务器端收到连接请求的时候并不是马上调用accept()函数,而是把它放入到请求信息的等待队列中。

  3. 读写信息

  4. 关闭连接

套接字的多种可选项

  1. 函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <sys/types.h>
    #include <sys/socket.h>

    /* Put the current value for socket FD's option OPTNAME at protocol level LEVEL
    into OPTVAL (which is *OPTLEN bytes long), and set *OPTLEN to the value's
    actual length. Returns 0 on success, -1 for errors. */
    extern int getsockopt (int sock, int __level, int __optname,
    void *__optval, socklen_t *optlen) __THROW;

    /* Set socket FD's option OPTNAME at protocol level LEVEL
    to *OPTVAL (which is OPTLEN bytes long).
    Returns 0 on success, -1 for errors. */
    extern int setsockopt (int sock, int __level, int __optname,
    const void *__optval, socklen_t __optlen) __THROW;

    参数:

    1. sock:网络文件描述符;也就是前面sock函数的返回值

    2. __level:选项所在协议层。 可选的协议层如下:

协议层 功能
SOL_SOCKET 套接字相关通用可选项的设置
IPPROTO_IP 在IP层设置套接字的相关属性
IPPROTO_TCP 在TCP层设置套接字相关属性
  1. __optname:需要访问的选项名 (取决于level) :

    optname表格

  2. __optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。

  3. __optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。

    PS:__THROW是linux平台C库才有的东西,类似于throw()

  1. 使用案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    1. closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
    BOOL bReuseaddr=TRUE;
    setsockopt (s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));


    2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历
    TIME_WAIT的过程:
    BOOL bDontLinger = FALSE;
    setsockopt (s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));


    3. 在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
    int nNetTimeout=1000;//1秒
    //发送时限
    setsockopt (socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
    //接收时限
    setsockopt (socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));


    4. 在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
    (异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据
    和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
    // 接收缓冲区
    int nRecvBuf=32*1024;//设置为32K
    setsockopt (s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
    //发送缓冲区
    int nSendBuf=32*1024;//设置为32K
    setsockopt (s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));


    5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响
    程序的性能:
    int nZero=0;
    setsockopt (socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));


    6. 同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
    int nZero=0;
    setsockopt (socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));


    7. 一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
    BOOL bBroadcast=TRUE;
    setsockopt (s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));


    8. 在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可
    以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的
    作用,在阻塞的函数调用中作用不大)
    BOOL bConditionalAccept=TRUE;
    setsockopt (s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));


    9 . 如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们
    一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体
    应用的要求(即让没发完的数据发送出去后在关闭socket)?
    struct linger {
    u_short l_onoff;
    u_short l_linger;
    };
    linger m_sLinger;
    m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
    // 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
    m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
    setsockopt (s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

getaddinfo函数以及addrinfo结构体

getaddinfo函数

IPv4中使用gethostbyname()函数完成主机名到地址解析,这个函数仅仅支持IPv4,且不允许调用者指定所需地址类型的任何信息,返回的结构只包含了用于存储IPv4地址的空间。IPv6中引入了getaddrinfo()的新API,它是协议无关的,既可用于IPv4也可用于IPv6。getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个addrinfo的结构(列表)指针而不是一个地址清单。这些addrinfo结构随后可由套接口函数直接使用。

头文件

1
#include <netdb.h>

函数原型

1
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
  • hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)

  • service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等

  • hints:该参数指向用户设定的 struct addrinfo 结构体,只能设定该结构体中 ai_family、ai_socktype、ai_protocol 和 ai_flags 四个域,其余部分必须设置为0或者NULL。如果设置全为0,等价于 ai_socktype = 0, ai_protocol = 0,ai_family = AF_UNSPEC, ai_flags = 0

  • 调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。

  • result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。

  • 返回值:0——成功,非0——出错

    • EAI_ADDRFAMILY

      指定的主机上没有请求的address family对应的网络地址.

    • EAI_AGAIN

      DNS(name server)返回临时性错误. 可以稍后重试.

    • EAI_BADFLAGS

      hints.ai_flags 包含了无效的标志; 或者 hints.ai_flags 包含了 AI_CANONNAME 标志但是 name 是 NULL.

    • EAI_FAIL

      DNS(name server)返回永久性错误

    • EAI_FAMILY

      不支持的 address family(hints.ai_family).

    • EAI_MEMORY

      内存耗尽.

    • EAI_NODATA

      指定的网络主机存在,但是其未定义任何网络地址.

    • EAI_NONAME

      nodename 或者 servname 未知;或者两者都设置为NULL;或者设置了 AI_NUMERICSERV 标志但是 servname 不是一个数字化的端口名字符串。

    • EAI_SERVICE

      请求的socket类型不支持请求的服务类型.例如服务类型是 “shell” (基于流的socket服务),但是 hints.ai_protocol 是 IPPROTO_UDP 或者hints.ai_socktype 是 SOCK_DGRAM;或者 servname 不是NULL 但是 hints.ai_socktype 是 SOCK_RAW (原始套接字不支持服务的概念).

    • EAI_SOCKTYPE

      不支持请求的socket类型. 例如, hints.ai_socktype 和 hints.ai_protocol 冲突 (例如分别是SOCK_DGRAM、IPPROTO_TCP).

    • EAI_SYSTEM

      系统调用错误,检查 errno.

addrinfo结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

/* ======================Types of sockets====================== */
enum __socket_type {
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams. */
SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams of fixed maximum length. */
SOCK_RAW = 3, /* Raw protocol interface. */
SOCK_RDM = 4, /* Reliably-delivered messages. */
SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based,datagrams of fixed maximum length. */
SOCK_DCCP = 6, /* Datagram Congestion Control Protocol. */
SOCK_PACKET = 10, /* Linux specific way of getting packets at the dev level. For writing rarp and other similar things on the user level. */

/* Flags to be ORed into the type parameter of socket and socketpair and used for the flags parameter of paccept. */
SOCK_CLOEXEC = 02000000, /* Atomically set close-on-exec flag for the new descriptor(s). */
SOCK_NONBLOCK = 00004000 /* Atomically mark descriptor(s) as non-blocking. */
};

/* ============Protocol families(只列出常用几个)================= */
#define PF_UNSPEC 0 /* Unspecified. */
#define PF_LOCAL 1 /* Local to host (pipes and file-domain). */
#define PF_INET 2 /* IP protocol family. */
#define PF_IPX 4 /* Novell Internet Protocol. */
#define PF_APPLETALK 5 /* Appletalk DDP. */
#define PF_INET6 10 /* IP version 6. */
#define PF_TIPC 30 /* TIPC sockets. */
#define PF_BLUETOOTH 31 /* Bluetooth sockets. */

/* ==============Address families(只列出常用几个)================= */
#define AF_UNSPEC PF_UNSPEC
#define AF_LOCAL PF_LOCAL
#define AF_UNIX PF_UNIX
#define AF_FILE PF_FILE
#define AF_INET PF_INET
#define AF_IPX PF_IPX
#define AF_APPLETALK PF_APPLETALK
#define AF_INET6 PF_INET6
#define AF_ROSE PF_ROSE
#define AF_NETLINK PF_NETLINK
#define AF_TIPC PF_TIPC
#define AF_BLUETOOTH PF_BLUETOOTH

/* ====Possible values for `ai_flags' field in `addrinfo' structure.===== */
#define AI_PASSIVE 0x0001 /* Socket address is intended for `bind'. */
#define AI_CANONNAME 0x0002 /* Request for canonical name. */
#define AI_NUMERICHOST 0x0004 /* Don't use name resolution. */
#define AI_V4MAPPED 0x0008 /* IPv4 mapped addresses are acceptable. */
#define AI_ALL 0x0010 /* Return IPv4 mapped and IPv6 addresses. */
#define AI_ADDRCONFIG 0x0020 /* Use configuration of this host to choose returned address type. */
#ifdef __USE_GNU
#define AI_IDN 0x0040 /* IDN encode input (assuming it is encoded
in the current locale's character set) before looking it up. */
#define AI_CANONIDN 0x0080 /* Translate canonical name from IDN format. */
#define AI_IDN_ALLOW_UNASSIGNED 0x0100 /* Don't reject unassigned Unicode code points. */
#define AI_IDN_USE_STD3_ASCII_RULES 0x0200 /* Validate strings according to STD3 rules. */
#endif
#define AI_NUMERICSERV 0x0400 /* Don't use name resolution. */

/* =======================struct addrinfo======================= */
struct addrinfo {
int ai_flags; /* 附加选项,多个选项可以使用或操作结合 */
int ai_family; /* 指定返回地址的协议簇,取值范围:AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNSPEC(IPv4 and IPv6) */
int ai_socktype; /* enum __socket_type 类型,设置为0表示任意类型 */
int ai_protocol; /* 协议类型,设置为0表示任意类型,具体见上一节的 Ip Protocol */
socklen_t ai_addrlen; /* socket address 的长度 */
struct sockaddr *ai_addr; /* socket address 的地址 */
char *ai_canonname; /* Canonical name of service location. */
struct addrinfo *ai_next; /* 指向下一条信息,因为可能返回多个地址 */
};

与socket接口的交互案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/******************************************************************************
* Function: sock_connect
*
* Input
* servername: URL of server to connect to (NULL for server mode)
* port: port of service
*
* Output
* none
*
* Returns
* socket (fd) on success, negative error code on failure
*
* Description
* 连接socket,如果指定了服务器名称,那么会启动一个client连接到指定的服务器以及端口;
* 否则会在指定窗口上监听可能进入的连接。
* Connect a socket. If servername is specified a client connection will be
* initiated to the indicated server and port. Otherwise listen on the
* indicated port for an incoming connection.
*
******************************************************************************/
static int sock_connect(const char *servername, int port)
{
struct addrinfo *resolved_addr = NULL;
struct addrinfo *iterator;
char service[6];
int sockfd = -1;
int listenfd = 0;
int tmp;
struct addrinfo hints =
{
.ai_flags = AI_PASSIVE,
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM};
if (sprintf(service, "%d", port) < 0)
goto sock_connect_exit;
/* Resolve DNS address, use sockfd as temp storage */
sockfd = getaddrinfo(servername, service, &hints, &resolved_addr);
if (sockfd < 0)
{
fprintf(stderr, "%s for %s:%d\n", gai_strerror(sockfd), servername, port);
goto sock_connect_exit;
}
/* Search through results and find the one we want */
for (iterator = resolved_addr; iterator; iterator = iterator->ai_next)
{
sockfd = socket(iterator->ai_family, iterator->ai_socktype, iterator->ai_protocol);
if (sockfd >= 0)
{
if (servername){
/* Client mode. Initiate connection to remote */
if ((tmp = connect(sockfd, iterator->ai_addr, iterator->ai_addrlen)))
{
fprintf(stdout, "failed connect \n");
close(sockfd);
sockfd = -1;
}
}
else
{
/* Server mode. Set up listening socket an accept a connection */
listenfd = sockfd;
sockfd = -1;
if (bind(listenfd, iterator->ai_addr, iterator->ai_addrlen))
goto sock_connect_exit;
listen(listenfd, 1);
sockfd = accept(listenfd, NULL, 0);
}
}
}
sock_connect_exit:
if (listenfd)
close(listenfd);
if (resolved_addr)
freeaddrinfo(resolved_addr);
if (sockfd < 0)
{
if (servername)
fprintf(stderr, "Couldn't connect to %s:%d\n", servername, port);
else
{
perror("server accept");
fprintf(stderr, "accept() failed\n");
}
}
return sockfd;
}

PS

  1. socket非阻塞和阻塞模式
  2. 使用epoll,poll,select的案例

参考

  1. https://github.com/Tyler-ytr/Socket-Program
  2. 简单理解Socket
  3. setsockopt与getsockopt
  4. unix网络编程
  5. socket函数的domain、type、protocol解析
  6. https://www.cnblogs.com/fnlingnzb-learner/p/7542770.html