Linux APUE学习:2、网络socket编程

代码地址:https://github.com/RoxyKko/APUE/tree/master/ch3

必背图

image-20230226214756739

image-20230226214823652

  • TCP/IP -> 网络接口层

三大作用:

  • ​ 数据封装/解封成为帧(frame)
  • ​ 控制帧传输
  • ​ 流量控制

Q:什么是数据封装/解封?为什么要进行数据封装/解封成为帧?

A:数据封装是在所发数据包上附加本地以及目标地址、加纠错字节、以及加密处理等。数据解封是数据封装的逆过程,将发送方发过来的信息经过拆解协议包进而获得业务数据的过程。

作用:保证数据安全(防止被监听窃取数据)、可靠(数据不丢失)传输,每一帧除了需要传输的数据,还有发送方的物理地址(寄件人地址)、接受方物理地址(收件人地址)以及检错和控制信息(保证数据的无差错到达)。

Q:什么是控制帧传输?为什么要?

A:接收方通过对帧的差错编码(奇偶校验码或 CRC 码)的检查,来判断帧在传输过程中是否出错,并向发送发进行反馈,如果传输发生差错,则需要重发纠正。对于发送方,发送帧后同时启动定时器,定时器Timeout之前没有收到反馈,则认为帧传输出错,自动重发,每个帧都会进行编号,以免接受方一直重复接收同一帧。

Q:流量控制

​ 对发送方的发送速率做适当的限制,防止接收方性能不足,前面来不及接收的帧将被后面不断发送来的帧“淹没”

TCP的3次握手

image-20230303232744565

握手的过程实际上是在通知对方自己的初始化序号(Initial Sequence Number),简称ISN,也就是上图中的x和y。x和y会被当作之后传输数据的一个依据,以保证TCP报文在传输过程中不会混乱

socket编程

image-20230304113547209

image-20230304113533996

注意网络字节序和主机字节序

​ 使用htons、htonl、ntohs、ntohl互相转换,n代表net网络字节序的意思,h代表host主机字节序的意思

​ 因为主机字节序就是我们平常说的大端和小端模式,我们不确定是大端还是小端,不同的CPU有不同的字节序类型。而网络字节序统一为大端字节序,所以我们使用主机(网络)上传(下载)到网络(主机)上时要先使用上述四个函数先进行转换以防止出错

image-20230304113915780

IPv4点分十进制字符串和32位整形数据之间的互相转换

常用inet_aton()inet_ntoa()

image-20230304114422106

socket_client代码

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <stdio.h>
#include <sys/types.h> // for socket
#include <sys/socket.h> // for socket
#include <string.h> // fpr strerror and memset
#include <errno.h> // for errno
#include <netinet/in.h> // for sockaddr_in
#include <arpa/inet.h> // for inet_aton
#include <fcntl.h> // for open
#include <unistd.h> // for read/write/close
#include <getopt.h> // for getopt_long
#include <stdlib.h> // for atoi

#define MSG_STR "Hello Wrold!"

void print_usage(char *progname);

int main(int argc, char **argv)
{
int socket_fd = -1;
int rv = -1;
struct sockaddr_in servaddr;
char *servip = NULL;
int port = 0;
char buf[1024];
// 用在getopt_long中
int opt = -1;
const char *optstring = "i:p:h";
struct option opts[] =
{
{"help", no_argument, NULL, 'h'},
{"ipaddr", required_argument, NULL, 'i'},
{"prot", required_argument, NULL, 'p'},
{0, 0, 0, 0}
};

while ((opt = getopt_long(argc, argv, optstring, opts, NULL)) != -1)
{
switch (opt)
{
case 'i' :
servip = optarg;
break;
case 'p' :
port = atoi(optarg);
break;
case 'h' :
print_usage(argv[0]);
return 0;
}
}

if( !servip || !port )
{
print_usage(argv[0]);
return 0;
}
// 创建socket并获得socket描述符
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
// 创建socket失败
if(socket_fd < 0)
{
printf("Create socket failure: %s\n", strerror(errno));
return -1;
}
// 创建socket成功
printf("Create socket[%d] successfully\n",socket_fd);

// 初始化结构体,将空余的8位字节填充为0
// 设置参数,connect连接服务器
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
inet_aton(servip, &servaddr.sin_addr);
rv = connect(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));

// 连接服务器失败
if(rv < 0)
{
printf("Connect to server[%s:%d] failure : %s\n",
servip, port, strerror(errno));
return -2;
}
// 连接服务器成功,打印服务器IP地址和端口号
printf("Connect to server[%s:%d] successfully!\n", servip, port);

// 向服务器发送一次消息,Hello World!
rv = write(socket_fd, MSG_STR, strlen(MSG_STR));
if(rv < 0)
{
printf("Write to server by sockfd[%d] failure : %s\n",
socket_fd, strerror(errno));
return -3;
}
while(1)
{
// 每次进入循环清空缓冲区
// 持续读取服务器发送的数据存入缓冲区
memset(buf, 0, sizeof(buf));
rv = read(socket_fd, buf, sizeof(buf));
if(rv < 0)
{
printf("Read data form server by sockfd[%d] failure : %s \n",
socket_fd, strerror(errno));
break;
}
else if(rv == 0)
{
printf("Socket[%d] get disconnect!\n", socket_fd);
break;
}
else if(rv > 0)
{
// 读取成功,本机(客户端)打印读取到的数据
// 读取到的字节数就是read()的返回值
printf("Read %d bytes data form Server: %s\n",
rv, buf);
}

// 读取成功后进入此,向服务器发送接收到的数据
rv = write(socket_fd, buf, sizeof(buf));
if(rv < 0)
{
printf("Write to server by sockfd[%d] failure : %s\n",
socket_fd, strerror(errno));
break;
}
}

close(socket_fd);
return 0;
}


void print_usage(char *progname)
{
printf("%s usage: \n", progname);
printf("-i(--ipaddr): sepcify server IP address\n");
printf("-p(--port): sepcify server port \n");
printf("-h(--help): printf help information \n");

return ;
}

socket_server代码

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include <stdio.h>
#include <sys/types.h> // for socket
#include <sys/socket.h> // for socket
#include <string.h> // fpr strerror and memset
#include <errno.h> // for errno
#include <netinet/in.h> // for sockaddr_in
#include <arpa/inet.h> // for inet_aton
#include <fcntl.h> // for open
#include <unistd.h> // for read/write/close
#include <getopt.h> // for getopt_long
#include <stdlib.h> // for atoi

#define MSG_STR "Hello Wrold! \n"
#define MAX_CLIENT 5

void print_usage(char *progname);

int main(int argc, char **argv)
{
int socket_fd, client_fd = -1;
int rv = -1;
struct sockaddr_in servaddr;
struct sockaddr_in clientaddr;
socklen_t cliaddr_len;
int port = 0;
char buf[1024];
// 用在getopt_long中
int opt = -1;
const char *optstring = "p:h";
struct option opts[] =
{
{"help", no_argument, NULL, 'h'},
{"prot", required_argument, NULL, 'p'},
{0, 0, 0, 0}
};

while ((opt = getopt_long(argc, argv, optstring, opts, NULL)) != -1)
{
switch (opt)
{
case 'p' :
port = atoi(optarg);
break;
case 'h' :
print_usage(argv[0]);
return 0;
}
}

if( !port )
{
print_usage(argv[0]);
return 0;
}
// 创建socket并获得socket描述符
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
// 创建socket失败
if(socket_fd < 0)
{
printf("Create socket failure: %s\n", strerror(errno));
return -1;
}
// 创建socket成功
printf("Create socket[%d] successfully\n",socket_fd);

// 初始化结构体,将空余的8位字节填充为0
// 设置参数,connect连接服务器
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
// 监听所有网卡的所有IP地址
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

rv = bind(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));

// bind链接到port失败
if(rv < 0)
{
printf("Socket[%d] bind on port [%d] failure : %s\n",
socket_fd, port, strerror(errno));
return -2;
}
// bind链接到port成功
printf("Socket[%d] bind on port [%d] successfully!\n", socket_fd, port);

// 开启监听
listen(socket_fd, MAX_CLIENT);


memset(&clientaddr, 0, sizeof(clientaddr));
while(1)
{
printf("Waiting for client connect...\n");
client_fd = accept(socket_fd, (struct sockaddr*)&clientaddr, &cliaddr_len);
if(client_fd < 0)
{
printf("Accept client connect failure: %s\n", strerror(errno));
break;
}
printf("Accept client connect from %s:%d\n",
inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
while(client_fd)

// 每次进入循环清空缓冲区
// 持续读取客户端发送的数据存入缓冲区
memset(buf, 0, sizeof(buf));
rv = read(client_fd, buf, sizeof(buf));
if(rv < 0)
{
printf("Read data form client by sockfd[%d] failure : %s \n",
client_fd, strerror(errno));
close(client_fd);
continue;
}
else if(rv == 0)
{
printf("Socket[%d] get disconnect!\n", client_fd);
close(client_fd);
continue;
}
else if(rv > 0)
{
// 读取成功,本机(服务端)打印读取到的数据
// 读取到的字节数就是read()的返回值
printf("Read %d bytes data form Server: %s\n",
rv, buf);
}

// 读取成功后进入此,向客户端发送接收到的数据
rv = write(client_fd, buf, sizeof(buf));
if(rv < 0)
{
printf("Write to client by sockfd[%d] failure : %s\n",
client_fd, strerror(errno));
close(client_fd);
}


}

close(socket_fd);
return 0;
}


void print_usage(char *progname)
{
printf("%s usage: \n", progname);
printf("-p(--port): sepcify server port \n");
printf("-h(--help): printf help information \n");

return ;
}