UNIX Socket(IPC)

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也是可靠的,消息既不会丢失也不会顺序错乱。

UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIX Domain Socket通讯的。

使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。

UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。


今天我们介绍如何编写Linux下的TCP程序,关于UDP程序可以参考这里:

http://blog.csdn.net/htttw/article/details/7519971

本文绝大部分是参考《Linux程序设计(第4版)》的第15章套接字


服务器端的步骤如下:

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




上面是整个流程,我们先给出一个例子,具体分析会在之后给出。例子实现的功能是客户端发送一个字符到服务器,服务器将这个字符+1后送回客户端,客户端再把它打印出来

Makefile:

[plain]  view plain copy
  1. all: tcp_client.c tcp_server.c  
  2.     gcc -g -Wall -o tcp_client tcp_client.c  
  3.     gcc -g -Wall -o tcp_server tcp_server.c  
  4.   
  5. clean:  
  6.     rm -rf *.o tcp_client tcp_server  

tcp_server.c:

[cpp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7.   
  8. int main()  
  9. {  
  10.   /* delete the socket file */  
  11.   unlink("server_socket");  
  12.     
  13.   /* create a socket */  
  14.   int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
  15.     
  16.   struct sockaddr_un server_addr;  
  17.   server_addr.sun_family = AF_UNIX;  
  18.   strcpy(server_addr.sun_path, "server_socket");  
  19.     
  20.   /* bind with the local file */  
  21.   bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
  22.     
  23.   /* listen */  
  24.   listen(server_sockfd, 5);  
  25.     
  26.   char ch;  
  27.   int client_sockfd;  
  28.   struct sockaddr_un client_addr;  
  29.   socklen_t len = sizeof(client_addr);  
  30.   while(1)  
  31.   {  
  32.     printf("server waiting:\n");  
  33.       
  34.     /* accept a connection */  
  35.     client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);  
  36.       
  37.     /* exchange data */  
  38.     read(client_sockfd, &ch, 1);  
  39.     printf("get char from client: %c\n", ch);  
  40.     ++ch;  
  41.     write(client_sockfd, &ch, 1);  
  42.       
  43.     /* close the socket */  
  44.     close(client_sockfd);  
  45.   }  
  46.     
  47.   return 0;  
  48. }  


tcp_client.c:

[cpp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7.   
  8. int main()  
  9. {  
  10.   /* create a socket */  
  11.   int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
  12.     
  13.   struct sockaddr_un address;  
  14.   address.sun_family = AF_UNIX;  
  15.   strcpy(address.sun_path, "server_socket");  
  16.     
  17.   /* connect to the server */  
  18.   int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));  
  19.   if(result == -1)  
  20.   {  
  21.     perror("connect failed: ");  
  22.     exit(1);  
  23.   }  
  24.     
  25.   /* exchange data */  
  26.   char ch = 'A';  
  27.   write(sockfd, &ch, 1);  
  28.   read(sockfd, &ch, 1);  
  29.   printf("get char from server: %c\n", ch);  
  30.     
  31.   /* close the socket */  
  32.   close(sockfd);  
  33.     
  34.   return 0;  
  35. }  




如果我们首先运行tcp_client,会提示没有这个文件:


因为我们是以AF_UNIX方式进行通信的,这种方式是通过文件来将服务器和客户端连接起来的,因此我们应该先运行tcp_server,创建这个文件,默认情况下,这个文件会创建在当前目录下,并且第一个s表示它是一个socket文件



程序运行的结果如下图:




下面我们详细讲解:

1.

我们调用socket函数创建一个socket:

int socket(int domain, int type, int protocol)

domain:指定socket所属的域,常用的是AF_UNIX或AF_INET

AF_UNIX表示以文件方式创建socket,AF_INET表示以端口方式创建socket(我们会在后面详细讲解AF_INET)

type:指定socket的类型,可以是SOCK_STREAM或SOCK_DGRAM

SOCK_STREAM表示创建一个有序的,可靠的,面向连接的socket,因此如果我们要使用TCP,就应该指定为SOCK_STREAM

SOCK_DGRAM表示创建一个不可靠的,无连接的socket,因此如果我们要使用UDP,就应该指定为SOCK_DGRAM

protocol:指定socket的协议类型,我们一般指定为0表示由第一第二两个参数自动选择。

socket()函数返回新创建的socket,出错则返回-1


2.

地址格式:

常用的有两种socket域:AF_UNIX或AF_INET,因此就有两种地址格式:sockaddr_un和sockaddr_in,分别定义如下:

[cpp]  view plain copy
  1. struct sockaddr_un  
  2. {  
  3.   sa_family_t sun_family;  /* AF_UNIX */  
  4.   char sun_path[];         /* pathname */  
  5. }  
  6.   
  7.   
  8. struct sockaddr_in  
  9. {  
  10.   short int sin_family;          /* AF_INET */  
  11.   unsigned short int sin_port;   /* port number */  
  12.   struct in_addr sin_addr;       /* internet address */  
  13. }  

其中in_addr正是用来描述一个ip地址的:

[cpp]  view plain copy
  1. struct in_addr  
  2. {  
  3.   unsigned long int s_addr;  
  4. }  


从上面的定义我们可以看出,sun_path存放socket的本地文件名,sin_addr存放socket的ip地址,sin_port存放socket的端口号


3.

创建完一个socket后,我们需要使用bind将其绑定:

int bind(int socket, const struct sockaddr * address, size_t address_len)

如果我们使用AF_UNIX来创建socket,相应的地址格式是sockaddr_un,而如果我们使用AF_INET来创建socket,相应的地址格式是sockaddr_in,因此我们需要将其强制转换为sockaddr这一通用的地址格式类型,而sockaddr_un中的sun_family和sockaddr_in中的sin_family分别说明了它的地址格式类型,因此bind()函数就知道它的真实的地址格式。第三个参数address_len则指明了真实的地址格式的长度。

bind()函数正确返回0,出错返回-1



4.

接下来我们需要开始监听了:

int listen(int socket, int backlog)

backlog:等待连接的最大个数,如果超过了这个数值,则后续的请求连接将被拒绝

listen()函数正确返回0,出错返回-1



5.

接受连接:

int accept(int socket, struct sockaddr * address, size_t * address_len)

同样,第二个参数也是一个通用地址格式类型,这意味着我们需要进行强制类型转化

这里需要注意的是,address是一个传出参数,它保存着接受连接的客户端的地址,如果我们不需要,将address置为NULL即可。

address_len:我们期望的地址结构的长度,注意,这是一个传入和传出参数,传入时指定我们期望的地址结构的长度,如果多于这个值,则会被截断,而当accept()函数返回时,address_len会被设置为客户端连接的地址结构的实际长度。

另外如果没有客户端连接时,accept()函数会阻塞

accept()函数成功时返回新创建的socket描述符,出错时返回-1




6.

客户端通过connect()函数与服务器连接:

int connect(int socket, const struct sockaddr * address, size_t address_len)

对于第二个参数,我们同样需要强制类型转换

address_len指明了地址结构的长度

connect()函数成功时返回0,出错时返回-1



7.

双方都建立连接后,就可以使用常规的read/write函数来传递数据了



8.

通信完成后,我们需要关闭socket:

int close(int fd)

close是一个通用函数(和read,write一样),不仅可以关闭文件描述符,还可以关闭socket描述符




另一个例程:

[html]  view plain  copy
  1. socket服务器端:server.c  
[html]  view plain  copy
  1. //socket读写默认的是非阻塞的  
[html]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <malloc.h>  
  5. #include <sys/types.h>  
  6. #include <errno.h>  
  7. #include <sys/stat.h>  
  8. #include <fcntl.h>  
  9. #include <sys/select.h>  
  10. #include <unistd.h>  
  11. #include <termios.h>  
  12. #include <sys/stat.h>  
  13. /**********定时器头文件***************/  
  14. #include <sys/time.h>   
  15. #include <signal.h>   
  16. /***********进程间SOCKET通信头文件**********/  
  17. #include <sys/socket.h>   
  18. #include <sys/un.h>   
  19.   
  20. #define UNIX_DOMAIN "/tmp/UNIX2.domain"   
  21.   
  22. static char recv_php_buf[256];  //接收client数据的缓冲  
  23. static int recv_php_num=0;      //接收client数据的总长度  
  24. const char recv_php_buf1[20]={0x00,0x01,0x02,0x03,0x04,0x05,0x06};  
  25. void main()  
  26. {  
  27.     socklen_t clt_addr_len;   
  28.     int listen_fd;   
  29.     int com_fd;   
  30.     int ret=0;   
  31.     int i;   
  32.       
  33.     int len;   
  34.     struct sockaddr_un clt_addr;   
  35.     struct sockaddr_un srv_addr;   
  36.     while(1)  
  37.     {  
  38.         //创建用于通信的套接字,通信域为UNIX通信域   
  39.   
  40.         listen_fd=socket(AF_UNIX,SOCK_STREAM,0);   
  41.         if(listen_fd<0)  
  42.         {   
  43.             perror("cannot create listening socket");   
  44.             continue;   
  45.         }   
  46.         else  
  47.         {  
  48.             while(1)  
  49.             {  
  50.                 //设置服务器地址参数   
  51.                 srv_addr.sun_family=AF_UNIX;   
  52.                 strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1);   
  53.                 unlink(UNIX_DOMAIN);   
  54.                 //绑定套接字与服务器地址信息   
  55.                 ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));   
  56.                 if(ret==-1)  
  57.                 {   
  58.                     perror("cannot bind server socket");   
  59.                     close(listen_fd);   
  60.                     unlink(UNIX_DOMAIN);   
  61.                     break;   
  62.                 }   
  63.                 //对套接字进行监听,判断是否有连接请求   
  64.                 ret=listen(listen_fd,1);   
  65.                 if(ret==-1)  
  66.                 {   
  67.                     perror("cannot listen the client connect request");   
  68.                     close(listen_fd);   
  69.                     unlink(UNIX_DOMAIN);   
  70.                     break;   
  71.                 }   
  72.                 chmod(UNIX_DOMAIN,00777);//设置通信文件权限  
  73.                 while(1)  
  74.                 {  
  75.                     //当有连接请求时,调用accept函数建立服务器与客户机之间的连接   
  76.                     len=sizeof(clt_addr);   
  77.                     com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len);   
  78.                     if(com_fd<0)  
  79.                     {   
  80.                         perror("cannot accept client connect request");   
  81.                         close(listen_fd);   
  82.                         unlink(UNIX_DOMAIN);   
  83.                         break;   
  84.                     }   
  85.                     //读取并输出客户端发送过来的连接信息   
  86.                     memset(recv_php_buf,0,256);   
  87.                     recv_php_num=read(com_fd,recv_php_buf,sizeof(recv_php_buf));   
  88.                     printf("\n=====recv=====\n");  
  89.                     for(i=0;i<recv_php_num;i++)   
  90.                     printf("%d ",recv_php_buf[i]);   
  91.                     printf("\n");  
  92.                     /*if(recv_php_buf[0]==0x02)  
  93.                     {  
  94.                         if(recv_php_buf[recv_php_num-1]==0x00)  
  95.                         {  
  96.                             recv_php_buf[recv_php_num-1]=0x01;  
  97.                         }  
  98.                         else  
  99.                         {  
  100.                             recv_php_buf[recv_php_num-1]=0x00;  
  101.                         }  
  102.                     }  
  103.                     */  
  104.                     //recv_php_buf[20]+=1;  
  105.                     write(com_fd,recv_php_buf,recv_php_num);  
  106.                     printf("\n=====send=====\n");  
  107.                     for(i=0;i<recv_php_num;i++)   
  108.                     printf("%d ",recv_php_buf[i]);   
  109.                     printf("\n");  
  110.                     //write(com_fd,recv_php_buf,20);  
  111.                     close(com_fd);//注意要关闭连接符号,不然会超过连接数而报错  
  112.                 }  
  113.                   
  114.             }  
  115.   
  116.         }  
  117.     }  
[html]  view plain  copy
  1. socket用户端:client.c  
[cpp]  view plain  copy
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10. #include   
  11. #include   
  12. #include   
  13. /**********定时器头文件***************/  
  14. #include    
  15. #include    
  16. /***********进程间SOCKET通信头文件**********/  
  17. #include    
  18. #include    
  19.   
  20. #include   
  21. #pragma pack(1)         //设定为1字节对齐  
  22. #define UNIX_DOMAIN2 "/tmp/UNIX2.domain"   
  23. static char recv_php_buf[256]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};      
  24. struct test  
  25. {  
  26.     char a;  
  27.     int b;  
  28.     int c;  
  29.   
  30.   
  31.   
  32.   
  33. }se;  
  34. void main(void)  
  35. {  
  36.     int connect_fd;  
  37.     int ret=0;  
  38.     int i;  
  39.     static struct sockaddr_un srv_addr;   
  40.     printf("ipc通信线程\n");  
  41.     //while(1)  
  42.     //{  
  43.         //创建用于通信的套接字,通信域为UNIX通信域   
  44.         connect_fd=socket(AF_UNIX,SOCK_STREAM,0);   
  45.         printf("%d\n",connect_fd);   
  46.         if(connect_fd<0)  
  47.         {   
  48.             perror("cannot create communication socket");  
  49.             printf("%d\n",connect_fd);   
  50.             //continue;  
  51.         }   
  52.         else  
  53.         {  
  54.             //while(1)  
  55.             //{  
  56.                 srv_addr.sun_family=AF_UNIX;   
  57.                 strcpy(srv_addr.sun_path,UNIX_DOMAIN2);  
  58.               
  59.                 //连接服务器   
  60.                 ret=connect(connect_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));   
  61.                 if(ret==-1)  
  62.                 {   
  63.                     close(connect_fd);   
  64.                     printf("connect fail\n");  
  65.                     //break;            //重新创建socket  
  66.                 }  
  67.                 else  
  68.                 {     
  69.                 //否则,连接服务器成功  
  70.                 //while(1)  
  71.                 //{  
  72.                     se.a=0x01;  
  73.                     se.b=0x01020304;  
  74.                     se.c=0x05060708;  
  75.                     write(connect_fd,recv_php_buf,20);//将数据传送到外部应用程序,发送实际长度  
  76.                     //write(connect_fd,&se,sizeof(struct test));  
  77.                     memset(recv_php_buf,0,sizeof(recv_php_buf));                             //清空socket_buf  
  78.                     //sleep(1);  
  79.                     //fcntl(connect_fd,F_SETEL,O_NONBLOCK);  
  80.                     read(connect_fd,recv_php_buf,sizeof(recv_php_buf));  
  81.                     printf("receive over\n");  
  82.                     for(i=0;i<20;i++)  
  83.                     printf("%x ",recv_php_buf[i]);  
  84.                     //printf("%x ",se.a);  
  85.                     //printf("%x ",se.b);  
  86.                     //printf("%x ",se.c);  
  87.                     close(connect_fd);  
  88.                     //break;                              
  89.                 //}  
  90.                 }  
  91.               
  92.             //}  
  93.             //close(connect_fd);  
  94.         }  
  95.       
  96.     //}  
  97.   
  98. }  

你可能感兴趣的:(linux)