Linux 网络编程常用辅助函数 / 套接字超时设置 / 高级IO函数 / ioctl
最大地址结构
struct sockaddr_storage; // 足够大,能够支持任何套接字地址结构
从套接字获取信息
// 获取本地连接的地址
extern int getsockname (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __len) __THROW;
// 获取连接另一侧的地址
extern int getpeername (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __len) __THROW;
字节序转换
// 网络字节序到本地字节序
extern uint32_t ntohl (uint32_t __netlong);
extern uint16_t ntohs (uint16_t __netshort);
// 本地字节序到网络字节序
extern uint32_t htonl (uint32_t __hostlong);
extern uint16_t htons (uint16_t __hostshort);
字节操作函数
// 还有类似的 bset bcmp bcpy
extern void *memset (void *__s, int __c, size_t __n);
extern int memcmp (const void *__s1, const void *__s2, size_t __n);
extern void *memccpy (void *__restrict __dest, const void *__restrict __src, int __c, size_t __n);
地址转换函数,字符串与网络字节序间转换地址
// IPv4
// 废弃 extern in_addr_t inet_addr (const char *__cp) __THROW;
extern int inet_aton (const char *__cp, struct in_addr *__inp) __THROW;
extern char *inet_ntoa (struct in_addr __in) __THROW;
// IPv4 & IPv6
// p: presentation 表达式 n:numeric 数值
extern int inet_pton (int __af, const char *__restrict __cp, void *__restrict __buf) __THROW; // 字符串转二进制
extern const char *inet_ntop (int __af, const void *__restrict __cp, char *__restrict __buf, socklen_t __len ) __THROW; // 二进制转字符串
/*
socket长度可使用已有宏
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
*/
当 read write 时内核缓存区达到极限,此时操作的字节数可能比请求的小,所以需要再次调用read或write
readn writen
ssize_t readn(int fd, void *buf, size_t count) { // 可使用 recv() 与 MSG_WAITALL 代替
size_t nleft = count;
ssize_t nread;
char *bufp = (char *)buf;
while (nleft > 0) {
if ((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR) { // 系统调用被捕获信号中断
continue;
}
return -1;
} else if (nread == 0) {
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count) {
size_t nleft = count;
ssize_t nwrite;
char *bufp = (char *)buf;
while (nleft > 0) {
if ((nwrite = write(fd, bufp, nleft)) < 0) {
if (errno == EINTR) {
continue;
}
return -1;
} else if (nwrite == 0) {
continue;
}
bufp += nwrite;
nleft -= nwrite;
}
return count;
}
主机名与IP转换
// 仅支持IPv4,通过主机名查找IP地址
struct hostent *gethostbyname (const char *__name );
// 通过二进制IP地址找到响应主机名
struct hostent *gethostbyaddr (const void *__addr, __socklen_t __len, int __type);
// 查找主机的所有IP和名称信息
struct hostent
{
char *h_name; /* Official name of host. */
char **h_aliases; /* Alias list. */
int h_addrtype; /* Host address type. */
int h_length; /* Length of address. */
char **h_addr_list; /* List of addresses from name server. */
#ifdef __USE_MISC
# define h_addr h_addr_list[0] /* Address, for backward compatibility.*/
#endif
};
通过名称或端口从 /etc/services 中获取服务
struct servent *getservbyname (const char *__name, const char *__proto);
struct servent *getservbyport (int __port, const char *__proto);
{
struct servent *s = getservbyname("ftp", "tcp");
struct servent *s = getservbyport(21, "tcp");
}
// 对服务的描述
struct servent
{
char *s_name; /* Official service name. */
char **s_aliases; /* Alias list. */
int s_port; /* Port number. */
char *s_proto; /* Protocol to use. */
};
/// 支持 IPv4 IPv6
int getaddrinfo (const char *__restrict __name, // 主机名 或 ip
const char *__restrict __service, // 服务名 或 十进制端口号数串
const struct addrinfo *__restrict __req, // 指向某个 addrinfo 结构的指针,填入期望返回值的暗示,可为空
struct addrinfo **__restrict __pai);
const char *gai_strerror (int __ecode); // 返回 getaddrinfo 错误值对应的错误字符串
void freeaddrinfo (struct addrinfo *__ai); // 释放函数中分配的空间,参数为 getaddrinfo 的返回值
// 返回值是一个指向 addrinfo 链表的指针
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. */
};
/// 通过端口协议获取服务名称
int getnameinfo (const struct sockaddr *__restrict __sa,
socklen_t __salen, char *__restrict __host,
socklen_t __hostlen, char *__restrict __serv,
socklen_t __servlen, int __flags);
gethostbyname gethostbyaddr getservbyname getservbyport inet_ntoa 是不可重入的,他们都返回指向同一个静态结构的指针,但有已 _r 结尾的支持可重入的版本
UNP 中对 getaddrinfo 的封装
// 通过服务名查找服务的可用地址
int tcp_connect(const char* host, const char* serv) {
int sockfd, n;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0) {
return -1;
}
ressave = res;
do {
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0) {
continue;
}
// 查找到一个可用的地址,就退出循环
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0) {
break;
}
close(sockfd);
} while ((res = res->ai_next) != nullptr);
if (res == nullptr) {
return -1;
}
freeaddrinfo(ressave);
return sockfd;
}
int tcp_listen(const char* host, const char* serv, socklen_t* addrlenp) {
int listenfd, n;
const int on = 1;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0) {
return -1;
}
ressave = res;
do {
listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (listenfd < 0) { // socket失败,继续下一个
continue;
}
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0) { // bind成功,退出
break;
}
close(listenfd);
} while ((res = res->ai_next) != nullptr);
if (res == nullptr) { // 遍历完所有的地址,都没有成功
return -1;
}
if (listen(listenfd, 5) < 0) {
close(listenfd);
return -1;
}
if (addrlenp) {
*addrlenp = res->ai_addrlen;
}
freeaddrinfo(ressave);
return listenfd;
}
// 创建未连接的UDP套接字
int udp_client(const char* host, const char* serv, struct sockaddr** saptr,
socklen_t* lenp) {
int sockfd, n;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0) {
return -1;
}
ressave = res;
do {
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0) {
continue;
}
break;
} while ((res = res->ai_next) != nullptr);
if (res == nullptr) {
return -1;
}
*saptr = (struct sockaddr*)malloc(res->ai_addrlen);
memcpy(*saptr, res->ai_addr, res->ai_addrlen);
*lenp = res->ai_addrlen;
freeaddrinfo(ressave);
return sockfd;
}
// 创建已连接的UDP套接字
int udp_connect(const char* host, const char* serv) {
int sockfd, n;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0) {
return -1;
}
ressave = res;
do {
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0) {
continue;
}
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0) {
break;
}
close(sockfd);
} while ((res = res->ai_next) != nullptr);
if (res == nullptr) {
return -1;
}
freeaddrinfo(ressave);
return sockfd;
}
int udp_server(const char* host, const char* serv, socklen_t* addrlenp) {
int sockfd, n;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0) {
return -1;
}
ressave = res;
do {
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0) {
continue;
}
if (bind(sockfd, res->ai_addr, res->ai_addrlen) == 0) {
break;
}
close(sockfd);
} while ((res = res->ai_next) != nullptr);
if (res == nullptr) {
return -1;
}
if (addrlenp) {
*addrlenp = res->ai_addrlen;
}
freeaddrinfo(ressave);
return sockfd;
}
套接字超时
- 调用 alarm 超时发出 SIGALRM 信号
使用alarm时钟中断connect
int connect_timeout(int sockfd, const struct sockaddr* addr, socklen_t addrlen, int timeout) { Sigfunc* sigfunc; int n; sigfunc = Signal(SIGALRM, connect_alarm); if (alarm(timeout) != 0) { err_msg("connect_timeout: alarm was already set"); } if ((n = connect(sockfd, addr, addrlen)) < 0) { close(sockfd); if (errno == EINTR) { errno = ETIMEDOUT; } } alarm(0); Signal(SIGALRM, sigfunc); return (n); } static void connect_alarm(int signo) { return; }
- 在 select 中调用阻塞等待IO,此时相应的套接字要处于非阻塞模式
使用超时select等待fd
int readable_timeo(int fd, int sec) { fd_set rset; struct timeval tv; FD_ZERO(&rset); FD_SET(fd, &rset); tv.tv_sec = sec; tv.tv_usec = 0; return (select(fd + 1, &rset, NULL, NULL, &tv)); }
- 使用 SO_RCVTIMEO SO_SNDTTIMEO 套接字选项,对connect不适用
设置socket选项
tv.tv_sec = 5; tv.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
高级IO函数
ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);
ssize_t send (int __fd, const void *__buf, size_t __n, int __flags);
#include <sys/uio.h>
// 分散读 集中写 操作任意描述符
ssize_t readv (int __fd, const struct iovec *__iovec, int __count);
ssize_t writev (int __fd, const struct iovec *__iovec, int __count);
struct iovec
{
void *iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};
// 最通用的IO函数,可把 read readv recvfrom 全替换为 recvmsg
ssize_t recvmsg (int __fd, struct msghdr *__message, int __flags);
ssize_t sendmsg (int __fd, const struct msghdr *__message, int __flags);
struct msghdr
{
void *msg_name; /* Address to send to/receive from. */
socklen_t msg_namelen; /* Length of address data. */
struct iovec *msg_iov; /* Vector of data to send/receive into. */
size_t msg_iovlen; /* Number of elements in the vector. */
void *msg_control; /* Ancillary data (eg BSD filedesc passing). */
size_t msg_controllen; /* Ancillary data buffer length.
!! The type should be socklen_t but the
definition of the kernel is incompatible
with this. */
int msg_flags; /* Flags on received message. */
};
ioctl
传统上用于不适合归入其他精细定义类别的系统接口,POSIX提供了许多特殊函数用于替代ioctl请求。如 tcgetattr 处理终端、 tcflush 刷新io、sockatmark处理网络
#include <unistd.h>
int ioctl (int __fd, unsigned long int __request, ...); // 成功返回0,否则-1