多进程实现服务器并发
-
图A适合
-
子进程只负责跟客户端的沟通,需要连接描述符就够了。但它层父进程继承了设备描述符和连接描述符,所以直接关闭设备描述符就可以了。
代码示例:
t_stdio.h
#ifndef T_STDIO_H_
#define T_STDIO_H_
#include <stdio.h>
#define E_MSG(STRING, VAL) do{perror(STRING); return(VAL);}while(0)
# endif
网络模块: t_net.h, t_net.c
- 这部分会打包成动态库
t_net.h
#ifndef T_NET_H_
#define T_NET_H_
// 头文件的包含
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 类型的定义
typedef struct sockaddr SA;
typedef struct sockaddr_in SA4;
int s_bind(int, int, in_port_t);
int s_listen(int, int, in_port_t, int);
// 无来电显示
int n_accept(int);
// 带来电显示
int h_accept(int);
# endif
t_net.c
#include "t_net.h"
#include "t_stdio.h"
// 函数的定义 socket(2), bind(2)功能, 成功返回socket设备的文件描述符
// 失败返回-1
int s_bind(int domain, int type, in_port_t port) {
int fd;
SA4 serv;
// 创建socket设备, 并返回设备的文件描述符
fd = socket(domain, type, 0);
if(fd == -1)E_MSG("socket", -1);
serv.sin_family = AF_INET;
serv.sin_port=htons(port);
serv.sin_addr.s_addr=htonl(INADDR_ANY);
// 将fd和本地地址,端口号绑定
int b = bind(fd, (SA *)&serv, sizeof(SA4));
if(b==-1)E_MSG("bind", -1);
return fd;
}
int s_listen(int domain, int type, in_port_t port, int backlog){
int fd = s_bind(domain, type, port);
if(fd == -1)return -1; //这里也不用处理错误,s_bind中有错误处理
listen(fd, backlog);
return fd;
}
// 无来电显示
int n_accept(int fd){
int c_fd = accept(fd, NULL, NULL);
if(c_fd == -1)E_MSG("accpet", -1);
return c_fd;
}
// 带来电显示
int h_accept(int fd){
char IP[32];
SA4 cli;
socklen_t cli_len;
cli_len = sizeof(cli);
int c_fd = accept(fd, (SA *)&cli,&cli_len);
if(c_fd == -1)E_MSG("accpet", -1);
inet_ntop(AF_INET, &cli.sin_addr, IP, 32);
printf("client IP: %s\n", IP);
return c_fd;
}
业务模块: t_main.h, t_main.c
t_main.h
#ifndef T_MAIN_H_
#define T_MAIN_H_
int t_main(int);
#endif
t_main.c
#include <unistd.h>
#include <ctype.h>
#include "t_main.h"
#include <string.h>
int t_main(int fd){
char buf[128];
while(1){
int r = read(fd, buf, 128);
// 处理客户端请求的信息
for (int i=0; i < r; i++){
buf[i] = toupper(buf[i]);
}
// 将结果送回客户端
write(fd, buf, r);
if(strcmp(buf, "EXIT")==0)break;
}
return 0;
}
服务端: server.c
server.c
#include <stdio.h>
#include "t_net.h"
#include "t_stdio.h"
#include "t_main.h"
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
void handle(int n) {
wait(NULL);
return;
}
int main(void){
// 另一种“非阻塞”回收子进程资源的办法
// 收到子进程结束的信号SIGCHLD,再执行wait, 而不是直接把wait写到父进程的代码了
signal(SIGCHLD, handle);
int s_fd = s_listen(AF_INET, SOCK_STREAM, 55577, 5);
if(s_fd == -1)return -1; //这里就直接返回-1了,因为错误已经在s_bind里处理过了。
while (1)
{
int c_fd = h_accept(s_fd);
if(c_fd == -1)return -1;
// 创建子进程,子进程继承父进程的文件描述符
pid_t pid = fork();
if(pid==-1)E_MSG("fork", -1);
if(pid == 0){// 子进程负责的任务
close(s_fd); // 用不到设备描述符,关掉
t_main(c_fd); // 业务处理
close(c_fd); // 关闭本次连接
exit(0); // 终止子进程
} else { // 父进程负责的任务
close(c_fd); // 用不到链接描述符,关掉
// 这里回收子进程资源,如果直接用wait,会阻塞的。还是没有办法达到并发的效果
// 第二次的请求还是会先放到未决连接队列中,等阻塞完成回收资源,再处理第二次的请求
// 当然本次的例子只是转换大小写,感觉不来的.
// wait(NULL);
}
}
return 0;
}
客户端: client.c
client.c
#include "t_stdio.h"
#include <unistd.h>
#include <string.h>
#include "t_net.h"
#include <stdio.h>
int main(int argc, char *argv[]){
char buf[128]; // 接受服务端传来的数据
// char *msg = "this is a test ....\n";
char msg[128];
SA4 serv; //定义初服务器地址变量
// 创建socket设备,返回设备的文件描述符
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)E_MSG("socket", -1);
// 服务器的信息初始化
serv.sin_family = AF_INET;
serv.sin_port=htons(55572);
// text ---> binary, 结果放在serv.sin_addr
inet_pton(AF_INET, argv[1], &serv.sin_addr);
// 使用fd向服务器发起连接
int c = connect(fd, (SA *)&serv, sizeof(serv));
if(c==-1)E_MSG("connect", -1);
//到这里三次握手就成功了
while(1){
gets(msg); // 会把回车转为'\0'
// 向服务器发送请求信息, strlen(3) 返回的长度不带'\0'
write(fd, msg, strlen(msg) + 1);
// 阻塞等待服务器的响应
int r = read(fd, buf, 128);
if(strcmp(buf, "EXIT")==0)break;
// 将相应信息输出到显示器
write(1, buf, r);
printf("\n");
}
// 关闭文件描述符
close(fd);
return 0;
}
# 制作动态库
# 1. 将t_net.c编译成与位置无关目标文件
$ gcc -c -fPIC t_net.c
# 2. 将第一步生成的目标文件打包到动态库文件中
$ gcc -shared -o libt_net.so t_net.o
# 3. 将动态文件库移动到动态链接器默认的路径下
$ sudo mv libt_net.so /lib/.
# 4. 使用动态链接生成可执行文件
$ gcc server.c t_main.c -lt_net -o serv
# 客户端可以不用动态链接,好像因为它没有用到t_het.h中声明的函数,只是用头文件和宏定义
$ gcc client.c -o cli