项目中直接用的moduo库,但我这里想要自己写一下简单epoll和线程池,但结果并没有写出来,这里记录下学习进度,以后有能力再完成吧。
一、TCP编程
TCP编程协议是面向链接的,可靠的,字节流服务,这个在编程中就有体现(socket bind listen accept connect),头文件#include <sys/socket.h> 。
- int socket (int 协议族, int 字节流或数据报,int 0);
TCP协议簇:AF_INIT TCP字节流:SOCK_STREAM - 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回环地址,让客户端可以访问自己 - int listen(int sockfd, int size) 启动监听,三次握手就在这个阶段
size:最多监听的数目,现在只算已经完成3次握手的,以前还算正在3次握手的 - int accept(int sockfd, struct sockaddr *cli, int *len);
返回一个客户端链接的文件描述符,cli记录下客户端的信息 - int connect(int sockfd, (sockaddr*)&ser, int len);客户端连接服务器
二、epoll
- 函数
(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 - 代码
#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)在事件发生前开辟线程,并在线程中死循环,不断处理事件。
(2)使用信号量控制线程,初始化为0,在线程函数中执行wait操作,等待事件发生,主线程中当一个事件来临时执行post操作。
(3)有事件发生的连接(文件描述符),由于1线程池是先开辟的,不能再传入参数,2事件就绪速度大于处理速度,这就需要一个队列来存储就绪的文件描述符,有主线程中事件发生就push,子线程事件处理就pop。 - 代码
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);
}
四、待学习
- 总结:上述代码都连最最基本的需求都满足不了。epoll实际上还是一个处理器 工作,只不过它可以同时监听多个文件描述符。线程池倒是有多个处理器工作,不过只能监听几个文件描述符,最重要的是它把宝贵的资源都浪费在了recv上,那么怎么解决?目前,我就卡在了这个地方。
- 问题和尝试:第一我给线程函数的参数传入函数指针,这样让线程池模块可以独立出来,这个用C基本实现了,不过C++语法不通过,因为C++有模板,所以C上对void*的操作在C++上都是不通过的。第二,函数线程处理处理就绪的事件,处理就绪的事件需要sockfd,epoll,revents这些参数,所以我只能把他们都设置为全局变量,但由于第一条的原因无法用stl库,这就还需要我写一个queue,而我就做到这里停了下来。但这还没完,还有问题三,主线程是什么都不做还是处理一下客户端连接,如果主线程什么都不做,epoll_wait不断执行会带来的问题怎么处理?
- 解决方案:
(1)继续按照自己的思路写(想想还是算了吧)
(2)刨析libevent库
(3)刨析muduo库