Socket编程实践(13) --UNIX域名协议

本文涉及的产品
.cn 域名,1个 12个月
简介:

UNIX域名协议

   UNIX域套接字与TCP相比, 在同一台主机上, UNIX域套接字更有效率, 差点儿是TCP的两倍(因为UNIX域套接字不须要经过网络协议栈,不须要打包/拆包,计算校验和,维护序号和应答等,仅仅是将应用层数据从一个进程复制到还有一个进程, 并且UNIX域协议机制本质上就是可靠的通讯, 而网络协议是为不可靠的通讯设计的).

   UNIX域套接字能够在同一台主机上各进程之间传递文件描写叙述符;

   UNIX域套接字与传统套接字的差别是用路径名来表示协议族的描写叙述;

   UNIX域套接字也提供面向流和面向数据包两种API接口,相似于TCP和UDP,可是面向消息的UNIX套接字也是可靠的,消息既不会丢失也不会顺序错乱。

   使用UNIX域套接字的过程和网络socket十分相似, 也要先调用socket创建一个socket文件描写叙述符, family指定为AF_UNIX, type能够选择SOCK_DGRAM/SOCK_STREAM;

 

UNIX域套接字地址结构:

#define UNIX_PATH_MAX    108
struct sockaddr_un
{
    sa_family_t sun_family;               /* AF_UNIX */
    char        sun_path[UNIX_PATH_MAX];  /* pathname */
};

基于UNIX域套接字的echo-server/client程序

/**Server端**/
void echoServer(int sockfd);
int main()
{
    signal(SIGCHLD, sigHandlerForSigChild);
    int listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (listenfd == -1)
        err_exit("socket error");

    char pathname[] = "/tmp/test_for_unix";
    unlink(pathname);
    struct sockaddr_un servAddr;
    servAddr.sun_family = AF_UNIX;
    strcpy(servAddr.sun_path, pathname);
    if (bind(listenfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
        err_exit("bind error");
    if (listen(listenfd, 128) == -1)
        err_exit("listen error");

    while (true)
    {
        int connfd = accept(listenfd, NULL, NULL);
        if (connfd == -1)
            err_exit("accept error");

        pid_t pid = fork();
        if (pid == -1)
            err_exit("fork error");
        else if (pid > 0)
            close(connfd);
        else if (pid == 0)
        {
            close(listenfd);
            echoServer(connfd);
            close(connfd);
            exit(EXIT_SUCCESS);
        }
    }
}
void echoServer(int sockfd)
{
    char buf[BUFSIZ];
    while (true)
    {
        memset(buf, 0, sizeof(buf));
        int recvBytes = read(sockfd, buf, sizeof(buf));
        if (recvBytes < 0)
        {
            if (errno == EINTR)
                continue;
            else
                err_exit("read socket error");
        }
        else if (recvBytes == 0)
        {
            cout << "client connect closed..." << endl;
            break;
        }

        cout << buf ;
        if (write(sockfd, buf, recvBytes) == -1)
            err_exit("write socket error");
    }
}
/**Client端代码**/
void echoClient(int sockfd);
int main()
{
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1)
        err_exit("socket error");

    char pathname[] = "/tmp/test_for_unix";
    struct sockaddr_un servAddr;
    servAddr.sun_family = AF_UNIX;
    strcpy(servAddr.sun_path, pathname);
    if (connect(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
        err_exit("connect error");

    echoClient(sockfd);
}
void echoClient(int sockfd)
{
    char buf[BUFSIZ] = {0};
    while (fgets(buf, sizeof(buf), stdin) != NULL)
    {
        if (write(sockfd, buf, strlen(buf)) == -1)
            err_exit("write socket error");
        memset(buf, 0, sizeof(buf));
        int recvBytes = read(sockfd, buf, sizeof(buf));
        if (recvBytes == -1)
        {
            if (errno == EINTR)
                continue;
            else
                err_exit("read socket error");
        }
        cout << buf ;
        memset(buf, 0, sizeof(buf));
    }
}

UNIX域套接字编程注意点

   1.bind成功将会创建一个文件。权限为0777 & ~umask

   2.sun_path最好用一个/tmp文件夹下的文件的绝对路径, 并且server端在指定该文件之前首先要unlink一下;

   3.UNIX域协议支持流式套接口(须要处理粘包问题)与报式套接口(基于数据报)

   4.UNIX域流式套接字connect发现监听队列满时,会立马返回一个ECONNREFUSED。这和TCP不同,假设监听队列满,会忽略到来的SYN,这导致对方重传SYN。

 

传递文件描写叙述符

socketpair

#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);

创建一个全双工的流管道

參数:

   domain: 协议家族, 能够使用AF_UNIX(AF_LOCAL)UNIX域协议, 并且在Linux上, 该函数也就仅仅支持这一种协议;

   type: 套接字类型, 能够使用SOCK_STREAM

   protocol: 协议类型, 一般填充为0;

   sv: 返回的套接字对;

socketpair 函数跟pipe 函数是相似: 仅仅能在具有亲缘关系的进程间通信。但pipe 创建的匿名管道是半双工的,而socketpair 能够觉得是创建一个全双工的管道。

能够使用socketpair 创建返回的套接字对进行父子进程通信, 例如以下例:

int main()
{
    int sockfds[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) == -1)
        err_exit("socketpair error");

    pid_t pid = fork();
    if (pid == -1)
        err_exit("fork error");
    // 父进程, 仅仅负责数据的打印
    else if (pid > 0)
    {
        close(sockfds[1]);
        int iVal = 0;
        while (true)
        {
            cout << "value = " << iVal << endl;
            write(sockfds[0], &iVal, sizeof(iVal));
            read(sockfds[0], &iVal, sizeof(iVal));
            sleep(1);
        }
    }
    // 子进程, 仅仅负责数据的更改(+1)
    else if (pid == 0)
    {
        close(sockfds[0]);
        int iVal = 0;
        while (read(sockfds[1], &iVal, sizeof(iVal)) > 0)
        {
            ++ iVal;
            write(sockfds[1], &iVal, sizeof(iVal));
        }
    }
}

sendmsg/recvmsg

#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

它们与sendto/send 和 recvfrom/recv 函数相似,仅仅只是能够传输更复杂的数据结构,不仅能够传输一般数据。还能够传输额外的数据。如文件描写叙述符。

//msghdr结构体
struct msghdr
{
    void         *msg_name;       /* optional address */
    socklen_t     msg_namelen;    /* size of address */
    struct iovec *msg_iov;        /* scatter/gather array */
    size_t        msg_iovlen;     /* # elements in msg_iov */
    void         *msg_control;    /* ancillary data, see below */
    size_t        msg_controllen; /* ancillary data buffer len */
    int           msg_flags;      /* flags on received message */
};
struct iovec                      /* Scatter/gather array items */
{
    void  *iov_base;              /* Starting address */
    size_t iov_len;               /* Number of bytes to transfer */
};

msghdr结构体成员解释:

   1)msg_name :即对等方的地址指针。不关心时设为NULL就可以;

   2)msg_namelen:地址长度。不关心时设置为0就可以;

   3)msg_iov:是结构体iovec 的指针, 指向须要发送的普通数据, 见下图。   

      成员iov_base 能够觉得是传输正常数据时的buf;

      成员iov_len 是buf 的大小;

   4)msg_iovlen:当有n个iovec 结构体时,此值为n。

   5)msg_control:是一个指向cmsghdr 结构体的指针(见下图), 当须要发送辅助数据(如控制信息/文件描写叙述符)时, 须要设置该字段, 当发送正常数据时, 就不须要关心该字段, 并且msg_controllen能够置为0;

   6)msg_controllen:cmsghdr 结构体可能不止一个(见下图):

   7)flags: 不用关心;


//cmsghdr结构体
struct cmsghdr
{
    socklen_t cmsg_len;    /* data byte count, including header */
    int       cmsg_level;  /* originating protocol */
    int       cmsg_type;   /* protocol-specific type */
    /* followed by unsigned char cmsg_data[]; */
};

为了对齐,可能存在一些填充字节(见下图)。跟系统的实现有关,但我们不必关心,能够通过一些函数宏来获取相关的值。例如以下:

#include <sys/socket.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);	
//获取辅助数据的第一条消息
struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);	//获取辅助数据的下一条信息
size_t CMSG_ALIGN(size_t length);	
size_t CMSG_SPACE(size_t length);
size_t CMSG_LEN(size_t length);	//length使用的是的(实际)数据的长度, 见下图(两条填充数据的中间部分)
unsigned char *CMSG_DATA(struct cmsghdr *cmsg);


进程间传递文件描写叙述符

/**演示样例: 封装两个函数send_fd/recv_fd用于在进程间传递文件描写叙述符**/
int send_fd(int sockfd, int sendfd)
{
    // 填充 name 字段
    struct msghdr msg;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    // 填充 iov 字段
    struct iovec iov;
    char sendchar = '\0';
    iov.iov_base = &sendchar;
    iov.iov_len = 1;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // 填充 cmsg 字段
    struct cmsghdr cmsg;
    cmsg.cmsg_len = CMSG_LEN(sizeof(int));
    cmsg.cmsg_level = SOL_SOCKET;
    cmsg.cmsg_type = SCM_RIGHTS;
    *(int *)CMSG_DATA(&cmsg) = sendfd;
    msg.msg_control = &cmsg;
    msg.msg_controllen = CMSG_LEN(sizeof(int));

    // 发送
    if (sendmsg(sockfd, &msg, 0) == -1)
        return -1;
    return 0;
}
int recv_fd(int sockfd)
{
    // 填充 name 字段
    struct msghdr msg;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    // 填充 iov 字段
    struct iovec iov;
    char recvchar;
    iov.iov_base = &recvchar;
    iov.iov_len = 1;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // 填充 cmsg 字段
    struct cmsghdr cmsg;
    msg.msg_control = &cmsg;
    msg.msg_controllen = CMSG_LEN(sizeof(int));

    // 接收
    if (recvmsg(sockfd, &msg, 0) == -1)
        return -1;
    return *(int *)CMSG_DATA(&cmsg);
}
int main()
{
    int sockfds[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) == -1)
        err_exit("socketpair error");

    pid_t pid = fork();
    if (pid == -1)
        err_exit("fork error");
    // 子进程以仅仅读方式打开文件, 将文件描写叙述符发送给子进程
    else if (pid ==  0)
    {
        close(sockfds[1]);
        int fd = open("read.txt", O_RDONLY);
        if (fd == -1)
            err_exit("open error");
        cout << "In child,  fd = " << fd << endl;
        send_fd(sockfds[0], fd);
    }
    // 父进程从文件描写叙述符中读取数据
    else if (pid > 0)
    {
        close(sockfds[0]);
        int fd = recv_fd(sockfds[1]);
        if (fd == -1)
            err_exit("recv_fd error");
        cout << "In parent, fd = " << fd << endl;

        char buf[BUFSIZ] = {0};
        int readBytes = read(fd, buf, sizeof(buf));
        if (readBytes == -1)
            err_exit("read fd error");
        cout << buf;
    }
}

分析:

   我们知道。父进程在fork 之前打开的文件描写叙述符。子进程是能够共享的,可是子进程打开的文件描写叙述符,父进程是不能共享的,上述程序就是举例在子进程中打开了一个文件描写叙述符,然后通过send_fd 函数将文件描写叙述符传递给父进程。父进程能够通过recv_fd 函数接收到这个文件描写叙述符。先建立一个文件read.txt 后输入几个字符,然后执行程序;

 

注意:

   (1)仅仅有UNIX域协议才干在本机进程间传递文件描写叙述符;

   (2)描写人物的价值的叙述性说明进程间传输文件传输文件不是叙述性休息(其实send_fd/recv_fd这两个值不同), 但创造在接收过程中一个新的文件中的符号的说明中被描述, 该文件和该文件识别符和发送处理的叙述性描述中的符号的说明中被描述被传递到内核在同一文件表项.







本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5044136.html,如需转载请自行联系原作者


相关文章
|
6月前
|
网络协议 安全 Unix
UNIX域套接字(Unix Domain Socket,UDS)之所以高效
UNIX域套接字(Unix Domain Socket,UDS)之所以高效
487 3
|
6月前
|
监控 安全 Unix
UNIX域套接字(Unix Domain Socket)在安全性和隐私性
UNIX域套接字(Unix Domain Socket)在安全性和隐私性
285 2
|
10天前
|
网络协议 网络安全 网络虚拟化
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算。通过这些术语的详细解释,帮助读者更好地理解和应用网络技术,应对数字化时代的挑战和机遇。
45 3
|
5月前
|
网络协议 安全 Java
Java网络编程入门涉及TCP/IP协议理解与Socket通信。
【6月更文挑战第21天】Java网络编程入门涉及TCP/IP协议理解与Socket通信。TCP/IP协议包括应用层、传输层、网络层和数据链路层。使用Java的`ServerSocket`和`Socket`类,服务器监听端口,接受客户端连接,而客户端连接指定服务器并交换数据。基础示例展示如何创建服务器和发送消息。进阶可涉及多线程、NIO和安全传输。学习这些基础知识能助你构建网络应用。
50 1
|
4月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
56 4
|
3月前
|
域名解析 编解码 负载均衡
【域名解析DNS专栏】域名解析中的EDNS扩展:提升DNS协议灵活性
在互联网中,DNS作为连接用户与网络资源的关键桥梁,其传统协议在面对复杂网络环境时显现出局限性。EDNS(扩展机制)应运而生,通过在DNS请求和响应中添加额外选项和字段,提升了DNS的功能和灵活性。EDNS不仅提高了查询效率和支持更大范围的数据类型,还能增强安全性并通过负载均衡提升系统稳定性。例如,允许指定更大的UDP数据包大小以减少分片和重传,支持DNSSEC加强安全性验证,以及通过Python示例代码展示了如何在DNS查询中使用EDNS选项。随着技术发展,EDNS将在域名解析领域扮演更重要角色。
172 0
|
5月前
|
程序员 API 开发者
Socket与HTTP协议的实践
【6月更文挑战第4天】本文介绍了Python中的网络编程,包括Socket编程和基于HTTP协议的实践。Socket编程是网络通信的基础,Python的`socket`模块简化了其使用。文中展示了服务器和客户端的简单示例,以及如何通过多线程处理多个客户端连接。另外,文章讨论了HTTP协议,推荐了`requests`库,并给出了发送GET和POST请求的例子。最后,总结了Socket编程和HTTP协议在网络编程中的应用及其在Web开发和API交互中的重要性。
66 5
|
5月前
|
网络协议 应用服务中间件 网络性能优化
解析TCP /UDP协议的 socket 调用的过程
【6月更文挑战第2天】该文介绍了传输层的两种主要协议TCP和UDP的区别。TCP是面向连接、可靠的,提供顺序无错的数据传输,而UDP则是无连接、不可靠的,不保证数据顺序或不丢失。
|
5月前
网络编程中的互联网协议 , IP地址 , 域名 , 端口 , 架构 , 网页数据请求 , 响应码
网络编程中的互联网协议 , IP地址 , 域名 , 端口 , 架构 , 网页数据请求 , 响应码
|
6月前
|
域名解析 编解码 负载均衡
【域名解析DNS专栏】域名解析中的EDNS扩展:提升DNS协议灵活性
【5月更文挑战第27天】EDNS(Extension Mechanisms for DNS)是为了解决传统DNS协议在复杂网络环境下的灵活性和扩展性问题而诞生的技术。它允许在DNS请求和响应中添加额外选项,提高查询效率,支持更大数据范围,增强安全性,并实现负载均衡和故障转移。通过在DNS消息中包含EDNS部分,客户端和服务器能交换更多信息,实现更复杂的逻辑。EDNS的使用示例代码展示了如何在Python中创建和处理EDNS选项。随着技术进步,EDNS将在域名解析领域扮演更重要角色。
197 1
下一篇
无影云桌面