linux系统编程09-进程间通信
介绍
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 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);
}
}
运行结果:
项目相关:
父进程做好管道,子进程 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 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
命令:创建管道文件
阻塞:没凑齐读写双方时会阻塞
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 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 后的结果
注意点:
-
即使先发送,后接收,也能收到之前发送的内容,说明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 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
信号量数组的必要性:
- 银行家算法中,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 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);
}
运行结果:
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 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后,执行 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 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); }
运行结果:
广播
多播与广播需要设置:用到函数 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 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); }
运行结果:
多播
实现在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 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); }
运行结果:
查看网络设备名称和编号 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 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
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 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); }
运行结果:
- 进程阻塞在
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 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); } }
运行结果:
缺点:无法应对突发请求,没有弹性
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); }
运行结果:
*
:未创建.
:空闲x
:busy
client:
client*1
client*2
client*3
clietn*4
注意:
因为 server_st
数组是 mmap
出来的,所以父子进程都可见。
scan_pool
的时候更新 idle_count
计数