在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式。记录一下。
管道通信(匿名,有名)
管道通信,在一个进程之中,只能单一的对其写或者是读,而不可以及执行写操作又执行读操作。这一点,我们可以将其想象成我们的水管,分别连着不同的两端,在有水流的时候,一端只能进行输入,另一端只能进行输出,而不可以同时输入和输出。
管道又分为有名管道和匿名管道,两者的区别在于对于匿名管道,其只能在具有亲缘关系的父子进程之间进行消息通信。管道的通信是借助于在我们的磁盘上生成一个文件,然后对文件的读写来实现我们的通信,而且数据被读之后,在文件中将会被删除掉。
匿名
匿名管道的实现较为简单,实现代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h> #define BUFFER_SIZE 9 int main(int argc, char const *argv[])
{
int n;
int fd[];
pid_t pid;
char sendBuffer[BUFFER_SIZE]="welcome!";
char receiveBuff[BUFFER_SIZE];
memset(receiveBuff,,sizeof(receiveBuff)); if (pipe(fd)==-)
{
fprintf(stderr, "pipe:%s\n",strerror(errno));
exit();
} if ((pid=fork())<)
{
fprintf(stderr, "fork:%s\n",strerror(errno));
exit();
}else if (pid>)//parent process
{
close(fd[]);//close the read part
write(fd[],sendBuffer,BUFFER_SIZE);
printf("parent process send:%s\n", sendBuffer);
}else
{
close(fd[]);//close the write part
read(fd[],receiveBuff,BUFFER_SIZE);
printf("child process receive:%s\n",receiveBuff);
}
return ;
} /*
[Test]
gcc -o pipe pipe.c
./pipe
parent process send:welcome!
child process receive:welcome
*/
1.创建管道2.通过fork函数分叉出子进程3.写数据4.读数据5.关闭管道读端和写端。这里也除了管道函数,还要特别说一下的是fork函数。
fork函数返回的数据在父进程中是子进程pid,而对子进程返回的是0,这并不是说,子进程的pid就是零,还有一个函数是getpid(),执行这个函数后,在子进程和父进程中得到的都是该进程pid,而不是fork函数的返回值。fork函数执行的时候,是将父进程中全部的数据置入自己进程之中,和父进程中的数据不是同步的了。
通过pipe函数我们创建了一个管道,管道接收的参数是一个长度为2的一维数组,此时,此时在0位返回的是读端口,1位返回的是写端口,当我们要对数据进行写入的时候,我们需要关闭其读端口,如果对其进行读操作,我们要关闭写端口。类似于读文件的操作,制定数据和大小,然后通过read和write函数对其进行读写。
有名
通过对于匿名管道的分析,再到有名管道,为什么有名管道可以被非亲缘进程找到利用?因为它有名呀,对的,如果在家中,父亲要和儿子谈话,只需说出来就好了,因为信道上的接听者只有父亲和儿子,所以即使不指儿子的名字和儿子说,儿子也是知道是在和他讲,但是如果要和别人讲话,而且由很多人同时在侦听信道的时候,如果我们不指定名字,他们就不会知道我们是在跟谁讲话。所以对于有名管道,我们首先要对其指定一个名字,然后指定作为写端或者是读端,然后对其进行操作。
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h> #define FIFO_NAME "/home/louxj/workspace/networking/ipc/myfifo"
#define PIPE_BUFF 12 int main(int argc, char const *argv[])
{
int res=;
int pipe_id;
char buffer[PIPE_BUFF]="hello,fifo"; if (access(FIFO_NAME,F_OK)==)//test wether the file exits
{
if ((res=mkfifo(FIFO_NAME,)))!=)
{
fprintf(stderr, "mkfifo:%s\n", strerror(errno));
}
if ((pipe_id=open(FIFO_NAME,O_RDWR))!=-)
{
if (write(pipe_id,buffer,sizeof(buffer)))
{
printf("write success\n");
close(pipe_id);
}else
{
fprintf(stderr, "write:%s\n",strerror(errno));
exit();
}
}else
{
fprintf(stderr, "open:%s\n",strerror(errno));
}
}
return ;
}
因为管道是通过本地磁盘上的文件进行信息的交换,因此我们需要给予其本地磁盘上的一个文件目录,然后根据该目录通过access()函数来获取该管道,如果获取失败,那么我们就按照给定的目录创建一个,创建管道的时候,我们需要制定是对其进行读还是写,创建好之后,通过指定模式打开我们的管道,此时会得到一个管道号,然后根据获得管道标志号,作为参数,进行read和write操作。下面是读端的执行代码。
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h> #define FIFO_NAME "/home/louxj/workspace/networking/ipc/myfifo"
#define PIPE_BUFF 12 int main(int argc, char const *argv[])
{
char buffer[PIPE_BUFF];
int pipe_id;
int size = sizeof(buffer); if ((pipe_id=open(FIFO_NAME,O_RDWR))!=-)
{
read(pipe_id,(void *)buffer,size);
printf("%s\n",(char *)buffer);
}else
{
fprintf(stderr, "open:%s\n", strerror(errno));
exit();
}
return ;
}
Socket通信
Socket通信,不仅仅是一台主机上的两个进程可以进行通信,还可以让处在因特网中的两个进程进行通信。在两个进程进行通信的时候,首先本地的进程在运行的时候会绑定一个端口,然后我们本地为该进程生成一个缓冲区,返回一个值,即为socket作为对其进行标记,每当本地进程和远程一个进程建立连接的时候,就会根据远程进程的信息和本地进程的信息生成一个socket,然后双方借助于socket就可以进行通信,运输层得到的数据写入socket标志的缓冲区,然后在里面进行相应的操作之后将其提交给网络层。相比其它的几种处理方式,该中方式比较麻烦。多于服务端,通过listen阻塞监听,监听到有连接请求,通过accept函数得到一个本地与之对应的缓冲区,然后创建一个进程用来和该连接进行交互,然后通过receive来接收信息,由于c语言大一学过去之后,基本没怎么再看过,所以写来的时候还是遇到了几个小坑。这里实现的是当连接建立后,服务端给本地端发送一个连接建立提示,然后客户端可以向服务端发送消息,服务端给予一个I don't know的回复。
服务端代码
/////////////////////////////////////////
// the server code for TCP socket //
///////////////////////////////////////// #include <netinet/in.h> //for sockaddr_in
#include <sys/types.h> //for socket
#include <sys/socket.h> //for socket
#include <stdio.h> //for printf
#include <stdlib.h> //for exit
#include <string.h> //for memset
#include <time.h> //for time_t and time
#include <fcntl.h>
#include <errno.h> //for errno #define SERVER_PORT 5790
#define LENGTH_OF_QUEUE 20
#define BUFFER_SIZE 1024 int main(int argc, char const *argv[])
{
struct sockaddr_in server_addr;//define a universal socket struct bzero(&server_addr,sizeof(server_addr));//clear the memory to zero
server_addr.sin_family=AF_INET;//IPv4 protocol
server_addr.sin_addr.s_addr=htons(INADDR_ANY);//set the IP address with localhost IP address
server_addr.sin_port=htons(SERVER_PORT);//set the nameed port //time_t now; //create the socket for the server
int server_socket;
if ((server_socket=socket(AF_INET,SOCK_STREAM,))==-)
{
fprintf(stderr, "socket:%s\n", strerror(errno));
exit();
} //bind the socket with the socket address
if ((bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))==-)
{
fprintf(stderr, "bind:%s\n", strerror(errno));
exit();
} //set the server's socket to listen for the request from the clients
if ((listen(server_socket,LENGTH_OF_QUEUE))==-)
{
fprintf(stderr, "listen:%s\n", strerror(errno));
exit();
} //set the server running all the time
while()
{
struct sockaddr_in client_addr;//define a socket struct for each client
socklen_t length=sizeof(client_addr);
int new_server_socket;
if ((new_server_socket=accept(server_socket,(struct sockaddr*)&client_addr,&length))<)
{
fprintf(stderr, "accept:%s\n", strerror(errno));
break;
} //set the notice info to send to the client
char buffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE);
strcpy(buffer,"Hello,coming from the server!");
strcat(buffer,"\n"); send(new_server_socket,buffer,BUFFER_SIZE,); bzero(buffer,BUFFER_SIZE); //receive the data to send to the client
length=recv(new_server_socket,buffer,BUFFER_SIZE,);
if (length<)
{
fprintf(stderr, "recv:%s\n", strerror(errno));
exit();
}
//display the data receive from the client
printf("%s\n",buffer); //send the local file stream to the client
int stream;
if ((stream=open("send_data",O_RDWR))==-)
{
fprintf(stderr, "open:%s\n", strerror(errno));
exit();
}else
{
printf("The file send_data was opened.\n");
} int lengthsize=;
int i=;
while((lengthsize=read(stream,buffer,sizeof(buffer)))>)
{
printf("lengthsize=%d\n",lengthsize);
//printf("%s\n", buffer);
if (send(new_server_socket,buffer,lengthsize,)<)
{
printf("send file failed!\n");
break;
}
else
{
printf("the %d time to send...\n",++i);
}
bzero(buffer,BUFFER_SIZE);
}
if (close(stream)==-)
{
fprintf(stderr, "close%s\n", strerror(errno));
exit();
}
close(new_server_socket);//close the connection with the client
} //close the socket for listening
close(server_socket);
return ;
} /*
[Test]
gcc -o tcp_server tcp_server.c
./tcp_server
Hello,this is client A!
The file send_data was opened.
lengthsize=1024
the 1 time to send...
lengthsize=1024
the 2 time to send...
lengthsize=1024
the 3 time to send...
lengthsize=1024
the 4 time to send...
lengthsize=1024
the 5 time to send...
lengthsize=805
the 6 time to send...
*/
其中涉及到创建进程与结束进程等还是比较复杂的。下面通过一个流程图说明其工作原理
对于其中的一些函数,有自己的英文注释,想锻炼下英文表达能力,但是表达在语法和意思上还是有些错误的。
客户端工作代码
/////////////////////////////////////////
// the client code for TCP socket //
///////////////////////////////////////// #include <netinet/in.h> //for sockaddr_in
#include <sys/types.h> //for socket type
#include <sys/socket.h> //for socket()
#include <stdio.h> //for printf
#include <stdlib.h> //for exit
#include <string.h> //for memset
#include <time.h> //for time_t and time
#include <arpa/inet.h> //for INTERNET definition
#include <fcntl.h>
#include <errno.h> //for errno #define SERVER_PORT 5790
#define BUFFER_SIZE 1024 int main(int argc, char const *argv[])
{
if (argc!=)
{
printf("Usage:%s ServerIPAddress\n",argv[]);
exit();
} struct sockaddr_in client_addr;//define a universal socket struct
bzero(&client_addr,sizeof(client_addr));//clear the memory to zero
client_addr.sin_family=AF_INET;//internet protocol family
client_addr.sin_addr.s_addr=htons(INADDR_ANY);//INADDR_ANY represents the localhost ip address
client_addr.sin_port=htons();//0 represents the system will automatically allocate a free port int client_socket;
if ((client_socket=socket(AF_INET,SOCK_STREAM,))<)
{
fprintf(stderr, "socket:%s\n", strerror(errno));
exit();
} if (bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr))<)
{
fprintf(stderr, "bind:%s\n", strerror(errno));
exit();
} struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;//IPv4 protocol
if (inet_aton(argv[],&server_addr.sin_addr)==)//convert the input ip address
{
fprintf(stderr, "server address:%s\n", strerror(errno));
exit();
}
server_addr.sin_port=htons(SERVER_PORT); socklen_t server_addr_lentth=sizeof(server_addr);
if (connect(client_socket,(struct sockaddr*)&server_addr,server_addr_lentth)<)
{
fprintf(stderr, "can't connect to:%s because that %s:\n", argv[],strerror(errno));
exit();
} char buffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE); int length;
if ((length=recv(client_socket,buffer,BUFFER_SIZE,))<)
{
fprintf(stderr, "recv:%s\n", strerror(errno));
exit();
}
printf("\n%s\n",buffer); bzero(buffer,BUFFER_SIZE); strcpy(buffer,"Hello,this is client A!\n");
send(client_socket,buffer,BUFFER_SIZE,); int stream;
if ((stream=open("receive_data",O_RDWR))==-)
{
printf("The file data was not opened!\n");
}else
bzero(buffer,BUFFER_SIZE); length=;
while(length=recv(client_socket,buffer,BUFFER_SIZE,))
{
if (length<)
{
printf("Receive data from server %s failed!\n", argv[]);
break;
} int write_lenth=write(stream,buffer,sizeof(buffer));
if (write_lenth<length)
{
printf("File write Failed!\n");
break;
}
bzero(buffer,BUFFER_SIZE);
}
printf("Receive File From Server [%s] Finished.\n",argv[]); //close the file
close(stream); //close the socket
close(client_socket);
return ;
} /*
[Test]
gcc -o tcp_client tcp_client.c
./tcp_client 127.0.0.1
Hello,coming from the server!
Receive File From Server [127.0.0.1] Finished.
*/
共享内存通信方式
- int shmget(key_t key, size_t size, int shmflg);
- void *shmat(int shm_id, const void *shm_addr, int shmflg);
- int shmdt(const void *shmaddr);
- int shmctl(int shm_id, int command, struct shmid_ds *buf);
- struct shmid_ds
- {
- uid_t shm_perm.uid;
- uid_t shm_perm.gid;
- mode_t shm_perm.mode;
- };
共享内存的通信方式,系统根据我的偏好设置在内存中开辟一块空间,并对其进行相应的指定,然后我们的另一个进程可以按照同样的指定,也就是其标记,对其进行访问。创建共享内存,得到共享内存后,想内存中写入数据,然后另个进程得到内存,然后从中读出数据。
写入方代码实现:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h> #define PATH "/tmp"
#define SIZE 1024
#define ID 0 int main(int argc, char const *argv[])
{
void *shmAddr=NULL;
char dataAddr[]="Hello";
key_t key=ftok(PATH,ID);//create an unique key for the current IPC //create a shared memory area in the current process memory
int shmid;
if ((shmid=shmget(key,SIZE,|IPC_CREAT))==-)
{
fprintf(stderr, "shmget:%s\n", strerror(errno));
exit();
} shmAddr=shmat(shmid,(void*),);//map the shared memory to the process memory
if(shmAddr==(void*)-)
{
fprintf(stderr, "shmat:%s\n", strerror(errno));
}
strcpy(shmAddr,dataAddr);//copy the dataAddr to shmAddr //disconnect the process memory with the shared memory
if (shmdt(shmAddr)==-)
{
fprintf(stderr, "shmdt:%s\n", strerror(errno));
}
return ;
} /*
[Test]
gcc -o shm_write shm_write.c
./shm_write
*/
读取方代码实现:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h> #define PATH "/tmp"
#define SIZE 1024
#define ID 0 int main(int argc, char const *argv[])
{
char * shmAddr;
char * dataAddr="world";
key_t key=ftok(PATH,ID); //create a shared memory area int the current pprocess memory
int shmid;
if ((shmid=shmget((key_t)key,SIZE,|IPC_CREAT))==-)
{
fprintf(stderr, "shmget:%s\n", strerror(errno));
exit();
} shmAddr=shmat(shmid,(void*),);//map the shared memory to the process memory
if(shmAddr==(void*)-)
{
fprintf(stderr, "shmat:%s\n", strerror(errno));
} printf("%s\n",shmAddr); shmdt(shmAddr);
shmctl(shmid,IPC_RMID,NULL); return ;
} /*
[Test]
gcc -o shm_read shm_read.c
./shm_read
Hello
*/
消息队列通信
消息队列和有名管道有些类似的地方,最大相同点在于它可以用于在不同的进程之间进行通信,但是管道有一个劣势是,对于接收端其对与管道内的数据只能是接受,而不可以对其进行过滤选择,同时在写和读的时候还会出现堵塞.对于消息队列其在接收的时候也是会发生堵塞的,解除阻塞,只有当其接收到合适的消息或者该队列被删除了,这个阻塞才会解除。对于消息队列的通信流程是创建消息-》获得队列--》向队列中发送消息--》从队列中取出消息。
接收端实现代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h> #define MSG_FILE "/tmp" //a pathname for generating a unique key
#define BUFFER 512 //set the buffer size to 255 bytes
#define PERM S_IRUSR|S_IWUSR //allow the user to read and write struct msgbuffer
{
long mtype;
char mtext[BUFFER+];
};
typedef struct msgbuffer msgbuf; int main(int argc, char const *argv[])
{
//create a unique key
key_t key;
if ((key=ftok(MSG_FILE,BUFFER))==-)
{
fprintf(stderr, "ftok:%s\n", strerror(errno));
exit();
}else
{
printf("generate a key=%d\n", key);
} //get a message queue
int msgid;
msgbuf msg;
if ((msgid=msgget(key,PERM|IPC_CREAT))==-)
{
fprintf(stderr, "msgget:%s\n", strerror(errno));
exit();
} //get a message from the queue everytime
int i;
for (i = ; i < ; ++i)
{
msgrcv(msgid,&msg,sizeof(msgbuf),,);
printf("Receiver receive: %s\n", msg.mtext);
} //send the message to notice the sender
msg.mtype=;
char * myask="3 messages have received from you";
strncpy(msg.mtext,myask,BUFFER);
msgsnd(msgid,&msg,sizeof(msgbuf),IPC_NOWAIT); return ;
} /*
[Test]
gcc -o msg_receiver msg_receiver.c
./msg_reveiver
generate a key=589827
Receiver receive: I'm sender,there are some messages for you.
Receiver receive: Message 1
Receiver receive: Message 2
*/
发送端实现代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h> //for msg_get(),msg_rcv() #define MSG_FILE "/tmp" //a pathname for generating a unique key
#define BUFFER 512 //set the buffer size to 255 bytes
#define PERM S_IRUSR|S_IWUSR //allow the user to read and write struct msgbuffer
{
long mtype;
char mtext[BUFFER+];
};
typedef struct msgbuffer msgbuf; //create three message
char* message[]={"I'm sender,there are some messages for you.","Message 1","Message 2"}; int main(int argc, char const *argv[])
{
msgbuf msg;
key_t key;
int msgid; //create a unique key
if ((key=ftok(MSG_FILE,BUFFER))==-)
{
fprintf(stderr, "ftok:%s\n", strerror(errno));
exit();
}else
{
printf("generate a key=%d\n", key);
} //get a message queue
if ((msgid=msgget(key,PERM|IPC_CREAT))==-)
{
fprintf(stderr, "msgget:%s\n", strerror(errno));
exit();
} //set the type of the message
msg.mtype=; //send three messages to the receiver
int i;
for (i = ; i <; ++i)
{
strncpy(msg.mtext,message[i],BUFFER);
msgsnd(msgid,&msg,sizeof(msgbuf),IPC_NOWAIT);
} //receive the response from the receiver
memset(&msg,'\0',sizeof(msgbuf));//clear the msgbuf
/*receive the message,the third arguments show the type of message will be received */
msgrcv(msgid,&msg,sizeof(msgbuf),,);
printf("This is a message from the receiver,%s\n", msg.mtext); //delete the message queue.
if (msgctl(msgid,IPC_RMID,)==-)
{
fprintf(stderr, "ftok:%s\n", strerror(errno));
exit();
}
return ;
} /*
[Test]
gcc -o msg_sender msg_sender.c
./msg_sender
generate a key=589827
This is a message from the receiver,3 messages have received from you
*/
对于消息通信,其中的几个函数,这里在说明一下,msgrcv和msgsnd两个函数,对于其中的参数,第一个指定的是我们的消息队列的标示符,然后第二个是消息区域,然后是我们的消息长度,然后是消息类型,最后一个是用来指定是否堵塞。
上述为Linux下进程间通信的四种方式,实际开发中,我们传输数据类型和进程间的关系选择合适的方式。