Linux 网络编程常用辅助函数

佚名 / 2023-08-05 / 原文

最大地址结构

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;
}