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

Linux socket编程(3):利用fork实现服务端与多个客户端建立连接

上一节,我们实现了一个客户端/服务端的Socket通信的代码,在这个例子中,客户端连接上服务端后发送一个字符串,而服务端接收到字符串并打印出来后就关闭所有套接字并退出了。

上一节的代码较为简单,在实际的应用中,客户端和服务端需要像一个聊天室一样能够收发信息,但这样就引出了一些问题:

1、服务端程序需要既能accept新的客户端请求,又能实时获与已经建立连接的客户端发来的消息

2、客户端程序需要既能从stdin获取用户输入,又能实时获取从服务端发来的消息

下面就来解决这两个问题。

文章目录

  • 1 代码改进
    • 1.1 fork函数
    • 1.2 服务端代码
    • 1.3 客户端代码
    • 1.4 实验结果
  • 2 代码优化:信号机制之kill和signal函数
    • 2.1 kill函数
    • 2.2 signal函数
    • 2.3 客户端代码改进
    • 2.4 实验结果
  • 3 完整代码
    • 3.1 服务端
    • 3.2 客户端

1 代码改进

以服务端的问题为例,在accept了一个客户端之后,就在循环中无限检测这个套接字是否有新的数据到来。此时如果又有一个客户端想要进行连接,由于服务端没有在accept,那么就无法建立连接。在这里介绍其中一种解决方法:fork函数。

1.1 fork函数

fork函数是Linux系统中用于创建新进程的系统调用。它在调用进程内部创建一个与父进程几乎完全相同的子进程。这个子进程是父进程的副本,包括代码段、数据段、堆栈等,但具有不同的进程ID(PID)。

pid_t fork(void);

返回值fork函数在父进程中返回两次,在父进程中返回的值为子进程的PID,而在子进程中返回的值为0。若系统资源不足,则返回-1。

例子:创建一个父进程,然后使用fork函数创建了一个子进程,分别输出它们的PID。父进程和子进程可以并发执行,并且各自具有不同的PID。

#include <stdio.h>
#include <unistd.h>int main() {pid_t child_pid = fork();if (child_pid == -1) {perror("fork");return 1;}if (child_pid == 0) {// This is the child processprintf("Child process, PID: %d\n", getpid());} else {// This is the parent processprintf("Parent process, PID: %d, Child PID: %d\n", getpid(), child_pid);}return 0;
}

运行结果:

在这里插入图片描述

可以看到if的两个分支并发执行,并输出PID。

1.2 服务端代码

现在就将accept放入while循环中,来监听多个客户端的请求,对于不同客户端fork一个子进程对数据进行处理,更改代码如下:

在这里插入图片描述

其中socket_read函数如下:

在这里插入图片描述

  • 这里服务端在收到客户端消息后,固定回复server gets message successfully

这里有几个点需要注意:

(1)在父进程中关闭子进程的socket,在子进程中关闭父进程的socket?

关闭套接字只会影响当前进程的套接字,不会直接影响其他进程的套接字,包括父进程的套接字。这是因为每个进程都有其独立的文件描述符表。在子进程中关闭套接字后,父进程的套接字仍然保持打开状态,不受影响。

(2)recv()函数

  • 返回0时,一般表示客户端断开了连接
  • 该函数不会在结尾添加\0,需要自行添加

1.3 客户端代码

客户端现在需要实现一遍通过stdin输入数据并发给服务端,一边接收服务端的数据。同样地,这也可以使用fork来解决:

在这里插入图片描述

这里在子进程中接收服务端的消息,在父进程中接收标准输入的数据并发送给服务端。

1.4 实验结果

在这里插入图片描述

2 代码优化:信号机制之kill和signal函数

在前面的代码中,send最好和recv一样判断返回值,在返回0的时候表示对端断开了连接,此时要关闭套接字。

现在以客户端为例再来理一下这里面的逻辑,在客户端中,子进程用来接收服务端的消息,父进程用来接收标准输入。如果服务端断开了连接,客户端的子进程的recv将返回0,从而退出;但是从逻辑上来看,服务端都退出了,此时父进程监听stdin也没有什么意义了。

**所以我们需要在客户端中实现:在子进程退出的同时,主动关闭父进程。**我们可以利用Linux中的信号机制来实现这个功能,这里简单地介绍一下killsignal函数的使用:

2.1 kill函数

kill是一个用于发送信号给指定进程的系统调用。它的原型如下:

int kill(pid_t pid, int sig);
  • pid参数指定目标进程的PID。可以使用不同的PID值来选择不同的目标:正整数表示具体的进程,0表示向与调用进程属于同一个进程组的所有进程发送信号,-1表示向所有具有权限的进程发送信号,-2表示向与调用进程属于同一个会话的所有进程发送信号。
  • sig参数是要发送的信号的编号
    • 通常我们可以使用常见的信号宏(如SIGTERMSIGKILLSIGINT等)作为参数,这些特殊的信号宏的回调函数由内核实现。比如进程接收到SIGKILL信号时,它会被立即终止。
    • 我们也可以使用一些自定义的宏(如SIGUSR1),然后自定义回调函数

2.2 signal函数

signal 函数用于注册信号处理函数,即在收到特定信号时执行的用户定义的函数。它的原型如下:

void (*signal(int signum, void (*handler)(int)))(int);
  • signum 参数是要处理的信号的编号。
  • handler 参数是一个函数指针,指向信号处理函数。通常,你可以使用一个函数来处理特定信号,或者使用 SIG_IGN表示忽略信号,使用SIG_DFL表示使用默认的信号处理方式。

2.3 客户端代码改进

我们需要实现在子进程退出的同时,主动关闭父进程:

1、在父进程代码开始处注册信号处理函数

在这里插入图片描述

其中handler函数如下:

在这里插入图片描述

也就是说,在父进程收到SIGUSR1信号的时候,会调用exit退出。

2、在子进程退出时调用kill函数向父进程发送SIGUSR1信号

在这里插入图片描述

  • 这里的getppid()用于获取父进程的pid

当然这里只是举一个例子,实际上可以不用在父进程中注册信号和回调函数,直接使用内核提供的SIGKILL信号,在子进程最后调用kill(getppid(), SIGUSR1),效果也是一样的。

2.4 实验结果

在这里插入图片描述

3 完整代码

3.1 服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>void socket_read(int s, struct sockaddr_in *addr)
{char buffer[1024];while (1){ssize_t bytes_received = recv(s, buffer, sizeof(buffer), 0);if (bytes_received == -1) {perror("Receive failed");} else if(bytes_received == 0){printf("peer closed\n");break;} else {buffer[bytes_received] = '\0';printf("recv from %s:%d = %s\n",inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), buffer);send(s, "server gets message successfully\n", strlen("server gets message successfully\n"), 0);}}
}int main() {// 创建套接字int server_socket = socket(AF_INET, SOCK_STREAM, 0);if (server_socket == -1) {perror("Socket creation failed");exit(1);}int reuse = 1;setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));// 绑定套接字到IP地址和端口struct sockaddr_in server_address;server_address.sin_family = AF_INET;server_address.sin_port = htons(8080);server_address.sin_addr.s_addr = INADDR_ANY;if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {perror("Bind failed");close(server_socket);exit(1);}// 设置服务器套接字为监听状态if (listen(server_socket, 5) == -1) {perror("Listen failed");close(server_socket);exit(1);}printf("Server listening on port 8080...\n");// 接受客户端连接struct sockaddr_in client_address;socklen_t client_len = sizeof(client_address);pid_t child_pid;while(1){int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_len);if (client_socket == -1) {perror("Accept failed");close(server_socket);exit(1);}// 打印客户端的IP和端口号printf("accept new:ip=%s port=%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));// 创建子进程child_pid = fork();if (child_pid == -1) {perror("fork");return 1;}if (child_pid == 0) {// 在子进程中可以关闭掉父进程的套接字close(server_socket);// 数据接收函数,在while中循环读socket是否有数据并输出socket_read(client_socket, &client_address);// 如何该子进程对应的客户端退出了,该函数返回,应该退出子进程,防止子进程acceptexit(1);} else {// 在父进程中可以关闭掉子进程的套接字,继续下一次while循环执行acceptclose(client_socket);}}return 0;
}

3.2 客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <signal.h>void handler(int sig)
{printf("recv a sig=%d\n", sig);exit(1);
}int main() {// 创建套接字int client_socket = socket(AF_INET, SOCK_STREAM, 0);if (client_socket == -1) {perror("Socket creation failed");exit(1);}// 设置服务器地址和端口struct sockaddr_in server_address;server_address.sin_family = AF_INET;server_address.sin_port = htons(8080);server_address.sin_addr.s_addr = inet_addr("127.0.0.1");// 连接到服务器if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {perror("Connection failed");close(client_socket);exit(1);}// 创建子进程pid_t child_pid;child_pid = fork();if (child_pid == -1) {perror("fork");return 1;}if (child_pid == 0) {// 子进程用来接收服务端发来的消息char recv_buf[1024];while(1){ssize_t bytes_received = recv(client_socket, recv_buf, sizeof(recv_buf), 0);if (bytes_received == -1) {perror("Receive failed");} else if(bytes_received == 0){printf("peer closed\n");break;} else{recv_buf[bytes_received] = '\0';printf("recv %ld bytes from server:%s\n", bytes_received, recv_buf);}}close(client_socket);kill(getppid(), SIGUSR1);} else {// 父进程用来从stdin中接收数据signal(SIGUSR1, handler);char stdin_buf[1024];while(fgets(stdin_buf, sizeof(stdin_buf), stdin) != NULL){send(client_socket, stdin_buf, strlen(stdin_buf), 0);}close(client_socket);}return 0;
}

相关文章:

Linux socket编程(3):利用fork实现服务端与多个客户端建立连接

上一节&#xff0c;我们实现了一个客户端/服务端的Socket通信的代码&#xff0c;在这个例子中&#xff0c;客户端连接上服务端后发送一个字符串&#xff0c;而服务端接收到字符串并打印出来后就关闭所有套接字并退出了。 上一节的代码较为简单&#xff0c;在实际的应用中&…...

若依Linux与Docker集群部署

若依Linux集群部署 1. 若依2.MYSQL Linux环境安装2.1 MYSQL数据库部署和安装2.2 解压MYSQL安装包2.3 创建MYSQL⽤户和⽤户组2.4 修改MYSQL⽬录的归属⽤户2.5 准备MYSQL的配置⽂件2.6 正式开始安装MYSQL2.7 复制启动脚本到资源⽬录2.8 设置MYSQL系统服务并开启⾃启2.9 启动MYSQL…...

20.2 设备树中的 platform 驱动编写

一、设备树下的 platform 驱动 platform 驱动框架分为总线、设备和驱动&#xff0c;总线不需要我们去管理&#xff0c;这个是 Linux 内核提供。在有了设备树的前提下&#xff0c;我们只需要实现 platform_driver 即可。 1. 修改 pinctrl-stm32.c 文件 先复习一下 pinctrl 子系…...

C++实现ransac

目录 一、ransac算法原理 1.1、算法概念 1.2、图解 二、c实现ransac 2.1、设置随机样本和离群点 2.2、随机抽取样本 2.3、内点计算 2.4、更新参数 2.2、完整代码 一、ransac算法原理 1.1、算法概念 随机抽样一致性 (RANSAC) 是一种迭代方法&#xff0c;用于根据一组包…...

DNS域名解析服务

1.概述 1.1.产生原因 IP 地址:是互联网上计算机唯一的逻辑地址&#xff0c;通过IP 地址实现不同计算机之间的相互通信&#xff0c;每台联网计算机都需要通过I 地址来互相联系和分别&#xff0c;但由于P 地址是由一串容易混淆的数字串构成&#xff0c;人们很难记忆所有计算机的…...

【milkv】2、mpu6050驱动添加及测试

前言 本章介绍mpu6050的驱动添加以及测试。 其中驱动没有采用sdk提供的驱动&#xff0c;一方面需要配置irq&#xff0c;另一方面可以学习下如何通过ko方式添加驱动。 一、参考文章 驱动及测试文件编译流程&#xff1a; https://community.milkv.io/t/risc-v-milk-v-lsm6ds…...

SpringCloud Alibaba(中):服务熔断降级-Sentinel

Sentinel Sentinel是阿里巴巴开源的分布式系统流量防卫防护组件&#xff0c;主要对分布式系统中的流量进行控制、熔断降级等保护操作。Sentinel的目标是成为互联网级别分布式系统的流量防卫防护组件&#xff0c;它与系统的各个部分集成&#xff0c;保护着系统的入口和出口。 …...

模型的训练专题

训练目标在数学上指定了模型应该如何从训练数据中学习和获取能力。训练基础模型的当前现状涉及特定于模型的目标。我们设想&#xff0c;未来基础模型的训练目标将反映两个变化&#xff1a;从系统证据和评估中得出的原则性选择&#xff0c;以及跨数据源和模式提供丰富、可扩展和…...

深入解析 Azure 机器学习平台:架构与组成部分

Azure机器学习平台是Microsoft Azure提供的一种云上机器学习服务&#xff0c;为开发者和数据科学家提供了一个全面且易于使用的环境来创建、训练、部署和管理机器学习模型。本文将对Azure机器学习平台的基本架构和组成部分进行深入解析&#xff0c;帮助读者全面了解该平台的工作…...

使用百度语音识别技术实现文字转语音的Java应用

探讨如何使用百度语音识别技术将文字转换为语音的Java应用。百度语音识别技术是一种强大的语音识别服务&#xff0c;可以将输入的文字转换为自然流畅的语音输出。我们将使用Java编程语言来实现这个应用&#xff0c;并提供相应的源代码。 首先&#xff0c;我们需要准备一些前提…...

【C#学习】文件操作

文章目录 常见操作拷贝文件检测文件夹是否存在并创建判断文件是否存在删除文件夹下的所有文件保留文件夹获取指定目录下的所有文件名删除 常见操作 拷贝文件 System.IO.File.Copy(sourcePath, targetPath); 检测文件夹是否存在并创建 //if directory not exit,then establis…...

Chrome版本对应Selenium版本

1.获得浏览器版本号和驱动 浏览器版本: 119.0.6045.124 浏览器驱动版本: 119.0.6043.1 / 120.0.6051.0 访问 https://vikyd.github.io/download-chromium-history-version/ 2. 安装selenium pip install selenium4.1.1 -i http://pypi.mirrors.ustc.edu.cn/simple/ --trusted…...

Day29力扣打卡

打卡记录 美丽塔 II&#xff08;前后缀分解 单调栈&#xff09; 链接 大佬的题解 class Solution:def maximumSumOfHeights(self, a: List[int]) -> int:n len(a)suf [0] * (n 1)st [n] # 哨兵s 0for i in range(n - 1, -1, -1):x a[i]while len(st) > 1 and …...

java源码用到的设计模式

Java 中有许多常用的设计模式&#xff0c;它们是为了解决特定问题而被反复使用和验证的经验总结。以下是一些常见的 Java 设计模式&#xff1a; 创建型模式 工厂模式 (Factory Pattern): 提供一个创建对象的接口&#xff0c;但是由子类决定实例化哪个类。例如&#xff1a;java…...

high perfermance computer usage

简单记一下hpc的使用&#xff1a; hpc就是一些科研机构或者大学建立的服务器中心。我这大学的每一位学生&#xff0c;可以轻松使用hpc批量跑数据&#xff0c;也可以新建自己的server跑一些local data&#xff0c;后者每个学生账号最大是32核512G的运行内存&#xff0c;体验非常…...

51单片机+DS1302设计一个电子钟(LCD1602显示时间)

一、前言 电子钟是一种能够准确显示时间的设备&#xff0c;广泛应用于家庭、办公场所和公共场所&#xff0c;为人们提供了方便和准确的时间信息。本项目设计一个基于51单片机的电子钟&#xff0c;使用DS1302作为RTC时钟芯片&#xff0c;LCD1602作为显示屏&#xff0c;并通过串…...

vue项目中在scss代码中使用data中的变量

尽管在日常开发中&#xff0c;这类情况实际上很少出现。 VUE2: 在HTML中使用时&#xff0c;请确保将cssVars绑定在需要使用CSS变量的元素或该元素的上层元素上。 <template><div :style"cssVars"><div class"test"/></div><…...

uni-app报错“本应用使用HBuilderX x.x.x 或对应的cli版本编译,而手机端SDK版本是x.x.x不匹配的版本可能造成应用异常”

uniapp开发的一个跨平台软件&#xff0c;在安卓模拟器上启动的时候报警告&#xff1a; 官方给的解释&#xff1a;uni-app运行环境版本和编译器版本不一致的问题 - DCloud问答 解决办法有两个 方法一&#xff1a;添加忽略警告的配置 项目根目录下找到 manifest.json&#xf…...

[sd_scripts]之train

https://github.com/kohya-ss/sd-scripts/blob/main/docs/train_README-zh.mdhttps://github.com/kohya-ss/sd-scripts/blob/main/docs/train_README-zh.md 支持模型fine-tune&#xff0c;dreambooth&#xff0c;lora&#xff0c;textual inversion。 1.数据准备 在任意多个…...

samba 共享目录write permission deny问题修复 可读取内容但不可修改 删除 新增文件

关于 update/delete/write permission deny问题修复 0.首先在服务器端执行testparm -s &#xff0c;测试 Samba 配置并显示结果。需确保服务器端参数 read only No &#xff0c;共享目录有写入权限 一、若配置了允许匿名访问&#xff0c;使用匿名访问来操作smb需要做如下处理…...

MBTI性格测试

简介 MBTI&#xff08;Myers‑Briggs Type Indicator&#xff0c;迈尔斯‑布里格斯类型指标&#xff09;是基于荣格心理类型理论发展出的性格类型工具&#xff0c;由凯瑟琳库克布里格斯及其女儿伊莎贝尔布里格斯迈尔斯创建。它通过四对偏好维度将个体的认知与行为倾向归纳为 16…...

嘈杂工业场景下的自适应VAD与双码本声纹识别鉴权系统:基于端侧轻量化神经网络与向量量化(VQ)重构

在大型化工车间、能源集控中心以及金融极密隔离库房中&#xff0c;离线声纹识别是物理访问控制和身份安全核验的重要生物特征屏障。然而&#xff0c;在环境本底噪声高达80dB以上的恶劣工业场景下&#xff0c;常规的语音活动检测&#xff08;VAD&#xff09;会频繁误触&#xff…...

WPF虚拟桌宠组件:可嵌入、高性能、工程化UI生命体

1. 这不是“桌面宠物”&#xff0c;而是一个可嵌入的WPF UI组件化生命体你可能在Windows XP时代见过那只晃着尾巴、偶尔打哈欠的3D小猫&#xff0c;也可能在Win10系统托盘里点开过一个会眨眼的像素狐狸——但那些是独立进程、是系统级小工具、是“看一眼就关掉”的轻量娱乐。而…...

解决Claude Code Token不足问题并享受Taotoken活动价

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 解决Claude Code Token不足问题并享受Taotoken活动价 应用场景类&#xff0c;聚焦于使用Claude Code时遇到Token配额紧张的开发者&…...

孤舟笔记 互联网常用框架篇二 Dubbo服务请求失败怎么处理?集群容错策略你用过几种

文章目录先说结论Failover&#xff1a;换家店试试Failfast&#xff1a;不行就算了Failsafe&#xff1a;忘了这事Failback&#xff1a;回头再说Forking&#xff1a;同时点几家Broadcast&#xff1a;通知所有人怎么选择回答技巧与点评加分回答面试官点评个人网站分布式系统中&…...

打不开JupyterLab

因为安装某些依赖导致JupyterLab的依赖被动升级或降级&#xff0c;从而影响了JupyterLab的运行&#xff0c;此时可以SSH登录到实例&#xff0c;然后输入jupyter-lab命令进行确认&#xff0c;如果执行命令报错则说明是此问题&#xff0c;那么可以通过pip install jupyterlab再次…...

别让依赖毁了你的实验:记一次Vision Mamba复现中causal_conv1d与mamba-ssm的版本“打架”事件

Vision Mamba复现实战&#xff1a;破解依赖冲突的工程化解决方案在深度学习项目的复现过程中&#xff0c;依赖管理往往是最容易被忽视却又最常导致问题的环节。最近在复现Vision Mamba模型时&#xff0c;我遭遇了一场典型的Python依赖"战争"——causal_conv1d与mamba…...

如何让旧款Mac运行最新系统:OpenCore Legacy Patcher完整指南

如何让旧款Mac运行最新系统&#xff1a;OpenCore Legacy Patcher完整指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 想让你的老旧Mac设备重新焕发活力&a…...

企业级Veo 2提示词治理框架(含合规校验/版本回溯/效果归因三模块)——仅限首批500名开发者开放》

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;Veo 2提示词治理框架的核心定位与演进逻辑 Veo 2提示词治理框架并非单纯的技术工具升级&#xff0c;而是面向AIGC生产环境规模化、合规化与可审计化需求的战略性基础设施重构。其核心定位在于将离散、经…...

程序员的物理级打字肌肉记忆训练指南:从一指禅到无意识盲打的科学路径

程序员的物理级打字肌肉记忆训练指南&#xff1a;从一指禅到无意识盲打的科学路径 在日常写代码或重构时&#xff0c;你是否遇到过这种场景&#xff1a; 脑子里已经构思好了完美的重构逻辑&#xff0c;但在输入 >、{} 或 _ 时&#xff0c;手指本能地一顿&#xff0c;视线不…...