当前位置: 首页 > 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需要做如下处理…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...