首页 > 其他分享 >26.高并发服务器

26.高并发服务器

时间:2023-09-07 12:45:44浏览次数:45  
标签:26 int cfd 并发 fd 进程 服务器 include 客户端

26.高并发服务器

阻塞函数在阻塞期间若收到信号,会被信号终端,errno设置为EINTR,
这个错误不应该看成一个错误。

while (1)
{
	cfd = accept();
	while (1)
	{
		n = read(cfd, buf, sizeof(buf));
		if (n <= 0)
		{
			break;
		}
	}
}
  • 解决办法1:

将cfd设置为非阻塞: fcntl
假如有多个客户端连接请求,cfd只会保留最后一个文件描述符的值

  • 解决方法2:

使用多进程: 让父进程监听接受新的连接, 子进程处理新的连接(接收和发送数据);

父进程还负责回收子进程

  • 处理流程:

1.创建socket,得到一个监听的文件描述符lfd---socket()
2.将lfd和IP和端口port进行绑定-----bind();
3.设置监听----listen()
4.进入

while (1)
{
    //等待有新的客户端连接到来
    cfd = accept();
    //fork一个子进程,让子进程去处理数据
    pid = fork();
    if (pid < 0)
    {
        exit(-1);
    }
    else if (pid > 0)
    {
        //关闭通信文件描述符cfd
        close(cfd);
    }
    else if (pid == 0)
    {
        //关闭监听文件描述符
        close(lfd);

        //收发数据
        while (1)
        {
            //读数据
            n = read(cfd, buf, sizeof(buf));
            if (n <= 0)
            {
                break;
            }

            //发送数据给对方
            write(cfd, buf, n);
        }

        close(cfd);

        //下面的exit必须有,防止子进程再去创建子进程
        exit(0);
    }
}
close(lfd);

这段代码是一个简化版本的基于fork()的TCP服务器程序。它不断地接受客户端的连接请求,然后通过fork()创建子进程来处理每个客户端的数据通信。

现在,让我们详细解析这段代码:

  1. 外层无限循环:

    while (1)
    

    这是一个无限循环,意味着服务器会持续地等待新的客户端连接。

  2. 接受客户端连接:

    cfd = accept();
    

    accept()函数等待并接受一个客户端连接,然后返回一个新的文件描述符cfd用于与该客户端通信。

  3. 创建子进程处理连接:

    pid = fork();
    

    fork()函数创建一个新的进程。这个新进程是原始进程的一个复制品。这里的目的是允许子进程处理新客户端的连接,而父进程则继续等待更多的客户端连接。

  4. 错误处理:

    if (pid < 0)
    {
        exit(-1);
    }
    

    如果fork()失败,则返回-1,这时,程序会退出。

  5. 父进程操作:

    else if (pid > 0)
    {
        //关闭通信文件描述符cfd
        close(cfd);
    }
    

    如果pid大于0,表示我们当前处于父进程。父进程将关闭与该特定客户端的连接文件描述符cfd,因为它将由子进程处理。

  6. 子进程操作:

    else if (pid == 0)
    {
        //关闭监听文件描述符
        close(lfd);
    
        //收发数据
        while (1)
        {
            //读数据
            n = read(cfd, buf, sizeof(buf));
            if (n <= 0)
            {
                break;
            }
    
            //发送数据给对方
            write(cfd, buf, n);
        }
    
        close(cfd);
        
        //下面的exit必须有, 防止子进程再去创建子进程
        exit(0);
    }
    

    如果pid等于0,表示我们现在处于子进程。子进程首先关闭监听文件描述符lfd,然后进入一个循环,不断地从客户端读取数据并立即将其写回。如果读取的数据长度为0或小于0(表示客户端断开连接或发生错误),子进程将退出循环,关闭与客户端的连接文件描述符cfd,然后正常退出。

  7. 关闭监听文件描述符:

    close(lfd);
    

    这段代码实际上永远不会被执行到,因为它位于外层无限循环的外部。

总结:这是一个使用fork()的多进程TCP服务器的简化示例,它可以同时处理多个客户端连接。当有新的客户端连接时,服务器会创建一个新的子进程专门用于处理该客户端的通信。这种方式可以实现多任务,但如果有大量客户端连接,则可能会导致系统资源的过度使用。实际的生产环境中可能会选择使用线程或事件驱动模型来处理大量的并发连接。

还需要添加的功能: 父进程使用SIGCHLD信号完成对子进程的回收
注意点: accept或者read函数是阻塞函数,会被信号打断,此时不应该视为一个错误。
errno=EINTR

父子进程能够共享的:
文件描述符(子进程复制父进程的文件描述符)
mmap共享映射区

问题:
1.子线程能否关闭lfd?
子线程不能关闭监听文件描述符lfd,原因是子线程和主线程共享文件描述符而不是复制的。
2.主线程能否关闭cfd?
主线程不能关闭cfd,主线程和子线程共享一个cfd,而不是复制的,close之后cfd就会被真正关闭。
3.多个子线程共享cfd,会有什么问题发生?

struct INFO
{
	int cfd;
	pthread_t threadID;
	struct sockaddr_in client;
};
struct INFO info[100];

//初始化INFO数组
for (i = 0; i < 100; i++)
{
	info[i].cfd = -1;
}


for (i = 0; i < 100; i++)
{
	if (info[i].cfd == -1)
	{
		//这块内存可以使用
	}
}

if (i == 100)
{
	//拒绝接受新的连接
	close(cfd);
}

作业:
1.改进多进程版本的服务器代码。
父进程使用SIGCHLD信号完成对子进程的回收。
2.改进多线程版本的服务器。

多线程版本的服务器开发流程:

{
	1 创建socket, 得到一个监听的文件描述符lfd-- - socket()
	2 将lfd和IP和端口port进行绑定---- - bind();
	3 设置监听----listen()
	4 while (1)
	{
		//接受新的客户端连接请求
		cfd = accept();

		//创建一个子线程
		pthread_create(&threadID, NULL, thread_work, &cfd);

		//设置线程为分离属性
		pthread_detach(threadID);
	}

	close(lfd);
}

子线程执行函数:

void* thread_work(void* arg)
{
	//获得参数: 通信文件描述符
	int cfd = *(int*)arg;
	while (1)
	{
		//读数据
		n = read(cfd, buf, sizeof(buf));
		if (n <= 0)
		{
			break;
		}

		//发送数据
		write(cfd, buf, n);
	}

	close(cfd);
}

问题:
1.子线程能否关闭lfd?
子线程不能关闭监听文件描述符lfd,原因是子线程和主线程共享文件描述符
而不是复制的。
2.主线程能否关闭cfd?
主线程不能关闭cfd,主线程和子线程共享一个cfd,而不是复制的,close之后cfd就会
被真正关闭.
3.多个子线程共享cfd,会有什么问题发生?

如何支持多个客户端---支持多并发的服务器

由于accept和read函数都会阻塞,如当read的时候,不能调用accept接受新的连接,当accept阻塞等待的时候不能read读数据.

第一种方案: 使用多进程,可以让父进程接受新连接,让子进程处理与客户端通信

思路: 让父进程accept接受新连接,然后fork子进程,让子进程处理通信,子进程处理完成后退出,父进程使用SIGCHLD信号回收子进程.

代码实现:

第二种方案: 使用多线程,让主线程接受新连接,让子线程处理与客户端通信; 使用多线程要将线程设置为分离属性,让线程在退出之后自己回收资源。

思考:如何不使用多进程或者多线程完成多个客户端的连接请求

可以将accept和read函数设置为非阻塞,调用fcntl函数可以将文件描述符设置为非阻塞, 让后再while循环中忙轮询。

//多进程版本的网络服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "wrap.h"

int main()
{
	//创建socket
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);
	
	//绑定
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
	
	//设置监听
	Listen(lfd, 128);
	
	pid_t pid;
	int cfd;
	char sIP[16];
	socklen_t len;
	struct sockaddr_in client;
	while(1)
	{
		//接受新的连接
		len = sizeof(client);
		memset(sIP, 0x00, sizeof(sIP));
		cfd = Accept(lfd, (struct sockaddr *)&client, &len);
		printf("client:[%s] [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));
		
		//接受一个新的连接, 创建一个子进程,让子进程完成数据的收发操作
		pid = fork();
		if(pid<0)
		{
			perror("fork error");
			exit(-1);
		}
		else if(pid>0)
		{
			//关闭通信文件描述符cfd
			close(cfd);			
		}
		else if(pid==0)
		{
			//关闭监听文件描述符
			close(lfd);
			
			int i=0;
			int n;
			char buf[1024];
			
			while(1)
			{
				//读数据
				n = Read(cfd, buf, sizeof(buf));
				if(n<=0)
				{
					printf("read error or client closed, n==[%d]\n", n);
					break;
				}
				//printf("client:[%s] [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));
				printf("[%d]---->:n==[%d], buf==[%s]\n", ntohs(client.sin_port), n, buf);
				
				//将小写转换为大写
				for(i=0; i<n; i++)
				{
					buf[i] = toupper(buf[i]);
				}
				//发送数据
				Write(cfd, buf, n);
			}
			
			//关闭cfd
			close(cfd);
			exit(0);
		}
	}
	
	//关闭监听文件描述符
	close(lfd);
	
	return 0;
}
#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;
}

/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	char   *ptr;

	ptr = vptr;
	nleft = n;

	while (nleft > 0) {
		if ((nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;
			else
				return -1;
		} else if (nread == 0)
			break;

		nleft -= nread;
		ptr += nread;
	}
	return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}

		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}

static 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;
}

int tcp4bind(short port,const char *IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET,SOCK_STREAM,0);
    bzero(&serv_addr,sizeof(serv_addr));
    if(IP == NULL){
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }else{
        if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(port);
    Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    return lfd;
}
#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

标签:26,int,cfd,并发,fd,进程,服务器,include,客户端
From: https://www.cnblogs.com/codemagiciant/p/17684526.html

相关文章

  • 服务器监控配置
    配置说明1、通过在服务器硬件管理口中配置SNMP协议开启进行对接,华为iBMC选择SNMP登陆Dell服务器web管理页面,开启SNMP功能并配置团体名称信息  登陆IBM服务器web管理页面,开启SNMP功能并配置团体名称信息 登陆华为服务器web管理页面,开启SNMP功能并配置团体名称信息......
  • Android并发编程高级面试题汇总(含详细解析 十八)
    Android并发编程高级面试题汇总最全最细面试题讲解持续更新中......
  • 免费服务器
    免费服务器3丰云。上手可直接安装nodejs17.x,18+需要 glibc-2.28,centos宝塔版可以在插件商店中安装pm2管理node项目。直通车https://www.sanfengyun.com 免费版最好不安装docker否则内存干满......
  • 【题解】CF2600DP 选练(23.9.5-23.9.6)
    低情商:感觉是比较套路的高情商:十分educational!!!CF258DLittleElephantandBrokenSorting题目描述:有一个\([1,n]\)的排列\(a\),会进行\(m\)次操作,操作为交换\((a_i,a_j)\)。每次操作都有\(50\%\)的概率进行。求进行\(m\)次操作以后的期望逆序对个数。\(n,m\le1......
  • tomcat服务器报错
    看问题报错信息里面说tomcat目录下conf下的aaa.xml报错根据目录找到aaa.xml发现aaa.xml里面是空的,删除了之后就不报错了不知道为啥这个就会报错,以前也出现过没有解决,这次通过问了问学长解决了这个问题,具体原因也不知道......
  • 物理机服务器应该注意的事
    物理机服务器应该注意的事1、选址服务器是个非常重要的硬件产品,对机房的也是有一定的要求的,比如温度、安全性,噪音、电源稳定性等等问题都需要解决!但是不是每个人都会选择自己建立一个机房,毕竟各方面加起来的成本都太高。这个时候可以选择一个专业可信赖的服务商进行服务器托管,只......
  • 物理机服务器应该注意的事
    物理机服务器应该注意的事1、选址服务器是个非常重要的硬件产品,对机房的也是有一定的要求的,比如温度、安全性,噪音、电源稳定性等等问题都需要解决!但是不是每个人都会选择自己建立一个机房,毕竟各方面加起来的成本都太高。这个时候可以选择一个专业可信赖的服务商进行服务器托管,只......
  • 24V直流DC浪涌过压保护推荐26V电压TVS二极管
    直流DC电源端口浪涌过压防护一直都是很多新老电子工程师关注的方案之一。不管是电源端口浪涌防护还是信号接口静电保护,浪涌静电防护,找东沃,电路保护不迷路!东沃电子专注于研发、生产、销售静电保护二极管(ESD)、瞬态抑制二极管(TVS)、陶瓷气体放电管(GDT)、压敏电阻(MOV)、自恢复保险丝(PPTC)、......
  • 【Leetcode刷题记录】1、统计参与通信的服务器;2、统计二叉树中好节点的数目;3、从两个
    1、统计参与通信的服务器题目:这里有一幅服务器分布图,服务器的位置标识在 m*n 的整数矩阵网格 grid 中,1表示单元格上有服务器,0表示没有。如果两台服务器位于同一行或者同一列,我们就认为它们之间可以进行通信。请你统计并返回能够与至少一台其他服务器进行通信的服务器的......
  • 升讯威在线客服系统的并发高性能数据处理技术:高性能TCP服务器技术
    我在业余时间开发维护了一款免费开源的升讯威在线客服系统,也收获了许多用户。对我来说,只要能获得用户的认可,就是我最大的动力。最近客服系统成功经受住了客户现场组织的压力测试,获得了客户的认可。客户组织多名客服上线后,所有员工同一时间打开访客页面疯狂不停的给在线客服发消......