5 Linux 网络编程基础 API
主机字节序和网络字节序
- 主机(小端)字节序:0x0201
- 网络(大端)字节序:0x0102,利于人看
#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htonl(unsigned long int hostshort);
unsigned long int ntohl(unsigned long int hostlong);
unsigned short int ntohl(unsigned long int hostshort);
通用 socket 地址
#include <bits/socket.h>
// socket 地址结构体
struct sockaddr
{sa_family_t sa_family; // 地址族类型 ≈ 协议族类型char sa_data[14];
};// 新的通用 socket 结构体
struct sockaddr_storage
{sa_family_t sa_family;unsigned long int __ss_align;char __ss_padding[128 - sizeof(__ss_align)];
};
专用 socket 地址
#include <bits/socket.h>
// UNIX 本地协议族
struct sockaddr_un
{sa_family_t sin_family; // AF_UNIX == PF_UNIXchar sun_path[108]; // 文件路径名
};// IPv4
struct sockaddr_in
{sa_family_t sin_family; // AF_INET == PF_INETu_int16_t sin_port; // 网络字节序struct in_addr sin_addr; // Ipv4 结构体地址
};
struct in_addr
{u_int32_t s_addr; // 网络字节序
};
IP 地址转换函数
// IPv4
#include <arpa/inet.h>
in_addr_t inet_addr(const char* strptr); // INADDR_NONE
int inet_aton(const char* cp, struct in_addr* inp);// 函数内部有一个静态变量存储转化结果,返回值指向该静态内存
char* inet_ntoa(struct in_addr in);
创建 socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- domain:底层协议族,PF_UNIX、PF_INET、PF_INET6
- type:服务类型,
- SOCK_STREAM ==> tcp
- SOCK_UGRAM ==> UDP
- SOCK_NONBLOCK 新创建的 socket 设为非阻塞的
- SOCK_CLOEXEC 用 fork 调用创建子进程时在子进程中关闭该 socket
- protocol:0
- return:
- 成功:socket 文件描述符
- 失败:-1,errno
命名 socket
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
- return:
- 成功:0
- 失败:-1,errno
- EACCES:被绑定的地址是受保护的地址,仅超级用户能够访问,比如普通用户将 socket 绑定到知名端口
- EADDRINUSE:被绑定的地址正在使用
监听 socket
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- backlog:提示内核监听队列的最大长度,完全处理连接状态的 socket 上线
处于半连接状态的 socket 的上线由/proc/sys/net/ipv4/tcp_max_syn_backlog
内核参数定义 - return:
- 成功:0
- 失败:-1,errno
监听队列的长度如果超过 backlog,服务器将不受理新的客户端连接,客户端也将收到 ECONNEREFUSED 错误
接受连接
- 监听 socket:执行过 listen 调用、处于 LISTEN 状态的 socket
- 连接 socket:处于 ESTABLISHED 状态的 socket
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
- accept 只是从监听队列中取出连接,而不论连接处于何种状态,更不关心网络状况的变化
- return:
- 失败:-1,errno
发起连接
- server 通过 listen 调用来被动接受连接
- client 通过 connect 调用来主动与 server 建立连接
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);
- return:
- 失败:-1,errno
- ECONNREUFUSED:目标端口不存在,拒绝被连接
- ETIMEDOUT:连接超时
- 失败:-1,errno
关闭连接
#include <unistd.h>
int close(int fd);
- close 只有将引用计数减为 0 时,才真正关闭连接
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
- 可立即终止连接(无需计数变为 0)
- howto:
- SHUT_RD:关闭读,丢弃接收缓冲区数据
- SHUT_WR:关闭写,发送缓冲区的数据会在真正关闭写之前发送出去,半关闭状态
- SHUT_RDWR:
TCP 数据读写
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
- return:
- 0:通信对方关闭连接
- -1:errno
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
- flags:通常 0,只对当前调用生效
flags | 含义 | send | recv |
---|---|---|---|
MSG_CONFIRM | 指示数据链路层协议持续监听对方的回应,直到得到答复,只适用于 SOCK_DGRAM 和 SOCK_RAW 类型的 socket | Y | N |
MSG_DONTROUTE | 不查看路由表,直接将数据发送给本地局域网络内的主机,发送者知道目标主机就在本地网络上 | Y | N |
MSG_DONTWAIT | 对 socket 的此次操作将是非阻塞的 | Y | Y |
MSG_MORE | 告诉内核应用程序还有更多数据要发送,内核将超时等待新数据写入 TCP 发送缓冲区后一并发送,可防止 TCP 发送过多小报文段,提高传输效率 | Y | N |
MSG_WAITALL | 读操作仅在读取到指定数量的字节后才返回 | N | Y |
MSG_PEEK | 窥探读缓存中的数据,此次读操作不会导致这些数据被清除 | N | Y |
MSG_OOB | 发送或接受紧急数据 | Y | Y |
MSG_NOSIGNAL | 往读端关闭的管道或者 socket 连接中写数据时不引发 SIGPIPE 信号 | Y | N |
只有最后一个字符被当成正真的带外数据接收,服务器对正常数据的接收将被带外数据截断
UDP 数据读写
#include <sys/types.h>
#include <sys/socket.h>
sszie_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, struct sockaddr* dest_addr, socklen_t addrlen);
这两个函数也可用于面向连接的 socket 的数据读写,只需要把最后两个参数设置为 NULL 以忽略发送端、接收端的 socket 地址,已经建立了连接,双方知道对方地址
通用数据读写函数
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);struct msghdr
{void* msg_name; // socket 地址socklen_t msg_namelen;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; // 这块内存长度
};
- msg_name:指定通信对方的 socket 地址,TCP 中为 NULL
- 分散读:对于 recvmsg,数据将被读取并存放在 msg_iovlen 块分散的内存中,这些内存的位置和长度由 msg_iov 指向的数组决定
- 集中写:对于 sendmsg,msg_iovlen 块分散内存中的数据将被一并发送
- msg_flags:无需设定,会复制 recvmsg、sendmsg 的 flags 参数的内容以影响数据读写过程。recvmsg 还会在调用结束前,将某些更新后的标志设置到 msg_flags 中
带外标记
内核通知应用程序带外数据到达方式:
- I/O 复用产生的异常事件
- SIGURG 信号
#include <sys/socket.h>
int sockatmark(int sockfd);
-
判断 sockfd 是否处于带外标记,即下一个被读取到的数据是否是带外数据
- SO_LINGER:控制 close 系统调用关闭 TCP 连接时的行为
-
建议 sock 在 listen 和 accept 之前设置选项信息
网络信息 API
#include <netdb.h>
// 获取主机的完整信息
/* 先在本地 /etc/hosts 配置文件中查找主机,再去 DNS 服务器 */
struct hostent* gethostbyname(const char* name);/** @param len 指定 addr 所指 IP 地址长度* @param type 指定 addr 所指 IP 地址类型*/
struct hostent* gethostbyaddr(const void* addr, size_t len, int type);struct hostent
{char* h_name; // 主机名char** h_aliases; // 主机别名int h_addrtype; // 地址类型int h_length; // 地址长度char** h_addr_list; // 网络字节序列出主机 IP 地址列表
};// 获取服务的完整信息,通过读取 /etc/services 文件获取服务信息
/** @param proto 服务类型* tcp 获取流服务* udp 获取数据报服务* NULL 获取所有类型服务*/
struct servent* getservbyname(const char* name, const char* proto);
struct servent* getservbyport(int port, const char* proto);struct servent
{char* s_name; // 服务名称char** s_aliases; // 服务别名列表int s_port; // 端口号char* s_proto; // 服务类型 tcp or udp
};/** 根据主机名获得 IP 地址(`gethostbyname`)* 根据服务名获得 port(`getservbyname`)* 是否可冲入取决于内部调用的方法* */
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result);struct addrinfo
{int ai_flags;int ai_family;int ai_socktype;int ai_protocol;socklen_t ai_addrlen;char* ai_canonname;struct sockaddr* ai_addr;struct addrinfo* ai_next;
};void freeaddrinfo(struct addrinfo* res);int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags);/* 将 getaddrinfo 和 getnameinfo 返回的错误码转换为字符串形式 */
const char* gai_strerror(int error);