UNIXC002 TCP并发服务器实现

多进程实现服务器并发

  • 图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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值