现在来陈述第二节遗留的问题,在多线程的场景下,假如主线程检测到新连接的***已连接套接字***上有读写事件发生时,那么此时如何把该套接字的IO事件交给子线程(Sub Reactor)去处理,一般情况下子线程是处于epoll_dispatch的调用上,如果没有事件便处于阻塞的状态。如何去唤醒阻塞在epoll_dispatch的子线程呢?这里就涉及到socketpair技术的应用。
在初始化EventLoop对象时,创建一个socketpair对象(端对端的通信,类似于进程间通信的管道),把ScoketPair一端套接字上可读事件添加到EventLoop的事件列表中去。
if(socketpair(AF_UNIX,SOCK_STREAM,0,eventLoop->socketPair) < 0)
{
printf("make socketPair error");
}
//socketPair[1]监听EVENT_READ事件,每个线程都有一个socketpair
struct channel *channel1 = channel_new(eventLoop->socketPair[1],EVENT_READ,handleWakeup,NULL,eventLoop);
event_loop_add_channel_event(eventLoop,eventLoop->socketPair[1],channel1);
void event_loop_wakeup(void *data)
{
struct event_loop *eventLoop = (struct event_loop *)data;
char one = 'a';
size_t n = write(eventLoop->socketPair[0],&one,sizof one);
if(n < 0)
{
printf("event_loop_wakeup error");
return ;
}
printf("event_loop_wakeup error");
}
//从socketpair[1]上读取该字符,成功读取,表示当前的EventLoop被唤醒
void handleWakeup(void *data)
{
struct event_loop *eventLoop = (struct event_loop *)data;
char one;
size_t n = read(eventLoop->socketPair[1],&one,sizeof one);
if(n != sizeof one)
{
printf("handle wakeuo error");
return ;
}
printf("handle wakeup success");
}
当发现添加channel事件的EventLoop对象和当前线程不是同一个线程,那么往被添加channel事件的EventLoop对象的socketpair[0]上面写一个字符,随便什么都可以。相当于用根“棍子”戳对方一下,嘿!大兄弟,有任务了,不要再阻塞了,赶紧醒过来。
介绍完socketPair对象,现在开始详细介绍读写缓冲区buffer的构造和结构。
//buffer既可以表示发送缓冲区,也可以表示接收缓冲区。
struct buffer
{
void *data;
//当前读取的位置
int readIndex;
//当前缓冲区中已经写到的位置
int writeIndex;
//缓冲区大小
int totalSize;
};
int buffer_front_spare_size(struct buffer *buffer1)
{
//已经读取过的空间,可以认为是空余的空间,可以再次填充其它数据把它覆盖掉
return buffer1->readIndex;
}
int buffer_readable_size(struct buffer *buffer1)
{
//当前写的位置减去读取的位置,代表缓冲区中剩余还未读取的空间
return buffer1->writeIndex - buffer1->readIndex;
}
int buffer_writeable_size(struct buffer *buffer1)
{
//总大小减去当前已经写的位置,代表还剩多少空间可写
return buffer1->totalSize - buffer1->writeIndex;
}
void make_room(struct buffer *buffer1,int size)
{
if(buffer_writeable_size(buffer1) >= size)
{
return ;
}
int readableSize = 0;
if(buffer_front_size(buffer1) + buffer_readable_size(buffer1) > size)
{
//把未读取完的数据拷贝到前面已经读取完的空间去
readableSize = buffer_readable_size(buffer1);
for(int i =0;i < size;i++)
{
memcpy(buffer1->data + i,buffer1->data + buffer1->readIndex,1);
}
//更新缓冲区的读写索引,从0开始
buffer1->readIndex = 0;
//当前写到的位置,就是上一次还未读取完的数据的大小,从0开始计算
buffer1->writeIndex = readableSize;
}else
{
//如果要拼接的数据超过了已读和待读空间大小的总和,那么重新开辟新的空间
void *temp = (void *)realloc(buffer1->data,buffer1->total_size + size);
buffer1->data = (void *)temp;
buffer1->total_size += size;
}
}
//拼接一段数据
void buffer_append(struct buffer *buffer1,void *data,int size)
{
if(data != NULL)
{
make_room(buffer1, size);
memcpy(buffer1->data + buffer1->writeIndex, data, size);
buffer1->writeIndex += size;
}
}
//拼接一个字符
void buffer_append_char(struct buffer *buffer1,char data)
{
make_room(buffer1,1);
buffer1->data[buffer1->writeIndex++] = data;
}
//拼接一段字符
void buffer_append_string(struct buffer *buffer1,char * str)
{
if(data != NULL)
{
int len = strlen(buffer1);
buffer_append_data(buffer1, data, len);
}
}
int buffer_socket_read(struct buffer *buffer1,int fd)
{
//为什么要使用readv和writev函数,因为客户端发送古来的数据很有可能是零散的,不连续的,数据是分布在零散的空间内,刚好readv和writev可以在零散地在多个内存空间内进行原子性的数据读写操作。
char additional_buffer[INIT_BUFFER_SIZE];
//iovec 顾名思义就是IO vector
struct iovec vec[2];
int max_writeable = buffer_writeable_size(buffer1);
//iov_base指向待读取的第一段内存空间的数据
vec[0].iov_base = buffer1->data + buffer1->writeIndex;
//iov_len代表第一段待读取内存空间数据的长度
vec[0].iov_len = max_writeable;
vec[1].iov_base = additional_buffer;
vec[1].iov_len = sizeof(additional_buffer);
int result = readv(fd,vec,2);
if(result < 0)
{
return -1;
}else if(result <= max_writeable)
{
//如果总共的数据长度小于接受缓冲区内可写的空间的长度,那么就直接把接收到的数据拼接在剩余的可写空间内
buffer1->writeIndex += result;
}else
{
buffer1->writeIndex = buffer1->total_size;
buffer_append(buffer1,additional_buffer,result - max_writeable);
}
return result;
}
char buffer_read_char(struct buffer *buffer1)
{
char c = buffer1->data[buffer1->readIndex++];
return c;
}
char * buffer_find_crlf(struct buffer *buffer1)
{
char *CRLF = "\r\n";
char *crlf = memmem(buffer1->data + buffer1->readIndex ,buffer_readable_size(buffer1),CRLF,2);
return crlf;
}
最后再来介绍下日志库的设计,一个完整的系统肯定少不了日志库的设计,在实际生产环境下,我们不可能使用printf向控制台执行日志的输出打印,我们肯定需要自行设计一套日志库出来,用于线上程序的追踪和问题的排查。
#ifndef _LOG_H_
#define _LOG_H_
#include "stdarg.h"
#define LOG_DEBUG_LEVEL 0
#define LOG_MSG_LEVEL 1
#define LOG_WARN_LEVEL 2
#define LOG_ERR_LEVEL 3
void TcpServer_log(int security,const char *msg);
void TcpServer_logx(int security,const char *errstr,const char *fmt,va_list ap);
void TcpServer_Msgx(const char *fmt,...);
void TcpServer_debugx(const char *fmt,...);
void error(int status,int err,char *fmt,...);
#ifdef LOG_MSG(msg);
TcpServer_log(LOG_MSG_LEVEL,msg);
#endif
#ifdef LOG_ERR(msg)
TcpServer_log(LOG_ERR_LEVEL,msg);
#endif
#endif
#include "log.h"
#include "syslog.h"
#include <errno.h>
#define MAXLINE 4096 //一个日志文件只有4KB
void error(int status,int err,char *fmt,...)
{
//va_list实际上是一个指针,用来指向error函数的各个不定参数。
va_list ap;
//va_start将va_list定义的指针指向error函数的第一个不定参数,也即是fmt参数后面的一个参数。
va_start(ap,fmt);
//将fmt(错误信息类型),错误信息内容拼接到标准错误中去(Linux环境下,有标准输入、标准输出、标准错误)
vfprintf(stderr,fmt,ap);
va_end(ap);
if(err)
{
fprintf(stderr," :%s,(%d)\n",strerror(err),err);
}
if(status)
{
exit(status);
}
}
static void
err_doit(int errnoflag, int level, const char *fmt, va_list ap)
{
int errno_save, n;
char buf[MAXLINE + 1];
errno_save = errno;
/* value caller might want printed */
vsnprintf(buf, MAXLINE, fmt, ap);
n = strlen(buf);
if (errnoflag)
snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));
strcat(buf, "\n");
fflush(stdout);
/* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(stderr);
return;
}
void TcpServer_log(int security,const char *msg)
{
const char *security_str;
switch(security)
{
case LOG_DEBUG_LEVEL:
security_str = "debug";
break;
case LOG_MSG_LEVEL:
security_str = "msg";
break;
case LOG_WARN_LEVEL:
security_str = "warn";
break;
case LOG_ERR_LEVEL:
security_str = "err";
break;
default:
security_str = "unkonow";
break;
}
fprintf(stdout,"[%s] %s\n",security_str,msg);
}
void TcpServer_logx(int security,const char *errstr,const char *fmt,va_list ap)
{
char buf[1024];
size_t len;
if(fmt != NULL)
{
vsnprintf(buf,sizeof(buf),fmt,ap);
}else
{
buf[0] = '\0';
}
if(errstr)
{
len = strlen(buf);
if(len < sizeof(buf) -3)
{
snprintf(buf + len,sizeof(buf) - len,": %s",errstr);
}
}
TcpServer_log(security,buf);
}
void TcpServer_msg(const char *fmt,...)
{
va_list ap;
va_start(ap,fmt);
TcpServer_logx(LOG_MSG_LEVEL,NULL,fmt,ap);
va_end(ap);
}
void TcpServer_msgx(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
TcpServer_logx(LOG_MSG_TYPE, NULL, fmt, ap);
va_end(ap);
}
void TcpServer_debugx(const char *fmt,...)
{
va_list ap;
va_start(ap,fmt);
TcpServer_logx(LOG_DEBUG_LEVEL,NULL,fmt,ap);
va_end(ap);
}
需要源码的同学请微信私聊,后期会更新至GitHub。