5 Linux 网络编程基础 API

5 Linux 网络编程基础 API

主机字节序和网络字节序

  • 主机(小端)字节序:0x0201
  • 网络(大端)字节序:0x0102,利于人看
c 复制代码
#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 地址

c 复制代码
#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 地址

c 复制代码
#include <bits/socket.h>
// UNIX 本地协议族
struct sockaddr_un
{
    sa_family_t sin_family; // AF_UNIX == PF_UNIX
    char sun_path[108];     // 文件路径名
};

// IPv4
struct sockaddr_in
{
    sa_family_t sin_family;  // AF_INET == PF_INET
    u_int16_t sin_port;      // 网络字节序
    struct in_addr sin_addr; // Ipv4 结构体地址
};
struct in_addr
{
    u_int32_t s_addr; // 网络字节序
};

IP 地址转换函数

c 复制代码
// 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

c 复制代码
#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

c 复制代码
#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

c 复制代码
#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
c 复制代码
#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 建立连接
c 复制代码
#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:连接超时

关闭连接

c 复制代码
#include <unistd.h>
int close(int fd);
  • close 只有将引用计数减为 0 时,才真正关闭连接
c 复制代码
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
  • 可立即终止连接(无需计数变为 0
  • howto:
    • SHUT_RD:关闭读,丢弃接收缓冲区数据
    • SHUT_WR:关闭写,发送缓冲区的数据会在真正关闭写之前发送出去,半关闭状态
    • SHUT_RDWR:

TCP 数据读写

c 复制代码
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
  • return:
    • 0:通信对方关闭连接
    • -1:errno
c 复制代码
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 数据读写

c 复制代码
#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 地址,已经建立了连接,双方知道对方地址

通用数据读写函数

c 复制代码
#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 信号
c 复制代码
#include <sys/socket.h>
int sockatmark(int sockfd);
  • 判断 sockfd 是否处于带外标记,即下一个被读取到的数据是否是带外数据

    • SO_LINGER:控制 close 系统调用关闭 TCP 连接时的行为
  • 建议 sock 在 listen 和 accept 之前设置选项信息

网络信息 API

c 复制代码
#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);
相关推荐
AlfredZhao8 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户97183563346614 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪16 小时前
linux 拷贝文件或目录到指定的位置
linux
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
小宇宙Zz1 天前
Maven依赖冲突
java·服务器·maven
网络研究院1 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理