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

套接字超时

  1. 调用 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; }
  2. 在 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));
    }
  3. 使用 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

 

posted @ 2023-08-05 12:06  某某人8265  阅读(51)  评论(0编辑  收藏  举报