最近的工作项目中有用到unix socket进行进程间通信,这个库我以前没有听说过,最近用起来才发现真的好用,对于Linux小白来说代码非常简单,虽说效率没有共享内存快,但是胜在操作方便,基本操作库函数与大家熟知的tcp socket非常相似(库函数名完全一样),支持单主机(主进程)多从机(从进程)并发通信互不干扰,使用到的系统存储资源仅仅是一个存在磁盘上的二进制文件。
unix socket通信和tcp socket通信的库函数名称完全一样,不同的地方有两点,第一是使用到的socket通信结构体对象struct sockaddr_un,要设置sun_family = AF_UNIX即通信族(?字面意思是这样,实际上可以理解为协议类型),而tcp/udp的socket通信的通信族则是AF_INET,这是第一点;第二点是,tcp/udp的socket通信,从机与主机握手确认身份是通过主机IP地址和端口号,而unix socket是通过存储于磁盘上的文件实现,这个文件由主进程bind()生成,只要主进程与从进程在生成socket(socket()函数)时读写的是同一个磁盘文件,那么就可以通过这个文件实现进程间通信,且多个从进程之间互不干扰,非常方便,Unix/Linux系统设计这个socket机制就是为了方便开发者这样调用,降低开发入门门槛。
在这次的帖子中,我设计了一个主进程+两个从进程构成了一个三进程的全双工通信系统,主进程里面有三个线程,分别是发送线程,接收线程1和接收线程2,在创建三个线程之前,先要做unix socket的初始化函数设计,即socket() bind() listen()等工作:
- int main()
- {
- int len,i;
- struct sockaddr_un un;
- fd_socket = socket(AF_UNIX,SOCK_STREAM,0);
- if(fd_socket < 0)
- {
- printf("Request socket failed!n");
- }
- un.sun_family = AF_UNIX;
- unlink(filename);
- strcpy(un.sun_path , filename);
- if(bind(fd_socket,(struct sockaddr *)&un , sizeof(un)) < 0)
- {
- printf("bind failed!n");
- }
- if(listen(fd_socket,MAX_CONNECT_NUM) < 0)
- {
- printf("listen failed!n");
- }
- pthread_create(&id2,NULL,Thread_Accept2,NULL);
- ...
复制代码
listen()下面当然就是轮询从进程接入即accpet(),为什么要用while(1)轮询呢,那就是即使从进程多次掉线重连也可以轮询到而不会拒绝第二次接入,accapt()轮询完毕之后就是recv()轮询,这个线程只能用于进行recv()的轮询,因为recv()函数是阻塞的,与tcp socket通信的特点完全一致:
- while(1)
- {
- struct sockaddr_un client_addr;
- char buffer[BUFFER_SIZE];
- bzero(buffer,BUFFER_SIZE);
- len = sizeof(client_addr);
- fd_accept = accept(fd_socket,NULL,NULL);
- pthread_create(&id1,NULL,Thread_Send,NULL);
- if(fd_accept < 0)
- {
- printf("accept failedn");
- }
- while(1)
- {
- int ret = recv(fd_accept,buffer,BUFFER_SIZE,0);
- ...
复制代码
因为recv()是阻塞类型会占用整个线程,因此发送线程就要另外开启了,发送线程可以发送任意报文到任意一个从进程,发送到哪个从进程取决于send()函数里面的fd_accpet参数是对应哪个从进程的,比如我这段函数就是两个从进程都发:
- int fd_socket,fd_accept,fd_accept2;
- void *Thread_Send(void *arg)
- {
- unsigned char buffer_input[BUFFER_SIZE];
- while(1)
- {
- scanf("%s",buffer_input);
- if(flag_c1_con)
- send(fd_accept,buffer_input,BUFFER_SIZE,0);
- if(flag_c2_con)
- send(fd_accept2,buffer_input,BUFFER_SIZE,0);
- }
- }
复制代码
前面说了,因为recv()线程是阻塞的,因此,如果主进程要同时监听两个从进程的发送报文,那就必须再开一个针对第二个从进程的接收线程,但是怎么确定这个recv()线程对应的是那个从进程呢,怎么做到不会跟多个接入的从进程搞乱呢?好在socket代码库的设计师也考虑到这点,把accpet()函数也设置成阻塞轮询用于轮询从进程的接入,这样的话第一个从进程接入就进了第一个运行的accept()函数,获得fd_accept1,第二个从进程的接入就进了第二个运行的accpet()函数,获得fd_accept2,先来后到,不会搞乱主进程的处理顺序,从上面的代码可以关联得到,Thread_Accept2线程是在主进程的主线程执行完accept()之后创建的,那个accept()已经用于接收第一个从进程的接入了,因此这个Thread_Accept2线程执行accpet()所获得的fd_accept2就一定是第二个接入的从进程而不是第一个:
- void *Thread_Accept2(void *arg)
- {
- unsigned char buffer2[BUFFER_SIZE];
- while(1)
- {
- fd_accept2 = accept(fd_socket,NULL,NULL);
- if(fd_accept2 < 0)
- {
- printf("accept2 failedn");
- }
- else flag_c2_con = true;
- while(1)
- {
- int ret = recv(fd_accept2,buffer2,BUFFER_SIZE,0);
- if(ret < 0)
- {
- printf("recv2 failedn");
- break;
- }
- else if(ret > 0)
- printf("recvbuf2=%sn",buffer2);
- else if(ret == 0)
- {
- printf("client2 disconnectedn");
- flag_c2_con = false;
- //pthread_cancel(id2);
- break;
- }
- }
- }
- }
复制代码
说完主进程,再来说说从进程,从进程不需要建立文件和监听,只管主动接入,所以代码简单很多:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- int fd_socket;
- #define BUFFER_SIZE 1024
- const char *filename="/home/proj/usf";
- pthread_t id1;
- void *Thread_Recv(void *arg)
- {
- unsigned char buffer_recv[BUFFER_SIZE];
- while(1)
- {
- int ret = recv(fd_socket,buffer_recv,BUFFER_SIZE,0);
- if(ret > 0)
- printf("recvbuf=%sn",buffer_recv);
- }
- }
- int main()
- {
- struct sockaddr_un un;
- char buffer[BUFFER_SIZE] = {"Interesting6666"};
- un.sun_family = AF_UNIX;
- strcpy(un.sun_path,filename);
- fd_socket = socket(AF_UNIX,SOCK_STREAM,0);
- if(fd_socket < 0)
- {
- printf("Request socket failedn");
- }
- if(connect(fd_socket,(struct sockaddr *)&un,sizeof(un)) < 0)
- {
- printf("connect socket failedn");
- }
- pthread_create(&id1,NULL,Thread_Recv,NULL);
- while(1)
- {
- //printf("send buf.n");
- //send(fd_socket,buffer,BUFFER_SIZE,0);
- //sleep(1);
- scanf("%s",buffer);
- send(fd_socket,buffer,BUFFER_SIZE,0);
- }
- return 0;
- }
复制代码
与tcp socket通信基本一致,多开一个线程负责阻塞接收,主线程负责发送,非常简单。
makefile别忘记了加-lpthread支持:
- PROG = main
- SRCS = main.cc lcd.cc camera.cc
- PROG_USS = uss
- SRCS_USS = uss.cc
- PROG_USC = usc
- SRCS_USC = usc.cc
- CLEANFILES = $(PROG)
- CFLAGS += -ljpeg -lpthread
- #LDFLAGS +=
- INCLUDES ?=
- all: $(PROG) $(PROG_USS) $(PROG_USC)
- $(PROG): $(SRCS)
- $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
- $(PROG_USS): $(SRCS_USS)
- $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
- $(PROG_USC): $(SRCS_USC)
- $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
- clean:
- rm -f $(CLEANFILES)
复制代码
实测效果,先运行主进程建立socket通信,在/home/proj下生成磁盘文件usf:
两个从进程接入:
主进程向两个从进程发送报文:
两个从进程向主进程分别发送报文:
从进程2主动关闭:
从进程2主动重连,然后两个从进程主动关闭并重连,最后主进程向从进程发送报文并收到两个从进程的主动发送报文:
总结来看,这次DEMO的所有测试都有一个大前提那就是主进程从来没有主动关闭,我在想,如果主进程主动关闭的话,会不会影响通信,然后试了一把,主进程关闭之后从进程也会在发送之后自动关闭,说明这个unix socket系统的稳定性是建立在主进程的稳定性上的,如果主进程突然中途gg,那么从进程也会跟着歇菜,但是实际项目
肯定有解决这个问题的方法,要么拼命提高主进程稳定性,要么做主进程gg之后的合理处理,这个看我今后的继续深入学习,总的来说unix socket真的是一个非常好用的开发工具,i了i了。
|