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部分(还在更新中):

✨🎉总结

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

所以,

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

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

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

相关推荐
SPC的存折2 小时前
1、Redis数据库基础
linux·运维·服务器·数据库·redis·缓存
爱学习的小囧3 小时前
VMware ESXi 6.7U3v 新版特性、驱动集成教程和资源包、部署教程及高频问答详情
运维·服务器·虚拟化·esxi6.7·esxi蟹卡驱动
小疙瘩3 小时前
只是记录自己发布若依分离系统到linux过程中遇到的问题
linux·运维·服务器
dldw7774 小时前
IE无法正常登录windows2000server的FTP服务器
运维·服务器·网络
我是伪码农5 小时前
外卖餐具智能推荐
linux·服务器·前端
汤愈韬5 小时前
下一代防火墙通用原理
运维·服务器·网络·security
皮皮林5515 小时前
强烈建议大家使用 Linux 做开发?
linux
IMPYLH6 小时前
Linux 的 od 命令
linux·运维·服务器·bash
Kk.08026 小时前
Linux(十一)fork实例练习、文件操作示例及相关面试题目分享
linux·运维·算法
2501_945318497 小时前
备考方案:针对数据分析师的知识结构,制定攻克赛一认证的最优学习路径
学习·百度