C语言:进程间通信(史上最强教程)

单工通信:只支持信号在一个方向上传输,任何时候不能改变信号的传输方向。

双工通信:允许信号在两个方向上传输。


半双工通信:允许信号在两个方向上传输,但某一时刻只允许信号在一个方向上单向传输。

全双工通信:允许信号在两个方向上传输,信号可以在两个方向上同时传输。

管道

由内核提供,管道为半双工机制,自同步机制。使用广泛。(管道必须凑齐读写双方才能够运行。)

匿名管道

/home/qt/c/linux_c/ipc/pipe

看不到管道的名称,适合有亲缘关系的进程通信。

pipe.c

       int pipe(int fildes[2]);
 

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

#define BUFSIZE 1024
int main()
{
    pid_t pid;
    int ret;
    int pd[2];
    char buf[BUFSIZE];
    int len;
    ret = pipe(pd); // 0是读,1是写
    if(ret<0)
    {
        perror("pipe()");
        exit(1);
    }
    pid = fork();
    if(pid == 0)
    {
        //child read   
        close(pd[1]);
        puts("Child Begin");
        len = read(pd[0],buf,BUFSIZE); //如果父进程不写,那么会阻塞等待
        write(1,buf,len);
        close(pd[0]);
        puts("Child End");
        exit(1);
        puts("Exit");
    }
    else if(pid >0)
    {
        sleep(1);
        // parent write
        close(pd[0]);
        write(pd[1],"Hello\n",6);
        close(pd[1]);
//        wait(NULL);
        exit(1);
    }

    return 0;
}


player.c 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define BUFSIZE 1024
int main()
{
    pid_t pid;
    int ret;
    int pd[2];
    char buf[BUFSIZE];
    int len;
    ret = pipe(pd); // 0是读,1是写
    if(ret<0)
    {
        perror("pipe()");
        exit(1);
    }
    pid = fork();
    if(pid == 0)
    {
        //child read   
        close(pd[1]);
        dup2(pd[0],0); //将管道重定向到标准输入。
        close(pd[0]);
        int fd = open("/dev/null",O_RDWR);
        dup2(fd,1);//关闭文件描述符1,然后文件描述符1执行 /dev/null
        dup2(fd,2);

        execl("usr/bin/mpg123","mpg123","-",NULL);
        perror("excel");
        exit(0);
    
    }

    else if(pid >0)
    {
        close(pd[0]);
        //父进程从网上收数据,在管道中写。
        

        close(pd[1]);
        wait(NULL);
        exit(0);
    }

    return 0;
}


命名管道

mkfifo namedfifo可以创建一个命名管道,文件类型为P

可以看到管道的名称,适合非亲缘关系的进程通信。

mkfifo

       int mkfifo(const char *pathname, mode_t mode);
 

XSI ->SysV

XSI(System Interface and Headers),代表一种Unix系统的标准 XSI IPC,依托标识符和键来实现的,如同管道靠文件描述符来实现一样。包含了三种通信机制:消息队列,信号量,共享内存。

ipcs命令:可以看到 消息队列,信号量数组,共享内存的信息。

key:ftok();拿到同一个key值。

       key_t ftok(const char *pathname, int proj_id); //hash的原串+杂志。

xxxget xxxop xxxctl
xxx -> msg  sem shm

 消息队列

消息队列是全双工机制有缓存数据的能力,通过ulimit -a查看

主动端:先发包的一方。
被动端:先收包的一方。

/home/qt/c/linux_c/ipc/xsi/msg/basic

       int msgget(key_t key, int msgflg);
       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);
       int msgctl(int msqid, int cmd, struct msqid_ds *buf);

proto.h
#ifndef PROTO_H
#define PROTO_H

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>


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


struct msg_st
{
    long mtype;
    char name[NAMESIZE];
    int math;
    int chinese;

};


#endif 
snder.c
#include <stdlib.h>
#include <stdio.h>
#include "proto.h"


int main()
{
    key_t key;

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

    msgid = msgget(key,0);
    if(msgid <0)
    {
        perror("msgget");
        exit(1);
    }
    struct msg_st sbuf;
    sbuf.mtype = 1;
    sprintf(sbuf.name,"%s","hello msg");
    sbuf.math = rand()%100;
    sbuf.chinese = rand()%100;

    int len;
    len = msgsnd(msgid,&sbuf,sizeof(sbuf) - sizeof(long),0);
    if(len <0)
    {
        perror("msgsnd()");
        exit(1);
    }
    puts("OK!");
//    msgctl();

    return 0;
}
recv.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include "proto.h"


int main()
{
    key_t key;

    key = ftok(KEYPATH,KEYPROJ);
    if(key <0)
    {
        perror("ftok()");
    }
    int msgid;
    msgid = msgget(key,IPC_CREAT|0600 );
    if(msgid<0)
    {
        perror("msgget");
    }
    struct msg_st rbuf;
    int len;
    while(1)
    {
        len = msgrcv(msgid,&rbuf,sizeof(rbuf)-sizeof(long),0,0 );//第三个参数指的是队列中包
的序号,0是顺序接收
         // len = msgrcv(msgid,&rbuf,sizeof(rbuf),0,0 ); //可以选择全部接受


        if(len <0)
        {
            perror("msgrcv");
        }
        printf("NAME  = %s \n",rbuf.name);
        printf("MATH  = %d \n",rbuf.math);
        printf("CHINESE = %d \n",rbuf.chinese);
    }
    msgctl(msgid,IPC_RMID,NULL);

    return 0;
}

命令:ipcrm 删除上述程序中产生的消息队列。ipc -q (msqid号)

消息队列ftp协议

#ifndef PROTO_H
#define PROTO_H

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>


#define KEYPATH "/etc/services"
#define KEYPROJ 'a'
#define PATHSIZE 1024
#define DATASIZE 1024
enum
{
    MSG_PATH =1,
    MSG_DATA,
    MSG_EOT
}

typedef struct msg_path_st
{
    long mtype; //must be MSG_PATH
    char path[PATHSIZE]; //ASCII带尾0的串

}msg_path_t;

typedef struct msg_data_st
{
    long mtype;
    char data[DATAMAX];
    int datalen;

}msg_data_t;

typedef struct msg_eot_st
{
    long mtype;
}msg_eot_t;

union msg_s2c_un
{
    long mtype; //提取公因子。
    msg_data_t datamsg;
    msg_eot_t  eotmsg;
};

#endif




#ifndef PROTO_H
#define PROTO_H

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>


#define KEYPATH "/etc/services"
#define KEYPROJ 'a'
#define PATHSIZE 1024
#define DATASIZE 1024
enum
{
    MSG_PATH =1,
    MSG_DATA,
    MSG_EOT
}

typedef struct msg_path_st
{
    long mtype; //must be MSG_PATH
    char path[PATHSIZE]; //ASCII带尾0的串

}msg_path_t;

typedef struct msg_s2c_st
{
    long mtype; /* 用MSG_DATA或者MSG_EOT进行判断*/
    char data[DATAMAX];
    int datalen;
    /*
    datalen >0 :data包
    datalen =0 :eot包
    */
}msg_data_t;

#endif




信号量

路径:c/linux_c/ipc/xsi/sem,P(取资源)V(还资源)操作。

       int semget(key_t key, int nsems, int semflg);
   int semop(int semid, struct sembuf *sops, size_t nsops);
       int semtimedop(int semid, struct sembuf *sops, size_t nsops,
                      const struct timespec *timeout);
       int semctl(int semid, int semnum, int cmd, ...);
 

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


#define FILENAME  "/tmp/out"
#define PROCNUM (10)

static int semid;

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

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

}

static void V()
{
    struct sembuf op;
    int ret;
    op.sem_num = 0;
    op.sem_op = 1;
    op.sem_flg = 0;
    ret = semop(semid,&op,1);
    if(ret<0)
    {
        perror("semop");
        exit(1);
    }
}

void func_add(void);

int main()
{
    pid_t pid;
    int i,j,mark;
    int err; 

    semid = semget(IPC_PRIVATE,1,0600);   //匿名ipc,用于父子进程通信
    if(semid <0)
    {
        perror("semget");
        exit(1);
    }
    int ret;
    ret = semctl(semid,0,SETVAL,1);//设置数组0下标的值为1.
    if(ret<0)
    {
        perror("semctl");
        exit(1);
    }

    for(i =0;i<PROCNUM;i++)
    {
        pid = fork();
        if(pid ==0)
        {
            func_add();
            exit(0);
        }
        if(pid >0)
        {

        }
    }

    for(i=0;i<PROCNUM;i++)
    {
        wait(NULL);
    }

    semctl(semid,0,IPC_RMID);
    return 0;
}

void func_add()
{
    FILE*fp;
    char line_buf[1024];
    int len_size = 1024;

    puts("Begin");

    fp = fopen(FILENAME,"r+");

    int fd = fileno(fp);
    P();

    fgets(line_buf,len_size,fp);
    
    fseek(fp,0,SEEK_SET);

    sleep(1);

    fprintf(fp,"%d \n",atoi(line_buf)+1);

    fflush(fp);
    V();

    fclose(fp);
    puts("End");
 }  

共享内存

  • 共享内存区域可能会被多个进程同时访问,需要使用同步机制(如互斥锁、信号量等)来防止数据竞争和不一致的情况。

       int shmget(key_t key, size_t size, int shmflg);
       void *shmat(int shmid, const void *shmaddr, int shmflg);
       int shmdt(const void *shmaddr);
       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
 

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


#define MEMSIZE 1024

int main(int argc,char*argv[])
{
    int shmid;
    pid_t pid;

    shmid = shmget(IPC_PRIVATE,MEMSIZE,0600);
    if(shmid<0)
    {
        perror("shmget");
        exit(1);
    }

    pid = fork();
    if(pid==0)
    {
        //child write
        char *ptr;
        ptr = shmat(shmid,NULL,0); //共享内存映射。
        if(ptr == (void*)-1)
        {
            perror("shmat");
            exit(1);
        }
        strcpy(ptr,"hello");
        shmdt(ptr); //解除映射
        exit(0);
    }
    if(pid >0)
    {
        wait(NULL);
        //parent read
        char *ptr;
        ptr = shmat(shmid,NULL,0);
        if(ptr == (void*)-1)
        {
            perror("shmat");
            exit(1);
        }
        puts(ptr);
        shmdt(ptr);
        shmctl(shmid,IPC_RMID,NULL);
        exit(0);
        
    }

    return 0;
}



网络套接字

网络套接字又称:socket。讨论:跨主机传输要注意的问题。

大小端问题

字节序经常被分为两类:

1. Big-Endian(大端):高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

2.Little-Endian(小端):低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

对于数据 0x12345678,假设从地址0x4000开始存放,在大端和小端模式下,存放的位置分别为:

内存地址    小端模式    大端模式
0x4003    0x12    0x78
0x4002    0x34    0x56
0x4001    0x56    0x34
0x4000    0x78    0x12
采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。

小端存储后:0x78563412  大端存储后:0x12345678

字节序问题

TCP/IP各层协议将字节序定义为Big-Endian,
因此TCP/IP协议中使用的字节序通常称之为网络字节序。

通常我们说的主机序(Host Order)就是遵循Little-Endian规则

就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。

主机字节序:host

网络字节序:network 

相互转换:_to_ _ :htons,htonl,ntohs,ntohl

数据对齐问题

结构体在传输数据时会进行内存对齐。解决方式是不进行数据对齐。

类型长度问题

long类型的大小没有指定,char有咩有符号也没有定义。int的大小在16位和32位长度不一样。

解决:int32_t ,uint32_t ,int64_t使用特定的数据类型。

SOCKET

socket是对TCP/IP协议的封装,它的出现只是使得程序员更方便地使用TCP/IP协议栈而已。socket本身并不是协议,它是应用层与TCP/IP协议族通信的中间软件抽象层,是一组调用接口(TCP/IP网络的API函数)

“TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。
  这个就像操作系统会提供标准的编程接口,比如win32编程接口一样。
  TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口

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

报式套接字

被动端:

1、取得socket
2、给socket取得地址
3、收/发消息
4、关闭socket

主动端:

1、取得socket
2、给socket取得地址(可以省略)
3、收/发消息
4、关闭socket

socket();
bind();
sendto();
recvform();
inet_pton();
inet_ntop();
getsockopt();
setsockopt();
 

netstat -anu 查看报式套接字的信息。

path:  c/linux_c/ipc/socket/udp/basic

定长实现

proto.h
#ifndef PROTO_H
#define PROTO_H

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>

#define IPSTRSIZE 128
#define NAMESIZE 11
#define RECVPORT "1989" //选择超过1024以上的端口

struct msg_st
{
    uint8_t  name[NAMESIZE]; // 8位相当于char
    uint32_t math;
    uint32_t  chinese;
}__attribute__((packed)); //告诉编译器结构体不用对齐




#endif 
recv.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "proto.h"


int main()
{
    int sd;
    sd  = socket(AF_INET,SOCK_DGRAM,0); //0代表默认的协议 IPPROTO_UDP
    if(sd <0)
    {
        perror("socket");
        exit(1);
    }
    int ret;
    struct sockaddr_in laddr;
    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(RECVPORT)); // 端口号 host to network 主机序列转为网络序列
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);  //ip地址 需要转为大整数
    
    ret = bind(sd,(void *)&laddr,sizeof(laddr)); //不同的协议族绑定的地址是不一样的,需要man 7 ip 查看。
    if(ret<0 )
    {
        perror("bind()");
        exit(1);
    }
    struct msg_st rbuf;
    struct sockaddr_in raddr;
    socklen_t raddr_len;
    char ipstr[IPSTRSIZE];
    /* raddr_len如果不初始化,那么第一次地址会出错,后续不会*/
    raddr_len = sizeof(raddr);
    while(1)
    {
        recvfrom(sd,&rbuf,sizeof(rbuf),0,(void *)&raddr,&raddr_len); //recv是流式套接字,recvform是报式,需要记录对端的地址。
        inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
        printf("---MSSSAGE FORM %s:%d---\n",ipstr, ntohs(raddr.sin_port) ); //大整数转点分式
        printf("NAME = %s\n",rbuf.name);//单字节传输不涉及到大段小端传输转换。
        printf("MATH = %d\n",ntohl( rbuf.math));
        printf("CHINESE = %d\n",ntohl( rbuf.chinese));

    }
    close(sd);

    return 0;
}
snder.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#include "proto.h"


int main(int argc,char**argv)
{
    if(argc<2)
    {
        printf("Usage argc 2...\n");
        exit(1);
    }

    int sd;
    sd = socket(AF_INET,SOCK_DGRAM,0);

//     bind(); //可以省略
    int len;
    struct msg_st sbuf;
    struct sockaddr_in raddr;
    strcpy(sbuf.name,"udp test");
    sbuf.math = htonl( rand()%100);
    sbuf.chinese = htonl(rand()%100);

    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(RECVPORT));
    inet_pton(AF_INET,argv[1],&raddr.sin_addr);

    len = sendto(sd,&sbuf,sizeof(sbuf),0,(void*)&raddr,sizeof(raddr) ); // tcp采用send
    
    if(len <0)
    {
        perror("sendto()");
        exit(1);
    }
    puts("Ok!");
    close(sd);

    return 0;
}

 变长实现

path:c/linux_c/ipc/socket/udp/var

proto.h
#ifndef PROTO_H
#define PROTO_H

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>

#define IPSTRSIZE 128
#define NAMEMAX 512-8-8  //udp包推荐长度512 - 8 抱头 - 8(已有的math和chinese) 

#define RECVPORT "1989" //选择超过1024以上的端口

struct msg_st
{
//    uint8_t  name[NAMESIZE]; //网络传输不能传递指针。
    uint32_t math;
    uint32_t  chinese;
    uint8_t name[1]; //变长结构体实现。/
}__attribute__((packed)); //告诉编译器结构体不用对齐




#endif 
recv.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "proto.h"


int main()
{
    int sd;
    sd  = socket(AF_INET,SOCK_DGRAM,0); //0代表默认的协议 IPPROTO_UDP
    if(sd <0)
    {
        perror("socket");
        exit(1);
    }
    int ret;
    struct sockaddr_in laddr;
    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(RECVPORT)); // 端口号 host to network 主机序列转为网络序列
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);  //ip地址 需要转为大整数
    
    ret = bind(sd,(void *)&laddr,sizeof(laddr)); //不同的协议族绑定的地址是不一样的,需要man 7 ip 查看。
    if(ret<0 )
    {
        perror("bind()");
        exit(1);
    }
    struct msg_st* rbufp;
    int size = sizeof(struct msg_st)+NAMEMAX-1;
    rbufp = malloc(rbufp);
    

    struct sockaddr_in raddr;
    socklen_t raddr_len;
    char ipstr[IPSTRSIZE];
    /* raddr_len如果不初始化,那么第一次地址会出错,后续不会*/
    raddr_len = sizeof(raddr);
    while(1)
    {
        recvfrom(sd,rbufp,size,0,(void *)&raddr,&raddr_len); //recv是流式套接字,recvform是报式,需要记录对端的地址。
        inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
        printf("---MSSSAGE FORM %s:%d---\n",ipstr, ntohs(raddr.sin_port) ); //大整数转点分式
        printf("NAME = %s\n",rbufp->name);//单字节传输不涉及到大段小端传输转换。
        printf("MATH = %d\n",ntohl( rbufp->math));
        printf("CHINESE = %d\n",ntohl( rbufp->chinese));

    }
    close(sd);

    return 0;
}
sender.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#include "proto.h"


int main(int argc,char**argv)
{
    if(argc<3)
    {
        printf("Usage argc 2...\n");
        exit(1);
    }
    if(strlen(argv[2])>NAMEMAX)
    {
        fprintf(stderr,"Name is to Long");
        exit(1);
    }

    int sd;
    sd = socket(AF_INET,SOCK_DGRAM,0);

//     bind(); //可以省略
    int len;
    struct msg_st* sbufp;
    int size = sizeof(struct msg_st)+strlen(argv[2]);
    sbufp =malloc(size);
    if(sbufp == NULL)
    {
        perror("malloc()");
        exit(1);
    }
    struct sockaddr_in raddr;
    
    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(RECVPORT));
    inet_pton(AF_INET,argv[1],&raddr.sin_addr);

    len = sendto(sd,sbufp,size,0,(void*)&raddr,sizeof(raddr) ); // tcp采用send
    
    if(len <0)
    {
        perror("sendto()");
        exit(1);
    }
    puts("Ok!");
    close(sd);

    return 0;
}

 多点通信

广播

path:c/linux_c/ipc/socket/udp/bcast  
man 7 socket 查看socket Options
ip ro sh:查看路由信息。
ip ro add default via 127.0.0.1:添加路由。

分为:全网广播,子网广播。默认不能够发出,需要特殊设置。

  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);

int val = 1;
setsockopt(sd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(val));//每一层,每一个要求需要的参数都不一样。SO_BINDTODEVICE 可以指定从哪个网卡出去。
inet_pton(AF_INET,"255.255.255.255",&raddr.sin_addr); //ip地址 255是广播

recv.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "proto.h"


int main()
{
    int sd;
    sd  = socket(AF_INET,SOCK_DGRAM,0); //0代表默认的协议 IPPROTO_UDP
    if(sd <0)
    {
        perror("socket");
        exit(1);
    }
    

    int ret;
    struct sockaddr_in laddr;
    int val = 1;
    setsockopt(sd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(val));

    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(RECVPORT)); // 端口号 host to network 主机序列转为网络序列
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);  //ip地址 需要转为大整数
    
    ret = bind(sd,(void *)&laddr,sizeof(laddr)); //不同的协议族绑定的地址是不一样的,需要man 7 ip 查看。
    if(ret<0 )
    {
        perror("bind()");
        exit(1);
    }
    struct msg_st rbuf;
    struct sockaddr_in raddr;
    socklen_t raddr_len;
    char ipstr[IPSTRSIZE];
    /* raddr_len如果不初始化,那么第一次地址会出错,后续不会*/
    raddr_len = sizeof(raddr);
    while(1)
    {
        recvfrom(sd,&rbuf,sizeof(rbuf),0,(void *)&raddr,&raddr_len); //recv是流式套接字,recvform是报式,需要记录对端的地址。
        inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
        printf("---MSSSAGE FORM %s:%d---\n",ipstr, ntohs(raddr.sin_port) ); //大整数转点分式
        printf("NAME = %s\n",rbuf.name);//单字节传输不涉及到大段小端传输转换。
        printf("MATH = %d\n",ntohl( rbuf.math));
        printf("CHINESE = %d\n",ntohl( rbuf.chinese));

    }
    close(sd);

    return 0;
}
snder.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#include "proto.h"


int main(int argc,char**argv)
{
    int sd;
    sd = socket(AF_INET,SOCK_DGRAM,0);
    int val= 1;
    int ret;
    //SO_BINDTODEVICE 可以指定从哪个网卡出去。
    ret = setsockopt(sd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(val)); //每一层,每一个要求需要的参数都不一样。
    if(ret<0)
    {
        perror("setsockopt()");
        exit(1);
    }

//     bind(); //可以省略
    int len;
    struct msg_st sbuf;
    struct sockaddr_in raddr;
    strcpy(sbuf.name,"udp test");
    sbuf.math = htonl( rand()%100);
    sbuf.chinese = htonl(rand()%100);

    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(RECVPORT));
    inet_pton(AF_INET,"255.255.255.255",&raddr.sin_addr); //ip地址 255是广播

    len = sendto(sd,&sbuf,sizeof(sbuf),0,(void*)&raddr,sizeof(raddr) ); // tcp采用send
    
    if(len <0)
    {
        perror("sendto()");
        exit(1);
    }
    puts("Ok!");
    close(sd);

    return 0;
}
多播

又称为组播。组播特殊地址:224.0.0.1,所有支持多播的节点都存在这个组中,并且无法离开。   

根据linux的网卡名称转为索引号:if_nametoindex();ip ad sh  或者ip address查看。

proto.h
#ifndef PROTO_H
#define PROTO_H

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <net/if.h>

#define MGROUP "224.2.2.2"

#define IPSTRSIZE 128
#define NAMESIZE 11
#define RECVPORT "1989" //选择超过1024以上的端口

struct msg_st
{
    uint8_t  name[NAMESIZE]; // 8位相当于char
    uint32_t math;
    uint32_t  chinese;
}__attribute__((packed)); //告诉编译器结构体不用对齐




#endif 
recv.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "proto.h"


int main()
{
    int sd;
    sd  = socket(AF_INET,SOCK_DGRAM,0); //0代表默认的协议 IPPROTO_UDP
    if(sd <0)
    {
        perror("socket");
        exit(1);
    }
    

    int ret;
    struct sockaddr_in laddr;
    struct ip_mreqn mreq;
    inet_pton(AF_INET,MGROUP,&mreq.imr_multiaddr);
    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);
    mreq.imr_ifindex = if_nametoindex("ens33");

    setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(RECVPORT)); // 端口号 host to network 主机序列转为网络序列
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);  //ip地址 需要转为大整数
    
    ret = bind(sd,(void *)&laddr,sizeof(laddr)); //不同的协议族绑定的地址是不一样的,需要man 7 ip 查看。
    if(ret<0 )
    {
        perror("bind()");
        exit(1);
    }
    struct msg_st rbuf;
    struct sockaddr_in raddr;
    socklen_t raddr_len;
    char ipstr[IPSTRSIZE];
    /* raddr_len如果不初始化,那么第一次地址会出错,后续不会*/
    raddr_len = sizeof(raddr);
    while(1)
    {
        recvfrom(sd,&rbuf,sizeof(rbuf),0,(void *)&raddr,&raddr_len); //recv是流式套接字,recvform是报式,需要记录对端的地址。
        inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
        printf("---MSSSAGE FORM %s:%d---\n",ipstr, ntohs(raddr.sin_port) ); //大整数转点分式
        printf("NAME = %s\n",rbuf.name);//单字节传输不涉及到大段小端传输转换。
        printf("MATH = %d\n",ntohl( rbuf.math));
        printf("CHINESE = %d\n",ntohl( rbuf.chinese));

    }
    close(sd);

    return 0;
}
snder.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <net/if.h>
#include "proto.h"


int main(int argc,char**argv)
{
    int sd;
    int ret;
    sd = socket(AF_INET,SOCK_DGRAM,0);
    
    struct ip_mreqn mreq;
    inet_pton(AF_INET,MGROUP,&mreq.imr_multiaddr); //转为大整数。struct in_addr
    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);
    mreq.imr_ifindex = if_nametoindex("ens33");

    ret = setsockopt(sd,IPPROTO_IP,IP_MULTICAST_IF,&mreq,sizeof(mreq)); //每一层,每一个要求需要的参数都不一样。
    if(ret<0)
    {
        perror("setsockopt()");
        exit(1);
    }

//     bind(); //可以省略
    int len;
    struct msg_st sbuf;
    struct sockaddr_in raddr;
    strcpy(sbuf.name,"udp test");
    sbuf.math = htonl( rand()%100);
    sbuf.chinese = htonl(rand()%100);

    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(RECVPORT));
    inet_pton(AF_INET,MGROUP,&raddr.sin_addr); //ip地址 MTROUP是组播

    len = sendto(sd,&sbuf,sizeof(sbuf),0,(void*)&raddr,sizeof(raddr) ); // tcp采用send
    
    if(len <0)
    {
        perror("sendto()");
        exit(1);
    }
    puts("Ok!");
    close(sd);

    return 0;
}

 UDP丢包原因是堵塞造成的,可以使用流控来解决。要是有闭环流控,等待包回来再发下一个包。

流式套接字

netstat -ant 查看流式套接字的信息。 netstat -ant|grep 5000查看TCP是否已经启动。
发生消息:nc 127.0.0.1 5000 可以模拟发送消息。或者  telnet 127.0.0.1 5000

C端:
        获取SOCKET
        给SOCKET取得地址(可省略)
        发送连接
        收发消息
        关闭
S端:
        获取SOCKET
        给SOCKET取得地址
        将SOCKET置为监听模式
        接受连接
        收发消息
        关闭。

基本版本

proto.h
#ifndef PROTO_H
#define PROTO_H

#define FMT_STAMP "%lld\n"
#define SERVERPORT "5000"




#endif
server.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include "proto.h"
#include <unistd.h>
#include <arpa/inet.h>
#include <time.h>

void server_job(int sd)
{
    
    char buf[1024];
    int len;
    int ret;
    len = sprintf(buf,FMT_STAMP,(long long)time(NULL) );
    
    ret = send(sd,buf,len,0);
    if(ret <0)
    {
        perror("send");
    }
}

int main()
{

    int sd;
   sd = socket(AF_INET,SOCK_STREAM,0);//IPPROTO_TCP   IPPROTO_SCTP
    if(sd<0)
    {
        perror("socket");
        exit(1);
    }
    int val = 1;
    setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));
    
    struct sockaddr_in laddr;
    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(SERVERPORT) );
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);

    int ret;

    ret = bind(sd,(void*)&laddr,sizeof(laddr));
    if(ret<0)
    {
        perror("bind");
        exit(1);
    }

   listen(sd,5); //能将接受全连接的节点数
    while(1)
    {
    struct sockaddr_in raddr;
    socklen_t raddr_len = sizeof(raddr);
    int newsd;

    newsd = accept(sd,(void*)&raddr,&raddr_len);
    if(newsd <0)
    {
      perror("accept");
      exit(1);
    }
    int ipport;
    char ipstr[1024];
    inet_ntop(AF_INET,&raddr.sin_addr,ipstr,1024);
    ipport = ntohs(raddr.sin_port);
    printf("Client:%s:%d \n",ipstr,ipport);
    
    server_job(newsd); //sned在这个函数里
    close(newsd);
    }
    close(sd);
    return 0;

}
client.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include "proto.h"
#include <unistd.h>
#include <arpa/inet.h>
#include <time.h>

int main(int argc,char**argv)
{
    if(argc<2)
    {
        fprintf(stderr,"Usage ...\n");
        exit(1);
    }
    int sd;
    sd = socket(AF_INET,SOCK_STREAM,0);
    if(sd <0)
    {
        perror("socket()");
        exit(1);
    }
    //bind();
    int ret;
    struct sockaddr_in raddr;
    raddr.sin_family = AF_INET;
    raddr.sin_port = htons( atoi(SERVERPORT));
    inet_pton(AF_INET,argv[1],&raddr.sin_addr);

    ret = connect(sd,(void *)&raddr,sizeof(raddr));
    if(ret <0)
    {
        printf("connect error \n");
        exit(1);
    }
    printf("connect Success \n");
    // recv();
    // close();
    FILE*fp;
    fp = fdopen(sd,"r+"); //将sd转为文件描述符 选择r /r+  

    if(fp == NULL)
    {
        perror("fdopen");
        exit(1);
    }
#if 0
    //发送了粘包的现象
    char buf[1024];
    fread(buf,1,1024,fp);
    printf("buf = %s \n",buf);
#endif 
    long long stamp;
    
    ret= fscanf(fp,FMT_STAMP,&stamp);  //正常应该读出一个数据
    if(ret <1)
    {
        printf("Bat format! \n");
    }
    else
    {
        printf("ret = %d | stamp = %lld \n",ret,stamp);
    }

    fclose(fp);
    return 0;

}





并发版本
 

需要改造服务端,服务端在建立连接后去干活,时间长的话会影响别的请求进来。

server.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include "proto.h"
#include <unistd.h>
#include <arpa/inet.h>
#include <time.h>

void server_job(int sd)
{
    
    char buf[1024];
    int len;
    int ret;
    len = sprintf(buf,FMT_STAMP,(long long)time(NULL) );
    
    ret = send(sd,buf,len,0);
    ret = send(sd,buf,len,0);
    if(ret <0)
    {
        perror("send");
    }
}

int main()
{

    int sd;
   sd = socket(AF_INET,SOCK_STREAM,0);//IPPROTO_TCP   IPPROTO_SCTP
    if(sd<0)
    {
        perror("socket");
        exit(1);
    }
    int val = 1;
    setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));
    
    struct sockaddr_in laddr;
    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(SERVERPORT) );
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);

    int ret;

    ret = bind(sd,(void*)&laddr,sizeof(laddr));
    if(ret<0)
    {
        perror("bind");
        exit(1);
    }

   listen(sd,5); //能将接受全连接的节点数
    while(1)
    {
    struct sockaddr_in raddr;
    socklen_t raddr_len = sizeof(raddr);
    int newsd;

    newsd = accept(sd,(void*)&raddr,&raddr_len);
    if(newsd <0)
    {
      perror("accept");
      exit(1);
    }
    int pid;
    pid = fork(); //fork之后 父子进程 都有 newsd和sd
    if(pid ==0)
    {
        //child
        int ipport;
        char ipstr[1024];
        inet_ntop(AF_INET,&raddr.sin_addr,ipstr,1024);
        ipport = ntohs(raddr.sin_port);
        printf("Client:%s:%d \n",ipstr,ipport);

        server_job(newsd); //sned在这个函数里
        close(newsd);
        close(sd);
        exit(0); 
    }
    if(pid >0)
    {
        close(newsd);
    }
    if(pid <0)
    {
        perror("fork()");
        exit(1);
    }
    
    }
    close(sd);
    return 0;

}
 webdl.c

模仿HTTP进行抓包

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <time.h>

int main(int argc,char**argv)
{
    if(argc<2)
    {
        fprintf(stderr,"Usage ...\n");
        exit(1);
    }
    int sd;
    sd = socket(AF_INET,SOCK_STREAM,0);
    if(sd <0)
    {
        perror("socket()");
        exit(1);
    }
    //bind();
    int ret;
    struct sockaddr_in raddr;
    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(80);
    inet_pton(AF_INET,argv[1],&raddr.sin_addr);

    ret = connect(sd,(void *)&raddr,sizeof(raddr));
    if(ret <0)
    {
        printf("connect error \n");
        exit(1);
    }
    printf("connect Success \n");
    // recv();
    // close();
    FILE*fp;
    fp = fdopen(sd,"r+"); //将sd转为文件描述符 选择r /r+  

    if(fp == NULL)
    {
        perror("fdopen");
        exit(1);
    }
    fprintf(fp,"GET /test.jpg\r\n\r\n");
    fflush(fp);
    while(1)
    {
        char rbuf[1024];
        int len;
        len = fread(rbuf,1,1024,fp);
        if(len ==0)
        {
            break;
        }
        fwrite(rbuf,1,len,stdout);

    }
    fclose(fp);
    return 0;

}





静态进程池版本

server.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include "proto.h"
#include <unistd.h>
#include <arpa/inet.h>
#include <time.h>

#define PROCNUM 4

void server_loop(int sd);

void server_job(int sd)
{
    
    char buf[1024];
    int len;
    int ret;
    len = sprintf(buf,FMT_STAMP,(long long)time(NULL) );
    
    ret = send(sd,buf,len,0);
    ret = send(sd,buf,len,0);
    if(ret <0)
    {
        perror("send");
    }
}

int main()
{

    int sd;
   sd = socket(AF_INET,SOCK_STREAM,0);//IPPROTO_TCP   IPPROTO_SCTP
    if(sd<0)
    {
        perror("socket");
        exit(1);
    }
    int val = 1;
    setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));
    
    struct sockaddr_in laddr;
    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(SERVERPORT) );
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);

    int ret;

    ret = bind(sd,(void*)&laddr,sizeof(laddr));
    if(ret<0)
    {
        perror("bind");
        exit(1);
    }

   listen(sd,5); //能将接受全连接的节点数
    int pid;
    for(int i=0;i <PROCNUM;i++)
    {
        pid = fork();
        if(pid<0)
        {
            perror("fork()");
            exit(1);
        }
        if(pid ==0)
        {
            server_loop(sd);
            exit(1);
        }
        
    }
    for(int i=0;i<PROCNUM;i++)
    {
        wait(NULL);
    }
    close(sd);
    exit(0);
}
void server_loop(int sd)
{
    while(1)
    {
    struct sockaddr_in raddr;
    socklen_t raddr_len = sizeof(raddr);
    int newsd;

    newsd = accept(sd,(void*)&raddr,&raddr_len); //本身能够实现互斥。
    if(newsd <0)
    {
      perror("accept");
      exit(1);
    }
    int ipport;
    char ipstr[1024];
    inet_ntop(AF_INET,&raddr.sin_addr,ipstr,1024);
    ipport = ntohs(raddr.sin_port);
    printf("[%d]Client:%s:%d \n",getpid(),ipstr,ipport);
    
    server_job(newsd); //sned在这个函数里
    close(newsd);
    }

}

动态进程池

while true;do(./client 127.0.0.1 &);sleep 1;done。运行脚本。

server.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include "proto.h"
#include <unistd.h>
#include <arpa/inet.h>
#include <time.h>

#define MINSPACE 5 // 进程最小的空闲数量
#define MAXSPACE 10 // 进程最大的空闲数量
#define MAXCLIENTS 20   //进程总上线

#define SIG_NOTIFY SIGUSR1 //自定义标准信号

enum
{
    STATE_IDLE=0,
    STATE_BUSY
};
struct server_st
{
    pid_t pid;
    int state;
//    int reuse; //可以采取计数,进程使用上限就杀掉,重启。

};
static int sd;

static struct server_st * serverpool;
static int idle_count =0,busy_count = 0; //idle spare 空闲的

void usr2_handler(int code)
{
    return ;
}

int server_job(int pos)
{
    int ppid;
    struct sockaddr_in raddr;
    socklen_t raddr_len;
    int client_sd;
    raddr_len = sizeof(raddr);
    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("accept");
                exit(1);
            }
        }
        serverpool[pos].state = STATE_BUSY;
        kill(ppid,SIG_NOTIFY);
        char ipstr[32];
        inet_ntop(AF_INET,&raddr.sin_addr,ipstr,32);
//        printf("[%d]:chilent:%s:%d \n",getpid(),ipstr,ntohs(raddr.sin_port));
        long long stamp = time(NULL);
        int len;
        char linebuf[1024];
        len = snprintf(linebuf,1024,FMT_STAMP,stamp);
        send(client_sd,linebuf,len,0);
        /* if error*/
        sleep(5);
        close(client_sd);
    }
}

int add_1_server()
{
    if((idle_count +busy_count)>=MAXCLIENTS )
    {
        return -1;
    }
    int slot;
    for(slot = 0;slot<MAXCLIENTS;slot++)
    {
        if(serverpool[slot].pid == -1 )
        {
            break;
        }
    }
    int pid;
    serverpool[slot].state = STATE_IDLE;
    pid = fork();
    if(pid <0)
    {
        perror("fork()");
        exit(1);
    }
    if(pid ==0)
    {
        server_job(slot);
        exit(0);
    }
    else
    {
        serverpool[slot].pid = pid;
        idle_count++;
    }
}

int del_1_server(void)
{
    if( (idle_count ==0))
        return -1;
    for(int 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;
}
int scan_pool()
{
    int idle= 0,busy=0;
    for(int 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,"Unknown state.\n");
            _exit(1);  //退出程序不清理
        }
    }
    idle_count = idle;
    busy_count = busy;
    return 0;
}

int main()
{
    struct sigaction sa,osa;
    sa.sa_handler =SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_NOCLDWAIT; //不用自己收尸,让子进程自行消亡。
    sigaction(SIGCHLD,&sa,&osa);//创建新行为,绑定旧行为  子进程终止会发出信号。
    
    sa.sa_handler = usr2_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIG_NOTIFY,&sa,&osa);
    
     sigset_t set,oset;
    sigemptyset(&set);
    sigaddset(&set,SIG_NOTIFY);
    sigprocmask(SIG_BLOCK,&set,&oset); //堵塞set中的信号,旧信号保存到oset中。
    serverpool = mmap(NULL,sizeof(*serverpool)*MAXCLIENTS,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0); //可以匿名映射,申请内存。
    if(serverpool == MAP_FAILED)
    {
        perror("serverpool");
        exit(1);
    }
    for(int 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;
    struct sockaddr_in laddr;
    setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));
    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(SERVERPORT));
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
    bind(sd,(void *)&laddr,sizeof(laddr));
    
    listen(sd,100);
    for(int i=0;i<MINSPACE;i++)
    {
        add_1_server();
    }
    while(1)
    {
        sigsuspend(&oset); //等待信号到来,打断我
        scan_pool();
        //control the pool
        if(idle_count >MAXSPACE)
        {
            for(int i=0;i<(idle_count - MAXSPACE);i++)
            {
                del_1_server();
            }
        }
        else if(idle_count <MINSPACE)
        {
            for(int i=0;i<(MINSPACE-idle_count);i++)
            {
                add_1_server();
            }
        }
        // print the    pool
        for(int 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); //设置 oset,相当于恢复
    close(sd);

    return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

only-lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值