nonocast / me

记录和分享技术的博客
http://nonocast.cn
MIT License
20 stars 0 forks source link

阅读笔记: Beej's Guide to Network Programming #301

Open nonocast opened 2 years ago

nonocast commented 2 years ago

Internet socket

Stream Socket借助TCP(The Transmission Control Protocol)实现重发和排序,确保数据完整性,而Datagram Socket则不检查包的达和顺序,但如果一个包到达,则这个包的内容是完整的,不会出现到半个包的情况。

TCP 會在傳輸層對將上層送來的過大訊息分割成多個分段(TCP segments),而 UDP 本身不會,UDP 是訊息導向的(message oriented),若 UDP 訊息過大時(整體封包長度超過 MTU),則會由 host 或 router 在 IP 層對封包進行分割,將一個 IP packet 分割成多個 IP fragments。IP fragmention 的缺點是,接收端的系統需要做 IP 封包的重組,將多個 fragments 重組合併為原本的 IP 封包,同時也會增加封包遺失的機率。如將一個 IP packet 分裂成多個 IP fragments,只要其中一個 IP fragment 遺失了,接收端就會無法順利重組 IP 封包,因而造成封包的遺失,若是高可靠度的應用,則上層協定需重送整個 packet 的資料。UDP, User Datagram Protocol就意味着将transimission control交给用户自行处理。

IPv4 and IPv6

IPv4的addr对应sockaddr_in (16 bytes):

/*
 * [XSI] Structure used by kernel to store most addresses.
 */
struct sockaddr {
    __uint8_t       sa_len;         /* total length */
    sa_family_t     sa_family;      /* [XSI] address family */
    char            sa_data[14];    /* [XSI] addr value (actually larger) */
};

/*
 * Socket address, internet style.
 */
struct sockaddr_in {
    __uint8_t       sin_len;
    sa_family_t     sin_family;
    in_port_t       sin_port;
    struct  in_addr sin_addr;
    char            sin_zero[8];
};

IPv6的addr对应sockaddr_in6 (28 bytes)

struct sockaddr_in6 {
    __uint8_t       sin6_len;       /* length of this struct(sa_family_t) */
    sa_family_t     sin6_family;    /* AF_INET6 (sa_family_t) */
    in_port_t       sin6_port;      /* Transport layer port # (in_port_t) */
    __uint32_t      sin6_flowinfo;  /* IP6 flow information */
    struct in6_addr sin6_addr;      /* IP6 address */
    __uint32_t      sin6_scope_id;  /* scope zone index */
};

IPv4和IPv6的前4字节的布局是一样的,如果字节不敏感,完全可以用IPv6的28字节布局来做IPv4,这样可以兼容IPv4, IPv6地址,通过不同类型指针去指向即可。

struct sockaddr_storage {
    sa_family_t ss_family; // address family
    char __ss_pad1[_SS_PAD1SIZE];
    int64_t __ss_align;
    char __ss_pad2[_SS_PAD2SIZE];
};

IP address 操作

仅 IPv4 (过弃)

// Dotted Decimal Notation (点分十进制) => 32 bits (Network Order)
// 192.168.0.1 => 0x0100a8c0
TEST_F(SockaddrTest, inet_addr) {
  in_addr_t in = inet_addr("192.168.0.1");
  uint8_t expected[] = {192, 168, 0, 1};
  EXPECT_EQ(memcmp(&in, expected, 4), 0);
}

//  Dotted Decimal Notation => in_addr 
TEST_F(SockaddrTest, inet_aton) {
  struct in_addr in;
  inet_aton("192.168.0.1", &in);
  uint8_t expected[] = {192, 168, 0, 1};
  EXPECT_EQ(memcmp(&in, expected, 4), 0);
}

// in_addr => Dotted Decimal Notation
// 0x0100a8c0 => 192.168.0.1
TEST_F(SockaddrTest, inet_ntoa_a) {
  int32_t data = 0x0100a8c0;
  char *cp = inet_ntoa(*(struct in_addr *)&data);
  EXPECT_EQ(strcmp(cp, "192.168.0.1"), 0);
}

TEST_F(SockaddrTest, inet_ntoa_b) {
  struct in_addr in;
  in.s_addr = inet_addr("192.168.0.1");
  char *cp = inet_ntoa(in);
  EXPECT_EQ(strcmp(cp, "192.168.0.1"), 0);
}

兼容 IPv6 (推荐)

// 192.168.0.1 => in_addr
// ::1 => in6_addr
TEST_F(SockaddrTest, inet_pton) {
  uint8_t expected4[] = {192, 168, 0, 1};
  uint8_t expected6[] = {[15] = 1};

  struct in_addr in;
  int rc = inet_pton(AF_INET, "192.168.0.1", &in);
  EXPECT_EQ(rc, 1);
  EXPECT_EQ(memcmp(&in, expected4, 4), 0);

  struct in6_addr in6;
  inet_pton(AF_INET6, "::1", &in6);
  EXPECT_EQ(memcmp(&in6, expected6, sizeof(in6_addr)), 0);
}

// in_addr => 192.168.0.1
// in6_addr => ::1
TEST_F(SockaddrTest, inet_ntop) {
  char ip4[INET6_ADDRSTRLEN];
  struct in_addr in;
  inet_aton("192.168.0.1", &in);
  const char *rp = inet_ntop(AF_INET, &in, ip4, INET_ADDRSTRLEN);
  EXPECT_EQ(strcmp(ip4, "192.168.0.1"), 0);
  EXPECT_EQ(strcmp(rp, "192.168.0.1"), 0);
  EXPECT_EQ(rp, ip4);
}

System Calls or Bust

getaddrinfo() - prepare to launch!

getaddrinfo帮助我们快速得到sockaddr,支持DNS解析和IPv6的支持,取代之前的gethostbyname。

如果你需要开启一个server, 那么对应的调用如下:

/*
 * 取代 gethostbyname()
 * host+service(port) => addrinfos
 * cat /etc/services
 * struct addrinfo {
 *   int ai_flags;             / * AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST * /
 *   int ai_family;            / * PF_xxx * /
 *   int ai_socktype;          / * SOCK_xxx * /
 *   int ai_protocol;          / * 0 or IPPROTO_xxx for IPv4 and IPv6 * /
 *   socklen_t ai_addrlen;     / * length of ai_addr * /
 *   char *ai_canonname;       / * canonical name for hostname * /
 *   struct sockaddr *ai_addr; / * binary address * /
 *   struct addrinfo *ai_next; / * next structure in linked list * /
 * };
 * return when hints = NULL:
 * 1. AF_INET4 SOCK_STREAM
 * 2. AF_INET4 SOCK_DGRAM
 * 3. AF_INET6 SOCK_STREAM
 * 4. AF_INET6 SOCK_DGRAM
*/
TEST_F(SockaddrTest, getaddrinfo_as_server) {
  char ip[INET6_ADDRSTRLEN];
  struct addrinfo *servinfo;

  // first NULL means local host
  int error = getaddrinfo(NULL, "http", NULL, &servinfo);
  EXPECT_EQ(error, 0);

  int count = 0;
  for (struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) {
    ++count;
    if (p->ai_family == AF_INET) {
      struct sockaddr_in *in = (struct sockaddr_in *)p->ai_addr;
      EXPECT_EQ(strcmp(inet_ntoa(in->sin_addr), "127.0.0.1"), 0);
      inet_ntop(in->sin_family, &in->sin_addr, ip, INET6_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "127.0.0.1"), 0);
    } else if (p->ai_family == AF_INET6) {
      struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)p->ai_addr;
      inet_ntop(in6->sin6_family, &in6->sin6_addr, ip, INET6_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "::1"), 0);
    }
  }

  EXPECT_EQ(count, 4);
  freeaddrinfo(servinfo);
}

// Server: hostname=NULL && AI_PASSIVE
TEST_F(SockaddrTest, getaddrinfo_as_server_with_hints) {
  char ip[INET6_ADDRSTRLEN];
  struct addrinfo *servinfo;
  struct addrinfo hints {
    .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE
  };

  // first NULL means local host
  int error = getaddrinfo(NULL, "http", &hints, &servinfo);
  EXPECT_EQ(error, 0);

  int count = 0;
  for (struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) {
    ++count;
    if (p->ai_family == AF_INET) {
      struct sockaddr_in *in = (struct sockaddr_in *)p->ai_addr;
      EXPECT_EQ(strcmp(inet_ntoa(in->sin_addr), "0.0.0.0"), 0);
      inet_ntop(in->sin_family, &in->sin_addr, ip, INET6_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "0.0.0.0"), 0);
    } else if (p->ai_family == AF_INET6) {
      struct sockaddr_in *in = (struct sockaddr_in *)p->ai_addr;
      inet_ntop(in->sin_family, &in->sin_addr, ip, INET6_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "::"), 0);
    }
  }

  EXPECT_EQ(count, 2);
  freeaddrinfo(servinfo);
}

如果你是client,则对应的代码如下:

TEST_F(SockaddrTest, getaddrinfo_as_client) {
  char ip[INET_ADDRSTRLEN];
  struct addrinfo *servinfo;
  struct addrinfo hints {
    .ai_family = AF_INET, .ai_socktype = SOCK_STREAM
  };

  int error = getaddrinfo("nonocast.cn", "http", &hints, &servinfo);
  EXPECT_EQ(error, 0);
  int count = 0;
  for (struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) {
    ++count;
    if (p->ai_family == AF_INET) {
      struct sockaddr_in *in = (struct sockaddr_in *)p->ai_addr;
      EXPECT_EQ(strcmp(inet_ntoa(in->sin_addr), "212.64.40.9"), 0);
      inet_ntop(in->sin_family, &in->sin_addr, ip, INET_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "212.64.40.9"), 0);
    }
  }

  EXPECT_EQ(count, 1);
  freeaddrinfo(servinfo);
}

socket()

然后,应该在getaddrinfo的基础上,call socket:

int s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

bind()

参考文档