草庐IT

linux网络编程-多进程实现TCP并发服务器

微尘8 2024-04-28 原文

服务端流程步骤

  • socket函数创建监听套接字lfd

  • bind函数将监听套接字绑定ip和端口

  • listen函数设置服务器为被动监听状态,同时创建一条未完成连接队列(没走完tcp三次握手流程的连接),和一条已完成连接队列(已完成tcp三次握手的连接)

  • accept函数循环的从已完成连接队列中提取连接,并返回一个新的套接字cfd跟客户端进行通信

  • fork函数创建一个子进程,让子进程与客户端进行通信

  • 子进程:read函数循环的从r缓冲区读取客户端发送的数据,write函数将要发送的数据写入w缓冲区

  • close函数关闭套接字

客户端流程步骤

  • socket函数创建套接字

  • connect函数连接服务器

  • write函数将要发送的数据写入w缓冲区,read函数从r缓冲区读取服务器发送给客户端的数据

  • close函数关闭套接字

客户端跟服务器通信流程图

相关函数


  1. int socket(int domain, int type, int protocol);

功能:创建一个用于网络通信的套接字文件描述符

参数:domain:协议族(AF_INET:ipv4,AF_INET6:ipv6,等等)

type:套接字类型(SOCK_DGRAM:udp,SOCK_STREAM:tcp,等等)

protocol:用于制定某个协议的特定类型,即type类型中的某个类型,通常不用管它,设置 为 0

返回值:成功则返回socket套接字描述符, 失败返回-1,并设置errno

头文件:#include <sys/socket.h>


  1. int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

功能:将sockfd绑定ip和端口

参数:sockfd:套接字

my_addr:存放有协议,ip,端口的结构体信息

addrlen:my_addr结构体大小

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <sys/types.h>

#include <sys/socket.h>


  1. int listen(int s, int backlog);

功能:让服务器处于被动监听状态,同时创建了一条未完成三次握手的连接队列和一条已经完成三次握 手的连接队列

参数:s:套接字

backlog:支持未完成连接和已完成连接之和的最大值,一般设置128

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <sys/socket.h>


  1. int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

功能:从已完成连接队列中提取客户端连接

参数:s:套接字

addr:存放成功连接的客户端的ip,端口等信息结构体

addrlen:存放addr结构体大小的变量地址

返回值:成功则返回一个非负整数标识这个连接套接字,是否返回-1

头文件:#include <sys/types.h>

#include <sys/socket.h>


  1. ssize_t read(int fd, void *buf, size_t count);

功能:从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中

参数:fd:文件描述符

buf:缓冲区

count:读count字节

返回值:成功时返回读取到的字节数,失败返回-1,并设置errno

头文件:#include <unistd.h>


  1. ssize_t write(int fd, const void *buf, size_t count);

功能:向文件描述符fd所引用的文件中写入从buf开始的缓冲区中count字节的数据

参数:fd:文件描述符

buf:缓冲区

count:写count字节

返回值:功时返回所写入的字节数,失败返回-1,并设置errno

头文件:#include <unistd.h>


  1. int close(int fd);

功能:关闭 一个文件描述符

参数:fd:要关闭的文件描述符

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <unistd.h>


  1. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:连接服务器

参数:sockfd:套接字

addr:服务器的ip,端口等结构体信息

addrlen:addr结构体大小

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <sys/socket.h>


  1. pid_t fork(void);

功能:创建一个子进程

参数:无

返回值:父进程返回子进程的进程id,子进程返回0

头文件:#include <unistd.h>


  1. int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

功能:设置信号集屏蔽字

参数:how:SIG_BLOCK (mask |= set),SIG_UNBLOCK (mask &= ~set),SIG_SETMASK (mask = set)

set:信号集

oldset:旧的信号集

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <signal.h>


  1. pid_t waitpid(pid_t pid, int *status, int options);

功能:回收已结束的子进程资源

参数:pid:pid<-1 等待进程组识别码为pid绝对值的任何子进程

pid=-1 等待任何子进程, 相当于wait()

pid=0 等待进程组识别码与目前进程相同的任何子进程

pid>0 等待任何子进程识别码为pid的子进程

status:子进程的结束状态值

options:WNOHANG 如果没有任何已经结束的子进程则马上返回, 不予以等待

WUNTRACED 如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会

返回值:成功返回子进程pid,失败返回-1

头文件: #include <sys/types.h>

#include <sys/wait.h>


服务端代码

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

#define BUF_SIZE 256

void do_sigchld(int signo, siginfo_t *siginfo, void *p)
{
    int status;
    pid_t pid;
    while((pid = waitpid(0, &status, WNOHANG)) > 0) //0:回收跟调用进程同组的子进程的资源,WNOHANG:不阻塞
    {
        if (WIFEXITED(status))
            printf("pid = %d, parent pid = %d, child pid = %d, exit status %d\n", pid, getpid(), siginfo->si_pid, WEXITSTATUS(status));
        else if (WEXITSTATUS(status))
            printf("pid = %d, parent pid = %d, child pid = %d, exit by signal %d\n ", pid, getpid(), siginfo->si_pid, WIFSIGNALED(status));
    }
}

void sys_err(const char *str, int err)
{
    perror(str);
    exit(err);
}

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        printf("%s port\n", argv[0]);
        exit(1);
    }
    //创建流式套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd < 0)
        sys_err("socket", 1);

    //绑定ip端口    
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons((unsigned short)atoi(argv[1]));
    server_addr.sin_addr.s_addr = 0; //0表示将本机所有ip都绑定上
    int ret = bind(lfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (ret < 0)
        sys_err("bind", 1);
    
    //监听
    ret = listen(lfd, 128);
    if (ret < 0)
        sys_err("listen", 1);
    
    //阻塞SIGCHLD信号
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_BLOCK, &set, NULL);
    int block = 1;
    //循环提取
    int cfd;
    pid_t pid;
    struct sockaddr_in client_addr; 
    socklen_t len = sizeof(client_addr);
    char buf[BUF_SIZE];
    ssize_t size;
    char ip[INET_ADDRSTRLEN] = "";
    while(1)
    {
        cfd = accept(lfd, (struct sockaddr *)&client_addr, &len);
        if (cfd < 0)
        {
            if (errno == EINTR)
                continue;

            sys_err("accept", 1);
        }

        printf("client ip = %s, port = %d connect success\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client_addr.sin_port));
        pid = fork();
        if (pid == 0)
        {
            //in child
            close(lfd); //关闭不用了的监听套接字
            //解除阻塞SIGCHLD信号
            sigprocmask(SIG_UNBLOCK, &set, NULL);
            block = 0;

            while(1)
            {
                memset(buf, 0, sizeof(buf));
                size = read(cfd, buf, sizeof(buf));
                if (size == 0) //客户端断开连接
                {
                    printf("client close\n");
                    break;
                }

                printf("%s\n", buf);
                write(cfd, buf, size);
            }

            break;
        }
        else if (pid > 0)
        {
            //in parent
            close(cfd); //关闭不用了的跟客户端通讯的套接字
            if (1 == block)
            {
                //先捕捉
                struct sigaction sa;
                sa.sa_sigaction = do_sigchld;
                sigemptyset(&sa.sa_mask);
                sa.sa_flags = SA_SIGINFO;
                sigaction(SIGCHLD, &sa, NULL);
                //后解除阻塞
                sigprocmask(SIG_UNBLOCK, &set, NULL);
                block = 0;
            }
        }
        else
            sys_err("fork", 1);
    }
    
    //关闭套接字
    if (pid == 0)
        close(cfd);
    else if (pid > 0)
        close(lfd);

    return 0;
}

客户端代码

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    if (argc < 3)
    {
        printf("%s sever_ip server_port\n", argv[0]);
        exit(1);
    }

    //创建流式套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket");
        exit(1);
    }

    //连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons((unsigned short)atoi(argv[2]));
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
    printf("connect ret = %d\n", ret);
    if (ret < 0)
    {
        perror("connect");
        exit(1);
    }

    //读写数据
    char buf_w[128] = "";
    char buf_r[128] = "";
    ssize_t size_r = 0;
    while(1)
    {
        fgets(buf_w, sizeof(buf_w), stdin);
        buf_w[strlen(buf_w) - 1] = 0;
        write(fd, buf_w, strlen(buf_w));
        size_r = read(fd, buf_r, sizeof(buf_r));
        if (size_r == 0) //服务器断开
            break;
        else
            printf("%s\n", buf_r);

        memset(buf_w, 0, sizeof(buf_w));
        memset(buf_r, 0, sizeof(buf_r));
    }

    //关闭套接字
    close(fd);

    return 0;
}

结果

  • 3个客户端连接服务器时

服务端

客户端

ps aux 可以看到有4个服务器进程(其中一个是主进程,用于监听处理新的连接,其他3个子进程用于跟客户端进行通信)

  • 回射服务信息截图

客户端1:

客户端2:

客户端3:

服务器收到进行打印:

3个客户端都退出时

服务端收到客户端退出,并回收跟客户端进行通信的3个子进程的资源

ps aux 查看可以看到只剩主进程,用于监听处理新的连接,其他3个子进程都已结束

有关linux网络编程-多进程实现TCP并发服务器的更多相关文章

  1. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  2. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  3. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  6. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  7. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  8. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  9. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  10. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

随机推荐