网络编程之Sockets Introduction(二)

1. inet_aton, inet_addr, and inet_ntoa Functions

#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
Returns: 1 if string was valid, 0 on error
in_addr_t inet_addr(const char *strptr);
Returns: 32-bit binary network byte ordered IPv4 address; INADDR_NONE if error
char *inet_ntoa(struct in_addr inaddr);
Returns: pointer to dotted-decimal string

函数的API的使用比较简单不赘述,注意inet_addr的一个潜在的缺点,UNP建议使用inet_aton代替inet_addr

The problem with this function is that all 232 possible binary values are valid IP addresses (0.0.0.0 through 255.255.255.255), but the function returns the constant INADDR_NONE (typically 32 one-bits) on an error. This means the dotted-decimal string 255.255.255.255 (the IPv4 limited broadcast address, Section 20.2) cannot be handled by this function since its binary value appears to indicate failure of the function.

inet_ntoa的缺点就是其返回的字符串存储在静态内存中,导致该函数不可重入,在APUE中讨论过可重入的相关内容,该函数一定不是信号安全的。

The inet_ntoa function converts a 32-bit binary network byte ordered IPv4 address into its corresponding dotted-decimal string. The string pointed to by the return value of the function resides in static memory. This means the function is not reentrant.

2. inet_pton and inet_ntop Functions

#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
Returns: 1 if OK, 0 if input not a valid presentation format,1 on error
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
Returns: pointer to result if OK, NULL on error

简单记忆的方式:The letters p and n stand for presentation and numeric,特别的对于inet_ntop,必须指定容纳转换结果的地址的大小,这个大小有两个预定义的宏供编程人员使用:

inet_ntop does the reverse conversion, from numeric (addrptr) to presentation (strptr). The len argument is the size of the destination, to prevent the function from overflowing the caller’s buffer. To help specify this size, the following two definitions are defined by including the <netinet/in.h> header:

#define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */

If len is too small to hold the resulting presentation format, including the terminating null, a null pointer is returned and errno is set to ENOSPC.

加个图:

UNP中关于inet_ptoninet_ntop的简单版本,只支持IPv4:

int inet_pton(int family, const char *strptr, void *addrptr)
{
   if (family == AF_INET) {
       struct in_addr in_val;
       if (inet_aton(strptr, &in_val)) {
          memcpy(addrptr, &in_val, sizeof(struct in_addr));
          return (1);
       }
       return (0);
   }
   errno = EAFNOSUPPORT;
   return (-1);
}
const char *  inet_ntop(int family, const void *addrptr, char *strptr, size_t len)
{
    const u_char *p = (const u_char *) addrptr;
    if (family == AF_INET) {
        char temp[INET_ADDRSTRLEN];
        //特别注意INET_ADDRSTRLEN为16,存储形如xxx.xxx.xxx.xxx加NULL总共16字节
        snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
        if (strlen(temp) >= len) {
            errno = ENOSPC;
            return (NULL);
        }
        strcpy(strptr, temp);
        return (strptr);
    }
    errno = EAFNOSUPPORT;
    return (NULL);
}

3. sock_ntop and Related Functions

inet_ntop并不是协议无关的函数,必须在family指定是IPv4或者IPv6,UNP中就提出了自己版本的sock_ntop,其是协议无关的,以下代码是其中一个片段:

#include "unp.h"
char *sock_ntop(const struct sockaddr *sockaddr, socklen_t addrlen);
Returns: non-null pointer if OK, NULL on error

char * sock_ntop(const struct sockaddr *sa, socklen_t salen)
{
    char portstr[8];
    static char str[128]; /* Unix domain is largest */
    switch (sa->sa_family) {
        case AF_INET:{
            struct sockaddr_in *sin = (struct sockaddr_in *) sa;
            if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
                return (NULL);
            //一般传给socket的地址或者从socket接收的地址都是大端的,但是这里
            //调用了ntohs处理了端口号,这个原因可以尝试解释下:
            //1. 网络地址默认都是大端,转换成字符串也应按照大端模式转化。
            //2. 端口号想要正确显示必须依据本地的字节模式。
            if (ntohs(sin->sin_port) != 0) {
                snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
                strcat(str, portstr);
            }
            return (str);
        }
    .....
}

以下函数都是UNP实现的版本,笔者并不深入这里,以后的实验笔者将使用原本的标准函数接口,而非UNP提供的版本:

#include "unp.h"
int sock_bind_wild(int sockfd, int family);
Returns: 0 if OK,1 on error
int sock_cmp_addr(const struct sockaddr *sockaddr1,
const struct sockaddr *sockaddr2, socklen_t addrlen);
Returns: 0 if addresses are of the same family and equal, else nonzero
int sock_cmp_port(const struct sockaddr *sockaddr1,
const struct sockaddr *sockaddr2, socklen_t addrlen);
Returns: 0 if addresses are of the same family and ports are equal, else nonzero
int sock_get_port(const struct sockaddr *sockaddr, socklen_t addrlen);
Returns: non-negative port number for IPv4 or IPv6 address, else1
char *sock_ntop_host(const struct sockaddr *sockaddr, socklen_t addrlen);
Returns: non-null pointer if OK, NULL on error
void sock_set_addr(const struct sockaddr *sockaddr, socklen_t addrlen, void *ptr);
void sock_set_port(const struct sockaddr *sockaddr, socklen_t addrlen, int port);
void sock_set_wild(struct sockaddr *sockaddr, socklen_t addrlen);

4. readn, writen, and readline Functions

因为read或者write有时候返回的字节数少于所要求的,所以需要多次调用。而造成这样的结果的原因如下所述:

A read or write on a stream socket might input or output fewer bytes than requested, but this is not an error condition. The reason is that buffer limits might be reached for the socket in the kernel. All that is required to input or output the remaining bytes is for the caller to invoke the read or write function again.

针对以上情况,UNP提出了自己版本的read和write:

#include "unp.h"
ssize_t readn(int filedes, void *buff, size_t nbytes);
ssize_t writen(int filedes, const void *buff, size_t nbytes);
ssize_t readline(int filedes, void *buff, size_t maxlen);
All return: number of bytes read or written,1 on error
#include "unp.h"
/* PAINFULLY SLOW VERSION -- example only */
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char c, *ptr;
    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
      again:
      if ( (rc = read(fd, &c, 1)) == 1) {
          *ptr++ = c;
          if (c == ’\n’)
          break; /* newline is stored, like fgets() */
      } else if (rc == 0) {
          *ptr = 0;
          return (n - 1); /* EOF, n - 1 bytes were read */
      } else {
          if (errno == EINTR)
          goto again;
          return (-1); /* error, errno set by read() */
      }
    }
    *ptr = 0; /* null terminate like fgets() */
    return (n);
}

特别强调该函数,该函数每读取的一个字节调用一次read,效率十分低下。如果希望能够提高该函数的效率使用标准IO函数的话,将面临一个问题:

The same stdio buffering that solves this performance problem creates numerous logistical problems that can lead to well-hidden bugs in your application. The reason is that the state of the stdio buffers is not exposed.

首先因为编程中要考虑一些特殊情况,如何处理这些特殊情况:

Good defensive programming techniques require these programs to not only expect their counterparts to follow the network protocol, but to check for unexpected network traffic as well. Such protocol violations should be reported as errors so that bugs are noticed and fixed, and also so that network applications can recover from problem traffic and continue working if possible.

如果使用标准IO将无法检测其buffer中的数据,从而无法处理这些特殊情况,需要要求标准IO中的buffer可见,可操作:

There are many line-based network protocols such as SMTP, HTTP, the FTP control connection protocol, and finger. So, the desire to operate on lines comes up again and again. But our advice is to think in terms of buffers and not lines. Write your code to read buffers of data, and if a line is expected, check the buffer to see if it contains that line.

UNP给出了一个修改后的版本,虽然该版本依然存在一些以后章节解释的问题,但是至少从效率和buffer对外可见上给予了解决方案:

#include "unp.h"
static int read_cnt;
static char *read_ptr;
static char read_buf[MAXLINE];
static ssize_t my_read(int fd, char *ptr)
{
    if (read_cnt <= 0) {
        again:
            if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
                if (errno == EINTR)
                    goto again;
                return (-1);
            } else if (read_cnt == 0)
                return (0);
            read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;
    return (1);
}
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char c, *ptr;
    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
        if ( (rc = my_read(fd, &c)) == 1) {
            *ptr++ = c;
            if (c == ’\n’)
                break; /* newline is stored, like fgets() */
        } else if (rc == 0) {
            *ptr = 0;
            return (n - 1); /* EOF, n - 1 bytes were read */
        } else
            return (-1); /* error, errno set by read() */
    }
    *ptr = 0; /* null terminate like fgets() */
    return (n);
}
ssize_t readlinebuf(void **vptrptr)
{
    if (read_cnt)
        *vptrptr = read_ptr;
    return (read_cnt);
}
  • The internal function my_read reads up to MAXLINE characters at a time and then returns them, one at a time.
  • The only change to the readline function itself is to call my_read instead of read.
  • A new function, readlinebuf, exposes the internal buffer state so that callers can check and see if more data was received beyond a single line.

以上程序设计的比较巧妙,虽然并不是什么比较好的实现,但是就上面PAINFULLY SLOW VERSION版本暴露的问题进行了修补,这种解决问题的思路比较重要。最明显的缺点就是使用了全局数组,和全局变量,这样该函数就既不是线程安全,更不是信号安全的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值