Linux学习之网络编程3(高并发服务器)

写在前面

Linux网络编程我是看视频学的,Linux网络编程,看完这个视频大概网络编程的基础差不多就掌握了。这个系列是我看这个Linux网络编程视频写的笔记总结。


高并发服务器

问题:

根据上一个笔记,我们可以写出一个简单的服务端和客户端通信,但是我们发现一个问题------服务器只能连接一个客户端。然而在实际生活中,我们发现一个服务器连接的客户端远远不止一个,所以我们就要做一个高并发服务器。

解决方法:

回看之前的代码,之所以只能一对一通信,是因为服务器只有一次执行accept的机会,一旦建立连接成功,就会去进行通信处理业务,而其他想要建立连接的服务器就没办法建立连接。因此我们想到在Linux系统编程中学的进程和线程,我们可以让父进程(主线程)去监听,一定有客户端请求建立连接,我们就创建子进程(其他线程)去和客户端建立连接进行通信,父进程(主线程)继续监听。


多进程并发服务器

思路(步骤):

  1. 前期准备工作:
    • 先用socket()生成一个套接字lfd用来监听
    • bind()对第一步生成的套接字绑定地址结构(绑的是服务器的地址结构)
    • listen()函数设置lfd的监听上限,最大是128.
  2. 进入循环,accept与客户端建立连接,得到用于通信的套接字的文件描述符cfd
  3. fork()创建子进程
  4. 对于父进程,由于父进程只是监听,不需要与客户端进行通信,所以我们就关闭cfd,注册信号捕捉函数,用来回收子进程,然后一直循环监听。
  5. 对于子进程,由于子进程只是进行通信,不需要监听,所以我们就关闭lfd,然后就与客户端进行通信,处理业务。

源代码:

c 复制代码
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>	
#include<signal.h>

#define PORT 6666

void sys_err(char* str)
{
	perror(str);
	exit(-1);
}

void wait_child(int signum)		//信号捕捉,回收子进程
{
	while((waitpid(0,NULL,WNOHANG))>0);
//	if(waitpid(0,NULL,0)!=-1)
//		printf("disconnect a client successfully\n");
	return;
}

int main()
{
	struct sockaddr_in addr_s,addr_c;
	socklen_t addr_c_len=sizeof addr_c;
	int lfd,cfd,res,n;
	pid_t pid;
	struct sigaction act;
	char buf[BUFSIZ],client_IP[1024];
	
	lfd=socket(AF_INET,SOCK_STREAM,0);
	if(lfd<0)
		sys_err("socket error");
		
	addr_s.sin_family=AF_INET;
	addr_s.sin_port=htons(PORT);
	addr_s.sin_addr.s_addr=htonl(INADDR_ANY);
	res=bind(lfd,(struct sockaddr*)&addr_s,sizeof addr_s);
	if(res==-1)
		sys_err("bind error");
		
	res=listen(lfd,128);
	if(res==-1)
		sys_err("listen error");
		
	while(1)
	{
		cfd=accept(lfd,(struct socket*)&addr_c,&addr_c_len);
		
		pid=fork();			//创建子进程
		
		if(pid==0)			//子进程
		{
			close(lfd);		//打印客户端的IP和端口号,可以省略
			printf("connect successfully,client IP:%s,port:%d\n",inet_ntop(AF_INET,&addr_c.sin_addr.s_addr,&client_IP,sizeof client_IP),ntohs(addr_c.sin_port));
			break;
		}
		
		else if(pid>0)
		{
			close(cfd);
			act.sa_handler=wait_child;
			sigemptyset(&act.sa_mask);
			act.sa_flags=0;
			sigaction(SIGCHLD,&act,NULL);
			continue;
		}
	}
	
	if(pid==0)		//父进程
	{
		while(1)
		{
			n=read(cfd,buf,sizeof buf);
			for(int i=0;i<n;i++)
				buf[i]=toupper(buf[i]);
			write(cfd,buf,n);
			write(STDOUT_FILENO,buf,n);
		}
	}
	
	close(cfd);
	close(lfd);
	
	return 0;
}

效果


多线程并发服务器

思路(步骤):

  1. 前期准备工作:
    • 先用socket()生成一个套接字lfd用来监听
    • bind()对第一步生成的套接字绑定地址结构(绑的是服务器的地址结构)
    • listen()函数设置lfd的监听上限,最大是128.
  2. 进入循环,accept与客户端建立连接,得到用于通信的套接字的文件描述符cfd
  3. 创建子线程
  4. 对于子线程,由于子线程只是进行通信,不需要监听,所以我们就关闭lfd,然后就与客户端进行通信,处理业务。
  5. 对于父进程,由于父线程只是监听,不需要与客户端进行通信,所以我们就关闭cfd,然后设置线程pthread_detach()分离或者使用pthread_join()回收子线程。

源代码

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<pthread.h>

#define PORT 6666

void sys_err(char* str)
{
	perror(str);
	exit(-1);
}

void* fun(void* arg)
{
	int cfd=(int) arg,n;
	char buf[BUFSIZ];
	while(1)
	{
		n=read(cfd,buf,sizeof buf);
		if(n==0)
		{
			printf("one client closed......\n");
			break;
		}
		for(int i=0;i<n;i++)
			buf[i]=toupper(buf[i]);
		write(cfd,buf,n);
		write(STDOUT_FILENO,buf,n);
	}
	close(cfd);
	return NULL;
}
	
	

int main()
{
	struct sockaddr_in addr_s,addr_c;
	socklen_t addr_c_len=sizeof addr_c;
	char c_IP[1024];
	int lfd,cfd,res;
	pthread_t tid;

	addr_s.sin_family=AF_INET;
	addr_s.sin_port=htons(PORT);
	addr_s.sin_addr.s_addr=htonl(INADDR_ANY);

	lfd=socket(AF_INET,SOCK_STREAM,0);
	if(lfd<0)
		sys_err("socket errro");

	res=bind(lfd,(struct sockaddr*)&addr_s,sizeof addr_s);
	if(res<0)
		sys_err("bind error");

	res=listen(lfd,128);	
	if(res<0)
		sys_err("listen error");
	
	printf("accepting connect........\n");
	while(1)
	{
		cfd=accept(lfd,(struct sockaddr*)& addr_c,&addr_c_len);
		if(cfd==-1)
			sys_err("accept error");
		printf("connect successfully,client ip:%s,port:%d\n",inet_ntop(AF_INET,&addr_c.sin_addr.s_addr,c_IP,sizeof c_IP),ntohs(addr_c.sin_port));

		res=pthread_create(&tid,NULL,fun,(void*)cfd);
		if(res!=0)
			fprintf(stderr,"pthread create error:%s",strerror(res));

		pthread_detach(tid);	
		if(res!=0)
			fprintf(stderr,"pthread create error:%s",strerror(res));
	}
	close(lfd);
	return 0;
}

效果


写在最后

个人亲身经验:我们学习的一系列Linux命令,一定要自己亲手去敲 。不要只是看别人敲代码,不要只是停留在眼睛看,脑袋以为自己懂了,等你实际上手去敲会发现许许多多的这样那样的问题。毕竟"实践出真知"。


如果你觉得我写的题解还不错的,请各位王子公主移步到我的其他题解看看

  1. 数据结构与算法部分(还在更新中):
  1. Linux部分(还在更新中):

✨🎉总结

"种一颗树最好的是十年前,其次就是现在"

所以,

"让我们一起努力吧,去奔赴更高更远的山海"

如果有错误❌,欢迎指正哟😋

🎉如果觉得收获满满,可以动动小手,点点赞👍,支持一下哟🎉

相关推荐
LIKEYYLL1 小时前
GNU Octave:特性、使用案例、工具箱、环境与界面
服务器·gnu
云云3211 小时前
搭建云手机平台的技术要求?
服务器·线性代数·安全·智能手机·矩阵
云云3211 小时前
云手机有哪些用途?云手机选择推荐
服务器·线性代数·安全·智能手机·矩阵
数据的世界012 小时前
.NET开发人员学习书籍推荐
学习·.net
cominglately2 小时前
centos单机部署seata
linux·运维·centos
魏 无羡2 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse2 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
四口鲸鱼爱吃盐2 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
木子Linux3 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
mit6.8243 小时前
Ubuntu 系统下性能剖析工具: perf
linux·运维·ubuntu