跨平台开发集群聊天项目三——TCP编程(epoll和线程池)

项目中直接用的moduo库,但我这里想要自己写一下简单epoll和线程池,但结果并没有写出来,这里记录下学习进度,以后有能力再完成吧。

一、TCP编程

TCP编程协议是面向链接的,可靠的,字节流服务,这个在编程中就有体现(socket bind listen accept connect),头文件#include <sys/socket.h> 。

  1. int socket (int 协议族, int 字节流或数据报,int 0);
    TCP协议簇:AF_INIT TCP字节流:SOCK_STREAM
  2. int bind(int sockfd, struct sockaddr* 服务器IP和端口,int len);
    struct sockaddr_in{地址组(IPV4), 端口号, IP地址};
    地址簇:PF_INIT
    端口号:htons()主机字节序转换为网络字节序(网络是大端模式,与阅读习惯一致)
    IP地址:inet_addr(“127.0.0.1”); inet_addr将字符串转换为点分十进制 127.0.0.1回环地址,让客户端可以访问自己
  3. int listen(int sockfd, int size) 启动监听,三次握手就在这个阶段
    size:最多监听的数目,现在只算已经完成3次握手的,以前还算正在3次握手的
  4. int accept(int sockfd, struct sockaddr *cli, int *len);
    返回一个客户端链接的文件描述符,cli记录下客户端的信息
  5. int connect(int sockfd, (sockaddr*)&ser, int len);客户端连接服务器

二、epoll

  1. 函数
    (1) 创建内核事件表 int epoll_create(int size);
    size现在已经没有意义了
    (2)管理int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    op:EPOLL_CTL_ADD增 、EPOLL_CTL_MOD改、EPOLL_CTL_DEL 删
    strcut epoll_event { short events; union epoll_data_t data;};
    data是联合体,通常用文件描述符fd
    (3)监听int epoll_wait(int epfd, struct epoll_event *events, int eventsLen, int timeout);
    events:返回就绪的数组,maxevents: 数组的长度, timeout=-1
  2. 代码
#if 0
//IO复用
int main()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);  //选择协议族
	assert(sockfd != -1);

	sockaddr_in ser;
	ser.sin_family = PF_INET;
	ser.sin_port = htons(6000);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd, (sockaddr*)& ser, sizeof(ser));//服务绑定,让客户端可以访问
	assert(res != -1);

	listen(sockfd, 5);  //启动监听,listen与accept之间完成

	int epoll = epoll_create(5);

	epoll_event event;
	event.events = EPOLLIN;
	event.data.fd = sockfd;
	epoll_ctl(epoll, EPOLL_CTL_ADD, sockfd, &event);

	while (1)
	{
		epoll_event revents[128];
		int n=epoll_wait(epoll, revents, 128, -1);

		for (int i = 0; i < n; ++i)
		{
			int fd = revents[i].data.fd;
			if (fd == sockfd)
			{
				sockaddr_in cli;
				socklen_t len = sizeof(cli);
				int c = accept(sockfd, (sockaddr*)& cli, &len);
				
				if (c > 0)
				{
					event.events = EPOLLIN | EPOLLRDHUP;
					event.data.fd = c;
					epoll_ctl(epoll, EPOLL_CTL_ADD, c, &event);
				}
			}
			else
			{
				if (revents[i].events & EPOLLRDHUP)
				{
					epoll_ctl(epoll, EPOLL_CTL_DEL, fd, NULL);
					std::cout << "one client break" << std::endl;
				}
				else
				{
					char recvbuf[128] = { 0 };
					int n = recv(fd, recvbuf, 127, 0);
					if (n <= 0)
					{
						std::cout << "one client break(EPOLLIN of type)" << std::endl;
						close(fd);
						break;
					}
					std::cout << recvbuf << std::endl;
					send(fd, "OK", 2, 0);
				}
			}
		}
	}

	close(sockfd);
}
#endif

三、线程池

由于线程的申请和销毁耗费了大量的事件,所以我们就想要线程在处理完一个事件后不销毁,直接去执行下一个事件,所以就有了"池"的概念。

  1. 框架
    (1)在事件发生前开辟线程,并在线程中死循环,不断处理事件。
    (2)使用信号量控制线程,初始化为0,在线程函数中执行wait操作,等待事件发生,主线程中当一个事件来临时执行post操作。
    (3)有事件发生的连接(文件描述符),由于1线程池是先开辟的,不能再传入参数,2事件就绪速度大于处理速度,这就需要一个队列来存储就绪的文件描述符,有主线程中事件发生就push,子线程事件处理就pop。
  2. 代码
std::queue<int> queC;
sem_t pthreadSem;

void* pthreadFun(void* data)
{
	while (1)
	{
		sem_wait(&pthreadSem);
		int c = queC.front();
		queC.pop();

		while (1)
		{
			char recvbuf[128] = { 0 };
			int n = recv(c, recvbuf, 127, 0);
			if (n <= 0)
			{
				std::cout << "one client break" << std::endl;
				close(c);
				break;
			}
			std::cout << recvbuf << std::endl;
			send(c, "OK", 2, 0);
		}
	}	
}

int main()
{
	sem_init(&pthreadSem, false, 0);
	
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);  //选择协议族
	assert(sockfd != -1);

	sockaddr_in ser;
	ser.sin_family = PF_INET;
	ser.sin_port = htons(6000);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd, (sockaddr*)& ser, sizeof(ser));
	assert(res != -1);

	listen(sockfd, 5);  

	//启动线程池
	for (int i = 0; i < 3; ++i)
	{
		pthread_t id;
		int res = pthread_create(&id, NULL, pthreadFun, NULL);
		assert(res == 0);
	}

	while (1)
	{
		sockaddr_in cli;
		socklen_t len = sizeof(cli);
		int c = accept(sockfd, (sockaddr*)& cli, &len);
		if (c < 0)
			continue;	

		queC.push(c);
		sem_post(&pthreadSem);
	}

	close(sockfd);
}

四、待学习

  1. 总结:上述代码都连最最基本的需求都满足不了。epoll实际上还是一个处理器 工作,只不过它可以同时监听多个文件描述符。线程池倒是有多个处理器工作,不过只能监听几个文件描述符,最重要的是它把宝贵的资源都浪费在了recv上,那么怎么解决?目前,我就卡在了这个地方。
  2. 问题和尝试:第一我给线程函数的参数传入函数指针,这样让线程池模块可以独立出来,这个用C基本实现了,不过C++语法不通过,因为C++有模板,所以C上对void*的操作在C++上都是不通过的。第二,函数线程处理处理就绪的事件,处理就绪的事件需要sockfd,epoll,revents这些参数,所以我只能把他们都设置为全局变量,但由于第一条的原因无法用stl库,这就还需要我写一个queue,而我就做到这里停了下来。但这还没完,还有问题三,主线程是什么都不做还是处理一下客户端连接,如果主线程什么都不做,epoll_wait不断执行会带来的问题怎么处理?
  3. 解决方案:
    (1)继续按照自己的思路写(想想还是算了吧)
    (2)刨析libevent库
    (3)刨析muduo库
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EPOLL线程池是在C++中常用的技术,用于实现高效的网络编程EPOLL是Linux下的一种I/O多路复用机制,可以同时监控多个文件描述符的状态变化,从而实现高并发的网络通信。线程池则是一种线程管理机制,通过预先创建一定数量的线程,将任务分配给这些线程来执行,从而提高程序的并发性能。 在C++中,可以使用EPOLL线程池来实现高性能的网络服务器。EPOLL负责监听和处理网络事件,而线程池则负责处理具体的业务逻辑。当有新的连接到来时,EPOLL会将该连接的文件描述符添加到监听列表中,并在有事件发生时通知线程池进行处理。线程池中的线程会从任务队列中取出任务,并执行相应的操作。通过EPOLL线程池的搭配使用,可以实现高并发的网络通信,并提高程序的性能和可扩展性。 关于EPOLL线程池的具体实现细节,可以参考相关的书籍和文章。例如,可以阅读《C++ Primer》和《C++ Concurrency in Action》等C++基础书籍,了解EPOLL线程池的基本原理和用法。此外,还可以参考一些经验分享的文章,如CSDN上的《Visual Studio高效调试手段与技巧总结》和《C++没落了?学习C++没有前途了?从业者给你揭晓答案》等,获取更多关于EPOLL线程池的实践经验和技巧。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* *3* [C/C++学习路线总结与分享](https://blog.csdn.net/chenlycly/article/details/127191075)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值