概述
Unix 域套接字是一种客户端和服务器在单主机上的 IPC 方法。Unix 域套接字不执行协议处理,不需要添加或删除网络报头,无需验证和,不产生顺序号,无需发送确认报文,比因特网域套接字的效率更高。Unix 域套接字提供字节流(类似于 TCP)和数据报(类似于 UDP)两种接口,UNIX域数据报服务是可靠的,既不会丢失消息也不会传递出错。UNIX域套接字是套接字和管道之间的混合物。
Unix 域套接字编程
地址结构:
struct sockaddr_un{
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* pathname */
};
存放在 sun_path 数组中的路径名必须以空字符结尾。下面是把一个路径名绑定到 Unix 域套接字上实现的程序:
/* 创建一个Unix域套接字,并bind一个路径名 */
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
int main(int argc, char **argv)
{
int sockfd, size;
socklen_t len;
struct sockaddr_un addr1, addr2;
if(argc != 2)
err_quit("usage: %s <pathname>", argv[0]);
bzero(&addr1, sizeof(addr1));
addr1.sun_family = AF_UNIX;
strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1);
/* 创建一个Unix域套接字 */
if( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
/* 若路径名在文件系统已存在,则bind会出错;所以先调用unlink删除要绑定的路径名,防止bind出错 */
unlink(argv[1]);
/* 将路径名bind绑定到该套接字上 */
size = offsetof(struct sockaddr_un, sun_path) + strlen(addr1.sun_path);
if(bind(sockfd, (struct sockaddr *)&addr1, size) < 0)
err_sys("bind error");
/* 显示已绑定的路径名 */
len = sizeof(addr2);
getsockname(sockfd, (struct sockaddr *)&addr2, &len);
printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
exit(0);
}
$ ./main /tmp/sock
bound name = /tmp/sock, returned len = 12
/*当该路径名存在,且不使用unlink函数时,会出现以下提示*/
$ ./main /tmp/sock
bind error: Address already in use
为了创建一对非命名的,相互连接的 UNXI 域套接字,用户可以使用socketopair函数。其实现如下:
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
/* 返回值:若成功则返回0,出错则返回-1 */
/* 说明
* 参数 domain 必须是 AF_LOCAL 或 AF_UNIX,protocol 必须为 0,type 可以是 SOCK_STREAM 或 SOCK_DGRAM,新创建的两个套接字描述符作为sockfd[0]和sockfd[1]返回;
Unix 域套接字函数
与因特网域套接字相比,Unix 域套接字有以下的区别:
- 由 bind 创建的路径名默认访问权限应为 0777,并按当前 umask 值进行修改;
- 路径名必须是一个绝对路径名,避免使用相对路径名。因为它的解析依赖于调用者的当前工作目录,若服务器绑定的是一个相对路径名,则客户端和服务器必须在相同的目录才能正常工作;
- 在 connect 调用中指定的路径名必须是一个当前绑定在某个已打开的 Unix 域套接字上的路径名,而且套接字类型必须一致;
- 调用 connect 连接一个 Unix 域套接字涉及的权限测试等价于调用 open 以只写方式访问相应的路径名;
- Unix 域字节流套接字类似于 TCP 套接字:它们都为进程提供一个无记录边界的字节流接口;
- 在 Unix 域字节流套接字中,若 connect 调用时发现监听套接字的队列已满,则立即返回 ECONNREFUSED 错误。而 TCP 套接字遇到这种情况,TCP 监听套接字忽略这些到达的 SYN 连接请求,TCP 客户端则会重发数次 SYN 报文段;
- Unix 域数据报套接字类似于 UDP 套接字:它们都提供一个保留记录边界的不可靠数据报;
- 为一个未绑定路径名的 Unix 套接字发送数据时,不会自动给该套接字绑定一个路径名。而 UDP 套接字在给一个未绑定的 UDP 套接字发送数据时,会自动为其绑定一个临时端口;
Unix 域字节流编程
服务器程序:
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <signal.h>
#include <errno.h>
#define QLEN 1024
typedef void Sigfunc(int);
extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
extern void str_echo(int);
static Sigfunc *MySignal(int signo, Sigfunc *func);
static Sigfunc *M_signal(int signo, Sigfunc *func);
static void sig_chld(int);
int main(int argc, char **argv)
{
int sockfd, conndfd, size;
socklen_t len;
pid_t childpid;
struct sockaddr_un cliaddr, servaddr;
if(argc != 2)
err_quit("usage: %s <pathname>", argv[0]);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path, argv[1]);
/* 创建一个Unix域套接字 */
if( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
/* 若路径名在文件系统已存在,则bind会出错;所以先调用unlink删除要绑定的路径名,防止bind出错 */
unlink(argv[1]);
/* 将路径名bind绑定到该套接字上 */
size = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path);
if(bind(sockfd, (struct sockaddr *)&servaddr, size) < 0)
err_sys("bind error");
/* 监听套接字 */
if(listen(sockfd, QLEN) < 0)
{
close(sockfd);
err_sys("listen error");
}
/* 信号处理 */
MySignal(SIGCHLD, sig_chld);
for( ; ;)
{
len = sizeof(cliaddr);
if( (conndfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len)) < 0)
{
if(errno == EINTR)
continue;
else
err_sys("accept error");
}
}
if( (childpid = fork()) == 0)
{
close(sockfd);
str_echo(conndfd);
exit(0);
}
close(conndfd);
}
void sig_chld(int signo)
{
pid_t pid;
int stat;
while( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
static Sigfunc *MySignal(int signo, Sigfunc *func)
{
Sigfunc *sigfunc;
if( (sigfunc = M_signal(signo, func)) == SIG_ERR)
err_sys("signal error");
return (sigfunc);
}
static Sigfunc *M_signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
/* 设置信号处理函数 */
act.sa_handler = func;
/* 初始化信号集 */
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(signo == SIGALRM)
{/* 若是SIGALRM信号,则系统不会自动重启 */
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
}
else
{/* 其余信号设置为系统会自动重启 */
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;
#endif
}
/* 调用 sigaction 函数 */
if(sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
客户端程序:
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
extern void str_cli(FILE *, int);
int
main(int argc, char **argv)
{
intsockfd;
struct sockaddr_unservaddr;
if(argc != 2)
err_quit("usage: %s <pathname>", argv[0]);
if( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path, argv[1]);
int err;
err = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
if( err < 0)
err_sys("connect error");
str_cli(stdin, sockfd);/* do it all */
exit(0);
}
参考资料:
《Unix 网络编程》