TCP 并发服务器构建

单循环服务器与并发服务器

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

并发服务器:服务端同一时刻可以处理多个客户端的任务。

TCP 并发服务器构建

1. TCP 建立连接:一对一 TCP 服务端并发模型

1.1 多进程

进程资源开销大;

安全性高。

1.2 多线程

线程相对于进程资源开销小;

在相同资源环境下,并发量比进程大。

1.3 线程池

目的:解决多线程或多进程模型中,**频繁创建和销毁线程(进程)**带来的时间消耗问题;

基于 生产者—消费者模型任务队列 实现的一套多线程框架。

1.4 IO 多路复用

IO:文件描述符(fd);

特点:在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测。

阻塞 IO 模式

fgets(stdin);

recv(connfd);

多个任务之间表现为同步的效果。

常见实现方式
  1. select

  2. poll

  3. epoll

2. select 实现 IO 多路复用

2.1 实现步骤

  1. 创建文件描述符集合:fd_set

  2. 添加关注的文件描述符到集合:FD_SET()

  3. 使用 select 将集合表传递给内核,内核开始监测事件

  4. 当内核监测到事件时,应用层 select 将解除阻塞,并获得相关的事件结果

  5. 根据 select 返回的结果做不同的任务处理

2.2 常用操作函数

void FD_CLR(int fd, fd_set *set);     // 从集合中移除文件描述符
int  FD_ISSET(int fd, fd_set *set);   // 判断文件描述符是否在集合中
void FD_SET(int fd, fd_set *set);     // 将文件描述符加入集合
void FD_ZERO(fd_set *set);            // 清空集合

2.3 select 函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

功能

传递文件描述符集合表给内核,并等待获取事件结果。

参数

nfds:关注的最大文件描述符 + 1;

readfds:读事件的文件描述符集合;

writefds:写事件的文件描述符集合;

exceptfds:其他事件的文件描述符集合;

timeout:设置 select 监测的超时时间

    NULL:不设置超时时间(select 一直阻塞等待)。

返回值

成功:返回内核监测到的事件个数;

失败:返回 -1

返回 0:超时时间到达,但没有事件发生。

#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> 
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h> 
#include <signal.h>
#include <sys/wait.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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值