常见的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;elseperr_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;elsereturn -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;elsereturn -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 100struct 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://xiahunao.cn/news/2813685.html

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

相关文章

设计模式(五)-观察者模式

前言 实际业务开发过程中&#xff0c;业务逻辑可能非常复杂&#xff0c;核心业务 N 个子业务。如果都放到一块儿去做&#xff0c;代码可能会很长&#xff0c;耦合度不断攀升&#xff0c;维护起来也麻烦&#xff0c;甚至头疼。还有一些业务场景不需要在一次请求中同步完成&…

使用R语言对线性回归模型中的异方差进行诊断和处理

一、数据准备 序号XY序号XY序号XY110.61143.521817.5211.61244.422813.4310.51345.12384.5411.21455.724930.45221563.4251112.4621.31669.7261213.4722.51768.6271226.2832.2187428127.4932.41975.5   1031.220710.5     二、对y和x,绘制散点图&#xff0c;并进行回归…

​LeetCode解法汇总235. 二叉搜索树的最近公共祖先

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给定一个二叉搜索树, 找到该树中两个指定…

4.8 Verilog过程连续赋值

关键词&#xff1a;解除分配&#xff0c;强制&#xff0c;释放 过程连续赋值是过程赋值的一种。赋值语句能够替换其他所有wire 或 reg 的赋值&#xff0c;改写wire 或 reg 类型变量的当前值。 与过程赋值不同的是&#xff0c;过程连续赋值表达式能被连续的驱动到wire 或 reg …

Selenium IDE插件录制网页,解放双手

1、 国内下载地址 https://www.crx4chrome.com/crx/77585/ &#xff0c;这个网络正常基本可以下载&#xff0c;目前最新版本是3.17.2。 点击Crx4Chrome下载。下载后的文件名称是&#xff1a;mooikfkahbdckldjjndioackbalphokd-3.17.2-Crx4Chrome.com.crx。 2、 安装 直接打开…

Netty NIO 非阻塞模式

1.概要 1.1 说明 使用非阻塞的模式&#xff0c;就可以用一个现场&#xff0c;处理多个客户端的请求了 1.2 要点 ssc.configureBlocking(false);if(sc!null){ sc.configureBlocking(false); channels.add(sc); }if(len>0){ byteBuffer.flip(); 2.代码 2.1 服务端代码 …

JavaWeb 自己给服务器安装SQL Server数据库遇到的坑

之前买的虚拟主机免费送了一个SQL Server数据库&#xff0c;由于服务器提供商今年下架我用的那款虚拟主机产品&#xff0c;所以数据库也被收回了。我买了阿里云云服务器&#xff0c;但是没有数据库&#xff0c;于是自己装了一个SQL Server数据库&#xff0c;总结一下遇到的坑。…

Mysql 的高可用详解

Mysql 高可用 复制 复制是解决系统高可用的常见手段。其思路就是&#xff1a;不要把鸡蛋都放在一个篮子里。 复制解决的基本问题是让一台服务器的数据与其他服务器保持同步。一台主库的数据可以同步到多台备库上&#xff0c;备库本身也可以被配置成另外一台服务器的主库。主…

Mysql的备份还原

模拟环境准备 创建一个名为school的数据库&#xff0c;创建一个名为Stuent的学生信息表 mysql> create database school; Query OK, 1 row affected (0.00 sec)mysql> use school; Database changed mysql> CREATE TABLE Student (-> Sno int(10) NOT NULL COMME…

使用R语言进行多元线性回归分析-多重共线的诊断

一、数据集 序号X1x2x3x4Y序号X1x2x3X4Y12666078.57831224472.51229155274.31954182293.12356850104.3111047426115.92143184787.6111140233483.8155263395.971266912113.311655922109.2111368812109.410771176102.73       1、从中选取主要变量&#xff0c;建立与因变…

Vue源码系列讲解——生命周期篇【八】(挂载阶段)

1. 前言 模板编译阶段完成之后&#xff0c;接下来就进入了挂载阶段&#xff0c;从官方文档给出的生命周期流程图中可以看到&#xff0c;挂载阶段所做的主要工作是创建Vue实例并用其替换el选项对应的DOM元素&#xff0c;同时还要开启对模板中数据&#xff08;状态&#xff09;的…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture09 Softmax多分类

代码&#xff1a; import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader import torch.nn as nn import torch.nn.functional as Fbatch_size 64 transform transforms.Compose([transforms.ToTensor(), transforms.Normali…

【HarmonyOS】鸿蒙开发之Stage模型-基本概念——第4.1章

Stage模型-基本概念 名词解释 AbilityStage:应用组件的“舞台“ UIAbility:包含UI界面的应用组件&#xff0c;是系统调度的基本单元 WindowStage:组件内窗口的“舞台“ Window&#xff1a;用来绘制UI页面的窗口 HAP:Harmony Ability Package(鸿蒙能力类型的包) HSP:Harmony Sh…

【洛谷学习自留】p5707 上学迟到

解题思路&#xff1a; 1.先用给出的时间和速度&#xff08;如果无法整除&#xff0c;则时间加一&#xff09;&#xff0c;计算出时间&#xff08;分&#xff09;&#xff0c;然后将时间加上10分钟。 2.创建一个计时器&#xff0c;设置一个日期&#xff0c;保证时分秒部分&#…

Linux安装jdk、tomcat、MySQL离线安装与启动

一、JDK和Tomcat的安装 1.JDK安装 直接上传到Linux服务器的&#xff0c;上传jdk、tomcat安装包 解压JDK安装包 //解压jdk tar -zxvf jdk-8u151-linux-x64.tar.gz 置环境变量(JAVA_HOME和PATH) vim /etc/profile 在文件末尾添加以下内容&#xff1a; //java environment expo…

使用R语言进行线性回归模型异常点分析

一、数据集描述 某厂生产的一种电器年销售量Y与竞争对手的价格X1及本厂的价格X2有关&#xff0c;数据如下&#xff1a; 10个城市某种电器的年销售量和竞争对手的价格&#xff08;单位&#xff1a;元&#xff09;X1X2YX1X2Y1201001021401101001909012013015077155210461751509…

Redis 管道详解

Redis 管道 关键词&#xff1a;Pipeline Pipeline 简介 Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。通常情况下&#xff0c;一个 Redis 命令的请求、响应遵循以下步骤&#xff1a; 客户端向服务端发送一个查询请求&#xff0c;并监听 Socket 返回&#xff08…

GEE入门篇|遥感专业术语(实践操作4):光谱分辨率(Spectral Resolution)

目录 光谱分辨率&#xff08;Spectral Resolution&#xff09; 1.MODIS 2.EO-1 光谱分辨率&#xff08;Spectral Resolution&#xff09; 光谱分辨率是指传感器进行测量的光谱带的数量和宽度。 您可以将光谱带的宽度视为每个波段的波长间隔&#xff0c;在多个波段测量辐射亮…

width:100%和width:auto有啥区别

项目中使用了with属性&#xff0c;突然好奇auto 和 100% 的区别&#xff0c;特地搜索实践总结了一下观点 一、 width属性介绍二、 代码带入三、 分析比较四、 总结 一、 width属性介绍 width 属性用于设置元素的宽度。width 默认设置内容区域的宽度&#xff0c;但如果 box-siz…

【Unity每日一记】角色控制器Character Contorller

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…