socket概念

运行在计算机中的两个程序通过socket建立起一个管道,数据在管道中传输,socket把复杂的TCP/IP协议族隐藏了起来,对程序员来说,只要用好socket相关函数,就可以完成网络通信。

socket分类

socket提供了流(stream)和数据报(datagram)两种通信机制,即流socket和数据报socket

  • 流socket基于TCP协议,传输数据不会丢失、重复、顺序错乱,可靠且双向
  • 数据报socket基于UDP协议,不需要建立和维持链接,可能会丢失和错乱。效率高

相关的结构

  1. socket函数

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

    参数:

    • domain: 协议族。常用的协议族有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE。在通信中必须采用对应的协议族,AF_INET决定要用ipv4地址(32bit)与端口号(16bit)的组合。
    • type: socket类型。常用的类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET等。SOCK_STREAM是面向连接的socket,针对于TCP服务;SOCK_DGRAM是无连接的socket,针对于UDP服务
    • protocol: 指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议

    返回值:

    • 成功返回一个int型的socket句柄,失败返回-1,错误原因存于 errno 中

    注意:

    • 一个程序最多可以打开1024个socket(因为linux系统限制一个文件可以打开文件数为1024,ulimit -a命令可以查到)
  2. 字节序转换函数
    网络字节序由TCP/IP规定,采用大端存储格式;主机字节序跟CPU有关,有大端和小端。所以为了通信需要将主机字节序转为网络字节序。

    • htons(): 16位无符号 主机转网络
    • ntohs(): 16位无符号 网络转主机
    • htonl(): 32位无符号 主机转网络
    • ntohl(): 32位无符号 网络转主机
  3. sockaddr结构体

    1
    2
    3
    4
    5
    struct sockaddr
    {
    unsigned short sa_family; //地址类型,AF_XXX
    char sa_data[14]; //14字节=端口+地址+冗余位
    }

    sa_data不好操作,所以有了改进版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct sockaddr_in
    {
    unsigned short sa_family; //地址类型,AF_XXX
    unsigned short int sin_port; //2字节端口号
    struct in_addr sin_addr; //4字节地址号
    unsigned char sin_zero[8]; //为了兼容sockaddr留的
    }

    struct in_addr
    {
    unsigned long s_addr; //4字节地址号
    }
  4. bind函数

    1
    int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

    参数:

    • sockfd: socket句柄
    • addr: 存放通信端口和地址的结构体
    • addrlen: addr结构体长度

    返回值:

    • 成功返回0,失败返回-1,错误原因存于 errno 中

    注意:

    • 第二个参数为旧版本sockaddr,使用新版本sockaddr_in的话强转一下就好(struct sockaddr *)sockaddr_in
  5. 地址与网络字节序地址互相转换函数

    1. inet_aton函数

      1
      int inet_aton(const char *cp, struct in_addr *inp);

      参数:

      • cp: 字符串型IP地址
      • inp: 网络字节序地址

      返回值:

      • 成功返回非0,地址不正确返回0
    2. inet_ntoa函数

      1
      char *inet_ntoa(struct in_addr *in);

      参数:

      • in: 网络字节序地址

      返回值:

      • 字符串型IP地址
    3. gethostbyname函数

      1
      struct hostent* gethostbyname(const char *name);

      参数:

      • name: 可以是IP地址,可以是域名,可以是主机名

      返回值:

      • hostent: 网络字节序地址
  6. hostent结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct hostent
    {
    char *h_name; //主机名
    char **h_aliases; //主机所有别名
    int h_addrtype; //主机ip地址
    int h_length; //主机ip地址长度
    char **h_addr_list; //主机网络字节序型ip地址
    }

    #define h_addr h_addr_list[0]
  7. listen函数

    1
    int listen(int sockfd, int backlog);

    服务器端对端口开始进行监听,如果有客户端通过connect发起请求,内核将会通过三次握手建立连接,然后将建立好的连接放入:已完成连接队列

    sockfd: 服务端socket

    backlog: 已完成连接队列最大个数-1

    成功返回0,失败返回非0

  8. connect函数

    1
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    sockfd: 客户端socket

    addr: 要连接的服务端信息

    addrlen: addr长度

    成功返回0,失败返回非0

    连接成功或者超时前会阻塞

  9. accept函数

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

    从已完成连接队列中取出一个socket

    sockfd: 服务端的listen socket

    addr: 取出的客户端信息

    addrlen: addr长度

    成功返回一个socket句柄,可用于与该客户端的通信

    如果队列为空,则会一直阻塞

  10. send函数

    1
    ssize_t send(int sockfd, const void *buf, size_t len, flags)

    发信息函数

    sockfd: socket句柄

    buf: buffer

    len: buf长度

    flags: 填0,其他值意义不大

    成功返回发送字节数,出错返回-1,错误信息在errno

    注意:网络断开或者socket对端关闭,send不会立即报错,要过几秒

  11. recv函数

    1
    ssize_t recv(int sockfd, const void *buf, size_t len, flags)

    收信息函数

    sockfd: socket句柄

    buf: buffer

    len: buf最大长度

    flags: 填0,其他值意义不大

    成功返回已接受字节数,出错返回-1,对端关闭返回0,对端无发送则阻塞等待