Socket编程实践(4) --多进程并发server

简介: 1.Socket地址复用int getsockopt(int sockfd, int level, int optname, void *optval, so...

1.Socket地址复用

int getsockopt(int sockfd, int level, int optname,
               void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
               const void *optval, socklen_t optlen);

服务端尽可能使用SO_REUSEADDR,在绑定之前尽可能调用setsockopt来设置SO_REUSEADDR套接字选项。该选项可以使得server不必等待TIME_WAIT状态消失就可以重启服务器(对于TIME_WAIT状态会在后面续有叙述).

可以在bind之前添加代码(完整代码请参照博文最后):

    int on = 1;
    if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,
                   &on,sizeof(on)) == -1)
        err_exit("setsockopt SO_REUSEADDR error");

用以支持地址复用.

 

2.process-per-connecton

我们的echo服务器最大的缺点就是无法支持多客户连接,即使客户端能够连接到服务器上(client端connect时并没有出错返回), 服务器也不为该客户做服务,(直接没什么反应),虽然链接是有的(也就是说,客户端是已经连接到服务器上的了,但是服务器就是不搭理你....), 我们提出的改进方案是process-per-connection(一条连接一个进程, 我们在多线程那一章中曾经提出过一条连接一个线程, 这种方案相比较而言能够比多进程拥有更高的并发量);

/** 示例:echo server改进, 多进程模型(client并未更改)**/
void echo(int clientfd);
int main()
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
        err_exit("socket error");
    int on = 1;
    if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,
                   &on,sizeof(on)) == -1)
        err_exit("setsockopt SO_REUSEADDR error");

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8001);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)
        err_exit("bind error");
    if (listen(listenfd, SOMAXCONN) == -1)
        err_exit("listen error");

    struct sockaddr_in clientAddr;
    //谨记: 此处一定要初始化
    socklen_t addrLen = sizeof(clientAddr);
    while (true)
    {
        int clientfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);
        if (clientfd == -1)
            err_exit("accept error");
        //打印客户IP地址与端口号
        cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)
             << ", " << ntohs(clientAddr.sin_port) << endl;

        pid_t pid = fork();
        if (pid == -1)
            err_exit("fork error");
        else if (pid > 0)
            close(clientfd);
        //子进程处理链接
        else if (pid == 0)
        {
            close(listenfd);
            echo(clientfd);
            //子进程一定要exit, 否则的话, 该子进程也会回到accept处
            exit(EXIT_SUCCESS);
        }
    }
    close(listenfd);
}
void echo(int clientfd)
{
    char buf[512] = {0};
    int readBytes;
    while ((readBytes = read(clientfd, buf, sizeof(buf))) > 0)
    {
        cout << buf;
        if (write(clientfd, buf, readBytes) == -1)
            err_exit("write socket error");
        memset(buf, 0, sizeof(buf));
    }
    if (readBytes == 0)
    {
        cerr << "client connect closed..." << endl;
        close(clientfd);
    }
    else if (readBytes == -1)
        err_exit("read socket error");
}

完整代码实现:

http://download.csdn.net/detail/hanqing280441589/8458053

 

3. P2P聊天程序设计与实现

server端与client都有两个进程:

    (1)父进程负责从socket中读取数据将其写至终端, 由于父进程使用的是read系统调用的阻塞版本, 因此如果socket中没有数据的话, 父进程会一直阻塞; 如果read返回0, 表示对端连接关闭, 则父进程会发送SIGUSR1信号给子进程, 通知其退出;

    (2)子进程负责从键盘读取数据将其写入socket, 如果键盘没有数据的话, 则fgets调用会一直阻塞;

//serever端代码与说明
int main()
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
        err_exit("socket error");
    int on = 1;
    if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,
                   &on,sizeof(on)) == -1)
        err_exit("setsockopt SO_REUSEADDR error");

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8001);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)
        err_exit("bind error");
    if (listen(listenfd, SOMAXCONN) == -1)
        err_exit("listen error");

    struct sockaddr_in clientAddr;
    socklen_t addrLen = sizeof(clientAddr);
    int clientfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);
    if (clientfd == -1)
        err_exit("accept error");
    close(listenfd);
    //打印客户IP地址与端口号
    cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)
         << ", " << ntohs(clientAddr.sin_port) << endl;

    char buf[512] = {0};
    pid_t pid = fork();
    if (pid == -1)
        err_exit("fork error");
    //父进程: socket -> terminal
    else if (pid > 0)
    {
        int readBytes;
        while ((readBytes = read(clientfd, buf, sizeof(buf))) > 0)
        {
            cout << buf;
            memset(buf, 0, sizeof(buf));
        }
        if (readBytes == 0)
            cout << "client connect closed...\nserver exiting..." << endl;
        else if (readBytes == -1)
            err_exit("read socket error");
        //通知子进程退出
        kill(pid, SIGUSR1);
    }
    //子进程: keyboard -> socket
    else if (pid == 0)
    {
        signal(SIGUSR1, sigHandler);
        while (fgets(buf, sizeof(buf), stdin) != NULL)
        {
            if (write(clientfd, buf, strlen(buf)) == -1)
                err_exit("write socket error");
            memset(buf, 0, sizeof(buf));
        }
    }
    close(clientfd);
    exit(EXIT_SUCCESS);
}
//client端代码与说明
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
        err_exit("socket error");

    //填写服务器端口号与IP地址
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8001);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (connect(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)
        err_exit("connect error");

    char buf[512] = {0};
    pid_t pid = fork();
    if (pid == -1)
        err_exit("fork error");
    //父进程: socket -> terminal
    else if (pid > 0)
    {
        int readBytes;
        while ((readBytes = read(sockfd, buf, sizeof(buf))) > 0)
        {
            cout << buf;
            memset(buf, 0, sizeof(buf));
        }
        if (readBytes == 0)
            cout << "server connect closed...\nclient exiting..." << endl;
        else if (readBytes == -1)
            err_exit("read socket error");
        kill(pid, SIGUSR1);
    }
    //子进程: keyboard -> socket
    else if (pid == 0)
    {
        signal(SIGUSR1, sigHandler);
        while (fgets(buf, sizeof(buf), stdin) != NULL)
        {
            if (write(sockfd, buf, strlen(buf)) == -1)
                err_exit("write socket error");
            memset(buf, 0, sizeof(buf));
        }
    }
    close(sockfd);
    exit(EXIT_SUCCESS);
}

完整代码实现:

http://download.csdn.net/detail/hanqing280441589/8460013

目录
相关文章
|
算法 调度 UED
探索操作系统中的进程调度:理论与实践
【9月更文挑战第24天】 在数字世界的心脏跳动着的是操作系统,它像一位精明的指挥家,精心安排每个音符的演奏。本文将带你进入操作系统的内核,一探进程调度的秘密。我们将从简单的批处理系统谈起,穿越时间隧道,见证现代多道程序设计系统的复杂性与优雅。你将看到代码如何赋予理论以生命,理解调度算法背后的哲理。让我们一起跟随甘地的指引,成为我们希望在世界上看到的改变。
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
306 3
|
Python
解锁Python并发新世界:线程与进程的并行艺术,让你的应用性能翻倍!
【7月更文挑战第9天】并发编程**是同时执行多个任务的技术,提升程序效率。Python的**threading**模块支持多线程,适合IO密集型任务,但受GIL限制。**multiprocessing**模块允许多进程并行,绕过GIL,适用于CPU密集型任务。例如,计算平方和,多线程版本使用`threading`分割工作并同步结果;多进程版本利用`multiprocessing.Pool`分块计算再合并。正确选择能优化应用性能。
246 1
|
弹性计算 运维 监控
基于进程热点分析与系统资源优化的智能运维实践
智能服务器管理平台提供直观的可视化界面,助力高效操作系统管理。核心功能包括运维监控、智能助手和扩展插件管理,支持系统健康监控、故障诊断等,确保集群稳定运行。首次使用需激活服务并安装管控组件。平台还提供进程热点追踪、性能观测与优化建议,帮助开发人员快速识别和解决性能瓶颈。定期分析和多维度监控可提前预警潜在问题,保障系统长期稳定运行。
646 17
|
消息中间件 监控 算法
用Apifox调试Socket.IO接口,从原理到实践
传统HTTP协议"请求-响应"的离散式通信机制已难以满足需求,这正是Socket.IO这类实时通信框架的价值所在。
用Apifox调试Socket.IO接口,从原理到实践
|
算法 调度 UED
操作系统中的进程管理:原理与实践
在数字世界的心脏跳动着无数进程,它们如同细胞一般构成了操作系统的生命体。本文将深入探讨进程管理的奥秘,从进程的诞生到成长,再到最终的消亡,揭示操作系统如何协调这些看似杂乱无章却又井然有序的活动。通过浅显易懂的语言和直观的比喻,我们将一起探索进程调度的策略、同步机制的重要性以及死锁问题的解决之道。准备好跟随我们的脚步,一起走进操作系统的微观世界,解锁进程管理的秘密吧!
340 33
|
人工智能 PyTorch 算法框架/工具
Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
【8月更文挑战第6天】Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
|
算法 Java
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
该博客文章综合介绍了Java并发编程的基础知识,包括线程与进程的区别、并发与并行的概念、线程的生命周期状态、`sleep`与`wait`方法的差异、`Lock`接口及其实现类与`synchronized`关键字的对比,以及生产者和消费者问题的解决方案和使用`Condition`对象替代`synchronized`关键字的方法。
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
监控 算法 调度
探索操作系统中的进程管理:从理论到实践
【8月更文挑战第30天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅管理着硬件资源,还确保了软件的顺畅运行。本文将深入探讨操作系统中的一项核心功能——进程管理。我们将从基本概念出发,逐步深入到进程状态、调度算法,以及进程同步机制。通过实际代码示例,我们将看到理论如何转化为实践中的具体操作,从而更好地理解进程管理的精妙之处。无论你是初学者还是有一定基础的开发者,这篇文章都将为你揭开操作系统进程管理的神秘面纱。