linux网络编程 | c | 多进程并发服务器实现

server/2024/12/17 6:08:55/

多进程并发服务器

基于该视频完成

11-多进程并发服务器思路分析_哔哩哔哩_bilibili

通过的是非阻塞忙轮询的方式实现的

和阻塞等待的区别就是,阻塞是真的阻塞了,而这个方式是一直在问有没有请求有没有请求

文章目录

  • 多进程并发服务器
    • 1.核心思路&功能
    • 2.代码实现
      • warp.h
      • warp.c
      • multi_process_concurrency_sever.c
      • 运行图
    • 3.代码解释

1.核心思路&功能

实现一个服务器可以连接多个客户端,每当accept函数等待到客户端进行连接时 就创建一个子进程;

核心思路:让accept循环阻塞等待客户端,每当有客户端连接时就fork子进程,让子进程去和客户端进行通信,父进程用于监听并使用信号捕捉回收子进程;(子进程关闭用于监听的套接字lfd,父进程关闭用于通信的cfd)

**功能:**客户端输入小写字符串,服务器转成大写返回给客户端

2.代码实现

warp.h

#ifndef __WRAP_H_
#define __WRAP_H_
#include<sys/epoll.h>
//#include<event2/event.h>
#include<sys/select.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<string.h>
#include<dirent.h>
#include<sys/stat.h>
#include<wait.h>
#include<sys/mman.h>
#include<signal.h>
#include<pthread.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<strings.h>
#include<netinet/ip.h>
#define SRV_PORT 1234void 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 addrlen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
size_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);#endif

warp.c

#include"warp.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 addrlen)
{int n;n=connect(fd,sa,addrlen);if(n<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;
}size_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 0;
}int Close(int fd)
{int n;if((n=close(fd))==-1)perr_exit("close error");return n;
}ssize_t Readn(int fd, void *vptr, size_t n)
{size_t nleft;ssize_t nread;char *ptr;ptr=vptr;nleft=n;while(nleft>0){if((nread=read(fd,ptr,nleft))<0){if(errno==EINTR)nread=0;elsereturn -1;}else if(nread==0)break;}
}
ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;char *ptr;ptr=(char *)vptr;nleft=n;while(nleft>0){if((nwritten=write(fd,ptr,nleft))<=0){if(nwritten<0&&errno==EINTR)nwritten=0;elsereturn -1;}nleft-=nwritten;ptr+=nwritten;}return n;
}ssize_t my_read(int fd, char  *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if(read_cnt<=0){
again:if((read_cnt=read(fd,read_buf,sizeof(read_buf)))<0){if(errno==EINTR)goto again;return -1;}else if(read_cnt==0)return 0;read_ptr=read_buf;}read_cnt--;*ptr=*read_ptr++;return 1;
}ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n,rc;char c,*ptr;ptr=vptr;for(n=1;n<maxlen;n++){if((rc=my_read(fd,&c))==1){*ptr++=c;if(c=='\n')break;}else if(rc==0){*ptr=0;return n-1;}else	return -1;}*ptr=0;return n;
}

multi_process_concurrency_sever.c

#include"warp.h"//回调函数,父进程用来完成子进程回收的
void catch_child(int signum)
{while((waitpid(0,NULL,WNOHANG))>0);return ;
}int main(int argc,char *argv[])
{int lfd,cfd;pid_t pid;int ret;char buf[1024];struct sockaddr_in srv_addr,clt_addr;socklen_t clt_addr_len;//地址结构清0bzero(&srv_addr,sizeof(srv_addr));srv_addr.sin_family=AF_INET;srv_addr.sin_port=htons(SRV_PORT);srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);lfd=Socket(AF_INET,SOCK_STREAM,0);Bind(lfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));Listen(lfd,128);clt_addr_len=sizeof(clt_addr);int i;while(1){cfd=Accept(lfd,(struct sockaddr *)&clt_addr,&clt_addr_len);pid=fork();if(pid<0){perr_exit("fork error");}else if(pid==0)//子进程{//关闭用不到的socketclose(lfd);for(;;){ret=Read(cfd,buf,sizeof(buf));if(ret==0){Close(cfd);exit(1);}//逻辑处理部分for(i=0;i<ret;i++)buf[i]=toupper(buf[i]);write(cfd,buf,ret);write(STDOUT_FILENO,buf,ret);}}else//父进程{//注册信号struct sigaction act;act.sa_handler=catch_child;sigemptyset(&act.sa_mask);act.sa_flags=0;ret=sigaction(SIGCHLD,&act,NULL);if(ret!=0){perr_exit("sigaction error");}Close(cfd);continue;}}return 0;
}
gcc warp.c multi_process_concurrency_sever.c -o multi_process_concurrency_sever

运行图

两个客户端访问服务器

image-20241212182721902

3.代码解释

1.指定的固定端口号为1234

也可以用argv接受ip和port参数

2.srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

  • sin_addrstruct sockaddr_in结构体中的一个嵌套结构体,其成员s_addr用于存放 IP 地址信息(以 32 位整数形式表示)。htonl函数和htons类似,不过它是将主机字节序的 32 位整数(通常用于 IP 地址)转换为网络字节序。INADDR_ANY是一个特殊的常量,它表示服务器端套接字可以绑定到本机的任意可用 IP 地址上,通过这行代码,将转换为网络字节序后的INADDR_ANY值赋给了srv_addr结构体的sin_addr.s_addr成员,使得服务器能够监听来自本机所有网络接口上对应端口的连接请求。

3.需要用到的调用都需要错误处理,封装到warp.c后进行使用

4.父子进程是共享文件描述符表的

  1. 父子进程共享文件描述符表的含义
    • 在 Unix 和 Linux 系统中,当父进程创建子进程时,子进程会继承父进程的文件描述符表。这意味着父子进程可以通过相同的文件描述符来访问同一个文件或 I/O 资源。文件描述符表是一个进程用于管理打开文件或其他 I/O 设备的表格,其中每个文件描述符是一个整数索引,对应着一个打开的文件或设备的相关信息(如文件状态、读写位置等)。
    • 例如,父进程打开了一个文件,得到文件描述符为 3,当创建子进程后,子进程也会有一个文件描述符为 3,并且这个文件描述符在父子进程中都指向同一个打开的文件。这种共享机制使得父子进程可以方便地共享文件资源,比如共同对一个文件进行读写操作。
  2. 父进程close(5)后子进程的情况
    • 当父进程执行close(5)操作后,只是父进程自己释放了文件描述符 5 所对应的资源。子进程的文件描述符表仍然保留文件描述符 5,并且子进程仍然可以使用文件描述符 5 来访问对应的文件。
    • 原因是父子进程的文件描述符表虽然在创建子进程时是共享的,但它们在操作文件描述符时是相互独立的。父进程的close操作不会影响子进程中已经继承的相同文件描述符。
  3. 子进程是否可以close(5)以及继续访问文件描述符 5 对应的文件
    • 子进程可以执行close(5)操作。当子进程执行close(5)后,它自己释放了文件描述符 5 对应的资源,之后就不能再通过文件描述符 5 来访问原来对应的文件了。
    • 在没有执行close(5)之前,子进程可以继续访问文件描述符 5 对应的文件。它可以进行正常的读写操作(前提是该文件是以合适的模式打开的,如可读可写模式),并且文件的读写位置等状态是在父子进程间共享的。例如,如果父进程已经读取了文件的一部分内容,子进程通过相同的文件描述符 5 继续读取文件时,会从父进程读取后的位置开始读取。

5.子进程做的事情:

close(lfd)关闭监听套接字

read()

逻辑处理:小写转大写

write()

6.父进程做的事情:子进程的回收

close(cfd)

注册信号捕捉函数

在回调函数中,完成子进程的回收


http://www.ppmy.cn/server/150816.html

相关文章

[图形编程] 【unity】【游戏开发】Shader基础7:着色器编程语言-HLSL、GLSL与CG的比较

在现代图形渲染中,着色器编程是核心内容之一,尤其在GPU上执行图形处理时,着色器决定了渲染效果的实现。着色器的编写通常使用高级编程语言,这些语言专门设计用于编写图形程序,虽然它们并不完全等同于像Python这样的通用高级语言,但相比汇编语言,它们提供了更高层次的抽象…

Fortify 24.2.0版本最新版 win/mac/linux

工具介绍&#xff1a; Fortify SCA作为一款业内主流的静态代码扫描工具&#xff0c;被广泛应用于白盒测试中。与其他静态代码扫描工具相比&#xff0c;Fortify SCA的突出优势主要在于更加广泛地支持的语言和开发平台、更全面和权威的安全规则库使扫描更加全面、更加智能化的自定…

el-table组件树形数据修改展开箭头

<style lang"scss" scoped> ::v-deep .el-table__expand-icon .el-icon-arrow-right:before {content: ">"; // 箭头样式font-size: 16px; }::v-deep .el-table__expand-icon{ // 没有展开的状态background-color: rgba(241, 242, 245, 1);color:…

MAC 头部、IPv4 头部、IPv6 头部、TCP 头部和 UDP 头部

MAC 头部 字段名称长度&#xff08;字节&#xff09;描述目标 MAC 地址6接收设备的 MAC 地址。源 MAC 地址6发送设备的 MAC 地址。以太网类型/长度2表示上层协议类型&#xff08;如 IPv4、IPv6&#xff09;或数据长度&#xff08;以太网 II 或 802.3&#xff09;。数据负载46-1…

云计算:开启数字化变革的强大引擎

一、云计算的基本概念 定义与内涵 云计算是一种基于互联网的计算模式&#xff0c;通过网络将大量的计算资源&#xff08;如服务器、存储、应用软件、服务等&#xff09;进行整合&#xff0c;以服务的形式提供给用户。它实现了资源的虚拟化&#xff0c;使得用户无需直接管理和维…

Kubernetes和ZStack分配CPU的机制

在 Kubernetes (k8s) 环境中限制 CPU 资源与在 ZStack 虚拟机中分配 CPU 资源的机制存在一些关键差异。以下是这两种环境下 CPU 分配机制的详细对比&#xff1a; Kubernetes 中的 CPU 管理 资源请求与限制&#xff1a;Kubernetes 允许你为每个容器指定 CPU 请求&#xff08;re…

webrtc学习----前端推流拉流,局域网socket版,一对多

提示&#xff1a;局域网socket版&#xff0c;一对多 文章目录 [TOC](文章目录) 前言一、教程二、webrtc工作流程三、推流端四、拉流五、socket服务六、效果七、备注总结 前言 ‌‌‌‌‌WebRTC&#xff08;Web Real-Time Communication&#xff09;‌是一种实时通讯技术&#x…

rabbitmq问题,消费者执行时间太长,超过心跳时间,消费者消失,任务堆积

rabbitmq问题&#xff0c;消费者执行时间太长&#xff0c;超过心跳时间&#xff0c;消费者消失&#xff0c;任务堆积 1.python多线程使用rabbitmq包地址 flask_rabbitmq 2.解决后的包 import json import logging import signal import sys import threading import time i…