提到socket套接字,就不得不想到网络socket编程,在服务器与客户端的程序编写时,都会使用到 socket() 函数来创建一个用于tcp通信的套接字,在使用connect()与bind() 连接或者监听,这是用于客户端与服务器端的通信,然而,在进程间通信(IPC)中同样可以用到socket() 这个函数来创建通信的套接字,我们称之为Unix域socket 或 命名socket;
命名socket与普通的TCP/IP网络 socket相比具有以下特点:
对于网络socket编程,当我们调用socket() 函数选择第二个参数时,通常选择
SOCK_STREAM 这个参数,这个参数代表我们的数据将会走 TCP/IP 协议,数据在传输时,会经过TCP多层模型的封装,在数据中加入相应的头来使数据
" 冤有头债有主 ";数据在tcp模型中树这样传输的:
服务器端或者客户端在创建好用于通信的套接字并且三次握手连接成功后,调用write() 与 read() 函数对描述符进行操作即可完成网络通信,具体的网络socket编程可以参考博客:socket相关博客;
对于Unix域socket,用于同一主机不用进程间的通信,既然不同于网络socket() 用于不同主机间的通信,Unix域socket就会有他的优点;在同一主机中,使用socket来完成不同进程间通信,可以不用经过TCP/IP多层的封装,直接通过内核,获取到通信的套接字,连接成功后,可以直接发送,因此,传输效率会大大提高;如图:
命名socket与TCP/IP网络socket通信使用的是同一套接口,只是地址结构与某些参数不同:TCP/IP网络socket通过IP地址和端口号来标识,而UNIX域协议中使用普通文件系统路径名标识。所以命名socket和普通socket只是在创建socket和绑定服务器标识的时候与网络socket有区别,具体如下:
int socket(int domain, int type, int protocol);
说明:创建一个socket,可以是TCP/IP网络socket,也是命名socket,具体由domain参数决定。该函数的返回值为生成的套接字描述符。
参数domain指定协议族:对于命名socket/Unix域socket,其值须被置为
AF_UNIX或AF_LOCAL;如果是网络socket则其值应该为 AF_INET; 参数type指定套接字类型,它可以被设置为
SOCK_STREAM(流式套接字)或 SOCK_DGRAM(数据报式套接字); 参数protocol应被设置为 0;
SOCK_STREAM 式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用 struct sockaddr_un 类型的变量。
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* 路径名 */
};
这里面有一个很关键的东西,命名socket命名方式有两种。一是普通的命名,socket会根据此命名创建一个同名的socket文件,客户端连接的时候通过读取该socket文件连接到socket服务端。这种方式的弊端是服务端必须对socket文件的路径具备写权限,客户端必须知道socket文件路径,且必须对该路径有读权限。另外一种命名方式是抽象命名空间,这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。后者的实现过程与前者的差别是,后者在对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0,即sun_path[0] = 0。这里第一种方式比较常见,下面的例程中我们就使用了该方法。
代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define SOCKET_PATH "/tmp/socket.domain"
void err_quit(char *estr)
{
perror(estr);
exit(-1);
}
int main(int argc,char *argv[])
{
char buf[1024];
int i;
int listen_fd = -1;
int client_fd;
int rv = -1;
struct sockaddr_un servaddr; //unix域socket套接字
struct sockaddr_un cliaddr;
socklen_t addrlen = sizeof(servaddr);
listen_fd = socket(AF_UNIX,SOCK_STREAM,0);
if(listen_fd < 0)
{
err_quit("socket create fail");
}
printf("create sockfd[%d] ok!\n",listen_fd);
if(!access(SOCKET_PATH,F_OK))
{
remove(SOCKET_PATH);
}
bzero(&servaddr,addrlen);
servaddr.sun_family = AF_UNIX;
strncpy(servaddr.sun_path,SOCKET_PATH,sizeof(servaddr.sun_path));
if(bind(listen_fd,(struct sockaddr *)&servaddr,addrlen) < 0)
{
printf("Create socket failure:%s\n",strerror(errno));
unlink(SOCKET_PATH);
return -1;
}
listen(listen_fd,13);
while(1)
{
printf("Start waiting and accept new client connect......\n");
client_fd = accept(listen_fd,(struct sockaddr *)&cliaddr,&addrlen);
if(client_fd < 0)
{
printf("Accept new client failure:%s\n",strerror(errno));
return -2;
}
memset(buf,0,sizeof(buf));
if((rv = read(client_fd,buf,sizeof(buf))) < 0)
{
printf("Read from client[%d] failure:%s\n",client_fd,strerror(errno));
close(client_fd);
continue;
}
else if(rv == 0)
{
printf("socket connet disconneted\n");
close(client_fd);
continue;
}
printf("Read massage from client[%d]:%s\n",listen_fd,buf);
for(i = 0;i < rv;i++)
{
buf[i] = toupper(buf[i]);
}
if(write(client_fd,buf,rv) < 0)
{
printf("Write to client[%d] failure:%s\n",client_fd,strerror(errno));
close(client_fd);
continue;
}
printf("Write %d bytes data to client[%d]\n",rv-1,client_fd);
close(client_fd);
sleep(1);
}
close(listen_fd);
}
#include
#include
#include
#include
#include
#include
#include
#include
#define SOCKET_PATH "/tmp/socket.domain"
void err_quit(char *estr)
{
perror(estr);
exit(-1);
}
int main(int argc,char *argv[])
{
char buf[1024] = {0};
int sockfd = -1;
int rv = -1;
struct sockaddr_un servaddr;
socklen_t addrlen = sizeof(servaddr);
if((sockfd = socket(AF_UNIX,SOCK_STREAM,0)) < 0)
{
err_quit("socket create failure");
}
printf("Create sockfd[%d] ok\n",sockfd);
bzero(&servaddr,sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strncpy(servaddr.sun_path,SOCKET_PATH,sizeof(servaddr.sun_path)-1);
if(connect(sockfd,(struct sockaddr *)&servaddr,addrlen) < 0)
printf("Connect to unix domain socket server on \"%s\" failure:%s\n",SOCKET_PATH,strerror(errno));
printf("connect unix domain socket \"%s\" ok!\n",SOCKET_PATH);
fgets(buf,sizeof(buf),stdin);
if((rv = write(sockfd,buf,strlen(buf))) < 0)
{
printf("Write to server failure:%s\n",strerror(errno));
close(sockfd);
return -1;
}
printf("Actually write %d bytes data to server:%s\n",rv-1,buf);
bzero(&buf,sizeof(buf));
printf("start read\n");
if((rv = read(sockfd,buf,sizeof(buf))) < 0)
{
printf("Read to server failure:%s\n",strerror(errno));
close(sockfd);
return -1;
}
else if(0 == rv)
{
printf("socket connet disconnected\n");
close(sockfd);
return -3;
}
printf("Read %d bytes data from server:%s\n",rv-1,buf);
close(sockfd);
return 0;
}
运行服务器端代码:
在客户端输入要发送的信息:hello world goodbye
服务器端接收到信息,并返回大写;这就使用Unix域socket完成了不通进程间的通信了。
服务器循环等待下一个客户端连接: