多进程回声服务器

多进程回声服务器

服务器搭建思路

  1. 处理僵尸进程
  2. 生成服务端
  3. 循环接收客服端连接并开辟新进程处理新连接

以下是服务器实例:

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/tcp.h>
#include<wait.h>
#include<signal.h>
#include<bits/sigaction.h>

//子进程退出信号回调函数
void ChildExitHandle()
{
    //使用waitpid()函数
    int status;
    pid_t pid = waitpid(-1,&status,WNOHANG);//监控任意子进程、状态信息、非阻塞
    if (WIFEXITED(status))
    {
        printf("Child process exited.\n");
        printf("The child process's id is %d\n",WEXITSTATUS(status));
    }
    
}

//错误处理
void error_handing(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

int main(int argc, char const *argv[])
{
    //步骤1:处理僵尸进程 -> 使用sigaction()函数
    struct sigaction sig_cfg;
    sig_cfg.sa_handler = ChildExitHandle;
    sigaction(SIGCLD,&sig_cfg,NULL);

    //步骤2.1:生成服务端socket
    int server_sock;
    server_sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
    if (server_sock == -1)
    {
        error_handing("socket() error");
    }

    //步骤2.2:绑定监听端口
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(atoi("3333"));
    if (bind(server_sock,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    {
        error_handing("bind() error");
    }

    //步骤2.3:开启端口监听
    if (listen(server_sock,5) == -1)
    {
        error_handing("listen() error");
    }

    //步骤3:循环接收客服端连接并开辟新进程处理新连接
    while (1)
    {
        //步骤3.1:接受连接
        int accept_sock;
        struct sockaddr accept_addr;
        socklen_t len = sizeof(accept_addr);
        accept_sock = accept(server_sock,&accept_addr,&len);//第二个参数存储接入的客户端的地址信息
        if (accept_sock == -1)
        {
            //error_handing("accept() error");
            continue;
        }
        else
        {
            printf("New client connected...\n");
        }

        //步骤3.2:开辟新进程与客户端交互
        pid_t pid = fork();

        if (pid == 0)//子进程
        {
            //子进程同时也复制了父进程的server_sock,要关闭
            close(server_sock);

            //步骤3.3:开始交互
            const int message_size = 10;
            char message[message_size];
            int read_len = 0;
            while((read_len=read(accept_sock, message, message_size))!=0)
                write(accept_sock, message, read_len);

            //步骤3.3:交互完毕,关闭连接
            close(accept_sock);

            //步骤3.4:子进程退出
            exit(2);
        }
        else//父进程
        {
            //父进程同时也包含了子进程的accept_sock,要关闭
            close(accept_sock);
        }
        
    }
    close(server_sock);
    return 0;
}

客户端实例:采用分割I/O的方法,将输入和出输出分割,简化逻辑

c 复制代码
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <arpa/inet.h> 
#include <sys/socket.h> 

#define BUF_SIZE 30
void error_handling(char *message); 
void read_routline(int sock, char *buf);
void write_routline(int sock, char *buf);

int main(int argc, char *argv[]) 
{
    
    int sock; 
    pid_t pid;
    struct sockaddr_in serv_addr;
    char message[BUF_SIZE]; 
    int str_len,i; 

    
    if (argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1); 
    }

    //步骤1:创建套接字
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1) 
        error_handling("socket() error"); 

    //步骤2:连接端口
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET; // 地址族设置为IPv4
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]); // 设置服务器IP地址
    serv_addr.sin_port = htons(atoi(argv[2])); // 设置服务器端口号

    // 发送连接请求
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        error_handling("connect() error"); 
        else{
            printf("Connected ok\n");
        }
    
    //步骤3:进行通讯
    pid=fork();
    if(pid==0){
        write_routline(sock,message);
    }else{
        read_routline(sock,message);
        close(sock);
        return 0;
    }

}


void error_handling(char* message){
    fputs(message, stderr); 
    fputc('\n', stderr); 
    exit(1); 
}

void write_routline(int sock, char *buf){
    while(1){
        fgets(buf, BUF_SIZE, stdin);
        if(!strcmp(buf,"q\n")||!strcmp(buf,"Q\n")){
            shutdown(sock, SHUT_WR);
            return;
        }
        write(sock, buf, strlen(buf));
    }
}

void read_routline(int sock, char *buf){
    while(1){
        int str_len=read(sock, buf, BUF_SIZE);
        if(str_len==0){
            return;
        }
        buf[str_len]=0;
        printf("Message from server:%s", buf);
    }
}

使用客户端 :./client "服务端ip" "端口"

相关推荐
lybugproducer19 分钟前
深入 Linux 文件系统:从数据存储到万物皆文件
linux
烦躁的大鼻嘎27 分钟前
【Linux】深入Linux多线程架构与高性能编程
linux·运维·服务器·开发语言·c++·ubuntu
羚羊角uou33 分钟前
【Linux】system V共享内存
linux·运维·服务器
绝无仅有35 分钟前
大厂Redis高级面试题与答案
后端·面试·github
绝无仅有38 分钟前
面试问题之导致 SQL 查询慢的原因及优化建议
后端·面试·github
林克爱塞尔达44 分钟前
Linux入门(二)
linux·运维·chrome
破烂儿1 小时前
Ubuntu Server 安装图形界面和通过Window远程桌面连接服务器(Xrdp)
linux·服务器·ubuntu
存储服务专家StorageExpert1 小时前
手搓一个 DELL EMC Unity存储系统健康检查清单
linux·运维·服务器·存储维护·emc存储
笑口常开xpr2 小时前
Linux 库开发入门:静态库与动态库的 2 种构建方式 + 5 个编译差异 + 3 个加载技巧,新手速看
linux·c语言·动态库·静态库
小虾米vivian2 小时前
达梦:将sql通过shell脚本的方式放在后台执行
服务器·数据库·sql