管道
又内核提供,单工,自同步机制。使用广泛。(管道必须凑齐读写双方才能够运行。)
匿名管道
/home/qt/c/linux_c/ipc/pipe
看不到管道的名称,适合有亲缘关系的进程通信。
pipe.c
int pipe(int fildes[2]);
cpp
#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
cpp
#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
cpp
#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
cpp
#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
cpp
#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 );
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协议
cpp
#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
cpp
#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, ...);
cpp
#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);
cpp
#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。讨论:跨主机传输要注意的问题。
大小端问题
字节序经常被分为两类:
- 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
cpp
#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
cpp
#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
cpp
#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
cpp
#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
cpp
#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
cpp
#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是广播
多播
组播。
流式套接字
netstat -ant 查看流式套接字的信息。