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:
- all: tcp_client.c tcp_server.c
- gcc -g -Wall -o tcp_client tcp_client.c
- gcc -g -Wall -o tcp_server tcp_server.c
-
- clean:
- rm -rf *.o tcp_client tcp_server
tcp_server.c:
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
-
- unlink("server_socket");
-
-
- int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
-
- struct sockaddr_un server_addr;
- server_addr.sun_family = AF_UNIX;
- strcpy(server_addr.sun_path, "server_socket");
-
-
- bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
-
-
- listen(server_sockfd, 5);
-
- char ch;
- int client_sockfd;
- struct sockaddr_un client_addr;
- socklen_t len = sizeof(client_addr);
- while(1)
- {
- printf("server waiting:\n");
-
-
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);
-
-
- read(client_sockfd, &ch, 1);
- printf("get char from client: %c\n", ch);
- ++ch;
- write(client_sockfd, &ch, 1);
-
-
- close(client_sockfd);
- }
-
- return 0;
- }
tcp_client.c:
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
-
- int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
-
- struct sockaddr_un address;
- address.sun_family = AF_UNIX;
- strcpy(address.sun_path, "server_socket");
-
-
- int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
- if(result == -1)
- {
- perror("connect failed: ");
- exit(1);
- }
-
-
- char ch = 'A';
- write(sockfd, &ch, 1);
- read(sockfd, &ch, 1);
- printf("get char from server: %c\n", ch);
-
-
- close(sockfd);
-
- return 0;
- }
如果我们首先运行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,分别定义如下:
- struct sockaddr_un
- {
- sa_family_t sun_family;
- char sun_path[];
- }
-
-
- struct sockaddr_in
- {
- short int sin_family;
- unsigned short int sin_port;
- struct in_addr sin_addr;
- }
其中in_addr正是用来描述一个ip地址的:
- struct in_addr
- {
- unsigned long int s_addr;
- }
从上面的定义我们可以看出,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描述符
另一个例程:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <malloc.h>
- #include <sys/types.h>
- #include <errno.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <sys/select.h>
- #include <unistd.h>
- #include <termios.h>
- #include <sys/stat.h>
- /**********定时器头文件***************/
- #include <sys/time.h>
- #include <signal.h>
- /***********进程间SOCKET通信头文件**********/
- #include <sys/socket.h>
- #include <sys/un.h>
-
- #define UNIX_DOMAIN "/tmp/UNIX2.domain"
-
- static char recv_php_buf[256]; //接收client数据的缓冲
- static int recv_php_num=0; //接收client数据的总长度
- const char recv_php_buf1[20]={0x00,0x01,0x02,0x03,0x04,0x05,0x06};
- void main()
- {
- socklen_t clt_addr_len;
- int listen_fd;
- int com_fd;
- int ret=0;
- int i;
-
- int len;
- struct sockaddr_un clt_addr;
- struct sockaddr_un srv_addr;
- while(1)
- {
- //创建用于通信的套接字,通信域为UNIX通信域
-
- listen_fd=socket(AF_UNIX,SOCK_STREAM,0);
- if(listen_fd<0)
- {
- perror("cannot create listening socket");
- continue;
- }
- else
- {
- while(1)
- {
- //设置服务器地址参数
- srv_addr.sun_family=AF_UNIX;
- strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1);
- unlink(UNIX_DOMAIN);
- //绑定套接字与服务器地址信息
- 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);
- break;
- }
- //对套接字进行监听,判断是否有连接请求
- ret=listen(listen_fd,1);
- if(ret==-1)
- {
- perror("cannot listen the client connect request");
- close(listen_fd);
- unlink(UNIX_DOMAIN);
- break;
- }
- chmod(UNIX_DOMAIN,00777);//设置通信文件权限
- while(1)
- {
- //当有连接请求时,调用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);
- break;
- }
- //读取并输出客户端发送过来的连接信息
- memset(recv_php_buf,0,256);
- recv_php_num=read(com_fd,recv_php_buf,sizeof(recv_php_buf));
- printf("\n=====recv=====\n");
- for(i=0;i<recv_php_num;i++)
- printf("%d ",recv_php_buf[i]);
- printf("\n");
- /*if(recv_php_buf[0]==0x02)
- {
- if(recv_php_buf[recv_php_num-1]==0x00)
- {
- recv_php_buf[recv_php_num-1]=0x01;
- }
- else
- {
- recv_php_buf[recv_php_num-1]=0x00;
- }
- }
- */
- //recv_php_buf[20]+=1;
- write(com_fd,recv_php_buf,recv_php_num);
- printf("\n=====send=====\n");
- for(i=0;i<recv_php_num;i++)
- printf("%d ",recv_php_buf[i]);
- printf("\n");
- //write(com_fd,recv_php_buf,20);
- close(com_fd);//注意要关闭连接符号,不然会超过连接数而报错
- }
-
- }
-
- }
- }
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #include
- #include
-
- #include
- #include
-
- #include
- #pragma pack(1) //设定为1字节对齐
- #define UNIX_DOMAIN2 "/tmp/UNIX2.domain"
- static char recv_php_buf[256]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
- struct test
- {
- char a;
- int b;
- int c;
-
-
-
-
- }se;
- void main(void)
- {
- int connect_fd;
- int ret=0;
- int i;
- static struct sockaddr_un srv_addr;
- printf("ipc通信线程\n");
-
-
-
- connect_fd=socket(AF_UNIX,SOCK_STREAM,0);
- printf("%d\n",connect_fd);
- if(connect_fd<0)
- {
- perror("cannot create communication socket");
- printf("%d\n",connect_fd);
-
- }
- else
- {
-
-
- srv_addr.sun_family=AF_UNIX;
- strcpy(srv_addr.sun_path,UNIX_DOMAIN2);
-
-
- ret=connect(connect_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
- if(ret==-1)
- {
- close(connect_fd);
- printf("connect fail\n");
-
- }
- else
- {
-
-
-
- se.a=0x01;
- se.b=0x01020304;
- se.c=0x05060708;
- write(connect_fd,recv_php_buf,20);
-
- memset(recv_php_buf,0,sizeof(recv_php_buf));
-
-
- read(connect_fd,recv_php_buf,sizeof(recv_php_buf));
- printf("receive over\n");
- for(i=0;i<20;i++)
- printf("%x ",recv_php_buf[i]);
-
-
-
- close(connect_fd);
-
-
- }
-
-
-
- }
-
-
-
- }