0%

Socket网络编程

简单的Socket入门

读的是《Beej’s Guide to Network Programming》这一本书

What is Socket?

In UNIX, Everything is a file.

Unix的I/O操作通过读写file descriptor(一个跟已开启文件有关的整数)实现。Sockets是利用标准UNIX file descriptors与其他程序沟通的一种方式。

Sockets有很多种,这里只讨论Internet Sockets. 共有两种,流和数据报。

  • Stream Sockets
    • Connection-oriented and reliaby
  • Datagram Sockets
    • Message oriented.

相关数据结构

IP地址让我们能唯一地辨识Internet中的任意两台计算机,而再加上port number能标识本地计算机的进程。这样,通过 (IP, PORT)这一对二元组我们便能唯一地表示Internet中任意两台计算机中的任两个想要通信的进程。

所以在获得套接字描述符socket descriptor之前,我们得先取得地址。

  • Struct addrinfo
1
2
3
4
5
6
7
8
9
10
11
struct addrinfo 
{
int ai_flags; // AI_PASSIVE, AI_CANONNAME 等。
int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
int ai_protocol; // 用 0 表示 "any"
size_t ai_addrlen; // ai_addr 的大小,单位是 byte
struct sockaddr *ai_addr; // struct sockaddr_in 或 _in6
char *ai_canonname; // 典型的 hostname
struct addrinfo *ai_next; // 链表
};

这个结构体中存储了地址的相关信息,用来准备之后要用到的socket的地址结构或是host name和service name的查询。其中的成员包含了新的结构体struct sockaddr.

  • Struct sockaddr
1
2
3
4
5
struct sockaddr 
{
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};

内容很简单。

在字符数组中手动填充Port number和IP address很不人性化,工程师提供了更友善的版本struct sockaddr_in.

「in」 表示 「internet」🙃.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// IPv4 only  ————  struct sockaddr_in6 for IPv6
struct sockaddr_in
{
short int sin_family; // Address family, AF_INET
unsigned short int sin_port; // Port number
struct in_addr sin_addr; // Internet address
unsigned char sin_zero[8]; // 与 struct sockaddr 相同的大小
};

// IPv4 only ———— struct in6_addr for IPv6
struct in_addr
{
uint32_t s_addr; // that's a 32-bit int (4 bytes)
};
  • IPv4和IPv6的存储需要不同的数据结构。struct sockaddr_storage提供了足够大的空间使得其能支持两种版本IP.

  • 注意到**struct sockaddr, struct sockaddr_in , struct sockaddr_storage**这三种数据结构可以相互cast.

  • Getaddrinfo

在很久以前程序员需要手动填充struct addrinfo. 但现在有了getaddrinfo(),我们可以很方便的得到一个struct addrinfo.

1
2
3
int getaddrinfo(  const char *node, const char *service, 
const struct addrinfo *hints,
struct addrinfo **res);
  • node是一个网址或者是IP地址
  • service是一个服务名如”http”或者一个port number.
  • 结构体成员hint是一个被填充好部分信息的struct addrinfo,用来提供给getaddrinfo()以返回期望的信息,(如SOCK_STREAM, SOCK_DGRAM的选择等等). 可以为NULL.
  • res指向一个链表,表中的每个成员都会包含某种struct sockaddr(比如IPv4啦IPv6啦等等)。
  • 使用完后需要调用freeaddrinfo()来释放这个链表。

来一个例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//
// Show the IP address
//
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main(int argc, char const *argv[])
{
int status = -1;
struct addrinfo hint;
struct addrinfo *linkaddr, *temp;
char ipstr[INET6_ADDRSTRLEN];

if (argc != 2)
{
printf("INPUT THE HOSTNAME\n");
return 1;
}

// preprocess
memset(&hint, 0, sizeof(hint));
hint.ai_family = AF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;

if ((status = getaddrinfo(argv[1], NULL, &hint, &linkaddr)) != 0)
{
printf("CANNOT GET ADDRESS INFO\n");
return 1;
}

printf("IP addresses for %s:\n\n", argv[1]);


// Select the ip addresses from the linked list
for (temp = linkaddr; temp != NULL; temp = temp->ai_next)
{
void *address;
char *ipVersion;

// Get IP address itself
// If it is IPv4
if(temp->ai_family == AF_INET)
{
// Use sockaddr_in to choose the ip address
struct sockaddr_in *sa_in = (struct sockaddr_in *)(temp->ai_addr);
address = &(sa_in->sin_addr);
ipVersion = "IPv4";
}
else
{
struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *)(temp->ai_addr);
address = &(sa_in6->sin6_addr);
ipVersion = "IPv6";
}

// Binary mode to presentation
inet_ntop(temp->ai_family,address,ipstr,sizeof(ipstr));
printf("%s: %s\n",ipVersion,ipstr);
}

freeaddrinfo(linkaddr);

return 0;
}