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.mzph.cn/news/200879.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3.4 路由器的DHCP配置

实验3.4 路由器的DHCP配置 一、任务描述二、任务分析三、具体要求四、实验拓扑五、任务实施&#xff08;一&#xff09;配置基于接口地址池的DHCP1.交换机的基本配置2.路由器的基本配置3.开启路由器的DHCP服务器功能4.配置路由器接口的DHCP功能5.设置计算机使用DHCP方式获取IP地…

DS图应用--最短路径

Description 给出一个图的邻接矩阵&#xff0c;再给出指定顶点v0&#xff0c;求顶点v0到其他顶点的最短路径 Input 第一行输入t&#xff0c;表示有t个测试实例 第二行输入n&#xff0c;表示第1个图有n个结点 第三行起&#xff0c;每行输入邻接矩阵的一行&#xff0c;以此类…

Hello World!

一、minist数据集 深度学习编程特有的hello world程序&#xff1a;采用minist数据集完成意向特定深度学习项目 1、minist数据集介绍 MNIST数据集是一个广泛使用的手写数字识别数据集&#xff0c;它包含了许多不同人手写的数字图片。这个数据集被广泛用于研究手写数字识别&…

通过keepalived+nginx实现 k8s apiserver节点高可用

一、环境准备 K8s 主机配置&#xff1a; 配置&#xff1a; 4Gib 内存/4vCPU/60G 硬盘 网络&#xff1a;机器相互可以通信 k8s 实验环境网络规划&#xff1a; podSubnet&#xff08;pod 网段&#xff09; 10.244.0.0/16 serviceSubnet&#xff08;service 网段&#xff09;: 1…

【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安装后的效…

海外服务器和国内服务器有什么样的区别呢

海外服务器和国内服务器有什么样的区别呢&#xff0c;其实呢在外形方面是大同小异&#xff0c;除了外形还有一些其他方面还存在这一些差异。 一&#xff0c;地理位置的差异。 海外服务器——有可能在中国数据中心之外的任何国家地区&#xff0c;例如美国服务器&#xff0c;韩…

视频汇聚/音视频流媒体视频平台/视频监控EasyCVR分享页面无法播放,该如何解决?

国标GB28181安防视频监控/视频集中存储/云存储EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统…

ARM64安全特性之CET

ARM64 CET&#xff08;Control-flow Enforcement Technology&#xff09;是一种ARM架构的安全特性&#xff0c;旨在保护代码免受控制流劫持和恶意操作的影响。它引入了两个主要的机制&#xff1a;指令签名&#xff08;IBT&#xff09;和分支目标地址检查&#xff08;BTB&#x…

PRCD-1229 : An attempt to access configuration of database

今天维护oda一体机时&#xff0c;发现无法在grid用户下面关闭数据库实例&#xff0c;如下 ASM1:/home/gridoda0>srvctl stop database -d orcl -o immeidate PRCD-1229 : An attempt to access configuration of database orcl was rejected because its version 11.2.0.4.…

dockerdesktop推送镜像到dockerhub

1.查看镜像(打开powershell) docker ps2.打tag docker tag pengzx/aspnetcoredocker:v1 pengzx/aspnetcoredocker:v2pengzx/aspnetcoredocker:v1:本地的镜像名加版本号 pengzx/aspnetcoredocker:v2&#xff1a;需要上传的镜像名&#xff08;要以dockerhub的用户名开头/本地镜像…

移动产品经理常用的ChatGPT通用提示词模板

产品定位&#xff1a;如何明确移动产品的定位&#xff1f; 需求分析&#xff1a;如何进行移动产品的需求分析&#xff1f; 产品规划&#xff1a;如何制定移动产品的整体规划&#xff1f; UI设计&#xff1a;如何设计移动产品的用户界面&#xff1f; UX设计&#xff1a;如何…

软著项目推荐 深度学习的智能中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…

分享一个谷歌浏览器插件下载地址

xCrxDL - 下载谷歌浏览器(Chrome)扩展插件CRX 需要什么直接搜索下载就行&#xff0c;无广告&#xff0c;不用登录。

Python---魔术方法

1、什么是魔术方法 在Python中&#xff0c;__xxx__()的函数叫做魔法方法&#xff0c;指的是具有特殊功能的函数。 2、__init__()方法(初始化方法或构造方法) 思考&#xff1a;人的姓名、年龄等信息都是与生俱来的属性&#xff0c;可不可以在生产过程中就赋予这些属性呢&…

【工作生活】汽车电子嵌入式开发简介

目录 1. 目标 2. 要分享什么 3.1 行业知识 3.1.1车载行业知识&#xff1a; 3.1.2项目&#xff1a; 3.1.3开发测试工具&#xff1a; 3.2 硬件平台 3.3 基础知识 3.4 工作生活 3. 我们是谁 1. 目标 随着新能源汽车的快速崛起&#xff0c;汽车电子行业开始快速发展&…

【矩阵】54.螺旋矩阵(顺时针打印矩形元素)

题目 class Solution {public List<Integer> spiralOrder(int[][] matrix) {int m matrix.length, n matrix[0].length;int leftUpM 0, leftUpN 0, rightDownM m - 1, rightDownN n - 1;List<Integer> res new ArrayList<>();while (leftUpM < ri…

掌控安全 暖冬杯 CTF Writeup By AheadSec

本来结束时发到了学校AheadSec的群里面了的&#xff0c;觉得这比赛没啥好外发WP的&#xff0c;但是有些师傅来问了&#xff0c;所以还是发一下吧。 文章目录 Web签到&#xff1a;又一个计算题计算器PHP反序列化又一个PHP反序列化 Misc这是邹节伦的桌面背景图什么鬼&#xff1f;…

systick定时器

systick作为时基 用到的相关函数如下: void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)//设置时钟源 uint32_t SysTick_Config(uint32_t ticks) // 设置中断时间 SysTick_CLKSourceConfig函数 可以选用两个参数&#xff1a; 1.SysTick_CLKSource_HCLK_Div8 -AHB 时…

基于STM32 HAL库的光电传感器驱动程序实例

本文将使用STM32 HAL库编写一个光电传感器的驱动程序示例。首先&#xff0c;我们会介绍光电传感器的工作原理和应用场景。然后&#xff0c;我们将讲解如何选择合适的STM32芯片和光电传感器组合。接下来&#xff0c;我们会详细介绍使用STM32 HAL库编写光电传感器驱动程序的基本步…

Kafka 生产者 API 指南:深入理解生产者的实现与最佳实践

Kafka 是一个高性能、分布式的消息中间件系统&#xff0c;而其生产者 API 是连接应用程序与 Kafka 集群之间的纽带。本篇博客将深入探讨 Kafka 生产者 API 的核心概念、用法&#xff0c;以及一些最佳实践&#xff0c;帮助你更好地利用 Kafka 构建可靠的消息生产系统。 1. Kafk…