第八篇:socket 通讯服务器模型--并发服务器模型

socket通讯服务器模型.有多种类型,多进程只是其中比较少用的一种,原因是它太“重”。

在生产环境中,使用多进程模型可能不是最高效的选择,特别是在处理大量并发连接时。多线程模型或IO多路复用模型可能是更好的选择。

父子进程:

多进程并发服务器原型 

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    pid_t pid;

    // 创建子进程
    pid = fork();

    if (pid < 0) {  // 创建子进程失败
        fprintf(stderr, "Fork failed.\n");
        return 1;
    } else if (pid == 0) {  // 子进程
        printf("Hello from child process!\n");
    } else {  // 父进程
        printf("Hello from parent process!\n");
    }

    return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>  // 正确的头文件
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h>  // 为了使用 strerror

#define N 64

// 回收进程的资源
void handler(int signum)
{
    wait(NULL);
}

int main(int argc, char const *argv[])
{
    int sockfd;

    if (argc < 3)
    {
        printf("usage: %s <ip> <port>\n", argv[0]);
        return -1;
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2]));
    // 自动绑定所有的本机网卡的地址
    addr.sin_addr.s_addr = INADDR_ANY;  // 或者使用 inet_addr(argv[1]) 来绑定特定 IP

    int addrlen = sizeof(addr);

    if (bind(sockfd, (struct sockaddr *)&addr, addrlen) < 0)
    {
        perror("bind err");
        close(sockfd);
        return -1;
    }

    if (listen(sockfd, 5) < 0)
    {
        perror("listen err");
        close(sockfd);
        return -1;
    }

    printf("wait client connect\n");
    int clifd;
    struct sockaddr_in cliaddr;

    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = handler;
    sigaction(SIGCHLD, &sa, NULL);

    while (1)
    {
        clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &addrlen);
        if (clifd < 0)
        {
            perror("accept err");
            continue;
        }

        pid_t pid = fork();
        if (pid < 0)
        {
            perror("fork err");
            close(clifd);
            continue;
        }
        else if (pid == 0)  // 子进程
        {
            int ret;
            char buf[N] = {0};
            while (1)
            {
                ret = recv(clifd, buf, N, 0);
                if (ret < 0)
                {
                    perror("recv err");
                    continue;
                }
                else if (ret == 0)
                {
                    printf("peer exit\n");
                    break;
                }
                else
                {
                    printf("ip = %s, port = %d, data = %s\n",
                        inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buf);
                }
            }
            close(clifd);
            exit(0);
        }
        else  // 父进程
        {
            close(clifd);  // 关闭子进程已使用的套接字描述符
        }
    }

    close(sockfd);  // 这行代码实际上不会被执行,因为上面的循环是无限循环

    return 0;
}

这段代码实现了一个简单的多进程服务器,它能够监听来自客户端的连接请求,并在接受到连接后,通过创建一个新的子进程来处理每个客户端的通信。

包含的头文件

  • stdio.h:用于基本的输入输出函数,如printf
  • sys/types.hsys/socket.hnetinet/in.harpa/inet.h:这些头文件提供了网络编程所需的类型和函数,包括套接字创建、地址转换等。
  • unistd.h:提供对POSIX操作系统API的访问,如closeforkexit等。
  • stdlib.h:包含了一些常用的库函数,如atoi(字符串转整数)。
  • sys/stat.hfcntl.h:虽然在这段代码中没有直接使用,但通常用于文件操作。
  • signal.h:提供了信号处理的功能。
  • sys/wait.h:提供了等待进程结束的功能。
  • string.h:提供了字符串处理的函数,如memsetstrerror(尽管strerror在这段代码中未使用)。

宏定义

  • #define N 64:定义了缓冲区的大小为64字节。

函数

  • handler(int signum):这是一个信号处理函数,用于处理子进程结束的信号(SIGCHLD)。它调用wait(NULL)来回收结束的子进程的资源。

主函数 main

  1. 参数检查:程序需要两个参数:IP地址和端口号。如果参数不足,程序将打印用法信息并退出。

  2. 创建套接字:使用socket函数创建一个TCP套接字。

  3. 配置地址结构:将服务器的IP地址(错误地设置为INADDR_ANY,这通常用于绑定到所有可用接口,应该使用inet_addr(argv[1])来指定特定IP)和端口号配置到sockaddr_in结构中。

  4. 绑定套接字:使用bind函数将套接字与指定的IP地址和端口号绑定。

  5. 监听连接:使用listen函数使套接字进入监听状态,准备接受连接请求。

  6. 设置信号处理:使用sigaction函数设置SIGCHLD信号的处理函数为handler

  7. 接受连接:在一个无限循环中,使用accept函数接受来自客户端的连接请求。每当接受到一个连接时,都会创建一个新的子进程来处理这个连接。

  8. 子进程处理

    • 在子进程中,使用recv函数从客户端接收数据。
    • 打印接收到的数据以及客户端的IP地址和端口号。
    • 当客户端关闭连接(recv返回0)时,子进程也关闭套接字并退出。
  9. 父进程处理

    • 父进程在创建子进程后关闭子进程使用的套接字描述符(clifd),因为子进程已经有自己的描述符副本。
    • 父进程继续监听新的连接请求。

子进程退出的时候,经常需要清理一下“现场”

我们这里通过下面的方式实时:

struct sigaction sa;   //struct sigaction是一个结构体,用于指定如何处理信号
    memset(&sa, 0, sizeof(sa));
//下面将sa结构体中的sa_handler成员设置为handler。handler是一个函数指针,指向一个用户定义的函数,该函数将在接收到特定信号时被调用
    sa.sa_handler = handler;  
    sigaction(SIGCHLD, &sa, NULL);    //将SIGCHLD信号的处理方式设置为sa结构体中指定的方式

这是进程间通信和进程管理中的一个常见做法,特别是在需要处理子进程退出状态的情况下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值