博客新址,这里更有趣
在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,本身写了Linux下进程Socket通讯,在学习的过程当中,又接触到了其它的几种方式。记录一下。git
共享内存的通讯方式,系统根据个人偏好设置在内存中开辟一块空间,并对其进行相应的指定,而后咱们的另外一个进程能够按照一样的指定,也就是其标记,对其进行访问。建立共享内存,获得共享内存后,想内存中写入数据,而后另个进程获得内存,而后从中读出数据。
先贴出代码,写入方github
#include <stdlib.h> #include <sys/shm.h> #include <sys/ipc.h> #include <unistd.h> #include <string.h> #define PATH "" #define SIZE 512 #define ID 0 int main() { char * shmAddr; char * dataAddr = "Hello"; int key = ftok(PATH,ID); int shmID = shmget(key,SIZE,IPC_CREAT); shmAddr = shmat(shmID,NULL,0); strcpy(shmAddr,dataAddr); shmdt(shmAddr); exit(0); }
接收方算法
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/shm.h> #include <sys/ipc.h> #define PATH "" #define SIZE 0 #define ID 0 int main() { char * shmAddr; char * dataAddr = "Hello"; int key = ftok(PATH,ID); int shmID = shmget(key,SIZE,0); shmAddr = shmat(shmID,NULL,0); printf("%s\n",shmAddr); shmdt(shmAddr); shmctl(shmID, IPC_RMID, NULL); exit(0); }
代码比较简单,不难看出,经过函数shmget(),咱们获得了对一块共享内存的标记,而后经过shmdt(),而后和咱们的进程绑定获得这块共享内存的地址,而后便可输出该块内存区域中的数据。主要涉及几个参数,ftok(),shmget(),shmat()。编程
ftok():接受两个参数一个是文件目录,一个是咱们的projectid,对于第二个id,咱们这里能够随便制定,此处指定0,该函数能够为当前IPC生成一个惟一的键值。
shemget():第一个参数是咱们进程的键值,第二个参数是咱们指定内存区域的大小,若是制定为0则表明咱们只是打开,而不是去建立,第三个参数是制定咱们对该内存区域的操做模式权限。
shmat:该参数的做用是将咱们的共享内存映射到咱们的当前进程的内存空间。获得了其地址。第一个参数是咱们要映射的共享内存的标示符。第二个参数是制定将共享内存的映射到咱们进程空间的起始地址,若是制定为null,则会由系统自动分配一个。第三个参数是指定咱们的操做模式,是读仍是写,此处指定为0,表示既能够对其读又能够对其写。
shmdt:这个函数是用来断开当前进程和该共享内存区域的链接,
shmctl:该函数经过对于一些参数的指定来对共享内存区域进行一系列的控制,对于共享内存区域,设有一个计时器,当到达必定时间以后,若是没有进程和其绑定,该内存将会被回收。也能够不用手动对其回收。数组
下面运行下这两个程序
当咱们再次运行client2后
由于咱们的连个进程都已经和该共享内存区域解绑,并且也执行了移除,若是咱们不对其移除。则会服务器
管道通讯,在一个进程之中,只能单一的对其写或者是读,而不能够及执行写操做又执行读操做。这一点,咱们能够将其想象成咱们的水管,分别连着不一样的两端,在有水流的时候,一端只能进行输入,另外一端只能进行输出,而不能够同时输入和输出。网络
管道又分为有名管道和匿名管道,二者的区别在于对于匿名管道,其只能在具备亲缘关系的父子进程之间进行消息通讯。管道的通讯是借助于在咱们的磁盘上生成一个文件,而后对文件的读写来实现咱们的通讯,并且数据被读以后,在文件中将会被删除掉。socket
匿名管道的实现较为简单,实现代码:tcp
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUFFER_SIZE 9 int main() { int n; int fd[2]; pid_t pid; char buf[BUFFER_SIZE]; if(pipe(fd)==-1) { printf("error in create pipe\n"); exit(1); } if((pid=fork())<0) { printf("error in fork a sub process\n"); exit(1); } if(pid>0) { close(fd[0]); write(fd[1],"Welcome!",10); } else { close(fd[1]); read(fd[0],buf,9); printf("%s,son\n",buf); } return 0; }
1.建立管道2.经过fork函数分叉出子进程3.写数据4.读数据5.关闭管道读端和写端。这里也除了管道函数,还要特别说一下的是fork函数。函数
fork函数返回的数据在父进程中是子进程pid,而对子进程返回的是0,这并非说,子进程的pid就是零,还有一个函数是getpid(),执行这个函数后,在子进程和父进程中获得的都是该进程pid,而不是fork函数的返回值。fork函数执行的时候,是将父进程中所有的数据置入本身进程之中,和父进程中的数据不是同步的了。
经过pipe函数咱们建立了一个管道,管道接收的参数是一个长度为2的一维数组,此时,此时在0位返回的是读端口,1位返回的是写端口,当咱们要对数据进行写入的时候,咱们须要关闭其读端口,若是对其进行读操做,咱们要关闭写端口。相似于读文件的操做,制定数据和大小,而后经过read和write函数对其进行读写。
后来又发现的一些问题是对于读写端口的问题,当咱们在写的时候,读端口开着对其并无影响,其目的是以防万一,写入数据被自身读取走了。还有此处实现方式是经过创建一个管道文件,而后采用了常规的文件读写方式进行的读写,还会出现的一些是,当咱们read完一次以后,若是下面接着再read,将会出现一个问题,咱们还可以从中读出数据来,缘由是管道文件自己具备一个自身管理的,来负责对于管道文件的擦除,而其擦除速度较慢于咱们从中读出的速度,因此致使了这个问题。
经过对于匿名管道的分析,再到有名管道,为何有名管道能够被非亲缘进程找到利用?由于它有名呀,对的,若是在家中,父亲要和儿子谈话,只需说出来就行了,由于信道上的接听者只有父亲和儿子,因此即便不指儿子的名字和儿子说,儿子也是知道是在和他讲,可是若是要和别人讲话,并且由不少人同时在侦听信道的时候,若是咱们不指定名字,他们就不会知道咱们是在跟谁讲话。因此对于有名管道,咱们首先要对其指定一个名字,而后指定做为写端或者是读端,而后对其进行操做。
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <string.h> #define FIFO_NAME "/oswork/pipe/myfifo" int main() { int res; int pipe_id; char buffer[] = "Hello world"; if(access(FIFO_NAME,F_OK)==-1) { res = mkfifo(FIFO_NAME,O_WRONLY); if(res!=0) { printf("Error in creating fifo.\n"); exit(1); } } pipe_id = open(FIFO_NAME,O_WRONLY); if(pipe_id!= -1) { if(write(pipe_id,buffer,PIPE_BUF)>0){ close(pipe_id); }else{ printf("Error in writing.\n"); exit(1); } }else { printf("Error in opening.\n"); exit(1); }
由于管道是经过本地磁盘上的文件进行信息的交换,所以咱们须要给予其本地磁盘上的一个文件目录,而后根据该目录经过access()函数来获取该管道,若是获取失败,那么咱们就按照给定的目录建立一个,建立管道的时候,咱们须要制定是对其进行读仍是写,建立好以后,经过指定模式打开咱们的管道,此时会获得一个管道号,而后根据得到管道标志号,做为参数,进行read和write操做。下面是读端的执行代码。
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <string.h> #define FIFONAME "/oswork/pipe/myfifo" int main() { char buffer[PIPE_BUF+1]; int pipe_id; pipe_id = open(FIFONAME,O_RDONLY); if(pipe_id != -1) { read(pipe_id,buffer,PIPE_BUF); printf("%s",buffer); }else{ printf("error in reading\n"); } }
执行结果,在这里的读和写都是设置为堵塞执行方式,就是若是没读端,这个时候,写端就是处于堵塞状态
这个时候再执行读端
写端堵塞解除
Socket通讯,不只仅是一台主机上的两个进程能够进行通讯,还可让处在因特网中的两个进程进行通讯。在两个进程进行通讯的时候,首先本地的进程在运行的时候会绑定一个端口,而后咱们本地为该进程生成一个缓冲区,返回一个值,即为socket做为对其进行标记,每当本地进程和远程一个进程创建链接的时候,就会根据远程进程的信息和本地进程的信息生成一个socket,而后双方借助于socket就能够进行通讯,运输层获得的数据写入socket标志的缓冲区,而后在里面进行相应的操做以后将其提交给网络层。相比其它的几种处理方式,该中方式比较麻烦。多于服务端,经过listen阻塞监听,监听到有链接请求,经过accept函数获得一个本地与之对应的缓冲区,而后建立一个进程用来和该链接进行交互,而后经过receive来接收信息,因为c语言大一学过去以后,基本没怎么再看过,因此写来的时候仍是遇到了几个小坑。这里实现的是当链接创建后,服务端给本地端发送一个链接创建提示,而后客户端能够向服务端发送消息,服务端给予一个I don't know的回复。
服务端代码
#include <sys/type.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define PORT 9000 #define KEY 123 #define SIZE 1024 int main() { char buf[100]; memset(buf,0,100); int server_sockfd,client_sockfd; socklen_t server_len,client_len; struct sockaddr_in server_sockaddr,client_sockaddr; /*create a socket.type is AF_INET,sock_stream*/ server_sockfd = socket(AF_INET,SOCK_STREAM,0); /*the address of the sockt which is a struct include sinfamily,sin_port and sin_addr(struct) htons is a convert function,*/ server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*the length of the sever_sockt address*/ server_len = sizeof(server_sockaddr); /* first option is socket we create,the second option is level the left is the paraments'length and value */ int on; setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); /*bind a socket or rename a sockt*/ if(bind(server_sockfd,(struct sockaddr*)&server_sockaddr,server_len)==-1){ printf("bind error"); exit(1); } /*listen the socket,the second option is 5 which is the limit number of kernal handle*/ if(listen(server_sockfd,5)==-1){ printf("listen error"); exit(1); } client_len = sizeof(client_sockaddr); /*define a pid number*/ pid_t ppid,pid; while(1){ if(client_sockfd=accept(server_sockfd,(struct sockaddr*)&client_sockaddr,&client_len)==-1){ printf("connect error"); exit(1); } else{ //send and receiv all include a socket , content and the len. send(client_sockfd,"You have conected the server",strlen("You have connected the server"),0); } ppid = fork(); if(ppid == -1) { printf("fork 1 failed:"); } /*the subthread is used to receiver the message from client Create a process again when we create father process success*/ else if(ppid == 0){ int recvbytes; pid = fork(); if(pid == -1){ printf("fork 2 failed:"); exit(1); } else if(pid == 0) { while(1){ //set the 100 byte of the buf to 0 bzero(buf,100); if((recvbytes = recv(client_sockfd,buf,100,0))==-1) { perror("read client_sockfd failed:"); } //if the subProcess receive the message successfully else if(recvbytes != 0){ buf[recvbytes] = '\0'; usleep(1000); printf("%s:%d said:%s\n",inet_ntoa(client_sockaddr.sin_addr), ntohs(client_sockaddr.sin_port), buf); //send same message to the client. if(send(client_sockfd,buf,recvbytes,0)==-1){ perror("send error"); break; } } //when send the msg successfuly end up the method } } else if(pid>0) { } } else if(ppid>0){ //总父进程中关闭client_sockfd(由于有另外一个副本在子进程中运行了)返回等待接收消息 close(client_sockfd); } } return 0; } }
其中涉及到建立进程与结束进程等仍是比较复杂的。下面经过一个流程图说明其工做原理
对于其中的一些函数,有本身的英文注释,想锻炼下英文表达能力,可是表达在语法和意思上仍是有些错误的。
客户端工做代码
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> #define SERVER_PORT 9000 /* the port of the sever which will connected*/ #define MAXDATASIZE 100 /* the maxmum bytes every send*/ #define SERVER_IP "114.215.100.147" /* the ip address of the server*/ /*First we need to create a socket and then initialize the socket,then we invoke the connect,it will build a connection between client and server we designate*/ int main() { int sockfd, numbytes; char buf[MAXDATASIZE]; struct sockaddr_in server_addr; printf("\n======================client initialization======================\n"); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); bzero(&(server_addr.sin_zero),sizeof(server_addr.sin_zero)); if (connect(sockfd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) == -1){ perror("connect error"); exit(1); } //waitting for receive msg from the server,the receiver method is a block method,when the server break up //the connection ,is will close the socket. while(1) { bzero(buf,MAXDATASIZE); printf("\nBegin receive...\n"); if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1){ perror("recv"); exit(1); } else if (numbytes > 0){ int len, bytes_sent; buf[numbytes] = '\0'; printf("Received: %s\n",buf); printf("Send:"); char msgb[100]; scanf("%s",msg); len = strlen(msg); //sent to the server if(send(sockfd,msg,len,0) == -1){ perror("send error"); } } else { printf("soket end!\n"); } } close(sockfd); return 0; }
相比于服务器端,客户端的代码比较简单了,发起链接请求-->链接成功进入轮询-->接受消息————>发送消息。
执行结果
消息队列和有名管道有些相似的地方,最大相同点在于它能够用于在不一样的进程之间进行通讯,可是管道有一个劣势是,对于接收端其对与管道内的数据只能是接受,而不能够对其进行过滤选择,同时在写和读的时候还会出现堵塞.对于消息队列其在接收的时候也是会发生堵塞的,解除阻塞,只有当其接收到合适的消息或者该队列被删除了,这个阻塞才会解除。对于消息队列的通讯流程是建立消息-》得到队列--》向队列中发送消息--》从队列中取出消息。
实现代码
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/stat.h> #include <sys/msg.h> #define MSG_FILE "/oswork/message/sender.c" #define BUFFER 255 #define PERM S_IRUSR|S_IWUSR //message struct struct msgbuf { long mtype; char mtext[BUFFER+1]; }; int main() { struct msgbuf msg; key_t key; int msgid; int i; char *myask="I'm receiver, 3 messages received from you."; //create a key for if((key=ftok(MSG_FILE,66))==-1) { fprintf(stderr,"Creat Key Error:%s \n",strerror(errno)); exit(EXIT_FAILURE); } //get a message queue if((msgid=msgget(key,PERM|IPC_CREAT))==-1) { fprintf(stderr,"Creat MessageQueue Error:%s \n",strerror(errno)); exit(EXIT_FAILURE); } //get a message from the queue everytime for(i=0; i<3; i++) { msgrcv(msgid,&msg,sizeof(struct msgbuf),1,0); printf("Receiver receive: %s\n",msg.mtext); } msg.mtype=2; //send the message that I have received the message. strncpy(msg.mtext,myask,BUFFER); msgsnd(msgid,&msg,sizeof(struct msgbuf),0); return 1; }
the code of sender
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys.stat.h> #define MSG_FILE "/oswork/message/sender.c" #define BUFFER_SIZE 255 #define PERM S_IRUSR|S_IWUSR struct msgbuf { long mtype; char mtext[BUFFER_SIZE+1]; }; //create three messages char *message[3]={"I'm sender,there are some message for you.","Message1", "Message2"}; int main() { struct msgbuf msg; key_t key; int msgid; if((key=ftok(MSG_FILE,66))==-1) { printf("error in creating key\n"); exit(0); } if((msgid=msgget(key,PERM|IPC_CREAT))==-1) { printf("error in creating get message\n"); exit(0); } //set the type of the message msg.mtype=1; int i; //send the message for(i=0; i<3; i++) { strncpy(msg.mtext,message[i],BUFFER_SIZE); msgsnd(msgid,&msg,sizeof(struct msgbuf),2,0); } memset(&msg,'\0',sizeof(struct msgbuf)); /*receive the message,the third arguments show the type of message will be received */ msgrcv(msgid,&msg,sizeof(struct msgbuf),2,0); printf("%s\n",msg.mtext); //delete the message queue. if(msgctl(msgid,IPC_RMID,0)==-1) { printf("error in deleting msg queue\n"); exit(0); } return 1; }
对于消息通讯,其中的几个函数,这里在说明一下,msgrcv和msgsnd两个函数,对于其中的参数,第一个指定的是咱们的消息队列的标示符,而后第二个是消息区域,而后是咱们的消息长度,而后是消息类型,最后一个是用来指定是否堵塞。消息队列中的消息只要是被读出的,就会自动的被从队列中剔除掉。
上述为Linux下进程间通讯的四种方式,实际开发中,咱们传输数据类型和进程间的关系选择合适的方式。
前段时间在牛客网上将《剑指offer》书中算法题用Java实现了一遍,放在了Github上,欢迎一块儿交流,提高。https://github.com/Jensenczx/...