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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/221516.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【springboot】整合redis和定制化

1.前提条件:docker安装好了redis,确定redis可以访问 可选软件: 2.测试代码 (1)redis依赖 org.springframework.boot spring-boot-starter-data-redis (2)配置redis &#xff08;3&#xff09; 注入 Resource StringRedisTemplate stringRedisTemplate; 这里如果用Autowi…

HarmonyOS学习--了解基本工程目录

1.工程级目录 工程的目录结构如下&#xff1a; 其中详细如下&#xff1a; AppScope中存放应用全局所需要的资源文件。entry是应用的主模块&#xff0c;存放HarmonyOS应用的代码、资源等。oh_modules是工程的依赖包&#xff0c;存放工程依赖的源文件。build-profile.json5是工…

FPGA实现电机位置环、速度环双闭环PID控制

一、设计思路 主要设计思路就是根据之前写的一篇FPGA实现电机转速PID控制&#xff0c;前面已经实现了位置环的控制&#xff0c;思想就是通过电机编码器的当前位置值不断地修正PID去控制速度。 那为了更好的实现控制&#xff0c;可以在位置环后加上速度环&#xff0c;实现电机位…

【S32K3环境搭建】-0.2-安装S32DS product updates和 packages

目录 1 安装S32DS product updates和 packages 1.1 方法一&#xff1a;通过S32DS Extensions and Updates安装product updates和 packages 1.2 方法二&#xff1a;通过Install New Software…安装product updates和 packages 2 S32DS product updates和 packages安装后的效…

1-4节电池升降压充电IC解决方案

描述 MP2760是一款集成窄电压DC&#xff08;NVDC&#xff09;电源路径管理功能和USB On-the-Go(OTG)功能的升降压充电IC&#xff0c;兼容USB PD&#xff0c;适用于单节至4节串联的电池包应用。该芯片的充电输入电压范围广&#xff0c;可支持最高22V。 当启用电池放电模式&…

网络安全威胁——中间人攻击

中间人攻击 1. 定义2. 中间人攻击如何工作3. 常见中间人攻击类型4. 如何防止中间人攻击 1. 定义 中间人攻击&#xff08;Man-in-the-Middle Attack&#xff0c;简称MITM&#xff09;&#xff0c;是一种会话劫持攻击。攻击者作为中间人&#xff0c;劫持通信双方会话并操纵通信过…

Java数据结构之《最短路径》(难度系数100)

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度偏难(偏难理解)的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题…

Android Chips(标签)

目录 一、流式布局标签发展历程 二、类型及使用 2.1 Chip.Action(默认值) 2.2 Chip.Entry 2.3 Chip.Filter 2.4 Chip.Choice 三、常用事件 3.1 OnClickListener 3.2 OnCheckedChangeListener 3.3 OnCloseIconClickListener 四、ChipGroup 4.1 ChipGroup Chip.Choi…

计算机组成学习-中央处理器总结

复习本章时&#xff0c;思考以下问题&#xff1a; 1)CPU分为哪几部分&#xff1f;分别实现什么功能&#xff1f; 2)指令和数据均存放在内存中&#xff0c;计算机如何从时间和空间上区分它们是指令还是数据&#xff1f; 3)什么是指令周期、机器周期和时钟周期&#xff1f;它们之…

java小工具util系列3:JSON转实体类对象工具

文章目录 准备工作1.JSONObject获取所有的key2.集合中实体对象转换 list中Enrey转Dto3.字符串转List<BusyTimeIndicatorAlarmThreshold>4.json字符串转JSONObject5.list根据ids数组过滤list6.json字符串转JavaBean对象7.json对象转javabean8.jsonObject转map9.List\<U…

IDEA中,光标移动快捷键(Shift + 滚轮前后滚动:当前文件的横向滚动轴滚动。)

除此之外&#xff0c;其他常用的光标移动快捷键包括&#xff1a; Shift 滚轮前后滚动&#xff1a;当前文件的横向滚动轴滚动。Shiftenter&#xff1a;快速将鼠标移动到下一行。Ctrl ]&#xff1a;移动光标到当前所在代码的花括号结束位置。Ctrl 左方向键&#xff1a;光标跳转…

IDEA插件配置--maven篇

仓库地址 IDEA中maven插件仓库默认地址&#xff1a;C:\Users\Administrator.m2\repository 在D盘新建一个文件夹用做本地仓库地址&#xff0c;例如 D:\Program Files\maven\repository&#xff0c;将原先C盘路径下的repository拷贝到D盘 修改settings.xml配置文件 镜像地…

基于微服务架构的外卖系统源码开发

在当前互联网时代&#xff0c;外卖行业蓬勃发展&#xff0c;用户对于高效、智能的外卖服务需求不断增加。为了满足这一需求&#xff0c;采用微服务架构的外卖系统成为了开发的主流方向。本文将探讨基于微服务的外卖系统源码开发&#xff0c;涉及到关键技术和示例代码。 1. 微…

SpringBoot3-快速体验

1、pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.…

搞笑视频无水印下载,高清无水印视频网站!

搞笑视频无水印下载这件事情一直困扰了广大网友&#xff0c;每当看见好玩好笑的搞笑视频然而下载下来的时候&#xff0c;要么画质模糊就带有水印今天分享大家几个搞笑视频无水印下载方法。 这是一个非常良心的搞笑视频无水印下载小程序水印云&#xff0c;它支持图片去水印、视…

Flutter桌面应用程序定义系统托盘Tray

文章目录 概念实现方案1. tray_manager依赖库支持平台实现步骤 2. system_tray依赖库支持平台实现步骤 3. 两种方案对比4. 注意事项5. 话题拓展 概念 系统托盘&#xff1a;系统托盘是一种用户界面元素&#xff0c;通常出现在操作系统的任务栏或桌面顶部。它是一个水平的狭长区…

vscode git管理

vscode添加了git管理 1、如下按钮&#xff0c;可以看到本次的修改部分 2、安装git history 就可以查看每次的不同部分了

阿里云环境下的配置DNS和SLB的几种实践示例

一、背景 对于大多中小型公司来说&#xff0c;生产环境大多是购买阿里云或者腾讯云等等&#xff0c;也就存在以下需求&#xff1a; 外网域名内网域名SLB容器化部署 特别是前两项&#xff0c;一定是跳不过的。容器化部署&#xff0c;现在非K8S莫属了。 既然是购买阿里云&…

Codeforces Round 913 (Div. 3)(A~G)

1、编程模拟 2、栈模拟 3、找规律&#xff1f;&#xff08;从终止状态思考&#xff09; 4、二分 5、找规律&#xff0c;数学题 6、贪心&#xff08;思维题&#xff09; 7、基环树 A - Rook 题意&#xff1a; 直接模拟 // Problem: A. Rook // Contest: Codeforces - C…

JSON 语法详解:轻松掌握数据结构(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…