- 介绍
- 1. 管道
- 匿名管道
- 命名管道
- 2. IPC:XSI → SysV
- Message Queues
- Semaphore Arrays
- Shared Memory
- 3. 网络套接字socket
- udp
- 单播
- 广播
- 多播
- tcp
- 单进程
- 多进程
- pool_static:静态进程池
- pool_dynamic:动态进程池
- udp
介绍
1. 管道
- 命名管道就是一块磁盘上的文件,不同进程通过读写该文件进行通信
- 匿名管道同样是在磁盘上创建了文件,但是只返回文件描述符,其他进程看不到,所以只能用于有亲缘关系的进程
匿名管道
- 管道是阻塞的:有写者的时候,读者会阻塞读
##include <unistd.h>
int pipe(int pipefd[2]);
- 参数:
pid
文件描述符数组,pid[0]
读端,pid[1]
写端 - 返回值:
<0
失败
例子:pipe实现父子进程间通信
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<unistd.h>
##include<sys/wait.h>##define BUFSIZE 1024int 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);}
}
运行结果:
项目相关:
父进程做好管道,子进程 exec
变成播放器,然后父子进程通过管道通信
mpg123 [ options ] file-or-URL...or -
sudo apt install mpg123
→ man mpg123
mpg123 歌曲名
:播放歌曲, ‘-’表示从标准输入读取内容播放
##include<stdio.h>
##include<stdlib.h>
##include<unistd.h>
##include<sys/wait.h>##define BUFSIZE 1024int 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
命令:创建管道文件
阻塞:没凑齐读写双方时会阻塞
2. IPC:XSI → SysV
ipcs
命令:展现机制
key : ftok()
Message Queues
:xxxget() xxxop()[包括cv和snd] xxxctl()
Semaphore Arrays
Shared Memory
常规的操作流程:
- 创建实例:
ftok()
xxxget()
- 操作:
xxxop()
- 删除实例:
xxxctl()
Message Queues
示例:消息队列实现非亲缘关系进程间通信
示例代码:
proto.h
##ifndef PROTO_H__
##define PROTO_H__##define KEYPATH "/etc/services"
##define KEYPROJ 'g'##define NAMESIZE 32struct 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 后的结果
注意点:
-
即使先发送,后接收,也能收到之前发送的内容,说明msg有缓冲机制,缓冲区的大小可以通过
ulimit -a
查看 -
程序是异常终止,msg没有被销毁,如何解决
- 程序中加入信号和信号处理函数,在信号处理函数中关闭msg
ipcrm -q msgid
如ipcrm -q 0
例子2:实现ftp
状态机编程
协议数据设计:
方式1:
##ifndef PROTO_H__
##define PROTO_H__##define KEYPATH "/etc/services"
##define KEYPROJ 'a'##define PATHMAX 1024
##define DATAMAX 1024enum
{MSG_PATH=1,MSG_DATA,MSG_EOT
};//S端收到一种包
typedef struct msg_path_st
{long mtype; //must be MSG_PATHchar path[PATHMAX]; //ASCIIZ带尾0的串
}msg_path_t;//C端收到两种包
typedef struct msg_data_st
{long mtype; //must be MSG_DATAchar 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 1024enum
{MSG_PATH=1,MSG_DATA,MSG_EOT
};//S端收到一种包
typedef struct msg_path_st
{long mtype; //must be MSG_PATHchar path[PATHMAX]; //ASCIIZ带尾0的串
}msg_path_t;//C端收到两种包,合并为一种结构体
//没第一种做法好,只适用于eot中只用mtype的情况
typedef struct msg_s2c_st
{long mtype; //must be MSG_DATA or MSG_EOTchar data[DATAMAX];int datalen;/** datalen > 0 :data* < 0 :eot*/
}msg_s2c_t;##endif
Semaphore Arrays
信号量数组的必要性:
- 银行家算法中,AB两个信号量要同时被分配,防止死锁
- 两个进程逆序申请两个锁的时候,操作要是原子的
- 本质就是把几个信号量的操作原子化
例子:重构 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 1024int 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的元素值为1if(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);
}
运行结果:
Shared Memory
例子:父子进程通信,子写父读
示例代码:
##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);
}
运行结果:
3. 网络套接字socket
socket是什么:向下给各层数据传输协议,向上给两种传输方式(数据报、流式)封装出的接口
使用 socket
的方式
用domain协议族里的协议protocol,实现type类型的传输。返回的是文件描述符,当文件来操作。
udp
struct sockadd
实际上不存在,需要 man 7 ip
查看 socket format
得到对应的结构体
单播
例子1:基本使用
示例代码:
-
proto.h【规定数据报格式】
#ifndef PROTO_H__ #define PROTO_H__#define RCVPORT "1989" //封装端口(最好>1024)#define NAMESIZE 11struct 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 20int main() {int sd;struct sockaddr_in localaddr, remoteaddr; //具体是什么结构体取决于协议//看手册 man 7 xx(proto) 里的 Address_formatstruct msg_st rbuf;socklen_t remoteaddr_len;char ipstr[IPSTRSIZE];//创建socketsd = 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后,执行 netstat -anu
查看udp套接字使用情况
发送端没有bind端口任意指定
例子二:发送变长数组
思路: 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 20int main() {int sd;int size;struct sockaddr_in localaddr, remoteaddr; //具体是什么结构体取决于协议//看手册 man 7 xx(proto) 里的 Address_formatstruct 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在绑定阶段会绑定自身ipif(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); }
运行结果:
广播
多播与广播需要设置:用到函数 setsockopt
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
这个函数横跨ip,tcp,udp, level
指定协议, optname
是具体设置的项目, optval
是参数, optlen
是参数的sizeof。
level
和 optname
从哪里获取? man 7 ip/tcp/udp
查看 Socket Option
即可。
比如 man 7 udp
查看 socket Option,可知, level == SOL_SOCKET,打开广播的选项 opt == SO_BROADCAST
示例代码:
-
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 20int main() {int sd;struct sockaddr_in localaddr, remoteaddr; //具体是什么结构体取决于协议//看手册 man 7 xx(proto) 里的 Address_formatstruct 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在绑定阶段会绑定自身ipif(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); }
运行结果:
多播
实现在ip层, man 7 ip
→ Socket Option
发送方建立多播组,接受方如果要接受该多播组,就加入,所以协议(proto.h)中要加入多播组的字段
示例代码:
-
proto.h
#ifndef PROTO_H__ #define PROTO_H__#define MTROUP "224.2.2.2" //多播:D类地址 #define RCVPORT "1989" //封装端口(最好>1024)#define NAMESIZE 11struct 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 20int main() {int sd;struct sockaddr_in localaddr, remoteaddr; //具体是什么结构体取决于协议//看手册 man 7 xx(proto) 里的 Address_formatstruct 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在绑定阶段会绑定自身ipif(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); }
运行结果:
查看网络设备名称和编号 ip ad sh
:
注意: 224.0.0.1
所有支持多播的结点,都默认在这个组中,且无法离开,相当于 255.255.255.255
(广播)
tcp
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 1024static 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 ipif(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");elsefprintf(stdout,"stamp = %lld\n",stamp);fclose(fp);//rcve()//close()exit(0); }
运行结果:
./server
nc ip port
或 telnet ip port
发送请求
注意点查看多线程部分。
多进程
示例代码:
-
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 1024static 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); }
运行结果:
- 进程阻塞在
accept
,因为accept
互斥访问和阻塞
如果父进程调用了 accept
产生了 newsd
,如果不关闭 newsd
会导致 accept
认为该上次的传输没有结束,会继续等待上一个连接结束,导致新的传输阻塞,这提醒我们 accept
得到的文件描述符要及时关闭
-
server
异常结束时,端口未释放,再次执行时无法立刻重新绑定端口,bind()
抛异常netstat -anu
查看,端口未释放,无法立即绑定
解决:设置 sockopt
: man 7 socket
查看socket层的表示,找到对应命令,执行 setsockopt()
setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))
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 4static 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);} }
运行结果:
缺点:无法应对突发请求,没有弹性
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 80enum {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);//父进程屏蔽SIGNOTIFYsigemptyset(&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);//mallocif(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 poolif(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 statefor(i=0; i < MAXCLIENTS; i++){if(serverpool[i].pid == -1)putchar('*');else if(serverpool[i].state == STATE_IDLE)putchar('.');elseputchar('x');}putchar('\n');}sigprocmask(SIG_SETMASK,&oset,NULL); //恢复// close();exit(0); }
运行结果:
*
:未创建.
:空闲x
:busy
client:
client*1
client*2
client*3
clietn*4
注意:
因为 server_st
数组是 mmap
出来的,所以父子进程都可见。
scan_pool
的时候更新 idle_count
计数