发 帖  
原厂入驻New
[经验]

【100ASK_IMX6ULL(带屏) 开发板试用体验】基于unix socket的单主进程双从进程全双工通信

2020-11-8 15:08:18  376 开发板试用 unix
分享
0
       最近的工作项目中有用到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()等工作:

  1. int main()
  2. {
  3.     int len,i;
  4.     struct sockaddr_un un;
  5.     fd_socket = socket(AF_UNIX,SOCK_STREAM,0);
  6.     if(fd_socket < 0)
  7.     {
  8.         printf("Request socket faiLED!\n");
  9.     }
  10.     un.sun_family = AF_UNIX;
  11.     unlink(filename);
  12.     strcpy(un.sun_path , filename);
  13.     if(bind(fd_socket,(struct sockaddr *)&un , sizeof(un)) < 0)
  14.     {
  15.         printf("bind failed!\n");
  16.     }
  17.     if(listen(fd_socket,MAX_CONNECT_NUM) < 0)
  18.     {
  19.         printf("listen failed!\n");
  20.     }
  21.     <font size="4" color="DeepSkyBlue">pthread_create(&id2,NULL,Thread_Accept2,NULL);</font>
  22.     ...
复制代码

listen()下面当然就是轮询从进程接入即accpet(),为什么要用while(1)轮询呢,那就是即使从进程多次掉线重连也可以轮询到而不会拒绝第二次接入,accapt()轮询完毕之后就是recv()轮询,这个线程只能用于进行recv()的轮询,因为recv()函数是阻塞的,与tcp socket通信的特点完全一致:

  1.     while(1)
  2.         {
  3.         struct sockaddr_un client_addr;
  4.         char buffer[BUFFER_SIZE];
  5.         bzero(buffer,BUFFER_SIZE);
  6.         len = sizeof(client_addr);
  7.         <font size="4" color="RoyalBlue">fd_accept = accept(fd_socket,NULL,NULL);</font>
  8.         <font size="4" color="DeepSkyBlue">pthread_create(&id1,NULL,Thread_Send,NULL);</font>
  9.         if(fd_accept < 0)
  10.                 {
  11.             printf("accept failed\n");
  12.         }
  13.                 while(1)
  14.                 {
  15.                 int ret = recv(fd_accept,buffer,BUFFER_SIZE,0);
  16. ...
复制代码



因为recv()是阻塞类型会占用整个线程,因此发送线程就要另外开启了,发送线程可以发送任意报文到任意一个从进程,发送到哪个从进程取决于send()函数里面的fd_accpet参数是对应哪个从进程的,比如我这段函数就是两个从进程都发:
  1. int fd_socket,<font size="4" color="DeepSkyBlue">fd_accept,fd_accept2;</font>

  2. void *Thread_Send(void *arg)
  3. {
  4.     unsigned char buffer_input[BUFFER_SIZE];
  5.     while(1)
  6.     {
  7.         scanf("%s",buffer_input);
  8. <font size="4" color="DeepSkyBlue">        if(flag_c1_con)
  9.             send(fd_accept,buffer_input,BUFFER_SIZE,0);
  10.         if(flag_c2_con)
  11.             send(fd_accept2,buffer_input,BUFFER_SIZE,0);</font>
  12.     }
  13. }
复制代码

前面说了,因为recv()线程是阻塞的,因此,如果主进程要同时监听两个从进程的发送报文,那就必须再开一个针对第二个从进程的接收线程,但是怎么确定这个recv()线程对应的是那个从进程呢,怎么做到不会跟多个接入的从进程搞乱呢?好在socket代码库的设计师也考虑到这点,把accpet()函数也设置成阻塞轮询用于轮询从进程的接入,这样的话第一个从进程接入就进了第一个运行的accept()函数,获得fd_accept1,第二个从进程的接入就进了第二个运行的accpet()函数,获得fd_accept2,先来后到,不会搞乱主进程的处理顺序,从上面的代码可以关联得到,Thread_Accept2线程是在主进程的主线程执行完accept()之后创建的,那个accept()已经用于接收第一个从进程的接入了,因此这个Thread_Accept2线程执行accpet()所获得的fd_accept2就一定是第二个接入的从进程而不是第一个:
  1. void *Thread_Accept2(void *arg)
  2. {
  3.     unsigned char buffer2[BUFFER_SIZE];
  4.     while(1)
  5.     {
  6.         fd_accept2 = accept(fd_socket,NULL,NULL);
  7.         if(fd_accept2 < 0)
  8.         {
  9.             printf("accept2 failed\n");
  10.         }
  11.         else flag_c2_con = true;
  12.         while(1)
  13.         {
  14.           <font size="4" color="DeepSkyBlue">  int ret = recv(fd_accept2,buffer2,BUFFER_SIZE,0);</font>
  15.             if(ret < 0)
  16.             {
  17.                 printf("recv2 failed\n");
  18.                 break;
  19.             }
  20.             else if(ret > 0)
  21.                 printf("recvbuf2=%s\n",buffer2);
  22.             else if(ret == 0)
  23.             {
  24.                 printf("client2 disconnected\n");
  25.                 flag_c2_con = false;
  26.                 //pthread_cancel(id2);
  27.                 break;
  28.             }
  29.         }
  30.     }
  31. }
复制代码

说完主进程,再来说说从进程,从进程不需要建立文件和监听,只管主动接入,所以代码简单很多:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <sys/stat.h>
  5. #include <sys/socket.h>
  6. #include <sys/types.h>
  7. #include <sys/un.h>
  8. #include <errno.h>
  9. #include <stddef.h>
  10. #include <unistd.h>
  11. #include <pthread.h>

  12. int fd_socket;

  13. #define BUFFER_SIZE 1024
  14. const char *filename="/home/proj/usf";

  15. pthread_t id1;

  16. void *Thread_Recv(void *arg)
  17. {
  18.         unsigned char buffer_recv[BUFFER_SIZE];
  19.     while(1)
  20.     {
  21.                 int ret = recv(fd_socket,buffer_recv,BUFFER_SIZE,0);
  22.                 if(ret > 0)
  23.                         printf("recvbuf=%s\n",buffer_recv);
  24.         }
  25. }


  26. int main()
  27. {
  28.     struct sockaddr_un un;
  29.     char buffer[BUFFER_SIZE] = {"Interesting6666"};
  30.     un.sun_family = AF_UNIX;
  31.     strcpy(un.sun_path,filename);
  32.     fd_socket = socket(AF_UNIX,SOCK_STREAM,0);
  33.     if(fd_socket < 0)
  34.         {
  35.         printf("Request socket failed\n");
  36.     }
  37.     if(connect(fd_socket,(struct sockaddr *)&un,sizeof(un)) < 0)
  38.         {
  39.         printf("connect socket failed\n");
  40.     }
  41.         pthread_create(&id1,NULL,Thread_Recv,NULL);
  42.         while(1)
  43.         {
  44.                 //printf("send buf.\n");
  45.             //send(fd_socket,buffer,BUFFER_SIZE,0);
  46.                 //sleep(1);

  47.                 scanf("%s",buffer);
  48.                 send(fd_socket,buffer,BUFFER_SIZE,0);
  49.         }
  50.     return 0;
  51. }

复制代码
与tcp socket通信基本一致,多开一个线程负责阻塞接收,主线程负责发送,非常简单。

makefile别忘记了加-lpthread支持:
  1. PROG = main
  2. SRCS = main.cc lcd.cc camera.cc

  3. PROG_USS = uss
  4. SRCS_USS = uss.cc

  5. PROG_USC = usc
  6. SRCS_USC = usc.cc

  7. CLEANFILES = $(PROG)

  8. CFLAGS += -ljpeg -lpthread
  9. #LDFLAGS +=
  10. INCLUDES ?=

  11. all: $(PROG) $(PROG_USS) $(PROG_USC)

  12. $(PROG): $(SRCS)
  13.         $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)

  14. $(PROG_USS): $(SRCS_USS)
  15.         $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)

  16. $(PROG_USC): $(SRCS_USC)
  17.         $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)

  18. clean:
  19.         rm -f $(CLEANFILES)


复制代码


实测效果,先运行主进程建立socket通信,在/home/proj下生成磁盘文件usf:
27.jpg

两个从进程接入:
28.jpg

主进程向两个从进程发送报文:
29.jpg

两个从进程向主进程分别发送报文:
30.jpg

从进程2主动关闭:
31.jpg

从进程2主动重连,然后两个从进程主动关闭并重连,最后主进程向从进程发送报文并收到两个从进程的主动发送报文:
32.jpg

总结来看,这次DEMO的所有测试都有一个大前提那就是主进程从来没有主动关闭,我在想,如果主进程主动关闭的话,会不会影响通信,然后试了一把,主进程关闭之后从进程也会在发送之后自动关闭,说明这个unix socket系统的稳定性是建立在主进程的稳定性上的,如果主进程突然中途gg,那么从进程也会跟着歇菜,但是实际项目

肯定有解决这个问题的方法,要么拼命提高主进程稳定性,要么做主进程gg之后的合理处理,这个看我今后的继续深入学习,总的来说unix socket真的是一个非常好用的开发工具,i了i了。


评论

高级模式
您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发经验
关闭

站长推荐 上一条 /9 下一条

快速回复 返回顶部 返回列表