在父子进程的多任务架构中,父进程创建的网络套接字,fork出来子进程后,子进程是完全进行了复制。
3.1 验证代码
下面我们就以第二种网络模型为例进行验证。
代码如下:
/*
* ex020302_netprocess.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_EVENTS 10
#define BUFFER_SIZE 1024
#define PORT 5809
void initializeServerNet();
void closeServerFd();
void dispatchLoop();
void subprocess();
int listen_fd;
int main(int argc ,char *argv[])
{
initializeServerNet();
subprocess();
subprocess();
dispatchLoop();
closeServerFd();
return 0;
}
void subprocess()
{
int pid = -1;
pid = fork();
if(pid < 0)
{
printf("fork error[%s]\n",strerror(errno));
exit(-1);
}
else if(pid > 0)
{
// parent.
return;
}
else
{
// child
dispatchLoop();
exit(0);
}
}
void initializeServerNet()
{
struct sockaddr_in server_addr;
// 创建监听socket
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1)
{
perror("socket");
exit(EXIT_FAILURE);
}
// 绑定地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1)
{
perror("bind");
close(listen_fd);
exit(EXIT_FAILURE);
}
// 开始监听
if (listen(listen_fd, SOMAXCONN) == -1)
{
perror("listen");
close(listen_fd);
exit(EXIT_FAILURE);
}
}
void closeServerFd()
{
close(listen_fd);
}
void dispatchLoop()
{
int conn_fd;
// 主循环
while (1)
{
// 新的连接
conn_fd = accept(listen_fd, NULL, NULL);
if (conn_fd == -1)
{
printf("[%d] accept wakeup, but failure.\n", getpid());
sleep(1);
continue;
}
printf("[%d] accept a new client [%d]. \n",getpid(), conn_fd);
close(conn_fd);
}
closeServerFd();
}
说明
- 在主进程中创建网络套接字,绑定地址,并启动监听;
- 创建多个工作子进程;
- 工作子进程中继承了监听套接字;
- 每个工作子进程可以独立与客户端建立连接,并处理消息;
这里会创建两个子进程,在父进程和子进程中都会对同一个socket监听连接请求。
3.2 网络客户端
为了方便验证,我们编写一个简单的客户端程序。
/*
* ex020302_client.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 4808
#define BUFFER_SIZE 1024
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE] = {0};
const char *message = "Hello, Server!";
int port = SERVER_PORT;
if(argc > 1)
{
port = atoi(argv[1]);
}
// 创建套接字
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 配置服务器地址信息
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
// 将IP地址从字符串转换为二进制形式
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0)
{
perror("Invalid address/ Address not supported");
close(sockfd);
exit(EXIT_FAILURE);
}
// 连接到服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("Connection Failed");
close(sockfd);
exit(EXIT_FAILURE);
}
for(int i = 0; i < 20; i++)
{
// 发送消息到服务器
send(sockfd, message, strlen(message), 0);
printf("Message sent: %s\n", message);
// 接收服务器的响应
int bytes_received = recv(sockfd, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received < 0)
{
perror("Error in receiving");
}
else if (bytes_received == 0)
{
printf("Server closed the connection\n");
}
else
{
buffer[bytes_received] = '\0'; // 确保字符串以空字符结尾
printf("Message received from server: %s\n", buffer);
}
sleep(1);
}
// 关闭套接字
close(sockfd);
return 0;
}
说明
- 客户端带一个参数,是服务端的端口号;
- 创建socket,并且与服务端(IP:port)连接;
- 不断发送和接收消息,重复20次;
- 实际上服务端只是接收建立连接,不会接收和发送响应,这已经达到了测试的目的;
3.3 结果验证
- 编译服务端,并且运行
这里没有使用deamon后台服务的方式运行,会停在这里。
[senllang@hatch ex02]$ gcc ex020105_forksocket.c -o test
[senllang@hatch ex02]$ ./test
- 编译客户端,并运行
在另外一个终端编译和运行网络客户端程序;
服务端默认的端口号是5809,作为参数输入。
[senllang@hatch ex02]$ gcc ex020302_client.c -o client_opt
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
为了模拟多客户端的场景,我们将客户端启动多次;
可以看到客户端启动后,打印了发送的消息,之后就退出了,
因为服务端与客户端建立连接成功后,随即就关闭了连接。
- 运行结果
可以看到服务端打印的信息。
[1205954] accept a new client [4].
[1205955] accept a new client [4].
[1205956] accept a new client [4].
[1205954] accept a new client [4].
[1205955] accept a new client [4].
[1205956] accept a new client [4].
服务端启动了三个进程,可以看到三个进程的PID分别为1205954
,1205955
,1205956
,它们都可以收到来自客户端的连接请求。
同时出现了一件很有意思的事情,三个进程轮流进行处理连接请求,这里主要避免了惊群的问题。