常见的socket函数封装和多进程和多线程实现服务器并发

news2025/2/25 2:04:05

常见的socket函数封装和多进程和多线程实现服务器并发

  • 1.常见的socket函数封装
  • 2.多进程和多线程实现服务器的并发
    • 2.1多进程服务器
    • 2.2多线程服务器
    • 2.3运行效果

1.常见的socket函数封装

在这里插入图片描述

accept函数或者read函数是阻塞函数,会被信号打断,我们不能让它停止,所以我们应该进行一些封装操作。

//wrap.h
 
#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
 
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif

下面是相关函数的实现

//wrap.c
 
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
 
void perr_exit(const char *s)
{
	perror(s);
	exit(-1);
}
 
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;
 
again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");     
	}
	return n;
}
 
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
 
	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");
 
    return n;
}
 
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
 
	if ((n = connect(fd, sa, salen)) < 0)
		perr_exit("connect error");
 
    return n;
}
 
int Listen(int fd, int backlog)
{
    int n;
 
	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");
 
    return n;
}
 
int Socket(int family, int type, int protocol)
{
	int n;
 
	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");
 
	return n;
}
 
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;
 
again:
	if ( (n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}
 
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;
 
again:
	if ( (n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}
 
int Close(int fd)
{
    int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");
 
    return n;
}

2.多进程和多线程实现服务器的并发

当有多个客户端向服务器发送数据的时候,我们如何去操作,这就涉及到了我们的多线程和多进程开发了,下面看看如何来实现。

2.1多进程服务器

(1)首先我们想如何通过多进程来实现呢?那么我们得想清楚父子进程分别来干啥,我们可以这样,父进程来获取连接

(2)然后子进程来进行通信发送数据给服务端。

(3)最后我们利用信号的方式来回收子进程,防止出现僵尸进程。

/*多进程实现并发,主进程中使用sigaction函数回收子进程*/
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include "wrap.h"
 
void sighandler(int sig)
{
    pid_t wpid;
	//回收子进程
	while(1)
	{
		wpid = waitpid(-1, NULL, WNOHANG);
		if(wpid <= 0)
		{
			break;
		}
	}
}
 
int main()
{
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);
 
    //设置端口复用
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
 
    struct sockaddr_in serverAddr;
    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(lfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
 
    Listen(lfd, 128);
 
    //将SIGCHLD信号阻塞
	sigset_t mask;
	sigemptyset(&mask);
	sigaddset(&mask, SIGCHLD);
	sigprocmask(SIG_BLOCK, &mask, NULL);
 
    int cfd;
    pid_t mpid;
    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(clientAddr);
    while(1)
    {
        cfd = Accept(lfd, (struct sockaddr*)&clientAddr, &len);
 
        mpid = fork();
        if (mpid < 0)
        {
            perror("fork error:");
            exit(0);
        }
        else if (mpid > 0)
        {
            close(cfd);
            //signal(SIGCHLD, sighandler);
 
            //注册信号处理函数
            struct sigaction act;
            act.sa_handler = sighandler;
            sigemptyset(&act.sa_mask);
            act.sa_flags = 0;
            sigaction(SIGCHLD, &act, NULL);
 
            //解除对SIGCHLD信号的阻塞
            sigprocmask(SIG_UNBLOCK, &mask, NULL);
        }
        else if(mpid == 0)
        {
            //子进程中执行消息收发
            close(lfd);
 
            char buf[1024];
            int nLen; 
            char cIP[16];
            while(1)
            {
                memset(buf, 0, sizeof(buf));
                nLen = Read(cfd, buf, sizeof(buf));
                if (nLen <= 0)
                {
                    perror("read error:");
                    break;
                }
                printf("%s--%d: %s\n", inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, cIP, sizeof(cIP))
                                   , ntohs(clientAddr.sin_port), buf);
                
                strcat(buf, "---recvied");
                Write(cfd, buf, strlen(buf));
            }
            close(cfd);
            exit(0);  //子进程退出,防止子进程继续创建子进程
        }
    }
    close(lfd);
    return 0;
}

2.2多线程服务器

接下来就是多线程服务器如何去实现呢?

我们可以参考上面的多进程开发:
(1)首先我们利用主进程来获取连接。

(2)然后利用子线程来和服务器进行通信给服务器发送数据。

(3)最后设置线程分离属性,任务完成后自动回收子线程。

注意:

(1)线程和进程之间是有不同的,线程的文件描述符时共享的,一旦有一个新的连接过来的时候,所有的通信文件描述符cfd都会改变,但是进程时写时拷贝的,所以进程不会出现这种情况。因此在使用线程开发时,我们要分别给他们开辟空间,这里可以用一个结构体,不同线程使用不同的空间

(2)由于线程的文件描述符是共享的,所以我们不可以关闭父线程的通信文件描述符,这样会导致子线程的通信文件描述符全关闭,导致子线程无法正常通信;而进程程会有计数引用,只会是通信文件描述符的引用次数减1,不会直接全部关闭。

下面是代码:

/*多线程实现并发, 解决多个子线程共享cfd存在的问题*/
#include "wrap.h"
#include <pthread.h>
 
#define MAX_NUM 100
 
struct PthreadInfo
{
    int cfd;  //若为-1表示可用, 大于0表示已被占用
    pthread_t threadID;
    struct sockaddr_in clientAddr;
};
//定义结构体数组,不同的线程访问不同的内存
struct PthreadInfo info[MAX_NUM];
 
//线程执行函数
void* mythread(void* arg)
{
    struct PthreadInfo* p = (struct PthreadInfo*)arg;
 
    char buf[1024];
    int cfd = p->cfd;
    ssize_t len;
    while (1)
    {
        memset(buf, 0, sizeof(buf));
        len = Read(cfd, buf, sizeof(buf));
        if (len <= 0)
        {
            perror("read error:");
            close(cfd);
            p->cfd = -1;  //设置为-1表示该位置可用
            pthread_exit(NULL);
        }
        
        printf("%s\n", buf);
 
        strcat(buf, "---recvied");
        Write(cfd, buf, strlen(buf));
    }
    
}
 
void init_info()
{
    //初始化数组,当cfd = -1时表明这块内存空间可以使用
    for (size_t i = 0; i < MAX_NUM; i++)
    {
        info[i].cfd = -1;
    }
}
 
int find_index()
{
    int i;
    for(i = 0; i < MAX_NUM; i++)
    {
        if (info[i].cfd == -1)
        {
            break;
        }
    }
    if (i == MAX_NUM)
    {
        return -1;
    }
    return i;
}
 
 
int main()
{
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);
 
    //设置端口复用
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
 
    struct sockaddr_in serverAddr;
    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(lfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
 
    Listen(lfd, 128);
 
    //初始化
	init_info();
 
    int cfd;
	int ret;
	int idx;
	socklen_t len;
	struct sockaddr_in client;
    while (1)
    {
        len = sizeof(client);
        bzero(&client, len);
 
        cfd = Accept(lfd, (struct sockaddr*)&client, &len);
 
        //找数组中空闲的位置
        idx = find_index();
        if (idx == -1)
        {
            close(cfd);
            continue;
        }
 
        //对空闲位置的元素的成员赋值
        info[idx].cfd = cfd;
        memset(&info[idx].clientAddr, &client, len);
 
        //创建子线程---该子线程完成对数据的收发
        ret = pthread_create(&info[idx].threadID, NULL, mythread, &info[idx]);
        if(ret!=0)
		{
			printf("create thread error:[%s]\n", strerror(ret));
			exit(-1);
		}
		
		//设置子线程为分离属性
		pthread_detach(info[idx].threadID);
        
    }
    
    close(lfd);
    return 0;
}

我们在写的时候发现当一些进程完成通信以后,关闭文件描述符,我们的空间是无法进行回收的,这样就会大大浪费空间,因此我们可以写一个函数来返回结束通信的空间位置可利用的空间,来使用这块空间。

int find_index()
{
    int i;
    for(i = 0; i < MAX_NUM; i++)
    {
        if (info[i].cfd == -1)
        {
            break;
        }
    }
    if (i == MAX_NUM)
    {
        return -1;
    }
    return i;
}

2.3运行效果

下面我们看看效果

1.这是连接的第一个客户端,可以看到通信正常
在这里插入图片描述
2.这是连接的第二个客户端,通信也正常

在这里插入图片描述3.我们用命令来看看连接的状态

在这里插入图片描述
可以看到tcp连接是一个双向的可靠连接,我们连接了两个客户端,所以有四个连接,可以看到都处于ESTABLISHESD的状态,可以看出是达到了效果。两个客户端和服务端的通信都是正常的。

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

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

相关文章

基于最小二乘正弦拟合算法的信号校正matlab仿真,校正幅度,频率以及时钟误差,输出SNDR,SFDR,ENOB指标

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 最小二乘正弦拟合 4.2 SNDR、SFDR 和 ENOB 计算 4.3 校正 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ..........................…

vscode 如何连接 WSL (不能通过 IP 地址连接)

来源&#xff1a;https://www.cnblogs.com/wxdblog/p/17234342.html vscode (remote-ssh) 连接 WSL 不能使用 IP地址 连接&#xff0c;需要安装 WSL 扩展才行

《TCP/IP详解 卷一》第9章 广播和本地组播

目录 9.1 引言 9.2 广播 9.2.1 使用广播地址 9.2.2 发送广播数据报 9.3 组播 9.3.1 将组播IP地址转换为组播MAC地址 9.3.2 例子 9.3.3 发送组播数据报 9.3.4 接收组播数据报 9.3.5 主机地址过滤 9.4 IGMP协议和MLD协议 9.4.1 组成员的IGMP和MLD处理 9.4.2 组播路由…

【kubernetes】关于k8s集群的声明式管理资源

目录 一、声明式管理方法 二、资源配置清单管理 1、导出资源配置清单 2、修改资源配置清单并应用 2.1离线修改 2.2在线修改 三、通过资源配置清单创建资源对象 获取K8S资源配置清单文件模板&#xff1f; 关于配置清单常见的字段 方案一&#xff1a;手写yaml配置文件 …

Apache Paimon 主键表解析

Primary Key Table-主键表 1.概述 主键表是创建表时的默认表类型&#xff0c;用户可以插入、更新或删除表中的记录&#xff1b; 主键由一组列组成&#xff0c;这些列包含每条记录的唯一值&#xff1b; Paimon通过对每个桶中的主键排序来强制数据有序&#xff0c;允许用户在…

【README 小技巧】 展示gitee中开源项目start

【README 小技巧】 展示gitee中开源项目start <a target"_blank" hrefhttps://gitee.com/wujiawei1207537021/wu-framework-parent><img srchttps://gitee.com/wujiawei1207537021/wu-framework-parent/badge/star.svg altGitee star/></a>

2024年腾讯云4核8G5M云服务器并发量支持多少人在线?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

小程序应用、页面、组件生命周期

引言 微信小程序生命周期是指在小程序运行过程中&#xff0c;不同阶段触发的一系列事件和函数。这一概念对于理解小程序的整体架构和开发流程非常重要。本文将介绍小程序生命周期的概念以及在不同阶段触发的关键事件&#xff0c;帮助开发者更好地理解和利用小程序的生命周期。 …

Github 2024-02-25开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-02-25统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目5Jupyter Notebook项目2TypeScript项目2非开发语言项目1HTML项目1C项目1Dart项目1 Python - 100天…

SSM SpringBoot vue物资管理系统

SSM SpringBoot vue物资管理系统 系统功能 登录注册 个人中心 财务管理 商品类别管理 供应商信息管理 商品信息管理 进货入库管理 销售出库管理 商品盘点管理 开发环境和技术 开发语言&#xff1a;Java 使用框架: SSM(Spring SpringMVC Mybaits)或SpringBoot Mybaits 前…

【学习总结】什么是弹性负载均衡? LB和ELB的区别

[Q&A] 什么是 LB (Load Balancer) 负载均衡器&#xff1a; 这是一个广泛的概念&#xff0c;泛指任何用于在网络流量进入时进行分配以实现服务器集群间负载均衡的设备或服务。传统的负载均衡器可以是硬件设备&#xff0c;也可以是软件解决方案&#xff0c;其基本目标是将客…

国创证券:机器人概念掀涨停潮 迈赫股份、丰立智能等多股涨停

机器人概念26日盘中走势活跃&#xff0c;到发稿&#xff0c;中亚股份、中威电子、迈赫股份、丰立智能、东杰智能等多股涨停&#xff0c;机器人、巨能股份、本川智能、江苏北人等涨超12%。 消息面上&#xff0c;人形机器人方面&#xff0c;据媒体报道&#xff0c;特斯拉近日通过…

【监督学习之支持向量机(SVM)】

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 目录 简述概要知识图谱基本原理支持向量线性SVM与非线性SVM优化问题软间隔与正则化SVM的应用实现 简述概要 了解监督学习-支持向量机&#xff08;SVM&#xff09; 知识图谱 支持向量机&#xff08;Support Vector Machine&…

【已解决】用ArcGIS处理过的数据在QGIS中打开发生偏移怎么办?| 数据在ArcGIS中打开位置正常,在QGIS中偏移

1. 问题描述 栅格或者矢量数据用ArcGIS打开时位置正确&#xff08;可以和其他数据对应上&#xff09;。但是用QGIS打开后发现位置不对 2. 问题的原因 因为该数据用了ArcGIS自定义的坐标系&#xff0c;QGIS不支持&#xff0c;识别有误。因此在数据QGIS中的坐标系参数有误&a…

基于FPGA的9/7整数小波变换和逆变换verilog实现,包含testbench

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 9/7整数小波变换原理 4.2 逆变换过程 5.算法完整程序工程 1.算法运行效果图预览 将测试结果导入到matlab显示 2.算法运行软件版本 vivado2019.2&#xff0c;matlab2022a 3.部分核心程…

深度学习 精选笔记(4)线性神经网络-交叉熵回归与Softmax 回归

学习参考&#xff1a; 动手学深度学习2.0Deep-Learning-with-TensorFlow-bookpytorchlightning ①如有冒犯、请联系侵删。 ②已写完的笔记文章会不定时一直修订修改(删、改、增)&#xff0c;以达到集多方教程的精华于一文的目的。 ③非常推荐上面&#xff08;学习参考&#x…

postman访问k8s api

第一种方式&#xff1a; kubectl -n kubesphere-system get sa kubesphere -oyaml apiVersion: v1 kind: ServiceAccount metadata:annotations:meta.helm.sh/release-name: ks-coremeta.helm.sh/release-namespace: kubesphere-systemcreationTimestamp: "2023-07-24T07…

影像仪激光扫描功能,无缝连接2D/3D混合测量

在现代工业生产领域&#xff0c;影像仪用于质量控制和产品检测&#xff0c;是一个不可或缺的工具。它通过高精度的成像和图像处理技术&#xff0c;可以及时发现产品的缺陷和异常&#xff0c;以保证产品质量的稳定性和一致性。 影像仪的重要性及其面临的挑战 在工业生产方面&a…

免杀实战-EDR对抗

文章目录 杀软分析BOF.NET 杀软分析 x64dgb简单调试发现该edr在r3环对ntdll.dll和kernel32.dll关键函数均存在hook&#xff0c;这里硬盘读取原来的dll进行重新加载&#xff0c;原理如图 loader // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #in…

矩阵的范数 matrix norm Frobenius norm 弗罗贝尼乌斯 范数

1&#xff0c;矩阵范数的定义 矩阵的范数&#xff0c;matrix norm即矩阵的模&#xff1b;它把一个矩阵空间变成为赋范线性空间&#xff1b; 从一个矩阵空间映射到非负实数的函数 满足以下条件&#xff1a; 1&#xff0c;严格的正定性。对于 , 则 ; and if , must ; 2&…