Linux下的TCP/IP编程----进程及多进程服务端

在之前的学习中我们的服务端同一时间只能为一个客户端提供服务,即使是将accept()函数包含在循环中,也只能是为多个客户端依次提供服务,并没有并发服务的能力,这显然是不合理的。通过多进程的使用,我们可以很便捷的实现服务端的多进程,这样就可以同时为多个客户端提供服务。



首先我们要理解程序,进程,进程ID,僵尸进程,线程的概念。

程序:广泛的说就是为了达到某一目的二规定的途径,在编程中具体的就是为了实现某一功能而编写的代码实体,是静态的。

进程:程序的一次动态执行就是一个进程,它是占用了一定内存空间,正在运行的程序。

进程ID:在操作系统中,PCB(进程控制块)是进程存在的唯一标识,而其中进程ID则用于区分进程控制块,在系统中是唯一的,每个进程都有一个唯一的进程ID用于区分。

线程:进程更小的资源集合体,它不持有系统资源,而是通过向所在进程申请资源运行,是系统进行调度的最小单位。一个进程中可以包含有多个线程。

僵尸进程:在进程执行完之后本因该销毁,但有时因为一些原因而是的其保留了下来,虽然其已经释放了绝大部分的系统资源,但是仍保留了一小部分系统资源,对于这种进程我们称之为”僵尸进程”。

僵尸进程产生的原因:在Linux中,为了使得子进程在结束之后其结束时的信息和状态可以被父进程查询到,所以设置了一个机制:即当进程运行完成之后会保留一部分的资源用于保存结束时的状态和信息(例如进程ID,返回值等),直到其父进程获取了这些信息,子进程才完全的释放了所占用的资源。由于这种机制的存在,当父进程中执行的任务比子进程繁重时(即子进程比父进程先结束)子进程就必须要等待父进程来主动的获取信息,这样就使得僵尸进程产生了。

附:僵尸进程的产生是由于子进程比父进程先结束,必要要等到父进程查询其结束信息而产生,那么自然而然的就考虑到若是父进程比子进程先结束又会发生声么呢?当子进程还没有执行完全部任务时,若是父进程已经结束运行,那么子进程会托管给系统中进程ID为1的进程(用于协助操作系统的进程),即子进程的父进程变成了进程ID为1的进程,这时候系统会自动进行信息的采集,以保证不会产生僵尸进程。



在了解了基本的概念之后,我们就可以尝试进行多进程的实践。创建进程的方式有很多,我们只了解创建多进程服务端的函数。

pid_t fork(void):复制当前进程成为新进程

成功时返回进程ID,失败时返回-1

  • 子进程中返回ID为0
  • 父进程中返回ID为子进程的进程ID

详解:通过调用fork()函数,我们可以复制当前的进程(调用fork()函数的进程)成为一个新的子进程,俩者拥有相同而又各自独立的内存空间(也就是把父进程的内存完全复制到了另一块内存中)。我们可以根据函数的返回值来区分父进程和子进程。

#include <stdio.h>
#include<unistd.h>

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

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

    //两个进程将根据PID的不同来执行不同的代码,在父进程中pid的值为子进程的进程ID,在子进程中pid的值为0
    if(pid == 0){
        printf("I am Child process  pid %d\n ",pid);
    }else{
        printf("I am parent process Child Pid %d \n",pid );
        sleep(30);
    }

    return 0;
}

对僵尸进程的解决办法:

1. 使用wait()或者是waitpid()函数:

pid_t wait(int *statloc):获取子进程的返回值

  • statloc(内存地址):当调用该函数时,若是存在已经结束的子进程,那么该子进程的返回值就保存在该地址所指的内空间中。若是没有已经结束的子进程,那么就会阻塞主进程,直到有子进程结束为止。

成功时返回终止的子进程ID,失败时返回-1

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

int main(int argc ,char *argv[]){
    //用于保存状态
    int status;
    //fork一个子进程
    pid_t pid = fork();
    if(pid == 0){
        return 3;
    }else{
        printf("Child PID :%d \n",pid);
        //在父进程中在fork一个子进程
        pid = fork();
        if(pid == 0){
            exit(7);
        }else{
            printf("Child PID :%d \n",pid);
            //获取子进程的返回值
            wait(&status);
            /**
             * 由于status所指向的单元中还包含其他的信息,所以要通过以下宏定义进行判断和分离
             * WIFEXITED(status)------子进程正常终止时返回真
             * WEXITSTATUS(status)------子进程的返回值
            **/

            //判断子进程是否正常结束
            if(WIFEXITED(status)){
                printf("Child send one : %d \n",WEXITSTATUS(status));
            }

            wait(&status);
            if(WIFEXITED(status)){
                printf("Child send one : %d \n",WEXITSTATUS(status));
            }
            sleep(60);

        }

    }
    return 0;
}

pid_t waitpid(pid_t pid , int *statloc , int options)获取子进程的返回值:

  • pid(进程ID):要结束的进程ID
  • statloc(地址指针):子进程的返回值等信息存放在该参数所指向的内存地址中
  • option(配置信息):传递头文件sys/wait.h中声明的常量
#include <stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(int argc , char * argv[]){
      //用于保存状态
    int status;
    //fork一个子进程
    pid_t pid = fork();
    if(pid == 0){
        sleep(15);
        return 24;
    }else{
        while(!waitpid(pid,&status,WNOHANG)){
            sleep(3);
            puts("sleap 3 seconds");
        }

        if(WIFEXITED(status)){
            printf("Child send %d \n",WEXITSTATUS(status));
        }
    }
return 0;
}

2. 通过信号量机制:

void (signal(int signum,void( func)(int)))(int)注册信号量产生时需要调用的函数:

  • signo(信号量常量):要注册的信号量类型

常见信号量:

信号量 对应事件
SIGALRM 到了使用alarm()函数注册的时间
SIGINT 输入Ctrl+C
SIGCHLD 子进程结束
  • (* func)(int)(函数地址指针):指向处理信号量的函数的地址

返回之前注册的函数指针。在发生信号量时会由系统唤醒正处于睡眠状态的线程(即使是sleep时间未到也会被唤醒)

#include <stdio.h>
#include<unistd.h>
#include<sys/wait.h>
/**超时处理函数**/
void time_out (int sig){
    if(sig == SIGALRM){
        puts("Time Out ");
        alarm(2);
    }

}

/**键盘输入处理函数**/

void key_control(int sig){
    if(sig == SIGINT){
        puts("Ctrl + C pressed");
    }
}

int main(int argc , char * argv[]){
   int i = 0;
    //向系统注册超时处理信号
    signal(SIGALRM,time_out);
    //向系统注册键盘输入Ctrl+C处理信号的
    signal(SIGINT,key_control);
    //设置时钟
    alarm(2);

    for(;i<3;i++){
        puts("wait.........");
        sleep(100);
    }
return 0;
}

int sigaction(int signo , const struct sigaction *act , struct signation *oldaction)处理系统发出的信号量

  • signo(信号量常数):传入需要监听的信号量
  • act(结构体指针):传入处理信号量的结构体的地址
  • oldaction(结构体指针):通过次参数可获得之前注册的处理信号量

的结构体地址成功时返回0,失败时返回-1

sigaction结构体详解:

struct sigaction{
    void (* sa_handler) (int);//保存信号处理函数的地址
    sigset_t sa_mask;  //一个调用信号捕捉函数之前要加到进程信号屏蔽字中的信号集
    int sa_flags;//信号处理选项
}

该结构体后边的两个字段时用于指定信号相关的选项和特性,在此只需要全部填为0即可

使用sigaction()函数:

#include <stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>

/**超时处理函数**/
void time_out(int sig){
        if(sig == SIGALRM){
            puts("Time Out ");
        }
        alarm(2);
}

int main(int argc ,char *argv[]){
    int i = 0;
    //声明一个sigaction变量
    struct sigaction act;
    /**初始化act变量中的值**/
    act.sa_handler = time_out;//初始化信号处理函数的地址
    act.sa_flags = 0;//初始化信号处理标志
    sigemptyset(&act.sa_mask);//用来将act.sa_mask信号集初始化并清空
    sigaction(SIGALRM,&act,0);//调用sigaction函数

    alarm(2);//设置时钟

    for(;i<3;i++){
        puts("wait..........");
        sleep(100);
    }
return 0;
}

有了这些关于进程,僵尸进程的处理的知识我们就可以自己做一个多进程的服务端,这样就可以同时为多个客户端提供服务。

多进程服务端:

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

#define BUFF_SIZE 30

void error_handling(char *message);
void read_child_proc(int sig);

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

    //服务端和客户端socket
    int server_socket;
    int client_socket;
    //服务端和客户端地址
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    //用于保存进程ID
    pid_t pid;
    //信号量结构体变量
    struct sigaction act;
    //用于保存socket地址长度
    socklen_t addr_size;
    //用于保存字符串长度
    int str_len;
    //用于记录设置信号量的结果
    int state;
    //字符缓冲
    char buff[BUFF_SIZE];
    //用于控制程序的结束与否
    bool is_running = true;
    //检查传入的参数个数是否合法
    if(argc!=2){
        printf("Usage : %s <port> \n",argv[0]);
        exit(1);
    }

    //初始化信号量机制
    act.sa_handler = read_child_proc;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    state = sigaction(SIGCHLD,&act,0);

    //初始化socket
    server_socket = socket(PF_INET,SOCK_STREAM,0);
    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(argv[1]));

    //绑定地址
      if(bind(server_socket,(struct sockaddr *) &server_addr,sizeof(server_addr)) == -1){
        error_handling("bind() error");
      }
    //设置监听
    if(listen(server_socket,5) == -1){
        error_handling("listen() error");
    }
    //通过循环来不断的提供服务
    while(is_running){
        addr_size = sizeof(client_addr);
        client_socket = accept(server_socket,(struct sockaddr *) &client_addr,&addr_size);
        //进行检查
        if(client_socket == -1){
            continue;
        }else{
            printf("new client connected...............\n");
        }
        //创建新的进程
        pid = fork();
        //检查是否创建成功
        if(pid == -1){
            close(client_socket);
            continue;
        }
        //子进程运行部分,在这里进行服务端和客户端的交互
        if(pid == 0){
            close(server_socket);
            //不断的向客户端发送读取到的数据,直到读取完毕
            while((str_len = read(client_socket,buff,BUFF_SIZE)) != 0){
                write(client_socket,buff,str_len);
            }
            //发送完毕之后关闭客户端的连接
            close(client_socket);
            puts("client disconnected.........");
            //子进程完成任务,返回
            return 0;
        }else{
            close(client_socket);
        }
    }
    //彻底关闭服务端,但是由于前边的while循环是死循环,正常情况下执行不到
    close(server_socket);
return 0;
}
/**子进程处理函数**/
void read_child_proc(int sig){
    pid_t  pid;
    int status;
    //在信号量处理函数中调用waitpid()函数来读取子进程的结束信息,彻底销毁子进程,同时父进程也可以根据status中的信息来对子进程的处理结果进程进一步的处理
    pid = waitpid(-1,&status,WNOHANG);
    printf("remove proc id : %d \n",pid);
}
/**出错处理函数**/
void error_handling(char * message){
    fputs(message,stderr);
    fputc(‘\n‘,stderr);
    exit(1);
}

自此就完成了一个多进程服务端程序的建立,我们可以使用该服务端同时为多个客户端提供服务。

时间: 2024-10-13 22:48:01

Linux下的TCP/IP编程----进程及多进程服务端的相关文章

c# TCP/IP编程

这东西很多朋友都有写过了,我也就写着玩玩,就当做个笔记吧.不废话了. TCP/IP在数据通信中被广泛的使用,自然得包含客户端和服务端,当然,自己自言自语不是什么不可以,可那样貌似有点神经. 好了,那就先来建立服务端吧. 1.新建一个项目,就取名叫MyServer吧,然后敲入如下代码: using System;using System.Net;using System.Net.Sockets; namespace MyServer{    class Program    {        st

c++ 网络编程(四)TCP/IP LINUX/windows下 socket 基于I/O复用的服务器端代码 解决多进程服务端创建进程资源浪费问题

原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9613861.html 好了,继上一篇说到多进程服务端也是有缺点的,每创建一个进程就代表大量的运算与内存空间占用,相互进程数据交换也很麻烦. 本章的I/O模型就是可以解决这个问题的其中一种模型...废话不多说进入主题-- I/O复用技术主要就是select函数的使用. 一.I/O复用预备知识--select()函数用法与作用 select()用来确定一个或多个套接字的状态(更为本质一点来讲是文

Linux下高并发网络编程

1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时, 最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统 为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄). 可使用ulimit命令查看系统允许当前用户进程打开的文件数限制: [[email protected] ~]$ ulimit -n 1024 这表示当前用户的每个进程最多允许同时打开1024个文件,这1024

转 学习linux下的c/c++编程

http://blog.csdn.net/byxdaz/article/details/3959680 1,先有linux环境搭minGW和cygwin都有点麻烦,最最简单的办法还是装个真正的linux,用虚拟机也好,在网络上的另一台机器也好.这样不仅快,而且你有了真正的环境.2.会C/C++语言(估计你会的)3.入门阶段熟悉gcc命令行,最基本的参数,如,-g,-W,-O,-o,-c 建议看man gcc(很大找想要的)4.编译第一个helloworld程序: 基本命令 gcc hellowo

Linux下的shell脚本编程-变量-算术表达式-判断语句-if分支语句

Linux下的shell脚本编程-变量-算术表达式-判断语句-if分支语句 一:实验环境 1):虚拟机 2):linux系统 二:实验目标 1): shell 基本语法 2):变量 3):表达式 4):判断语句 5): if表达式 三:实验脚本 第一块 一个简单的shell脚本程序 [[email protected] ~]# mkdir test [[email protected] test]# vim example1.sh #!/bin/bash #This is to show wha

linux下Eclipse进行C编程时动态链接库的生成和使用

引用 http://linux.chinaitlab.com/soft/864157.html 欢迎进入Linux社区论坛,与200万技术人员互动交流 >>进入 一.创建动态链接库1.创建工程new->project->c++ project选择Shared Library->Empty Project.输入工程名a,点击finish,完成工程的创建. 2.编写代码在windows下封装动态链接库时对要封的函数要用__declspec(dllexport)来标明,在linux

windows下重新安装TCP/IP协议栈

一.windows重装TCP/IP协议     前两天在windows下安装开发环境的时候,把系统的TCP/IP协议栈给搞跪了,导致系统无法ping localhost.无法在程序中创建socket等....于是对windows 7(64bit)下的TCP/IP协议栈进行了重装.尝试了很多方法,发现一个可以使用的: (1)删除注册表键值     开始——运行——regedit,找到 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Wins

Linux下的静态IP配置【weber出品】

配置Linux下的静态IP地址 因为服务器的IP地址是固定的,不像我们自己家的笔记本的IP是动态的.所以我们要将这个地址给写成静态的. 直接编辑这个这个配置文件即可: vi /etc/sysconfig/network-scripts/ifcfg-ethx DEVICE=eth0 BOOTPROTO=static HWADDR= ;这里是你网卡的物理地址,不用输入 ONBOOT=yes IPADDR=192.168.0.1 NETMASK=255.255.255.0 NETWORK=192.16

linux下的c语言编程删除文件夹

刚刚在学习开始做新项目的时候,学长布置了一项任务,就是在给定一个目录下,要将这个目录下一个星期之前的目录包括里面的文件全部删除,只保留这一个星期内的.百度了好久的资料,终于完成,记录一下防止忘记.(注:文件夹名称默认为日期格式,如20140716) #include<dirent.h> #include<sys/types.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #i