0%

网络编程入门

1. TCP/UDP网络通信流程图

1.1 TCP

image-20230926192207003

1.1.1 代码

server.c
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
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <bits/sockaddr.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#define SERVER_PORT 8888
#define BACKLOG 16
#define RECV_BUF_SIZE 1024

int main(int argc, char *argv[])
{
int iSocketServer;
int iSocketClient;
int iRet, iRecvLen, iClientCnt = 0;
struct sockaddr_in tSockServerAddr, tSockClientAddr;
socklen_t clientAddrlen;
unsigned char ucRecvBuf[RECV_BUF_SIZE];

socklen_t server_addrlen;

/* 对SIGCHLD信号的操作为忽略 */
signal(SIGCHLD,SIG_IGN);

// IPV4协议族,类型是可靠传输中的0号协议:TCP
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == iSocketServer)
{
printf("socket error\n");
return -1;
}

tSockServerAddr.sin_family = AF_INET;
tSockServerAddr.sin_port = htons(SERVER_PORT); // 转化主机字节序为网络字节序
tSockServerAddr.sin_addr.s_addr = INADDR_ANY; // 监测本机上所有IP
memset(tSockServerAddr.sin_zero, 0, 8);
// Socket文件,监听的IP和端口,结构体的Size
iRet = bind(iSocketServer, (struct sockaddr *)&tSockServerAddr, sizeof(struct sockaddr));
if(-1 == iRet)
{
printf("bind() failed\n");
return -1;
}
// Socket文件,最多允许多少路连接
iRet = listen(iSocketServer, BACKLOG);
if(-1 == iRet)
{
printf("listen() failed\n");
return - 1;
}

printf("start listen...\n");
while(1)
{
// 服务端Socket文件,客户端地址结构体,结构体长度,成功则返回客户端的Socket文件
clientAddrlen = sizeof(struct sockaddr);
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSockClientAddr, &clientAddrlen);
if(-1 != iSocketClient)
{
printf("accept success from client %d: %s\n", ++iClientCnt, inet_ntoa(tSockClientAddr.sin_addr));
if(!fork()) //fork()对于子进程返回0,对于父进程返回子进程的PID
{
/* 子进程 */
while (1)
{
/* 接收客户端发送的数据并显示处理 */
iRecvLen = recv(iSocketClient, &ucRecvBuf, RECV_BUF_SIZE - 1, 0);
if(iRecvLen == -1 || iRecvLen == 0) //发生错误或者流关闭
{
close(iSocketClient);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0';
printf("recv string from %d: length %d and string %s\n", iClientCnt, iRecvLen, ucRecvBuf);
}
}
}
}
}
close(iSocketServer);
return 0;
}
client.c
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
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <bits/sockaddr.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>

#define SERVER_PORT 8888
#define SEND_BUF_SIZE 1024
int main(int argc, char *argv[])
{
int iSocketClient;
struct sockaddr_in tSockServerAddr;
int iRet, iSendLength;
unsigned char ucSendBuf[SEND_BUF_SIZE];

if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}

/* 打开一个Socket文件 */
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == iSocketClient)
{
printf("socket error\n");
return -1;
}
/* 连接到服务器 */
tSockServerAddr.sin_family = AF_INET;
tSockServerAddr.sin_port = htons(SERVER_PORT); // 转化主机字节序为网络字节序
if( !inet_aton(argv[1], &tSockServerAddr.sin_addr) )
{
printf("IP: %s is invalid!\n", argv[1]);
return -1;
}
memset(tSockServerAddr.sin_zero, 0, 8);
iRet = connect(iSocketClient, (const struct sockaddr *)&tSockServerAddr, sizeof(struct sockaddr));
if(-1 == iRet)
{
printf("Error connecting\n");
return -1;
}
/* 发送数据 */
while (1)
{
fgets(ucSendBuf, SEND_BUF_SIZE - 1, stdin);
iSendLength = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if(-1 == iSendLength)
{
printf("Error sending\n");
close(iSocketClient);
return -1;
}
}
/* 关闭Socket文件 */
close(iSocketClient);
return 0;
}

1.1.2 僵尸进程

如果server.c没有加入代码 signal(SIGCHLD,SIG_IGN);,在先结束已经建立连接的客户端进程时,与之对应的服务端子进程就会产生如下图所示的情况,变成一个僵尸进程

image-20230926223605756

(1) 僵尸进程产生的原因

当一个子进程终止时,它的进程表项仍然保留在系统中,直到父进程通过 wait 或者 waitpid 等系统调用来获取子进程的终止状态。如果父进程没有正确处理子进程终止的消息,那么子进程的进程表项就会一直保留下来,成为僵尸进程。僵尸进程会占用有限的系统资源,并且如果产生大量的僵尸进程,可能会导致系统资源耗尽。

SIGCHLD 信号是在子进程终止或者停止时发送给父进程的。默认情况下,父进程会忽略该信号,并不会自动处理僵尸进程。

(2) 解决僵尸进程的一种方式

当在父进程中调用 signal(SIGCHLD, SIG_IGN) 时,意味着设置了 SIGCHLD 信号的处理方式为忽略。这样,父进程在子进程终止时不会收到 SIGCHLD 信号,也不会产生僵尸进程。系统的init进程会立即回收子进程的资源,并且不需要父进程调用 wait 或者 waitpid 来处理子进程的终止状态。

1.2 UDP

image-20230926192248900

1.2.1 代码

server.c
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
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <bits/sockaddr.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#define SERVER_PORT 8888
#define RECV_BUF_SIZE 1024
int main(int argc, char **argv)
{
int iSocketServer, iRet, iRecvLen;
struct sockaddr_in tSocketServerAddr, tSocketClientAddr;
unsigned char ucRecvBuf[RECV_BUF_SIZE];
socklen_t clientAddrLen;

iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == iSocketServer)
{
printf("socket error\n");
return -1;
}

tSocketServerAddr.sin_port = htons(SERVER_PORT);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, sizeof(tSocketServerAddr.sin_zero));
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if(-1 == iRet)
{
printf("bind error\n");
return -1;
}

printf("start listen...\n");
while (1)
{
clientAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketServer, ucRecvBuf, RECV_BUF_SIZE - 1, 0, (struct sockaddr *)&tSocketClientAddr, &clientAddrLen);
if(iRecvLen == -1)
{
printf("recv error\n");
return -1;
}
ucRecvBuf[iRecvLen] = '\0';
printf("recv string from %s: length %d and string %s\n", inet_ntoa(tSocketClientAddr.sin_addr), iRecvLen, ucRecvBuf);
}
close(iSocketServer);
return 0;
}
client.c
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
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <bits/sockaddr.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#define CLIENT_SEND_SIZE 1024
#define SERVER_PORT 8888

int main(int argc, char *argv[])
{
int iSocketClient, iSendLen;
struct sockaddr_in tSocketServerAddr;
unsigned char ucSendBuf[CLIENT_SEND_SIZE];

if(argc != 2)
{
printf("Usage: %s <server_ip>\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == iSocketClient)
{
printf("socket error\n");
return -1;
}
if(!inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("IP: %s is invalid!\n", argv[1]);
return -1;
}
tSocketServerAddr.sin_port = htons(SERVER_PORT);
tSocketServerAddr.sin_family = AF_INET;
memset(tSocketServerAddr.sin_zero, 0, sizeof(tSocketServerAddr.sin_zero));

while(1)
{
fgets(ucSendBuf, CLIENT_SEND_SIZE - 1, stdin);
iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if(-1 == iSendLen)
{
printf("Error sending\n");
close(iSocketClient);
return -1;
}
}
close(iSocketClient);
return 0;
}