《网络编程》Unix 域套接字

概述

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 域套接字有以下的区别:

  1. 由 bind 创建的路径名默认访问权限应为 0777,并按当前 umask 值进行修改;
  2. 路径名必须是一个绝对路径名,避免使用相对路径名。因为它的解析依赖于调用者的当前工作目录,若服务器绑定的是一个相对路径名,则客户端和服务器必须在相同的目录才能正常工作;
  3. 在 connect 调用中指定的路径名必须是一个当前绑定在某个已打开的 Unix 域套接字上的路径名,而且套接字类型必须一致;
  4. 调用 connect 连接一个 Unix 域套接字涉及的权限测试等价于调用 open 以只写方式访问相应的路径名;
  5. Unix 域字节流套接字类似于 TCP 套接字:它们都为进程提供一个无记录边界的字节流接口;
  6. 在 Unix 域字节流套接字中,若 connect 调用时发现监听套接字的队列已满,则立即返回 ECONNREFUSED 错误。而 TCP 套接字遇到这种情况,TCP 监听套接字忽略这些到达的 SYN 连接请求,TCP 客户端则会重发数次 SYN 报文段;
  7. Unix 域数据报套接字类似于 UDP 套接字:它们都提供一个保留记录边界的不可靠数据报;
  8. 为一个未绑定路径名的 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)
{
	int					sockfd;
	struct sockaddr_un	servaddr;

    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 网络编程》

时间: 2024-08-13 08:07:05

《网络编程》Unix 域套接字的相关文章

unix域套接字的UDP网络编程

unix域套接字的UDP网络编程,服务端如下: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> #define SA struct sockaddr #define PATHNAME

通过UNIX域套接字传递文件描述符

传送文件描述符是高并发网络服务编程的一种常见实现方式.Nebula 高性能通用网络框架即采用了UNIX域套接字传递文件描述符设计和实现.本文详细说明一下传送文件描述符的应用. 1. TCP服务器程序设计范式 ??开发一个服务器程序,有较多的的程序设计范式可供选择,不同范式有其自身的特点和实用范围,明了不同范式的特性有助于我们服务器程序的开发.常见的TCP服务器程序设计范式有以下几种: 迭代服务器 并发服务器,每个客户请求fork一个子进程 预先派生子进程,每个子进程无保护地调用accept 预先

使用Unix域套接字进行跨进程通信

Unix域套接字简介 <Unix环境高级编程>中对Unix域套接字有如下介绍: 虽然socketpair函数创建相互连接的一对套接字,但是每一个套接字都没有名字.这意味着无关进程不能使用它们. 我们可以命名unix域套接字,并可将其用于告示服务.但是要注意的是,UNXI与套接字使用的地址不同与因特网域套接字. UNIX域套接字的地址由sockaddr_un结构表示. 在linux2.4.22中,sockaddr_un结构按下列形式定义在有文件 struct sockaddr_un{ sa_fa

[python] 网络编程之套接字Socket、TCP和UDP通信实例

很早以前研究过C#和C++的网络通信,参考我的文章: C#网络编程之Tcp实现客户端和服务器聊天 C#网络编程之套接字编程基础知识 C#网络编程之使用Socket类Send.Receive方法的同步通讯 Python网络编程也类似.同时最近找工作笔试面试考察Socket套接字.TCP\UDP区别比较多,所以这篇文章主要精简了<Python核心编程(第二版)>第16章内容.内容包括:服务器和客户端架构.套接字Socket.TCP\UDP通信实例和常见笔试考题. 最后希望文章对你有所帮助,如果有不

Linux高级进程间通信:UNIX域套接字

UNIX域套接字简介 UNIX域套接字用于在同一台机器上运行的进程间的通信.虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高.UNIX域套接字仅仅复制数据,它们并不执行协议处理,不需要添加和删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文. UNIX域套接字提供流和数据报两种接口.UNIX域数据报服务是可靠的,既不会丢失消息也不会传递错误.UNIX域套接字是套接字和管道之间的混合物.为了创建一对非命名的.相互连接的UNIX域套接字,用户可以使用它们面向网络的域套接字接

TCP/IP网络编程之套接字类型与协议设置

套接字与协议 如果相隔很远的两人要进行通话,必须先决定对话方式.如果一方使用电话,另一方也必须使用电话,而不是书信.可以说,电话就是两人对话的协议.协议是对话中使用的通信规则,扩展到计算机领域可整理为"计算机间对话必备通信规则" 在TCP/IP网络编程之网络编程和套接字这一章中,我们已经介绍了如何创建套接字,但为了完全理解该函数,此处将继续展开讨论 #include <sys/socket.h> int socket(int domain, int type, int pr

UNIX域套接字(unix domain)

UNIX域套接字用于在同一台机器上运行的进程之间的通信. UNIX域套接字提供流和数据报两种接口. 创建一对非命名的.相互连接的UNIX域套接字. socketpair 1.命名UNIX域套接字 1)套接字地址格式,在linux下如下所示 struct sockaddr_un { sa_family_t sun_family; char sun_path[108]; } 绑定该地址: #include <stdlib.h> #include <string.h> #include

Linux网络编程——原始套接字实例:简单版网络数据分析器

通过<Linux网络编程--原始套接字编程>得知,我们可以通过原始套接字以及 recvfrom( ) 可以获取链路层的数据包,那我们接收的链路层数据包到底长什么样的呢? 链路层封包格式 MAC 头部(有线局域网) 注意:CRC.PAD 在组包时可以忽略 链路层数据包的其中一种情况: unsigned char msg[1024] = { //--------------组MAC--------14------ 0xb8, 0x88, 0xe3, 0xe1, 0x10, 0xe6, // dst

Linux网络编程——原始套接字实例:MAC 头部报文分析

通过<Linux网络编程——原始套接字编程>得知,我们可以通过原始套接字以及 recvfrom( ) 可以获取链路层的数据包,那我们接收的链路层数据包到底长什么样的呢? 链路层封包格式 MAC 头部(有线局域网) 注意:CRC.PAD 在组包时可以忽略 链路层数据包的其中一种情况: 1 unsigned char msg[1024] = { 2 //--------------组MAC--------14------ 3 0xb8, 0x88, 0xe3, 0xe1, 0x10, 0xe6,