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

常见的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的状态,可以看出是达到了效果。两个客户端和服务端的通信都是正常的。

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

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

相关文章

docker容器技术(3)

Docker技术编排 概述&#xff1a; docker建议我们每一个容器中只运行一个服务,因为docker容器本身占用资源极少,所以最好是将每个服务单独的分割开来但是这样我们又面临了一个问题&#xff1f; 如果我需要同时部署好多个服务,难道要每个服务单独写Dockerfile然后在构建镜像,…

【XR806开发板试用】socket客户端与虚拟机服务器通信交互测试以及终端交互

XR806 客户端准备工作。 1、连接wifi 2、创建socket连接服务器。 3、创建终端接收数据线程。 wifi_connect.c #include <stdio.h> #include <string.h> #include "wifi_device.h" #include "wifi_hotspot.h" #include "kernel/os/os.h…

桥接模式(Bridge Pattern) C++

上一节&#xff1a;适配器模式&#xff08;Adapter Pattern&#xff09; C 文章目录 0.理论1.组件2.使用场景 1.实践 0.理论 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它的核心思想是将抽象部分与其实现部分分离&#xff0c;使它们可…

区块链智能合约开发

一.区块链的回顾 1.区块链 区块链实质上是一个去中心化、分布式的可进行交易的数据库或账本 特征: 去中心化&#xff1a;简单来说&#xff0c;在网络上一个或多个服务器瘫痪的情况下&#xff0c;应用或服务仍然能够持续地运行&#xff0c;这就是去中心化。服务和应用部署在…

死区过滤器Deadband和DeadZone区别(应用介绍)

死区过滤器的算法和详细介绍专栏也有介绍,这里我们主要对这两个模块的区别和应用场景进行详细介绍 1、死区过滤器 https://rxxw-control.blog.csdn.net/article/details/128521007https://rxxw-control.blog.csdn.net/article/details/128521007 1、Deadband和DeadZone区别…

kafka学习笔记三

目录 第二篇 外部系统集成 第三篇 生产调优手册 第1章 kafka硬件配置选择 第2章 生产者调优 2.1 生产者核心参数配置 2.2 生产者如何提高吞吐量 2.3 数据可靠性 2.4 数据去重 2.5 数据有序 2.6 数据乱序 第3章 Kafka Broker调优 3.1 Broker核心参数配置 3.2 其他 …

k8s service的概念以及创建方法

Service 的功能&#xff1a; Service主要用于提供网络服务&#xff0c;通过Service的定义&#xff0c;能够为客户端应用提供稳定的访问地址&#xff08;域名或IP地址&#xff09;和负载均衡功能&#xff0c;以及屏蔽后端Endpoint的变化&#xff0c;是K8s实现微服务的核心资源。…

【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>

使用ffmpeg压缩视频

一、到ffmpeg官网下载文件包&#xff1a; Download FFmpeg 下载后找到 bin 下的3个exe文件&#xff0c;复制到自己本机的某个目录下, 如&#xff1a; 二、使用命令行压缩&#xff1a; ffmpeg -i input.mp4 -c:v libx265 -crf 28 -y output.mp4 这条命令使用 FFmpeg 工具对输…

QA 的未来:使用生成式 AI 进行 API 测试

QA 团队面临着比以往任何时候都更大的满足软件质量和发布速度期望的压力。继续阅读&#xff0c;了解 GenAI 如何改善开发人员和测试人员的工作体验&#xff0c;同时最大限度地提高团队生产力并提高软件质量。 软件质量差的后果正在日益严重&#xff0c;许多组织因功能缺陷和安…

LACP——链路聚合控制协议

LACP——链路聚合控制协议 什么是LACP&#xff1f; LACP&#xff08;Link Aggregation Control Protocol&#xff0c;链路聚合控制协议&#xff09;是一种基于IEEE802.3ad标准的实现链路动态聚合与解聚合的协议&#xff0c;它是链路聚合中常用的一种协议。 链路聚合组中启用了…

2024 值得推荐的免费开源 WAF

WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。区别于传统防火墙&#xff0c;WAF 工作在应用层&#xff0c;对基于 HTTP/HTTPS 协议的 Web 系统有着更好的防护效果&#xff0c;使其免于受到黑客的攻击。 开源 WAF 和商用 WAF&#xff08;奇安信…

Linux学习笔记11——用户组添加删除

Linux 是多用户多任务操作系统&#xff0c;换句话说&#xff0c;Linux 系统支持多个用户在同一时间内登陆&#xff0c;不同用户可以执行不同的任务&#xff0c;并且互不影响。 例如&#xff0c;某台 Linux 服务器上有 4 个用户&#xff0c;分别是 root、www、ftp 和 mysql&…

基于JAVA+Springboot+Thymeleaf前后端分离项目:社区疫情防控系统设计与实现

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

广和通发布基于MediaTek T300平台的RedCap模组FM330系列及解决方案

世界移动通信大会MWC 2024期间&#xff0c;广和通发布基于MediaTek T300平台的RedCap模组FM330系列&#xff0c;加速5G-A繁荣发展。FM330系列及其解决方案采用全球先进RedCap方案&#xff0c;满足移动宽带和工业互联对高能效的需求。 广和通FM330系列采用全球首款6nm制程且集成…

BTC网络 vs ETH网络

设计理念 BTC 网络 比特币是一种数字货币&#xff0c;旨在作为一种去中心化的、不受政府或金融机构控制的电子货币。其主要目标是实现安全的价值传输和储存&#xff0c;比特币的设计强调去中心化和抗审查。 ETH 网络 以太坊是一个智能合约平台&#xff0c;旨在支持分散的应…

Java SpringBoot微服务面试题

Java SpringBoot微服务面试题 前言1、什么是 SpringBoot&#xff1f;2、什么是起步依赖&#xff1f;3、什么是自动配置&#xff1f;4、什么是命令行界面&#xff1f;5、什么是Actuator监控器&#xff1f;6、SpringBoot 的核心注解有哪些&#xff1f;7、什么是YAML&#xff1f;8…

[数据集][目标检测]狗狗表情识别VOC+YOLO格式3971张4类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3971 标注数量(xml文件个数)&#xff1a;3971 标注数量(txt文件个数)&#xff1a;3971 标注…

基于java SSM springboot+redis网上水果超市商城设计和实现以及文档

基于java SSM springbootredis网上水果超市商城设计和实现以及文档 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 …

一文读懂什么是 OCR 识别

在数字化时代&#xff0c;信息处理和数据管理是企业运营的重要环节。然而&#xff0c;手工输入信息存在效率低和准确性低的问题&#xff0c;严重影响了企业的工作流程和决策过程。因此&#xff0c;OCR&#xff08;Optical Character Recognition&#xff09;识别技术的应用变得…