《简述Linux平台下服务器框架的搭建思路(三)》

现在来陈述第二节遗留的问题,在多线程的场景下,假如主线程检测到新连接的***已连接套接字***上有读写事件发生时,那么此时如何把该套接字的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。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值