当前位置:   article > 正文

C++ unix域套接字(Unix domain socket)客户端服务端代码示例demo、抽象套接字(Abstract Socket)

abstract socket

概念

Unix域套接字(Unix domain socket)是一种在同一台计算机上进程间进行通信的机制,它是在Unix和类Unix操作系统中广泛使用的一种IPC(进程间通信)方式。

与网络套接字(socket)不同,Unix域套接字不依赖于网络协议栈,而是使用文件系统作为通信的基础。它在文件系统中创建一个特殊类型的文件,进程可以通过该文件进行通信。

Unix域套接字提供了一种可靠且高效的进程间通信方式,适用于同一台计算机上的进程之间的通信。它具有以下特点:

  1. 可靠性:Unix域套接字使用可靠的传输机制,确保数据的完整性和可靠性。
  2. 高性能:由于通信在内核内部完成,不需要经过网络协议栈的处理,因此性能较高。
  3. 低延迟:进程之间的通信是通过内存进行的,因此延迟较低。
  4. 安全性:由于通信是在同一台计算机上进行的,不存在通过网络传输的风险,因此安全性较高。
  5. 跨平台性:Unix域套接字在不同的Unix和类Unix系统上都得到支持。

Unix域套接字可以用于各种进程间通信的应用场景,比如本地进程之间的通信、服务器与客户端之间的通信等。它在Unix系统上被广泛应用于各种服务和应用程序,如数据库服务器、Web服务器、进程间通信工具等。

unix域套接字原理

Unix域套接字的原理是基于文件系统的通信机制。它利用文件系统中的特殊文件来实现进程间的通信。

下面是一个简单的图解说明Unix域套接字的原理:

     +-----------+                       +-----------+
     |  Process 1|                       |  Process 2|
     +-----------+                       +-----------+
          |                                    |
          |  Unix Domain Socket (File)         |
          |                                    |
     +----------------------------------------------+
     |                                              |
     |                  Kernel                      |
     |                                              |
     +----------------------------------------------+
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  1. 进程1和进程2分别运行在同一台计算机上。
  2. 进程1创建一个Unix域套接字,该套接字被映射到文件系统中的一个特殊文件。
  3. 进程2也打开同一个特殊文件,通过该文件与进程1建立通信连接。
  4. 当进程1和进程2之间有数据传输时,数据被通过内核传输,而不经过网络协议栈。
  5. 内核将数据从进程1传输到进程2,并反之亦然。

在这个过程中,Unix域套接字的通信是通过文件系统来完成的。进程可以使用类似读写文件的方式来读取和写入套接字,而不需要网络协议的参与。

上面只是一个简化的图示,实际的实现细节更加复杂。不同的操作系统和编程语言可能有不同的具体实现方式,但基本原理是相似的。

总之,Unix域套接字利用文件系统的特殊文件作为通信的载体,在内核层实现进程间的通信,提供了高效、可靠且安全的进程间通信机制。

unix域套接字C++编码规范

服务端

  1. 包含头文件:

    #include <sys/socket.h>   // 套接字相关函数和结构
    #include <sys/un.h>       // Unix域套接字相关结构
    
    • 1
    • 2
  2. 创建套接字:

    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1) {
        // 处理套接字创建失败的情况
    }
    
    • 1
    • 2
    • 3
    • 4
  3. 绑定套接字到文件路径:

    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, "/path/to/socket", sizeof(addr.sun_path) - 1);
    
    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) == -1) {
        // 处理套接字绑定失败的情况
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  4. 监听连接:

    if (listen(sockfd, backlog) == -1) {
        // 处理监听失败的情况
    }
    
    • 1
    • 2
    • 3
  5. 接受连接请求:

    int clientfd = accept(sockfd, nullptr, nullptr);
    if (clientfd == -1) {
        // 处理接受连接失败的情况
    }
    
    • 1
    • 2
    • 3
    • 4
  6. 进行数据读写:

    char buffer[1024];
    ssize_t bytesRead = recv(clientfd, buffer, sizeof(buffer), 0);
    if (bytesRead == -1) {
        // 处理接收数据失败的情况
    }
    
    ssize_t bytesWritten = send(clientfd, buffer, bytesRead, 0);
    if (bytesWritten == -1) {
        // 处理发送数据失败的情况
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  7. 关闭套接字:

    close(sockfd);
    close(clientfd);
    
    • 1
    • 2

上述代码仅为示例,实际使用时需要根据具体情况进行适当的错误处理、数据解析等。此外,还可以使用异常处理、封装等技术来提高代码的可靠性和可维护性。

编写Unix域套接字的C++代码时,还应遵循一般的C++编码规范,如使用合适的命名规范、注释、模块化设计等。

客户端

  1. 包含头文件:

    #include <sys/socket.h>   // 套接字相关函数和结构
    #include <sys/un.h>       // Unix域套接字相关结构
    
    • 1
    • 2
  2. 创建套接字:

    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1) {
        // 处理套接字创建失败的情况
    }
    
    • 1
    • 2
    • 3
    • 4
  3. 连接到服务器:

    struct sockaddr_un serverAddr;
    memset(&serverAddr, 0, sizeof(struct sockaddr_un));
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, "/path/to/socket", sizeof(serverAddr.sun_path) - 1);
    
    if (connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr_un)) == -1) {
        // 处理连接失败的情况
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  4. 进行数据读写:

    char buffer[1024];
    ssize_t bytesRead = recv(sockfd, buffer, sizeof(buffer), 0);
    if (bytesRead == -1) {
        // 处理接收数据失败的情况
    }
    
    ssize_t bytesWritten = send(sockfd, buffer, bytesRead, 0);
    if (bytesWritten == -1) {
        // 处理发送数据失败的情况
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  5. 关闭套接字:

    close(sockfd);
    
    • 1

与服务器端代码相比,客户端代码的主要区别在于连接到服务器的步骤。客户端需要指定服务器的地址和套接字路径,并通过connect函数与服务器建立连接。

同样,需要根据实际情况进行适当的错误处理和数据解析。另外,遵循一般的C++编码规范,如命名规范、注释、模块化设计等,也是编写可靠和可维护代码的重要方面。

unix域套接字C++代码示例★★★★★

以下是一个简单的C++示例,演示了使用Unix域套接字的服务端和客户端。该示例中,客户端与服务端建立连接后,客户端连续发送消息给服务端,而服务端能够在客户端断开连接后继续监听。

服务端代码

server.cpp

#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

// #define SOCKET_PATH "/tmp/unix_socket_demo"
#define SOCKET_PATH "./unix_socket_demo"

int main()
{
    // 创建套接字
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        std::cerr << "Failed to create socket" << std::endl;
        return 1;
    }

    // 绑定套接字到文件路径
    struct sockaddr_un serverAddr;
    memset(&serverAddr, 0, sizeof(struct sockaddr_un));
    serverAddr.sun_family = AF_UNIX;
    unlink(SOCKET_PATH);    // arnold add 20230608 // 要unlink一下,否则无法重新绑定
    strncpy(serverAddr.sun_path, SOCKET_PATH, sizeof(serverAddr.sun_path) - 1);

    if (bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr_un)) == -1)
    {
        std::cerr << "Failed to bind socket" << std::endl;
        close(sockfd);
        return 1;
    }

    // 监听连接
    if (listen(sockfd, 5) == -1)
    {
        std::cerr << "Failed to listen" << std::endl;
        close(sockfd);
        return 1;
    }

    while (true)
    {
        std::cout << "Waiting for client connection..." << std::endl;

        // 接受连接请求
        int clientfd = accept(sockfd, nullptr, nullptr);
        if (clientfd == -1)
        {
            std::cerr << "Failed to accept connection" << std::endl;
            close(sockfd);
            return 1;
        }

        std::cout << "Client connected!" << std::endl;

        // 接收和发送数据
        char buffer[1024];
        ssize_t bytesRead;

        while ((bytesRead = recv(clientfd, buffer, sizeof(buffer), 0)) > 0)
        {
            std::cout << "Received message from client: " << buffer << std::endl;

            // 在此处进行需要的处理

            // 发送回复给客户端
            ssize_t bytesWritten = send(clientfd, buffer, bytesRead, 0);
            if (bytesWritten == -1)
            {
                std::cerr << "Failed to send response to client" << std::endl;
                close(clientfd);
                break;
            }
        }

        if (bytesRead == -1)
        {
            std::cerr << "Failed to receive data from client" << std::endl;
        }

        std::cout << "Client disconnected" << std::endl;
        close(clientfd);
    }

    close(sockfd);
    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

客户端代码

client.cpp

#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

// #define SOCKET_PATH "/tmp/unix_socket_demo"
#define SOCKET_PATH "./unix_socket_demo"

int main()
{
    // 创建套接字
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        std::cerr << "Failed to create socket" << std::endl;
        return 1;
    }

    // 连接到服务器
    struct sockaddr_un serverAddr;
    memset(&serverAddr, 0, sizeof(struct sockaddr_un));
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, SOCKET_PATH, sizeof(serverAddr.sun_path) - 1);

    while (true)
    {
        while (true)
        {
            if (connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr_un)) == -1)
            {
                // std::cerr << "Failed to connect to server" << std::endl;
                std::cerr << "Failed to connect to server, sleep 1s and reconnect..." << std::endl;
                sleep(1);
                continue;
                // close(sockfd);
                // return 1;
            }
            break;
        }

        std::cout << "Connected to server!" << std::endl;

        while (true)
        {
            try // 貌似这里try多余了,如果服务端是被强行关闭的,客户端这里是捕获不到的,也会强行结束
            {

                // 输入要发送的消息
                // std::cout << "Enter message to send (or 'q' to quit): ";
                // std::string message;
                std::string message = "client auto send a message";
                // std::getline(std::cin, message);

                // if (message == "q")
                // {
                //     break;
                // }

                // 发送消息给服务器
                ssize_t bytesWritten = send(sockfd, message.c_str(), message.length(), 0);
                if (bytesWritten == -1)
                {
                    std::cerr << "Failed to send message to server" << std::endl;
                    break;
                    // close(sockfd);
                    // return 1;
                }

                sleep(1);

                // 接收服务器的回复
                char buffer[1024];
                ssize_t bytesRead = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
                if (bytesRead == -1)
                {
                    std::cerr << "Failed to receive response from server" << std::endl;
                    break;
                    // close(sockfd);
                    // return 1;
                }

                buffer[bytesRead] = '\0';
                std::cout << "Received response from server: " << buffer << std::endl;
            }
            catch(std::exception& e)
            {
                std::cout << "catch(std::exception& e): " << e.what() << std::endl;
                break;
            }
        }

        // std::cout << "Disconnecting from server" << std::endl;
        // close(sockfd);
        // return 0;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

编译运行

开两个shell窗口,分别执行:

g++ server.cpp -o server && ./server
  • 1
g++ client.cpp -o client && ./client
  • 1

运行效果

服务端

在这里插入图片描述

客户端

在这里插入图片描述

可以看到,程序在启动后,在当前目录下自动创建了一个套接字文件,删不删也没关系,下回还能继续用:

在这里插入图片描述

使用unix域套接字注意事项

在使用Unix域套接字时,有一些注意事项需要考虑:

  1. 文件路径冲突:Unix域套接字使用文件系统中的特殊文件进行通信,因此需要选择一个合适的文件路径来创建套接字。确保选择的路径不会与现有的文件或目录发生冲突,并且有适当的权限设置。

  2. 套接字文件清理:在使用完Unix域套接字后,需要负责清理套接字文件。确保在程序终止或不再需要套接字时删除相关的套接字文件,以避免文件积累和资源浪费。

  3. 权限设置:Unix域套接字的文件权限设置很重要。确保只有有权访问该套接字的进程能够读取和写入套接字文件。适当设置文件权限可以提高安全性。

  4. 进程间通信协议:使用Unix域套接字进行进程间通信时,需要协调好通信的协议和数据格式。定义清晰的协议和消息格式可以确保通信的正确性和一致性。

  5. 错误处理:在使用Unix域套接字时,需要适当处理错误情况。例如,处理连接失败、通信中断或其他错误的情况,并采取适当的措施,如重新连接或报告错误。

  6. 平台兼容性:虽然Unix域套接字在不同的Unix和类Unix系统上得到广泛支持,但在使用时仍需注意平台兼容性问题。确保所使用的系统支持Unix域套接字,并了解相关系统调用和函数的差异。

使用Unix域套接字时需要关注文件路径冲突、文件清理、权限设置、通信协议、错误处理和平台兼容性等方面的注意事项,以确保通信的正确性、安全性和可靠性。

常见问题

服务端重新启动时,提示bind失败(启动时加个unlink)

解决办法:需要加个unlink

在这里插入图片描述

扩展:普通unix域套接字和抽象套接字

概念差异

抽象套接字(Abstract Socket)属于Unix域套接字(Unix Domain Socket)的一种特殊类型。Unix域套接字是一种用于本地进程间通信的机制,不依赖于网络协议栈,而是基于文件系统路径来标识和定位套接字。

抽象套接字是Unix域套接字的扩展,引入了一种特殊的命名约定,用于创建不依赖于文件系统路径的套接字。它只存在于内核中,不会在文件系统中创建对应的文件。抽象套接字的名称存储在套接字地址结构的文件路径字段中,但并不对应实际的文件路径。

因此,可以说抽象套接字是Unix域套接字的一种变体或特殊情况,它们共享相同的通信机制和特性,但在套接字的命名和可见性方面有所区别。

创建方法差异

创建抽象套接字和普通Unix域套接字的代码有一些区别。以下是它们之间的主要区别:

  1. 套接字地址结构类型:

    • 对于普通Unix域套接字,使用struct sockaddr_un结构来表示套接字地址。该结构包含一个文件路径字段(sun_path)和一个地址族字段(sun_family)。
    • 对于抽象套接字,使用struct sockaddr_un结构的变体来表示套接字地址。该变体结构中的文件路径字段(sun_path)不再表示实际的文件路径,而是用于存储套接字名称。
  2. 绑定套接字地址:

    • 对于普通Unix域套接字,将路径名指定为文件系统中的实际路径,使用bind()函数将套接字地址绑定到指定的路径上。
    • 对于抽象套接字,需要在sun_path字段中以特殊格式指定套接字名称,然后使用bind()函数将套接字地址绑定到特殊格式的路径上。

下面是创建抽象套接字和普通Unix域套接字的代码示例:

普通Unix域套接字的代码示例:

#include <sys/socket.h>
#include <sys/un.h>

int main() {
    // 创建套接字
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

    // 绑定套接字到文件路径
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, "/path/to/socket", sizeof(addr.sun_path) - 1);
    bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un));

    // 其他操作...

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

抽象套接字的代码示例:

#include <sys/socket.h>
#include <sys/un.h>

int main() {
    // 创建套接字
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

    // 绑定套接字到抽象名称
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path + 1, "abstract_socket", sizeof(addr.sun_path) - 2); // 在第二个字符位置插入名称
    addr.sun_path[0] = '\0'; // 将第一个字符设置为零字节,表示抽象套接字
    bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un));

    // 其他操作...

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

请注意,抽象套接字的名称是通过在sun_path字段的第二个字符位置插入名称来指定的,并在sun_path的第一个字符位置设置零字节。这是用于表示抽象套接字的特殊命名约定。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/158805
推荐阅读
相关标签
  

闽ICP备14008679号