网络编程

AF(Address Family)和PF(Protocol Family)是套接字库里面经常能够看到的两个前缀,如何查看系统调用的说明,这里可以通过man命令的用法,比如man 1 bind1表示查看shell命令,2表示查看系统调用,3表示查看库函数用法。熟悉下面几个函数的用法:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <errno.h>
extern int errno;
// socket – create an endpoint for communication
// #include <sys/socket.h>
/**
* @param domain 明确通信发生在哪些域
PF_LOCAL Host-internal protocols, formerly called PF_UNIX,
PF_UNIX Host-internal protocols, deprecated, use PF_LOCAL,
PF_INET Internet version 4 protocols,
PF_ROUTE Internal Routing protocol,
PF_KEY Internal key-management function,
PF_INET6 Internet version 6 protocols,
PF_SYSTEM System domain,
PF_NDRV Raw access to network device,
PF_VSOCK VM Sockets protocols
@param type 通信的语义
SOCK_STREAM (TCP)
SOCK_DGRAM (UDP)
SOCK_RAW (only super-user)
@param protocol 套接字使用的协议,一般使用在一个给定的协议族和套接字类型的情况下只有一个协议存在,其他情况下如果存在很多协议,就需要手动指定
IPPROTO_UDP/IPPROTO_TCP 定义在<netinet/in.h>
@return 如果出错返回-1, 否则返回套接字句柄
*/
int socket(int domain, int type, int protocol);
/**
* @param socket 监听的套接字
* @param backlog 定义等待连接的队列的最大长度,如果队列满了,再收到连接请求,客户端就会收到连接拒绝的信息
* @return 返回0表示成功,-1表示错误,同时会设置全局变量errno
*/
int listen(int socket, int backlog);

/**
* 为匿名套接字分配一个名称,当一个套接字使用socket函数创建时,它存在于(address family)名称空间,没有分配名称,bind会请求address分配给这个套接字
*
* @return 返回0表示成功,-1表示错误,同时会设置全局变量errno
*/
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

/**
* @param socket是一个套接字,通过socket函数创建,通过bind函数分配了一个地址,并且通过listen函数监听连接,accept函数会在等待连接的队列里面取出第一个连接,创建一个新的套接字,属性和原来的一样,,会为新的套接字分别一个新的文件描述符。如果目前队列中没有等待的连接并且套接字被标记为非阻塞,accept函数会阻塞调用者,直到连接准备就绪
* @param address 是一个结果参数,会被赋值为连接实体的地址,就是通信层实体的地址
* @param address_len 也是一个结果参数,需要初始化为address的空间大小,调用结束后会被赋值为返回的address的实际大小
* @return -1表示发生错误,可以查看errno的具体错误代码,如果成功,返回一个非负整数表示接受的套接字的句柄
*/
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);

/**
* 接收来自套接字的消息
* @param socket 接收来自哪个套接字的消息
* @param buffer 接收的消息存放的位置
* @param length buffer可用的内存大小
* @param flags flags可以取下面的值
MSG_OOB process out-of-band data
MSG_PEEK peek at incoming message
MSG_WAITALL wait for full request or error
* @return 收到的字节的个数,返回-1表示出现了错误
*/
ssize_t recv(int socket, void *buffer, size_t length, int flags);

/**
* 向一个套接字发送消息
* @param socket 待发送消息的套接字
* @param buffer 待发数据存放内存区域
* @param length 待发数据长度
* @param flags 发送数据标志
* @return -1表示出现错误
*/
ssize_t send(int socket, const void *buffer, size_t length, int flags);

int connect(int, const struct sockaddr *, socklen_t) __DARWIN_ALIAS_C(connect);
socklen_t * __restrict) __DARWIN_ALIAS_C(recvfrom);

/**
* #include <arpa/inet.h>
* 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];
};

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 */
};
struct hostent * gethostbyname(const char *name);

具体代码例子

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
70
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>

#define MAX_CONNECTION_QUEUE_SIZE 50
#define MAX_BUFF_SIZE 255

extern int errno;

int createServer(in_port_t port);
void startServer(int socket);

int main(int argc, char **argv) {
if (argc != 2) {
printf("[%s](%s:%d) - Usage: ./a.out <port>\n", __FILE__, __FUNCTION__, __LINE__);
exit(0);
}
int port = atoi(argv[1]);
int fd = createServer(port);
if (fd == -1) {
exit(0);
}
startServer(fd);
return 0;
}

int createServer(in_port_t port) {
int serverfd = socket(PF_INET, SOCK_STREAM, NULL);
if (serverfd == -1) {
printf("[%s](%s:%d) - socket create error code %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno));
return -1;
}

struct sockaddr_in address;
address.sin_family = PF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);

if (bind(serverfd, (struct sockaddr *)&address, sizeof(address)) == -1) {
printf("[%s](%s:%d) - address bind error code %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno));
return -1;
}

if (listen(serverfd, MAX_CONNECTION_QUEUE_SIZE) == -1) {
printf("[%s](%s:%d) - socket listen error code %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno));
return -1;
}

return serverfd;
}

void startServer(int socket) {
while (1) {
struct sockaddr_in address;
int address_len = sizeof(address);
int connection = accept(socket, &address, &address_len);
if (connection == -1) {
printf("[%s](%s:%d) - connection error code %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno));
} else {
char buf[MAX_BUFF_SIZE] = "";
ssize_t len = recv(connection, buf, sizeof(buf), 0);
buf[len] = '\0';
printf("[%s](%s:%d) - message: %s\n", __FILE__, __FUNCTION__, __LINE__, buf);
send(connection, buf, sizeof(buf), 0);
}
}
}

通过域名获取IP地址

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
#include <netdb.h>
#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char **argv) {
if (argc != 2) {
printf("Usage: ./dns <hostname>");
}
const char * hostname = argv[1];
struct hostent *host = gethostbyname(hostname);
if (host == NULL) {
herror("gethostbyname");
return 1;
}
printf("Official name is: %s\n", host->h_name);
printf("IP address: %s\n", inet_ntoa(*(struct in_addr*)host->h_addr));
printf("All address: ");
struct in_addr **addr_list = (struct in_addr **)host->h_addr_list;
struct in_addr addr;
for (int i = 0; addr_list[i] != NULL; i++) {
printf("%s ", inet_ntoa(*addr_list[i]));
}
printf("\n");
return 0;
}