前言:前些天实现了unix socket的通信,本想完完全全自己写一篇博客记录下来,但写的时候发现对于socket知识的理解还有所欠缺,故引用其他博客写的比较好的部分综合一下,这样让大家更容易理解。
一、Unix socket概述(参考于博客http://blog.csdn.net/bingqingsuimeng/article/details/8470029):
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIXDomain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。
二、socket工作流程:
服务端:
1. socket: 建立一个socket
2. bind: 将这个socket绑定在某个文件上(AF_UNIX)或某个端口上(AF_INET),我们会分别介绍这两种。
3. listen: 开始监听
4. accept: 如果监听到客户端连接,则调用accept接收这个连接并同时新建一个socket来和客户进行通信
5. read/write:读取或发送数据到客户端
6. close: 通信完成后关闭socket
客户端:
1. socket: 建立一个socket
2. connect: 主动连接服务器端的某个文件(AF_UNIX)或某个端口(AF_INET)
3. read/write:如果服务器同意连接(accept),则读取或发送数据到服务器端
4. close: 通信完成后关闭socket
如图所示(此图参考于博客http://blog.csdn.net/hguisu/article/details/7445768/,虽然是网络TCP的传输流程图,但原理流程是一样的):
三、unix socket的具体实现:
本例实现的功能:在linux下建立三个进程(分别是客户端socket发送进程、客户端socket接受进程、服务端),使用unix socket实现进程间的通信。
本文代码实现参考于http://blog.csdn.net/shanzhizi/article/details/16882087
客户端的作用是发送数据至服务端或从服务端接受数据。
服务端的作用是监听客户端的请求并处理。
在本例中,我对原代码做了一点改动,原代码中是client发送四次数据至server,server接收四次数据并显示。我改成了server一直接收连接并根据我定制的小协议来进行下一次的数据传输方向。
本协议每次完整的数据传输分为两个步骤:
1、在server端绑定socket并开启监听后,client会先发送一次命令数据,由命令数据决定下一次的数据方向。
2、client和server会依据命令数据,决定下一次是接受数据还是发送数据,以便做不同的处理。
#define READ_FROM_CLIENT 0X01
#define WRITE_TO_CLIENT 0x02
代码实现如下:
服务端server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_DOMAIN "/tmp/UNIX.domain"
#define READ_FROM_CLIENT 0X01
#define WRITE_TO_CLIENT 0x02
int main(void)
{
socklen_t clt_addr_len;
int listen_fd;
int com_fd;
int ret;
//int i;
static char data_buf[1024];
int len;
struct sockaddr_un clt_addr;
struct sockaddr_un srv_addr;
listen_fd = socket(PF_UNIX, SOCK_STREAM, 0);
if(listen_fd < 0) {
perror("cannot create communication socket");
return 1;
}
//set server addr_param
srv_addr.sun_family = AF_UNIX;
strcpy(srv_addr.sun_path,UNIX_DOMAIN);
unlink(UNIX_DOMAIN);
//bind sockfd & addr
ret = bind(listen_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if(ret == -1) {
perror("cannot bind server socket");
close(listen_fd);
unlink(UNIX_DOMAIN);
return 1;
}
//listen sockfd
ret = listen(listen_fd,1);
if(ret == -1) {
perror("cannot listen the client connect request");
close(listen_fd);
unlink(UNIX_DOMAIN);
return 1;
}
while(1) {
//have connect request use accept
len = sizeof(clt_addr);
com_fd = accept(listen_fd,(struct sockaddr*)&clt_addr,&len);
if(com_fd < 0) {
perror("cannot accept client connect request");
close(listen_fd);
unlink(UNIX_DOMAIN);
return 1;
}
//read and printf sent client info
printf("\nReceive from client:\n");
memset(data_buf, 0, 1024);
read(com_fd, data_buf, sizeof(data_buf));
printf("%d Request is %X\n", data_buf[0]);
//Read from client
if(data_buf[0] == READ_FROM_CLIENT) {
memset(data_buf, 0, 1024);
read(com_fd, data_buf, sizeof(data_buf));
printf("The data read from client is %s\n:",data_buf);
}
//Send to client
if(data_buf[0] == WRITE_TO_CLIENT) {
memset(data_buf, 0, 1024);
strcpy(data_buf, "message from server!!");
write(com_fd, data_buf, sizeof(data_buf));
printf("The data send to client is %s\n:",data_buf);
}
memset(data_buf, 0, 1024);
close(com_fd);
}
//close(listen_fd);
//unlink(UNIX_DOMAIN);
//return 0;
}
客户端client_sent.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_DOMAIN "/tmp/UNIX.domain"
int main(void)
{
int connect_fd;
int ret;
char snd_buf[1024];
static struct sockaddr_un srv_addr;
//create unix socket
connect_fd = socket(PF_UNIX, SOCK_STREAM, 0);
if(connect_fd < 0) {
perror("cannot create communication socket");
return 1;
}
srv_addr.sun_family = AF_UNIX;
strcpy(srv_addr.sun_path,UNIX_DOMAIN);
//connect server
ret = connect(connect_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if(ret == -1) {
perror("cannot connect to the server");
close(connect_fd);
return 1;
}
memset(snd_buf, 0, 1024);
snd_buf[0] = 0x01;
//send command
write(connect_fd, snd_buf, sizeof(snd_buf));
//send info server
memset(snd_buf, 0, 1024);
strcpy(snd_buf, "message from client");
write(connect_fd, snd_buf, sizeof(snd_buf));
close(connect_fd);
return 0;
}
客户端client_receive.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_DOMAIN "/tmp/UNIX.domain"
int main(void)
{
int connect_fd;
int ret;
char snd_buf[1024];
static struct sockaddr_un srv_addr;
//create unix socket
connect_fd = socket(PF_UNIX, SOCK_STREAM, 0);
if(connect_fd < 0) {
perror("cannot create communication socket");
return 1;
}
srv_addr.sun_family = AF_UNIX;
strcpy(srv_addr.sun_path,UNIX_DOMAIN);
//connect server
ret = connect(connect_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if(ret == -1) {
perror("cannot connect to the server");
close(connect_fd);
return 1;
}
memset(snd_buf, 0, 1024);
snd_buf[0] = 0x02;
//send command
write(connect_fd, snd_buf, sizeof(snd_buf));
//read from server
memset(snd_buf, 0, 1024);
read(connect_fd, snd_buf, sizeof(snd_buf));
printf("The data read from server is %s\n:",snd_buf);
close(connect_fd);
return 0;
}
使用
#gcc server.c -o server
#gcc client_sent -o client_sent
#gcc client_receive -o client_receive
分别生成对应的可执行文件
先在后台运行服务端:
#./server &
再直接运行客户端的发送程序和接受程序便可得到结果。(#./client_sent #./client_receive)
ps:本例中采用的read和write函数都是阻塞函数,会导致进程一直处于阻塞状态,若想了解非阻塞的传输方式,请参考博客http://blog.csdn.net/guxch/article/details/7041052。
若有什么不明白或技术交流,请留言,谢谢。