TCP并发服务器构建

发布于:2025-08-29 ⋅ 阅读:(23) ⋅ 点赞:(0)

TCP并发服务器构建:

单循环服务器:服务端同一时刻只能处理单个客户端的任务

并发服务器:服务端同一时刻能够处理多个客户端的任务

产生多个套接字可建立多个连接:

在这里插入图片描述

TCP服务端并发模型:

1:使用多进程

头文件:

#ifndef __HEAD_H__
#define __HEAD_H__


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <pthread.h>

#endif

代码

#include "head.h"

#define SER_PORT  50000
#define SER_IP    "192.168.0.179"

int init_tcp_ser()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);
	
	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}

	ret = listen(sockfd, 100);
	if (ret < 0)
	{
		perror("listen error");
		return -1;
	}

	return sockfd;
}

void wait_handler(int signo)
{
	wait(NULL);
}

int main(int argc, const char *argv[])
{

	signal(SIGCHLD, wait_handler);
	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);

	int sockfd = init_tcp_ser();
	if (sockfd < 0)
	{
		return -1;
	}

	while (1)
	{
		int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
		if (connfd < 0)
		{
			perror("accept error");
			return -1;
		}

		pid_t pid = fork();
		if (pid  > 0)
		{
		}
		else if (0 == pid)
		{
			char buff[1024] = {0};
			while (1)
			{
				memset(buff, 0, sizeof(buff));
				size_t cnt = recv(connfd, buff, sizeof(buff), 0);
				if (cnt < 0)
				{
					perror("recv error");
					break;
				}
				else if (0 == cnt)
				{
					printf("[%s : %d] : offline\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
					break;
				}
				printf("[%s : %d] : %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);

				strcat(buff, "---->ok");
				cnt = send(connfd, buff, strlen(buff), 0);
				if (cnt < 0)
				{
					perror("send error");
					break;
				}
			}
			close(connfd);
		}
	}

	close(sockfd);



	return 0;
}






2:使用多线程

#include "head.h"

#define SER_PORT  50000
#define SER_IP    "192.168.0.179"

int init_tcp_ser()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}

	//允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
	int optval = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);
	
	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}

	ret = listen(sockfd, 100);
	if (ret < 0)
	{
		perror("listen error");
		return -1;
	}

	return sockfd;
}

void *task(void *arg)
{

	char buff[1024] = {0};
	int connfd = *((int *)arg);

	while (1)
	{
		memset(buff, 0, sizeof(buff));
		size_t cnt = recv(connfd, buff, sizeof(buff), 0);
		if (cnt < 0)
		{
			perror("recv error");
			break;
		}
		else if (0 == cnt)
		{
			printf("offline\n");
			break;
		}
		printf("%s\n", buff);
		strcat(buff, "---->ok");
		cnt = send(connfd, buff, strlen(buff), 0);
		if (cnt < 0)
		{
			perror("send error");
			break;
		}

	}
	close(connfd);
}


int main(int argc, const char *argv[])
{
	pthread_t tid;

	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);

	int sockfd = init_tcp_ser();
	if (sockfd < 0)
	{
		return -1;
	}

	while (1)
	{
		int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
		if (connfd < 0)
		{
			perror("accept error");
			return -1;
		}
		
		pthread_create(&tid, NULL, task, &connfd);
		pthread_detach(tid);
	
	}

	close(sockfd);



	return 0;
}





线程池:

在这里插入图片描述

为了解决多线程或者多进程模型,在服务器运行过程,频繁创建和销毁线程(进程)带来的时间消耗问题,基于生产者和消费者编程模型,以及任务队列等,实现的一套多线程框架。

IO多路复用:

对多个文件描述符的读写操作可以复用一个进程。在不创建新的进程和线程的前提下,使用一个进程实现多个文件的读写的同时监测。三种方式:

linux内核监测事件:
在这里插入图片描述

1:select实现IO多路复用:

(1)创建文件描述符集合
(2)添加关注的文件描述符到集合
(3)使用select传递集合表给内核,内核检测事件
(4) 当内核监测到时间时,应用层select将解除阻塞,
(5)并将获得相关的事件结果做不同的任务处理
在这里插入图片描述
在这里插入图片描述
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5dcb7b86bd32400da9d5efe0df4f9a7f.png
在这里插入图片描述

在这里插入图片描述

头文件:
#ifndef __HEAD_H__
#define __HEAD_H__


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#endif

代码:
#include "head.h"


int main(int argc, const char *argv[])
{
	mkfifo("./myfifo", 0664);

	int fd = open("./myfifo", O_WRONLY);
	if (fd < 0)
	{
		perror("open fifo error");
		return -1;
	}

	while (1)
	{
		write(fd, "hello world", 11);
		sleep(1);
	}

	close(fd);
	return 0;
}

#include "head.h"


int main(int argc, const char *argv[])
{
	
	char buff[1024] = {0};
	mkfifo("./myfifo", 0664);
	
	int fifofd = open("./myfifo", O_RDONLY);
	if (fifofd < 0)
	{
		perror("open fifo error");
		return -1;
	}
	
	//1 创建文件描述符集合表
	fd_set rdfds;
	fd_set rdfdstmp;

	//2. 清空文件描述符集合表
	FD_ZERO(&rdfds);
	//3. 添加关注的文件描述符到集合中
	FD_SET(0, &rdfds);
	int maxfd = 0;
	FD_SET(fifofd, &rdfds);
	maxfd = maxfd > fifofd ? maxfd : fifofd;

	while (1)
	{
		rdfdstmp = rdfds;
		//4. 传递集合表给内核并等待返回到达事件的结果
		int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
		if (cnt < 0)
		{
			perror("select error");
			return -1;
		}
		
		if (FD_ISSET(0, &rdfdstmp))
		{
			fgets(buff, sizeof(buff), stdin);  //0
			printf("STDIN : %s\n", buff);
		}
		if (FD_ISSET(fifofd, &rdfdstmp))
		{
			memset(buff, 0, sizeof(buff));
			read(fifofd, buff, sizeof(buff));
			printf("FIFO : %s\n", buff);

		}
	}

	close(fifofd);
	return 0;
}

IO多路复用

#include "head.h"

#define SER_PORT  50000
#define SER_IP    "192.168.0.179"

int init_tcp_ser()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);
	
	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}

	ret = listen(sockfd, 100);
	if (ret < 0)
	{
		perror("listen error");
		return -1;
	}

	return sockfd;
}



int main(int argc, const char *argv[])
{

	char buff[1024] = {0};

	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);

	int sockfd = init_tcp_ser();
	if (sockfd < 0)
	{
		return -1;
	}

	//1. 创建文件描述符集合
	fd_set rdfds;
	fd_set rdfdstmp;
	FD_ZERO(&rdfds);
	
	//2. 添加关注的文件描述符到集合
	FD_SET(sockfd, &rdfds);
	int maxfd = sockfd;

	while (1)
	{
		rdfdstmp = rdfds;
		//3. 传递集合到内核,并等待返回监测结果
		int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
		if (cnt < 0)
		{
			perror("select error");
			return -1;
		}
		//4. 是否有监听套接字事件到达 ----》三次握手已完成,可以accept
		if (FD_ISSET(sockfd, &rdfdstmp))
		{
			int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
			if (connfd < 0)
			{
				perror("accept error");
				return -1;
			}
			FD_SET(connfd, &rdfds);
			maxfd = maxfd > connfd ? maxfd : connfd;
		}
		//5. 是否有通讯套接字事件到达
		for (int i = sockfd+1; i <= maxfd; i++)
		{
			if (FD_ISSET(i, &rdfdstmp))
			{
				memset(buff, 0, sizeof(buff));
				ssize_t cnt = recv(i, buff, sizeof(buff), 0);
				if (cnt < 0)
				{
					perror("recv error");
					FD_CLR(i, &rdfds);
					close(i);
					continue;
				}
				else if (0 == cnt)
				{
					FD_CLR(i, &rdfds);
					close(i);
					continue;
				}
				printf("%s\n", buff);
				strcat(buff, "--->ok");
				cnt = send(i, buff, strlen(buff), 0);
				if (cnt < 0)
				{
					perror("send error");
					FD_CLR(i, &rdfds);
					close(i);
					continue;
				}
			}
		}
		
	}
	close(sockfd);



	return 0;
}





在这里插入图片描述

2:poll

在这里插入图片描述

3:epoll

在这里插入图片描述

epoll:

1:创建文件描述符结合
2:添加关注的文件描述符
3:epoll通知内核开始进行事件监测
4:epoll返回时,获取到达事件的结果
5:

在这里插入图片描述
函数1:
在这里插入图片描述
函数2:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
函数3:
在这里插入图片描述
selsect和epoll区别:EPOLL没有文件描述符上限限制,selsect上限1024个,

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到