linux系统编程09-进程间通信

介绍

Untitled

1. 管道

Untitled

  • 命名管道就是一块磁盘上的文件,不同进程通过读写该文件进行通信
  • 匿名管道同样是在磁盘上创建了文件,但是只返回文件描述符,其他进程看不到,所以只能用于有亲缘关系的进程

匿名管道

  • 管道是阻塞的:有写者的时候,读者会阻塞读
#include <unistd.h>
int pipe(int pipefd[2]);
  • 参数: pid 文件描述符数组, pid[0] 读端, pid[1] 写端
  • 返回值: <0 失败

Untitled

例子:pipe实现父子进程间通信

示例代码:

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

##define BUFSIZE 1024

int main()
{
    size_t pid;
    int pd[2];
    int len;
    char buf[BUFSIZE];

    if(pipe(pd)<0)
    {
        perror("pipe()");
        exit(1);
    }

    pid = fork();
    if(pid < 0)
    {
        perror("fork()");
        exit(1);
    }

    if(pid == 0)  //child read
    {
        close(pd[1]);
        len = read(pd[0],buf,BUFSIZE);
        write(1,buf,len);
        close(pd[0]);
        exit(0);
    }
    else        //parent write
    {
        close(pd[0]);
        write(pd[1], "Hello", 6);
        close(pd[1]);
        wait(NULL);
        exit(0);
    }
}

运行结果:

Untitled

项目相关:

父进程做好管道,子进程 exec 变成播放器,然后父子进程通过管道通信

Untitled

mpg123 [ options ] file-or-URL...or -

sudo apt install mpg123man mpg123

mpg123 歌曲名 :播放歌曲, ‘-’表示从标准输入读取内容播放

Untitled

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

##define BUFSIZE 1024

int main()
{
    size_t pid;
    int pd[2];
    int len;
    char buf[BUFSIZE];

    if(pipe(pd)<0)
    {
        perror("pipe()");
        exit(1);
    }

    pid = fork();
    if(pid < 0)
    {
        perror("fork()");
        exit(1);
    }

    if(pid == 0)  //child read
    {
        close(pd[1]); //关闭写d端
        dup2(pd[0],0); //重定向读端到标准输入
        close(pd[0]);
        fd = open("/dev/null",O_RDWR);
        dup2(fd,1);
        dup2(fd,2);

        execl("/usr/bin/mpg123", "mpg123", "-", NULL);
        perror("execl()");
        exit(1);
    }
    else        //parent write
    {
        close(pd[0]); //关闭读端
        //父进程从网上收数据,往管道中x写
        close(pd[1]);
        wait(NULL);
        exit(0);
    }
}
ub

命名管道

mkfifo 命令:创建管道文件

阻塞:没凑齐读写双方时会阻塞

Untitled

Untitled

2. IPC:XSI → SysV

ipcs 命令:展现机制

key : ftok()

Message Queues :xxxget() xxxop()[包括cv和snd] xxxctl()

Semaphore Arrays

Shared Memory

Untitled

常规的操作流程:

  1. 创建实例: ftok() xxxget()
  2. 操作: xxxop()
  3. 删除实例: xxxctl()

Message Queues

Untitled

示例:消息队列实现非亲缘关系进程间通信

示例代码:

proto.h

##ifndef PROTO_H__
##define PROTO_H__

##define KEYPATH     "/etc/services"
##define KEYPROJ     'g'

##define NAMESIZE    32

struct msg_st
{
    long mtype; //msgrcv中对传输数据包规定要加
    char name[NAMESIZE];
    int math;
    int chinese;
};

##endif

snder.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>

#include"proto.h"

int main()
{
    key_t key;
    int msgid;
    struct msg_st sbuf;

    key = ftok(KEYPATH,KEYPROJ);
    if(key < 0)
    {
        perror("ftok()");
        exit(1);
    }

    //后运行的一方只需要拿到对应的msgid即可
    msgid = msgget(key,0);
    if(msgid < 0)
    {
        perror("msgget()");
        exit(1);
    }

    sbuf.mtype = 1;
    strcpy(sbuf.name, "Alan");
    sbuf.math = rand()%100;
    sbuf.chinese = rand()%100;
    if(msgsnd(msgid,&sbuf, sizeof(sbuf)-sizeof(long), 0) < 0)
    {
        perror("msgsnd()");
        exit(1);
    }

    puts("ok");
    //没创建,所以不用销毁:msgctl();

    exit(0);
}

rcver.h

#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include"proto.h"

int main()
{
    key_t key;
    int msgid;
    struct msg_st rbuf;

    key = ftok(KEYPATH,KEYPROJ);
    if(key < 0)
    {
        perror("ftok()");
        exit(1);
    }

    //主动方:创建,让机制运行起来
    msgid = msgget(key, IPC_CREAT|0600);
    if(msgid < 0)
    {
        perror("msgget()");
        exit(1);
    }

    while(1)
    {
        if(msgrcv(msgid,&rbuf,sizeof(rbuf)-sizeof(long),0,0) < 0)
        {
            perror("msgce()");
            exit(1);
        }
        printf("NAME = %s\n",rbuf.name);
        printf("MATH = %d\n",rbuf.math);
        printf("CHINESE = %d\n",rbuf.chinese);
    }

    msgctl(msgid, IPC_RMID, NULL);

    exit(0);
}

运行结果:

运行 ./rcver 后的结果

运行 ./rcver 后的结果

Untitled

注意点:

  1. 即使先发送,后接收,也能收到之前发送的内容,说明msg有缓冲机制,缓冲区的大小可以通过 ulimit -a 查看

    Untitled

  2. 程序是异常终止,msg没有被销毁,如何解决

    1. 程序中加入信号和信号处理函数,在信号处理函数中关闭msg
    2. ipcrm -q msgidipcrm -q 0

    Untitled

例子2:实现ftp

Untitled

状态机编程

状态机编程

协议数据设计:

方式1:

##ifndef PROTO_H__
##define PROTO_H__

##define KEYPATH     "/etc/services"
##define KEYPROJ     'a'

##define PATHMAX     1024
##define DATAMAX     1024

enum
{
    MSG_PATH=1,
    MSG_DATA,
    MSG_EOT
};

//S端收到一种包
typedef struct msg_path_st
{
    long mtype;         //must be MSG_PATH
    char path[PATHMAX]; //ASCIIZ带尾0的串
}msg_path_t;

//C端收到两种包
typedef struct msg_data_st
{
    long mtype;         //must be MSG_DATA
    char data[DATAMAX];
    int datalen;
}msg_data_t;

typedef struct msg_eot_st
{
    long mtype;         //must be MSG_EOT
}msg_eot_t;

//用union将两个包综合
//long mytpe实际不存在,用来提取公因子,方便判断类型
union msg_s2c_un
{
    long mtype;
    msg_data_t datamsg;
    msg_eot_t eotmsg;
}

##endif

方式2:

##ifndef PROTO_H__
##define PROTO_H__

##define KEYPATH     "/etc/services"
##define KEYPROJ     'a'

##define PATHMAX     1024
##define DATAMAX     1024

enum
{
    MSG_PATH=1,
    MSG_DATA,
    MSG_EOT
};

//S端收到一种包
typedef struct msg_path_st
{
    long mtype;         //must be MSG_PATH
    char path[PATHMAX]; //ASCIIZ带尾0的串
}msg_path_t;

//C端收到两种包,合并为一种结构体
//没第一种做法好,只适用于eot中只用mtype的情况
typedef struct msg_s2c_st
{
    long mtype;         //must be MSG_DATA or MSG_EOT
    char data[DATAMAX];
    int datalen;
    /*
     * datalen > 0 :data
     *         < 0 :eot
     */
}msg_s2c_t;

##endif

Semaphore Arrays

Untitled

信号量数组的必要性:

  1. 银行家算法中,AB两个信号量要同时被分配,防止死锁
  2. 两个进程逆序申请两个锁的时候,操作要是原子的
  3. 本质就是把几个信号量的操作原子化

Untitled

例子:重构 lockf,二十个进程同步往 /tmp/out 加1

示例代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<wait.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<errno.h>

##define PROCNUM     20
##define FILENAME    "/tmp/out"
##define LINESIZE     1024

int semid;

static void P(void)
{
    struct sembuf op[1];
    
    op[0].sem_num = 0;
    op[0].sem_op = -1;
    op[0].sem_flg = 0;

    while(semop(semid,op,1) < 0)
    {
        if(errno != EINTR || errno != EAGAIN)
        {
            perror("semop()");
            exit(1);
        }
    }
}

static void V(void)
{
    struct sembuf op[1];

    op[0].sem_num = 0;
    op[0].sem_op = 1;
    op[0].sem_flg = 0;

    if(semop(semid,op,1)<0)
    {
        perror("semop()");
        exit(1);
     }
}

static void func_add(void)
{
    FILE *fp;
    int fd;
    char linebuf[LINESIZE];

    fp = fopen(FILENAME, "r+");
    if(fp == NULL)
    {
        perror("fopen()");
        exit(1);
    }

    P();
    fgets(linebuf, LINESIZE, fp);
    fseek(fp, 0, SEEK_SET);
    //sleep(1);
    fprintf(fp, "%d\n",atoi(linebuf)+1);
    fflush(fp); //文件全缓冲
    V();

    fclose(fp);
}

int main()
{
    int i,err;
    size_t pid;
    //key_t key;

    //key = ftok();
    //1.创建:由于父子进程共享semid
    //所以不需要用key生成。1为数组元素
    semid = semget(IPC_PRIVATE,1,0600);
    if(semid<0)
    {
        perror("semget()");
        exit(1);
    }

    //2.初始化:设置下标为0的元素值为1
    if(semctl(semid,0,SETVAL,1) < 0)
    {
        perror("semctl");
        exit(1);
    }

    for(i = 0; i < PROCNUM; i++)
    {
        pid = fork();
        if(pid < 0)
        {
            perror("fork()");
            exit(1);
        }
        if(pid == 0)
        {
            func_add();
            exit(0);
        }
    }
    
    for(i = 0; i < PROCNUM; i++)
        wait(NULL);
    //3.移除:既然要销毁,0不当下标了
    semctl(semid,0,IPC_RMID);

    exit(0);
}

运行结果:

Untitled

Shared Memory

Untitled

例子:父子进程通信,子写父读

示例代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/wait.h>
#include<string.h>

##define MEMSIZE 1024
int main()
{
    pid_t pid;
    int shmid;
    char *ptr;

    //ftok(); 有亲缘关系,不需要
    shmid = shmget(IPC_PRIVATE,MEMSIZE ,0600);
    if(shmid < 0)
    {
        perror("shmif()");
        exit(1);
    }

    pid = fork();
    if(pid < 0)
    {
        perror("fork()");
        exit(1);
    }

    if(pid == 0)  //child write
    {
        ptr = shmat(shmid,NULL,0);
        if(ptr == (void*)-1)
        {
            perror("shmat()");
            exit(1);
        }
        strcpy(ptr, "Hello World");
        shmdt(ptr); //解除映射
        exit(0);
    }
    else          //parent read
    {
        wait(NULL);
        ptr = shmat(shmid, NULL,0);
        if(ptr == (void*)-1)
        {
            perror("shmat()");
            exit(1);
        }
        puts(ptr);
        shmdt(ptr);

        shmctl(shmid, IPC_RMID, NULL); //销毁实例
        exit(0);
    }

    exit(0);
}

运行结果:

Untitled

3. 网络套接字socket

Untitled

Untitled

socket是什么:向下给各层数据传输协议,向上给两种传输方式(数据报、流式)封装出的接口

Untitled

使用 socket 的方式

Untitled

Untitled

用domain协议族里的协议protocol,实现type类型的传输。返回的是文件描述符,当文件来操作。

udp

Untitled

struct sockadd 实际上不存在,需要 man 7 ip 查看 socket format 得到对应的结构体

单播

例子1:基本使用

示例代码:

  • proto.h【规定数据报格式】

    #ifndef PROTO_H__
    #define PROTO_H__
    
    #define RCVPORT "1989"   //封装端口(最好>1024)
    
    #define NAMESIZE    11
    
    struct msg_st
    {
        uint8_t name[NAMESIZE];
        uint32_t math;
        uint32_t chinese;
    }__attribute__((packed)); //告诉编译器不对齐
    
    #endif
    
  • recver.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<unistd.h>
    
    #include"proto.h"
    
    #define IPSTRSIZE 20
    
    int main()
    {
        int sd;
        struct sockaddr_in localaddr, remoteaddr; //具体是什么结构体取决于协议
        //看手册 man 7 xx(proto) 里的 Address_format
        struct msg_st rbuf;
        socklen_t remoteaddr_len;
        char ipstr[IPSTRSIZE];
    
    		//创建socket
        sd = socket(AF_INET,SOCK_DGRAM,0/*IPPROTO_UDP*/);//0表示默认协议
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
    
        localaddr.sin_family = AF_INET;
        localaddr.sin_port = htons(atoi(RCVPORT));
        inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr); //0.0.0.0在绑定阶段会绑定自身ip
    		//取得地址
        if(bind(sd,(void*)&localaddr,sizeof(localaddr)) < 0) //void *百搭
        {
            perror("bind()");
            exit(1);
        }
    
        /*!!!!*/
        remoteaddr_len = sizeof(remoteaddr);
    
        while(1)
        {
            recvfrom(sd,&rbuf,sizeof(rbuf),0,(void*)&remoteaddr,&remoteaddr_len);
            inet_ntop(AF_INET, &remoteaddr.sin_addr, ipstr, IPSTRSIZE);
            printf("----MESSAGE FROM %s:%d------\n", ipstr, ntohs(remoteaddr.sin_port));
            printf("NAME = %s\n",rbuf.name);
            printf("MATH = %d\n",ntohl(rbuf.math));
            printf("CHINESE = %d\n",ntohl(rbuf.chinese));
        }
    
        close(sd);
    
        exit(0);
    }
    
  • snder.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<unistd.h>
    
    #include"proto.h"
    
    int main(int argc, char **argv) //传ip地址
    {
        if(argc < 2)
        {
            fprintf(stderr, "Usage:...\n");
            exit(1);
        }
    
        int sd;
        struct msg_st sbuf;
        struct sockaddr_in raddr;
    
        sd = socket(AF_INET,SOCK_DGRAM,0);
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
        //bind();
        
        strcpy(sbuf.name, "Alan");
        sbuf.math = htonl(rand()%100);
        sbuf.chinese = htonl(rand()%100);
    
        raddr.sin_family = AF_INET;
        raddr.sin_port = htons(atoi(RCVPORT));
        inet_pton(AF_INET, argv[1], &raddr.sin_addr);
    
        if(sendto(sd,&sbuf, sizeof(sbuf), 0, (void*)&raddr, sizeof(raddr)) < 0)
        {
            perror("sendto()");
            exit(1);
        }
        puts("OK");
    
        close(sd);
        exit(0);
    }
    

运行结果:

运行recver.c后,执行 **** 查看udp套接字使用情况

运行recver.c后,执行 netstat -anu 查看udp套接字使用情况

Untitled

发送端没有bind端口任意指定

Untitled

例子二:发送变长数组

思路: proto改变变长数组,发送的时候malloc申请内存,接收的时候按最大空间接收

示例代码:

  • proto.h

    #ifndef PROTO_H__
    #define PROTO_H__
    
    #define RCVPORT "1989"   //封装端口(最好>1024)
    
    #define NAMEMAX    (512-8-8)
    
    struct msg_st
    {
        uint32_t math;
        uint32_t chinese;
        uint8_t name[1]; //变长数组
    }__attribute__((packed)); //告诉编译器不对齐
    
    #endif
    
  • snder.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<unistd.h>
    
    #include"proto.h"
    
    int main(int argc, char **argv) //传ip地址
    {
        if(argc < 3)
        {
            fprintf(stderr, "Usage:...\n");
            exit(1);
        }
    
        int sd;
        int size;
        struct msg_st *sbufp;
        struct sockaddr_in raddr;
    
        size = sizeof(struct msg_st)+strlen(argv[2]);
        sbufp = malloc(size);
    
        if(sbufp == NULL)
        {
            perror("malloc()");
            exit(1);
        }
    
        sd = socket(AF_INET,SOCK_DGRAM,0);
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
        //bind();
        if(strlen(argv[2]) > NAMEMAX)
        {
            fprintf(stderr, "NAME is too long\n");
            exit(1);
        }
    
        strcpy(sbufp->name, argv[2]);
        sbufp->math = htonl(rand()%100);
        sbufp->chinese = htonl(rand()%100);
    
        raddr.sin_family = AF_INET;
        raddr.sin_port = htons(atoi(RCVPORT));
        inet_pton(AF_INET, argv[1], &raddr.sin_addr);
    
        if(sendto(sd,sbufp, size, 0, (void*)&raddr, sizeof(raddr)) < 0)
        {
            perror("sendto()");
            exit(1);
        }
        puts("OK");
    
        close(sd);
        free(sbufp);
        exit(0);
    }
    
  • recver.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<unistd.h>
    
    #include"proto.h"
    
    #define IPSTRSIZE 20
    
    int main()
    {
        int sd;
        int size;
        struct sockaddr_in localaddr, remoteaddr; //具体是什么结构体取决于协议
        //看手册 man 7 xx(proto) 里的 Address_format
        struct msg_st *rbufp;
        socklen_t remoteaddr_len;
        char ipstr[IPSTRSIZE];
    
        sd = socket(AF_INET,SOCK_DGRAM,0/*IPPROTO_UDP*/);//0表示默认协议
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
    
        size = sizeof(struct msg_st) + NAMEMAX - 1;
        rbufp = malloc(size);
        if(rbufp == NULL)
        {
            perror("malloc()");
            exit(1);
        }
        localaddr.sin_family = AF_INET;
        localaddr.sin_port = htons(atoi(RCVPORT));
        inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr); //0.0.0.0在绑定阶段会绑定自身ip
    
        if(bind(sd,(void*)&localaddr,sizeof(localaddr)) < 0) //void *百搭
        {
            perror("bind()");
            exit(1);
        }
    
        /*!!!!*/
        remoteaddr_len = sizeof(remoteaddr);
    
        while(1)
        {
            recvfrom(sd,rbufp,size,0,(void*)&remoteaddr,&remoteaddr_len);
            inet_ntop(AF_INET, &remoteaddr.sin_addr, ipstr, IPSTRSIZE);
            printf("----MESSAGE FROM %s:%d------\n", ipstr, ntohs(remoteaddr.sin_port));
            printf("NAME = %s\n",rbufp->name);
            printf("MATH = %d\n",ntohl(rbufp->math));
            printf("CHINESE = %d\n",ntohl(rbufp->chinese));
        }
    
        close(sd);
    
        exit(0);
    }
    

运行结果:

Untitled

广播

Untitled

多播与广播需要设置:用到函数 setsockopt

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

这个函数横跨ip,tcp,udp, level 指定协议, optname 是具体设置的项目, optval 是参数, optlen 是参数的sizeof。

leveloptname 从哪里获取? man 7 ip/tcp/udp 查看 Socket Option 即可。

比如 man 7 udp 查看 socket Option,可知, level == SOL_SOCKET,打开广播的选项 opt == SO_BROADCAST

Untitled

Untitled

Untitled

Untitled

示例代码:

  • snder.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<unistd.h>
    
    #include"proto.h"
    
    int main(int argc, char **argv) //传ip地址
    {
    
        int sd;
        struct msg_st sbuf;
        struct sockaddr_in raddr;
    
        sd = socket(AF_INET,SOCK_DGRAM,0);
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
        
        //打开广播选项
        int val = 1;
        if(setsockopt(sd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(val)) < 0)
        {
            perror("setsockopt()");
            exit(1);
        }
    
        strcpy(sbuf.name, "Alan");
        sbuf.math = htonl(rand()%100);
        sbuf.chinese = htonl(rand()%100);
    
        raddr.sin_family = AF_INET;
        raddr.sin_port = htons(atoi(RCVPORT));
        inet_pton(AF_INET, "255.255.255.255", &raddr.sin_addr); //全网广播
    
        if(sendto(sd,&sbuf, sizeof(sbuf), 0, (void*)&raddr, sizeof(raddr)) < 0)
        {
            perror("sendto()");
            exit(1);
        }
        puts("OK");
    
        close(sd);
        exit(0);
    }
    
  • recver.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<unistd.h>
    
    #include"proto.h"
    
    #define IPSTRSIZE 20
    
    int main()
    {
        int sd;
        struct sockaddr_in localaddr, remoteaddr; //具体是什么结构体取决于协议
        //看手册 man 7 xx(proto) 里的 Address_format
        struct msg_st rbuf;
        socklen_t remoteaddr_len;
        char ipstr[IPSTRSIZE];
    
        sd = socket(AF_INET,SOCK_DGRAM,0/*IPPROTO_UDP*/);//0表示默认协议
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
    
        int val = 1;
        if(setsockopt(sd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(val)) < 0)
        {
            perror("setsockopt()");
            exit(1);
        }
    
        localaddr.sin_family = AF_INET;
        localaddr.sin_port = htons(atoi(RCVPORT));
        inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr); //0.0.0.0在绑定阶段会绑定自身ip
    
        if(bind(sd,(void*)&localaddr,sizeof(localaddr)) < 0) //void *百搭
        {
            perror("bind()");
            exit(1);
        }
    
        /*!!!!*/
        remoteaddr_len = sizeof(remoteaddr);
    
        while(1)
        {
            recvfrom(sd,&rbuf,sizeof(rbuf),0,(void*)&remoteaddr,&remoteaddr_len);
            inet_ntop(AF_INET, &remoteaddr.sin_addr, ipstr, IPSTRSIZE);
            printf("----MESSAGE FROM %s:%d------\n", ipstr, ntohs(remoteaddr.sin_port));
            printf("NAME = %s\n",rbuf.name);
            printf("MATH = %d\n",ntohl(rbuf.math));
            printf("CHINESE = %d\n",ntohl(rbuf.chinese));
        }
    
        close(sd);
    
        exit(0);
    }
    

运行结果:

Untitled

多播

实现在ip层, man 7 ipSocket Option

发送方建立多播组,接受方如果要接受该多播组,就加入,所以协议(proto.h)中要加入多播组的字段

示例代码:

  • proto.h

    #ifndef PROTO_H__
    #define PROTO_H__
    
    #define MTROUP  "224.2.2.2" //多播:D类地址
    #define RCVPORT "1989"   //封装端口(最好>1024)
    
    #define NAMESIZE    11
    
    struct msg_st
    {
        uint8_t name[NAMESIZE];
        uint32_t math;
        uint32_t chinese;
    }__attribute__((packed)); //告诉编译器不对齐
    
    #endif
    
  • snder.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<unistd.h>
    #include<net/if.h>
    
    #include"proto.h"
    
    int main(int argc, char **argv) //传ip地址
    {
    
        int sd;
        struct msg_st sbuf;
        struct sockaddr_in raddr;
    
        sd = socket(AF_INET,SOCK_DGRAM,0);
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
        
        //打开广播选项
        struct ip_mreqn mreq;
        inet_pton(AF_INET,MTROUP,&mreq.imr_multiaddr);
        inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);
        mreq.imr_ifindex = if_nametoindex("eth0");//网络设备名转网络索引号
        if(setsockopt(sd,IPPROTO_IP,IP_MULTICAST_IF,&mreq,sizeof(mreq)) < 0)
        {
            perror("setsockopt()");
            exit(1);
        }
    
        strcpy(sbuf.name, "Alan");
        sbuf.math = htonl(rand()%100);
        sbuf.chinese = htonl(rand()%100);
    
        raddr.sin_family = AF_INET;
        raddr.sin_port = htons(atoi(RCVPORT));
        inet_pton(AF_INET, MTROUP, &raddr.sin_addr); //全网广播
    
        if(sendto(sd,&sbuf, sizeof(sbuf), 0, (void*)&raddr, sizeof(raddr)) < 0)
        {
            perror("sendto()");
            exit(1);
        }
        puts("OK");
    
        close(sd);
        exit(0);
    }
    
  • recver.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<unistd.h>
    #include<net/if.h>
    
    #include"proto.h"
    
    #define IPSTRSIZE 20
    
    int main()
    {
        int sd;
        struct sockaddr_in localaddr, remoteaddr; //具体是什么结构体取决于协议
        //看手册 man 7 xx(proto) 里的 Address_format
        struct msg_st rbuf;
        socklen_t remoteaddr_len;
        char ipstr[IPSTRSIZE];
    
        sd = socket(AF_INET,SOCK_DGRAM,0/*IPPROTO_UDP*/);//0表示默认协议
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
    
        //加入多播组
        struct ip_mreqn mreq;
        inet_pton(AF_INET,MTROUP,&mreq.imr_multiaddr);
        inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);
        mreq.imr_ifindex = if_nametoindex("eth0");
    
        if(setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq)) < 0)
        {
            perror("setsockopt()");
            exit(1);
        }
    
        localaddr.sin_family = AF_INET;
        localaddr.sin_port = htons(atoi(RCVPORT));
        inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr); //0.0.0.0在绑定阶段会绑定自身ip
    
        if(bind(sd,(void*)&localaddr,sizeof(localaddr)) < 0) //void *百搭
        {
            perror("bind()");
            exit(1);
        }
    
        /*!!!!*/
        remoteaddr_len = sizeof(remoteaddr);
    
        while(1)
        {
            recvfrom(sd,&rbuf,sizeof(rbuf),0,(void*)&remoteaddr,&remoteaddr_len);
            inet_ntop(AF_INET, &remoteaddr.sin_addr, ipstr, IPSTRSIZE);
            printf("----MESSAGE FROM %s:%d------\n", ipstr, ntohs(remoteaddr.sin_port));
            printf("NAME = %s\n",rbuf.name);
            printf("MATH = %d\n",ntohl(rbuf.math));
            printf("CHINESE = %d\n",ntohl(rbuf.chinese));
        }
    
        close(sd);
    
        exit(0);
    }
    

运行结果:

Untitled

查看网络设备名称和编号 ip ad sh

Untitled

注意: 224.0.0.1 所有支持多播的结点,都默认在这个组中,且无法离开,相当于 255.255.255.255 (广播)

tcp

Untitled

struct sockadd 实际上不存在,需要 man 7 ip 查看 socket format 得到对应的结构体

单进程

示例代码:

  • proto.h

    #ifndef PROTO_H__
    #define PROTO_H__
    
    #define SERVERPORT "1989"
    
    #define FMT_STAMP "%lld\r\n"
    
    #endif
    
  • server.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<time.h>
    #include<unistd.h>
    
    #include"proto.h"
    
    #define IPSTRSIZE 40
    #define BUFSIZE   1024
    
    static void server_job(int sd)
    {
        int len;
        char buf[BUFSIZE];
        len = sprintf(buf,FMT_STAMP,(long long)time(NULL));
        if(send(sd,buf,len,0) < 0)
        {
            perror("sen()");
            exit(1);
        }
    
    }
    
    int main()
    {
        int sd,newsd;
        struct sockaddr_in laddr,raddr;
        socklen_t raddr_len;
        char ipstr[IPSTRSIZE];
    
        sd = socket(AF_INET,SOCK_STREAM,0/*IPPROTO_TCP*/);
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
    
        laddr.sin_family = AF_INET;
        laddr.sin_port = htons(atoi(SERVERPORT));
        inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
    
        if(bind(sd,(void*)&laddr,sizeof(laddr)) < 0)
        {
            perror("bind()");
            exit(1);
        }
    
        if(listen(sd,200)<0)//200全连接上限
        {
            perror("listen()");
            exit(1);
        }
    
        raddr_len = sizeof(raddr);
        while(1)
        {
            newsd = accept(sd,(void*)&raddr,&raddr_len);
            if(newsd < 0)
            {
                perror("accept()");
                exit(1);
            }
            inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
            printf("Client:%s:%d\n",ipstr,ntohs(raddr.sin_port));
            
            server_job(newsd); //send()
            close(newsd);
        }
        close(sd);
    
        exit(0);
    }
    
  • client.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    
    #include"proto.h"
    
    int main(int argc, char **argv)
    {
        int sd;
        struct sockaddr_in raddr;
        long long stamp;
        FILE *fp;
    
        if(argc < 2)
        {
            fprintf(stderr,"Usage:...\n");
            exit(1);
        }
    
        sd = socket(AF_INET,SOCK_STREAM,0);
        if(sd < 0)
        {
            perror("bind()");
            exit(1);
        }
        //bind();
        
        raddr.sin_family = AF_INET;
        raddr.sin_port =  htons(atoi(SERVERPORT));
        inet_pton(AF_INET,argv[1],&raddr.sin_addr); //server ip
        
        if(connect(sd,(void*)&raddr,sizeof(raddr)) < 0)
        {
            perror("connet()");
            exit(1);
        }
    
        //一切皆文件:socket转为stream操作
        fp = fdopen(sd,"r+");
        if(fp == NULL)
        {
            perror("fdopen()");
            exit(1);
        }
        
        if(fscanf(fp, FMT_STAMP, &stamp) < 1) //返回成功匹配的个数
            fprintf(stderr,"Bad format!\n");
        else
            fprintf(stdout,"stamp = %lld\n",stamp);
    
        fclose(fp);
    
        //rcve()
        //close()
    
        exit(0);
    }
    

运行结果:

./server

./server

 或  发送请求

nc ip porttelnet ip port 发送请求

Untitled

注意点查看多线程部分。

多进程

示例代码:

  • server.c 其他不变

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<time.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/wait.h>
    
    #include"proto.h"
    
    #define IPSTRSIZE 40
    #define BUFSIZE   1024
    
    static void server_job(int sd)
    {
        int len;
        char buf[BUFSIZE];
    
        len = sprintf(buf,FMT_STAMP,(long long)time(NULL));
        if(send(sd,buf,len,0) < 0)
        {
            perror("sen()");
            exit(1);
        }
    
    }
    
    int main()
    {
        int sd,newsd;
        struct sockaddr_in laddr,raddr;
        socklen_t raddr_len;
        char ipstr[IPSTRSIZE];
        pid_t pid;
    
        sd = socket(AF_INET,SOCK_STREAM,0/*IPPROTO_TCP*/);
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
    
        int val=1;
        if(setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val)) < 0)
        {
            perror("setsockopt()");
            exit(1);
        }
        
    
        laddr.sin_family = AF_INET;
        laddr.sin_port = htons(atoi(SERVERPORT));
        inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
    
        if(bind(sd,(void*)&laddr,sizeof(laddr)) < 0)
        {
            perror("bind()");
            exit(1);
        }
    
        if(listen(sd,200)<0)//200全连接上限
        {
            perror("listen()");
            exit(1);
        }
    
        raddr_len = sizeof(raddr);
        while(1)
        {
           // printf("****before accept****\n");
            newsd = accept(sd,(void*)&raddr,&raddr_len);
           // printf("****after accept****\n");
            if(newsd < 0)
            {
                perror("accept()");
                exit(1);
            }
    
            pid = fork();
            if(pid < 0)
            {
                perror("fork()");
                exit(1);
            }
            if(pid == 0)
            {
                close(sd);
                inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
                printf("Client:%s:%d\n",ipstr,ntohs(raddr.sin_port));
    
                server_job(newsd); //send()
                close(newsd);
                exit(0);
            }
           // printf("****end*****\n");
            close(newsd); //!!!
        }
        close(sd);
    
        exit(0);
    }
    

运行结果:

Untitled

  1. 进程阻塞在 accept ,因为 accept 互斥访问和阻塞

如果父进程调用了 accept 产生了 newsd ,如果不关闭 newsd 会导致 accept 认为该上次的传输没有结束,会继续等待上一个连接结束,导致新的传输阻塞,这提醒我们 accept 得到的文件描述符要及时关闭

Untitled

  1. server 异常结束时,端口未释放,再次执行时无法立刻重新绑定端口, bind() 抛异常

    Untitled

     查看,端口未释放,无法立即绑定

    netstat -anu 查看,端口未释放,无法立即绑定

解决:设置 sockoptman 7 socket 查看socket层的表示,找到对应命令,执行 setsockopt()

setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))

Untitled

pool_static:静态进程池

思路:预先创建四个进程,不断接收信息

示例代码:

  • server.c 其他不变

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<time.h>
    #include<unistd.h>
    #include<sys/wait.h>
    
    #include"proto.h"
    
    #define IPSTRSIZE 40
    #define BUFSIZE   1024
    #define PROCNUM   4
    
    static void server_loop(int sd);
    static void server_job(int sd)
    {
        int len;
        char buf[BUFSIZE];
        len = sprintf(buf,FMT_STAMP,(long long)time(NULL));
        if(send(sd,buf,len,0) < 0)
        {
            perror("send()");
            exit(1);
        }
    
    }
    
    int main()
    {
        int sd,i;
        struct sockaddr_in laddr;
        pid_t pid;
    
        sd = socket(AF_INET,SOCK_STREAM,0/*IPPROTO_TCP*/);
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
    
        int val=1;
        if(setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val)) < 0)
        {
            perror("setsockopt()");
            exit(1);
        }
    
        laddr.sin_family = AF_INET;
        laddr.sin_port = htons(atoi(SERVERPORT));
        inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
    
        if(bind(sd,(void*)&laddr,sizeof(laddr)) < 0)
        {
            perror("bind()");
            exit(1);
        }
    
        if(listen(sd,200)<0)//200全连接上限
        {
            perror("listen()");
            exit(1);
        }
        
        for(i = 0; i < PROCNUM; i++)
        {
            pid = fork();
            if(pid < 0)
            {
                perror("fork()");
                exit(1);
            }
            if(pid == 0)
            {
                server_loop(sd);
                exit(0);
            }
        }
        
        for(i = 0; i < PROCNUM; i++)
            wait(NULL);
        close(sd); //!!!
        exit(0);
    }
    
    static void server_loop(int sd)
    {
        struct sockaddr_in raddr;
        socklen_t raddr_len;
        int newsd;
        char ipstr[IPSTRSIZE];
    
        raddr_len = sizeof(raddr);
        while(1)
        {
            //accept本身实现了互斥和阻塞,所以不用互斥量或信号量数组或条件变量
            newsd = accept(sd,(void*)&raddr,&raddr_len);
            if(newsd < 0)
            {
                perror("accept()");
                exit(1);
            }
            inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
            printf("[%d]Client:%s:%d\n",getpid(),ipstr,ntohs(raddr.sin_port));
            
            server_job(newsd); //send()
            close(newsd);
        }
    }
    

运行结果:

Untitled

缺点:无法应对突发请求,没有弹性

pool_dynamic:动态进程池

  • server.c其他不变

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<signal.h>
    #include<errno.h>
    #include<unistd.h>
    #include<sys/mman.h>
    #include<time.h>
    
    #include"proto.h"
    
    #define MINSPARESERVER 5
    #define MAXSPARESERVER 10
    #define MAXCLIENTS     20
    
    #define SIG_NOTIFY     SIGUSR2
    #define IPSTRSIZE      40
    #define LINEBUFSIZE    80
    
    enum
    {
        STATE_IDLE=0,
        STATE_BUSY
    };
    
    struct server_st
    {
        pid_t pid;
        int state;
    //  int reuse;
        
    };
    
    static struct server_st *serverpool;
    static int idle_count = 0, busy_count = 0;
    static int sd;
    
    static void usr2_handler(int s)
    {
        return;
    }
    
    static void server_job(int pos)
    {
        pid_t ppid;
        struct sockaddr_in raddr;
        socklen_t raddr_len,len;
        int client_sd;
        time_t stamp;
        char ipstr[IPSTRSIZE];
        char linebuf[LINEBUFSIZE];
    
        ppid = getppid();
        while(1)
        {
            serverpool[pos].state = STATE_IDLE;
            kill(ppid,SIG_NOTIFY);//发信号通知父进程
    
            client_sd = accept(sd,(void*)&raddr,&raddr_len);
            if(client_sd < 0)
            {
                if(errno != EINTR && errno != EAGAIN)
                {
                    perror("accpet()");
                    exit(1);
                }
            }
            serverpool[pos].state = STATE_BUSY;
            kill(ppid,SIG_NOTIFY);
            //inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
            //printf("[%d]client:%s:%d\n",getpid(),ipstr,ntohs(raddr.sin_port));
            stamp = time(NULL);
            len = snprintf(linebuf, LINEBUFSIZE, FMT_STAMP,stamp);
            send(client_sd, linebuf, len, 0);
            /*if error*/
            sleep(5);
            close(client_sd);
        }
    }
    
    static int add_1_server(void)
    {
        int slot;
        pid_t pid;
    
        if(idle_count + busy_count >= MAXCLIENTS)
            return -1;
        for(slot = 0; slot < MAXCLIENTS; slot++)
        {
            if(serverpool[slot].pid == -1)
                break;
        }
        serverpool[slot].state = STATE_IDLE;
        pid = fork();
        if(pid < 0)
        {
            perror("fork()");
            exit(1);
        }
        if(pid == 0)    //child
        {
            server_job(slot);
            exit(0);
        }   
        else            //parent
        {
            serverpool[slot].pid = pid;
            idle_count++;
        }
        return 0;
    }
    
    static int del_1_server(void)
    {
        int i;
    
        if(idle_count == 0)
            return -1;
        for(i=0; i < MAXCLIENTS; i++)
        {
            if(serverpool[i].pid != -1 && serverpool[i].state == STATE_IDLE)
            {
                kill(serverpool[i].pid, SIGTERM);
                serverpool[i].pid = -1;
                idle_count--;
                break;
            }
        }
        return 0;
    }
    
    static int scan_pool(void)
    {
        int i;
        int busy = 0, idle = 0;
        for(i = 0; i < MAXCLIENTS; i++)
        {
            if(serverpool[i].pid == -1)
                continue;
            if(kill(serverpool[i].pid,0))
            {
                serverpool[i].pid = -1;
                continue;
            }
            if(serverpool[i].state == STATE_IDLE)
                idle++;
            else if(serverpool[i].state == STATE_BUSY)
                busy++;
            else
            {
                fprintf(stderr,"Unknow state.\n");
               // _exit(1);
               abort();
            }
        }
        idle_count = idle;
        busy_count = busy;
        return 0;
    }
    
    int main()
    {
        struct sigaction sa,o_sa;
        struct sockaddr_in laddr;
        sigset_t set,oset;
    
        //功能:子进程会自行消亡,不用父进程收尸
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_NOCLDWAIT;
        sigaction(SIGCHLD,&sa,&o_sa);
    
        //设置SIGNOTIFY信号处理函数
        sa.sa_handler = usr2_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        sigaction(SIG_NOTIFY,&sa,&o_sa);
    
        //父进程屏蔽SIGNOTIFY
        sigemptyset(&set);
        sigaddset(&set,SIG_NOTIFY);
        sigprocmask(SIG_BLOCK,&set,&oset);
    
        serverpool = mmap(NULL,sizeof(struct server_st)*MAXCLIENTS,PROT_READ|
                PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);//malloc
        if(serverpool == MAP_FAILED)
        {
            perror("mmap()");
            exit(1);
        }
    
        int i;
        for(i = 0; i < MAXCLIENTS; i++)
        {
            serverpool[i].pid = -1;
        }
    
        sd = socket(AF_INET,SOCK_STREAM,0);
        if(sd < 0)
        {
            perror("socket()");
            exit(1);
        }
    
        int val = 1;
        if(setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val)) < 0)
        {
            perror("setsockopt()");
            exit(1);
        }
    
        laddr.sin_family = AF_INET;
        laddr.sin_port = htons(atoi(SERVERPORT));
        inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
        if(bind(sd,(void*)&laddr,sizeof(laddr)) < 0)
        {
            perror("bind()");
            exit(1);
        }
    
        if(listen(sd,100) < 0)
        {
            perror("listen()");
            exit(1);
        }
    
        for(i = 0; i < MINSPARESERVER; i++)
        {
            add_1_server();
        }
    
        //信号驱动程序
        while(1)
        {
            sigsuspend(&oset);
            scan_pool();
            //control the pool
            if(idle_count > MAXSPARESERVER)
            {
                for(i=0;i<(idle_count-MAXSPARESERVER);i++)
                    del_1_server();
            }
            else if(idle_count<MINSPARESERVER)
            {
                for(i=0;i<(MINSPARESERVER-idle_count);i++)
                    add_1_server();
            }
            //printf the pool state
            for(i=0; i < MAXCLIENTS; i++)
            {
                if(serverpool[i].pid == -1)
                    putchar('*');
                else if(serverpool[i].state == STATE_IDLE)
                    putchar('.');
                else
                    putchar('x');
            }
            putchar('\n');
        }
    
        sigprocmask(SIG_SETMASK,&oset,NULL); //恢复
    
       // close();
    
        exit(0);
    }
    

运行结果:

  1. * :未创建
  2. . :空闲
  3. x :busy

client:

Untitled

client*1

client*2

client*3

clietn*4

注意:

因为 server_st 数组是 mmap 出来的,所以父子进程都可见。

scan_pool 的时候更新 idle_count 计数

posted @ 2025-09-17 00:09  Miaops  阅读(17)  评论(0)    收藏  举报