当前位置: 首页 > article >正文

Linux socket编程(11):Unix套接字编程及通信例子

Unix套接字是一种用于在同一台计算机上的进程间通信的一种机制。它是Linux和其他类Unix系统中的一项特性,通过在文件系统中创建特殊的套接字文件,进程可以通过这些套接字文件进行通信。

文章目录

  • 1 Unix和TCP套接字对比
  • 2 Unix套接字初始化流程
  • 3 例:服务端/客户端通信实现
    • 3.1 服务端
    • 3.2 客户端
    • 3.3 实验结果
    • 3.4 完整代码
  • 4 TCP与Unix中connect的区别

1 Unix和TCP套接字对比

Unix套接字适用于在同一计算机上运行的进程之间的通信,而TCP/IP套接字则设计用于在不同计算机上运行的程序之间通过网络进行通信。

比较因素Unix套接字TCP/IP套接字
全名也被称为进程间通信(IPC)套接字传输控制协议/互联网协议(TCP/IP)套接字
功能用于同一台计算机上运行的进程之间通信用于在不同计算机上运行的程序之间通信
要求进程之间通信无需主机名和端口号使用TCP/IP套接字进行程序通信需要主机名和端口号
速度由于进程在同一系统上运行,避免了一些检查和操作,因此通信速度较快相对于Unix套接字,在网络上进行通信时速度较慢

2 Unix套接字初始化流程

在Linux中进行Unix套接字编程通常涉及一系列系统调用和相关函数。下面是一个简要的介绍,以及在Unix环境中如何使用相关函数的一些例子:

1、创建套接字

int socket(int domain, int type, int protocol);
  • 在Unix域套接字编程中,domain参数通常设置为AF_UNIX。例:
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
  • type参数表示套接字的类型,可以选择SOCK_STREAMSOCK_DGRAM,取决于通信需求。

2、绑定套接字

使用bind()系统调用将套接字绑定到一个特定的文件路径。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 在Unix域套接字编程中,addr是一个指向struct sockaddr_un类型的指针,用于指定套接字的路径。例:
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/my_socket");

bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));

3、监听连接

使用listen()指定套接字处于被动监听状态。

int listen(int sockfd, int backlog);

backlog参数指定等待连接的队列长度。例:

listen(sockfd, 5);

4、接受连接

使用accept()系统调用接受来自客户端的连接请求。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

在Unix域套接字编程中,addr是一个指向struct sockaddr_un类型的指针,用于存储客户端的地址信息。

5、发送和接收数据

使用send()recv()系统调用在套接字之间传输数据。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

6、关闭套接字

使用close()系统调用关闭套接字。

int close(int sockfd);

3 例:服务端/客户端通信实现

现在通过一个例子来说明Unix套接字的使用。

功能:客户端需要一边接收用户的消息并转发给服务端,一边接收来自服务端的消息;服务端则在建立成功之后,对来自客户端的消息进行一个回显。

3.1 服务端

Unix的程序和TCP基本上一样,只是这里的地址需要指定一个本地的文件名,Unix通过这个文件来进行进程通信。

1、初始化套接字并接受连接

#define SOCKET_PATH "/tmp/unix_socket_example"
// 创建套接字
server_sock = socket(AF_UNIX, SOCK_STREAM, 0);
// 设置服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SOCKET_PATH);
// 绑定套接字到地址
bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 监听连接
listen(server_sock, 5);
// 接受连接
client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_len);

这里我们将套接字的地址设置为/tmp/unix_socket_example

2、接收消息并回显

char buffer[1024];
while (1)
{
    ssize_t received_bytes = recv(client_sock, buffer, sizeof(buffer), 0);
    if (received_bytes <= 0)
    {
        perror("Error receiving message");
        break;
    }

    printf("Received from client: %.*s", (int)received_bytes, buffer);

    if (send(client_sock, buffer, received_bytes, 0) == -1)
    {
        perror("Error sending message back to client");
        break;
    }
}

3.2 客户端

1、初始化套接字并连接到服务端

#define SOCKET_PATH "/tmp/unix_socket_example"
int client_sock;
struct sockaddr_un server_addr;
// 创建套接字
client_sock = socket(AF_UNIX, SOCK_STREAM, 0);
// 设置服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SOCKET_PATH);
// 连接到服务器
connect(client_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));

2、接收用户输入并发送给服务端

char buffer[1024];
while (1)
{
    printf("Enter a message: ");
    fgets(buffer, sizeof(buffer), stdin);
    send(client_sock, buffer, strlen(buffer), 0);
    ssize_t received_bytes = recv(client_sock, buffer, sizeof(buffer), 0);
    printf("Server's response: %.*s", (int)received_bytes, buffer);
}

代码比较简单,这里就不使用selectpoll等多路I/O复用方式监听用户输入和服务端的消息了,大家可以参考我之前的文章实现。

3.3 实验结果

如下图所示,服务端收到客户端的消息后再回显给客户端:

在这里插入图片描述

前面我们的地址设置为/tmp/unix_socket_example,也就是说在tmp目录下会创建一个unix_socket_example用于进程通信:
在这里插入图片描述

  • 开头的s表示套接字文件

现在重启一下服务端,提示我们地址已经被使用:

在这里插入图片描述

我们知道在TCP通信中也有这个问题,我们可以设置地址复用:

int reuse = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

但在Unix编程中,它是使用文件进行进程通信的,所以我们可以在服务端创建之前删除我们这个套接字文件:

在这里插入图片描述

再来运行一下服务端,发现可以创建了:

在这里插入图片描述

3.4 完整代码

1、服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "/tmp/unix_socket_example"

int main() {
    int server_sock, client_sock;
    struct sockaddr_un server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
	
	unlink(SOCKET_PATH);
    // 创建套接字
    server_sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_sock == -1) {
        perror("Error creating socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址结构
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SOCKET_PATH);

    // 绑定套接字到地址
    if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("Error binding socket");
        close(server_sock);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_sock, 5) == -1) {
        perror("Error listening for connections");
        close(server_sock);
        exit(EXIT_FAILURE);
    }

    printf("Server is listening for connections...\n");

    // 接受连接
    client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_len);
    if (client_sock == -1) {
        perror("Error accepting connection");
        close(server_sock);
        exit(EXIT_FAILURE);
    }

    printf("Connection established with a client.\n");

    // 接收和回显消息
    char buffer[1024];
    while (1) {
        // 接收来自客户端的消息
        ssize_t received_bytes = recv(client_sock, buffer, sizeof(buffer), 0);
        if (received_bytes <= 0) {
            perror("Error receiving message");
            break;
        }

        // 打印接收到的消息
        printf("Received from client: %.*s", (int)received_bytes, buffer);

        // 回显消息给客户端
        if (send(client_sock, buffer, received_bytes, 0) == -1) {
            perror("Error sending message back to client");
            break;
        }
    }

    // 关闭套接字
    close(client_sock);
    close(server_sock);
    unlink(SOCKET_PATH);

    return 0;
}

2、客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "/tmp/unix_socket_example"

int main() {
    int client_sock;
    struct sockaddr_un server_addr;

    // 创建套接字
    client_sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (client_sock == -1) {
        perror("Error creating socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址结构
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SOCKET_PATH);

    // 连接到服务器
    if (connect(client_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("Error connecting to server");
        close(client_sock);
        exit(EXIT_FAILURE);
    }

    printf("Connected to the server.\n");

    // 与用户交互,发送消息给服务器并接收回显消息
    char buffer[1024];
    while (1) {
        printf("Enter a message: ");
        fgets(buffer, sizeof(buffer), stdin);

        // 发送消息给服务器
        if (send(client_sock, buffer, strlen(buffer), 0) == -1) {
            perror("Error sending message to server");
            break;
        }

        // 接收来自服务器的回显消息
        ssize_t received_bytes = recv(client_sock, buffer, sizeof(buffer), 0);
        if (received_bytes <= 0) {
            perror("Error receiving message from server");
            break;
        }

        // 打印回显消息
        printf("Server's response: %.*s", (int)received_bytes, buffer);
    }

    // 关闭套接字
    close(client_sock);

    return 0;
}

4 TCP与Unix中connect的区别

在TCP中,如果服务器端的监听队列已满,connect()调用会阻塞,直到有连接插入为止,或者达到连接队列的最大长度。

  • 对于TCP如何非阻塞判断超时,可以参考我的另一篇文章:I/O系统调用(读/写/连接)的超时处理

而在Unix域套接字编程中,当客户端尝试使用connect()系统调用连接到服务器端的Unix域流式套接字时,如果服务器端的监听队列已满,connect()会立即返回ECONNREFUSED错误。这是因为Unix域套接字使用文件系统路径作为套接字地址,而服务器端在处理连接请求时,其实是在文件系统中创建一个套接字文件。如果监听队列已满,新的连接请求无法被立即处理,因此客户端会收到ECONNREFUSED错误。

以下是一些可能引起connect()返回ECONNREFUSED的情况:

  1. 服务器忙碌: 如果服务器端处理连接请求的速度不够快,导致监听队列已满,客户端可能会收到ECONNREFUSED
  2. 并发连接数过多: 如果系统中同时存在大量客户端尝试连接到服务器端,超过了服务器处理的能力,也可能导致监听队列满,从而引发ECONNREFUSED
  3. 套接字文件权限问题: 如果套接字文件所在的目录没有足够的权限,可能会导致服务器无法创建新的连接文件,从而引发ECONNREFUSED

http://www.kler.cn/a/159123.html

相关文章:

  • 【Chapter 3】Machine Learning Classification Case_Prediction of diabetes-XGBoost
  • 《生成式 AI》课程 第3講 CODE TASK 任务3:自定义任务的机器人
  • docker启动训练容器教程
  • centos7 升级openssl 与升级openssh 安装卸载 telnet-server
  • Python知识点精汇!字符串:定义、截取(索引)和其内置函数
  • shell编程--永久环境变量和字符串显位
  • 把 Windows 11 装进移动硬盘:Windows 11 To Go
  • 报错:Parsed mapper file: ‘file mapper.xml
  • Java中迭代Map和List最简单直接办法
  • Kafka 生产者 API 指南:深入理解生产者的实现与最佳实践
  • Python智能语音识别语翻译平台|项目后端搭建
  • 前端时间的失败总结复盘
  • 深度学习在单线性回归方程中的应用--TensorFlow实战详解
  • 十二、FreeRTOS之FreeRTOS任务相关API函数
  • 电子学会C/C++编程等级考试2023年03月(四级)真题解析
  • 12、组合模式(Composite Pattern,不常用)
  • javascript object转换成json格式
  • github首次将文件合到远端分支,发现名字不是master,而是main
  • 【Java 基础】20 多线程操作方法
  • 如何选呼叫中心的语音通道?
  • WordPiece词表的创建
  • Demystifying DeFi MEV Activities in Flashbots Bundle
  • 使用Redis做数据缓存
  • 数据结构和算法专题---3、失效算法与应用
  • 在Windows操作系统上使用rtsp simple server和ffmpeg推送录屏视频流
  • jsp在线辅助教育系统Myeclipse开发mysql数据库web结构java编程计算机网页项目