windows下Libevent +多线程(负载均衡分配法) 之文件传输

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012372584/article/details/84028623

一、先说一下服务端的流程:

1、主线程负责监听客户端的连接;

2、当有客户端连接时,主线程通过管道向相应的子线程发送监听套接字描述符,子线程通过负载均衡法选择出来;

3、当主线程发送监听描述符时,子线程的读管道回调函数会被回调;

4、子线程为收到的监听描述符设置读取回调、写回调、事件回调等回调函数;

5、子线程通过开启的事件循环,循环监听第4步的事件,并回调相应的回调函数。

二、客户端发送文件、服务端接收文件原则(与上一节的方法不同):

1、客户端发送文件时采用读取多少字节就发送多少字节,程序中设置成10000字节;

2、服务端接收多少字节就写文件多少字节

3、与上一节的方法相比,此方法对于大文件也适用。

三、服务端代码,代码中有详细的注释:

扫描二维码关注公众号,回复: 4139528 查看本文章
// LibeventMulThreadBalanceDemo.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "winsock2.h"
#include <process.h> 
#include <io.h>
#include <fcntl.h>
#include "event2/listener.h"
#include "event2/bufferevent.h"
#include "event2/thread.h"
#include "event.h"
#include "BufferManager.h"
#include "Windows.h"

const int g_nThreadNum = 10;   //开启的线程数量

typedef struct Libevent_Thread
{
	DWORD did;                //子线程ID
	struct event_base *base;  //子线程base
	struct event event;       //子线程event
	evutil_socket_t read_fd;  //读管道描述符
	evutil_socket_t write_fd; //写管道描述符
} LIBEVENT_THREAD;

typedef struct Dispatcher_Thread
{
	DWORD did;                //主线程ID
	struct event_base *base;  //主线程base
} DISPATCHER_THREAD;

LIBEVENT_THREAD *g_pThreads = new LIBEVENT_THREAD[g_nThreadNum];

DISPATCHER_THREAD g_DispatcherThread;

int g_nlastThread = 0;

typedef struct PictureInfo
{
	char szFileName[260];
	long nFileSize;
} PICTUREINFO;

//读缓冲去回调函数
void read_cb(struct bufferevent *bev, void *arg)
{
	BufferManager* bm = (BufferManager*)arg;

	//第一次接收
	if (bm->nFileSize == 0)
	{
		int nReceived = bufferevent_read(bev, bm->buf,10000);
		bm->nReceiveTotal += nReceived;

		if (nReceived >= sizeof(PICTUREINFO))
		{
			bm->nFileSize = ((PICTUREINFO*)bm->buf)->nFileSize;
			strcpy_s(bm->szImgName,sizeof(bm->szImgName),((PICTUREINFO*)bm->buf)->szFileName);

			bm->f = NULL;
			fopen_s(&bm->f,bm->szImgName,"wb");
			if (fwrite(bm->buf+sizeof(PICTUREINFO),bm->nReceiveTotal - sizeof(PICTUREINFO),1,bm->f) < 1){
				// write error
			}
		}
	}
	else if ((bm->nFileSize - bm->nReceiveTotal) >= 10000)
	{
		int nReceived = bufferevent_read(bev, bm->buf,10000);
		bm->nReceiveTotal += nReceived;

		if (fwrite(bm->buf,nReceived,1,bm->f) < 1){
			// write error
		}
	}
	else if((bm->nFileSize - bm->nReceiveTotal) >= 0)
	{
		int nReceived = bufferevent_read(bev, bm->buf, bm->nFileSize-bm->nReceiveTotal);
		bm->nReceiveTotal += nReceived;

		if (fwrite(bm->buf,nReceived,1,bm->f) < 1){
			// write error
		}
	}

	if (bm->nReceiveTotal == bm->nFileSize)
	{
		printf("收到的字节数: %d ,nThreadID = %d\n",bm->nReceiveTotal,GetCurrentThreadId());
		bm->iniParam();
	}
}

//写缓冲区回调函数
void write_cb(struct bufferevent *bev, void *arg)
{
	printf("成功写数据给客户端,写缓冲区回调函数被回调.\n");
}

//事件回调函数
void event_cb(struct bufferevent *bev,short events, void *arg)
{
	BufferManager* pThis = (BufferManager*)arg;

	if (events & BEV_EVENT_EOF)
	{
		printf("connection close.\n");
	} 
	else if(events & BEV_EVENT_ERROR)
	{
		printf("some other error.\n");
	}

	//注销事件导致事件循环退出,这样子线程也将退出
	bufferevent_free(bev);

	if (pThis)
	{
		delete pThis;
		pThis = NULL;
	}

	printf("bufferevent 资源已经被释放.\n");
}

//开启子线程的事件循环
unsigned __stdcall Work_Thread( void* pArguments )
{
	LIBEVENT_THREAD *pThis = (LIBEVENT_THREAD*)pArguments;
	
	event_base_dispatch(pThis->base);

	_endthreadex(0);
	return 1;
}

//管道读回调函数
void pipe_process(int fd, short which, void *arg)
{
	LIBEVENT_THREAD* pThis = (LIBEVENT_THREAD*)arg;

	//获取管道的读取描述符
	int readfd = pThis->read_fd;
	evutil_socket_t evsock;
	recv(readfd, (char*)&evsock, sizeof(evutil_socket_t), 0);

	//为新的连接关联事件
	struct bufferevent* bev;
	bev = bufferevent_socket_new(pThis->base, evsock, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE);

	BufferManager *bm = new BufferManager;

	//给bufferevent缓冲区设置回调
	bufferevent_setcb(bev, 
		read_cb,
		write_cb,
		event_cb,
		bm);

	//启动bufferevent的读缓冲区,读缓冲区默认是disable的.
	bufferevent_enable(bev, EV_READ);
}

//主线程监听器回调函数
void cb_listener(struct evconnlistener* listener, 
				evutil_socket_t fd, 
			    struct sockaddr *addr, 
				int len, 
				void *ptr)
{
	printf("new client connect.\n");

	//采用负载均衡算法为当前连接选择子线程
	int nCurThread = g_nlastThread % g_nThreadNum;
	g_nlastThread = nCurThread + 1;
	int sendfd = g_pThreads[nCurThread].write_fd;

	//将fd传给子线程
	send(sendfd, (char*)&fd, sizeof(evutil_socket_t), 0);
}

int _tmain(int argc, _TCHAR* argv[])
{
	//初始化网络库
#ifdef WIN32
	evthread_use_windows_threads();

	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	//为每个子线程的事件绑定管道的读写事件,子线程通过管道与主线程进行通信
	int nRet(0);
	for (int i = 0;i < g_nThreadNum;i++)
	{
		evutil_socket_t fds[2];
		if(evutil_socketpair(AF_INET, SOCK_STREAM, 0, fds) < 0) 
		{
			printf("create socketpair error,g_nThreadNum = %d\n",g_nThreadNum);
			return false;
		}

		//设置成无阻塞的socket
		evutil_make_socket_nonblocking(fds[0]);
		evutil_make_socket_nonblocking(fds[1]);

		g_pThreads[i].read_fd = fds[0];
		g_pThreads[i].write_fd = fds[1];

		//创建子线程的base
		g_pThreads[i].base = event_base_new();
		if (g_pThreads[i].base == NULL)
		{
			printf("event_base_new error,g_nThreadNum = %d\n",g_nThreadNum);
			return 0;
		}

		//将文件描述符和事件进行绑定,并加入到base中
		event_set(&g_pThreads[i].event,
			      g_pThreads[i].read_fd,
				  EV_READ | EV_PERSIST,
				  pipe_process,
				  &g_pThreads[i]);

		nRet = event_base_set(g_pThreads[i].base, &g_pThreads[i].event);
		nRet = event_add(&g_pThreads[i].event,NULL);
		if (nRet == -1)
		{
			printf("event_add error,g_nThreadNum = %d\n",g_nThreadNum);
			return 0;
		}
	}

	//创建子线程,并启动子线程的事件循环,在有注册事件(管道的读事件)的情况下循环不会退出
	for (int i = 0;i < g_nThreadNum;i++)
	{
		 _beginthreadex( NULL, 0, &Work_Thread, (void*)&g_pThreads[i], 0, (unsigned int*)&g_pThreads[i].did );
	}

	//初始化服务器地址结构
	struct sockaddr_in sSerAddr;
	memset(&sSerAddr, 0, sizeof(sSerAddr));
	sSerAddr.sin_family = AF_INET;
	sSerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	sSerAddr.sin_port = htons(8888);

	//创建主线程base
	g_DispatcherThread.base = event_base_new();
	if (g_DispatcherThread.base == NULL)
	{
		printf("g_DispatcherThread.base create error.\n");
		return 0;
	}

	//创建监听器
	struct evconnlistener *listener;
	listener = evconnlistener_new_bind(g_DispatcherThread.base, 
		cb_listener, 
		g_DispatcherThread.base, 
		LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 
		-1, 
		(struct sockaddr*)&sSerAddr, 
		sizeof(sSerAddr));

	if (listener == NULL)
	{
		printf("evconnlistener_new_bind error.\n");
		return 0;
	}

	//开启主线程的事件循环
	event_base_dispatch(g_DispatcherThread.base);

	for (int i = 0;i < g_nThreadNum;i++)
	{
		event_base_free(g_pThreads[i].base);
	}

	if (g_pThreads)
	{
		delete []g_pThreads;
		g_pThreads = NULL;
	}

	evconnlistener_free(listener);
	event_base_free(g_DispatcherThread.base);
	WSACleanup();

	return 0;
}

bufferManager类和上一节的是一样的,这里就不列出了,有需要的可以查看我的上一节博客。

客户端发送文件的主要代码:

WIN32_FIND_DATA FileInfo;
		HANDLE hFind = INVALID_HANDLE_VALUE;
		DWORD FileSize = 0;                   //文件大小

		char buf[10001] = {0};
		char *pbuf = NULL;
		ZeroMemory(&FileInfo,sizeof(WIN32_FIND_DATA));

		//获取文件大小
		hFind = FindFirstFile("3.手工布局.avi",&FileInfo); 
		if(hFind != INVALID_HANDLE_VALUE) 
		{
			FileSize = FileInfo.nFileSizeLow  + sizeof(PICTUREINFO);
		}
		FindClose(hFind);

		strcpy_s(((PICTUREINFO*)buf)->szFileName,"3.手工布局.avi");
		((PICTUREINFO*)buf)->nFileSize = FileSize;

		//第一次发送10000,带文件头
		FILE *f = NULL;
		fopen_s(&f,"3.手工布局.avi","rb");
		fread(buf + sizeof(PICTUREINFO),10000 - sizeof(PICTUREINFO),1,f);
		bufferevent_write(bev,buf,10000);
		FileSize -= 10000;

		while (FileSize >= 10000)
		{
			fread(buf,10000,1,f);
			bufferevent_write(bev,buf,10000);

			FileSize -= 10000;
		}

		if (FileSize > 0)
		{
			fread(buf,FileSize,1,f);
			bufferevent_write(bev,buf,FileSize);
		
			FileSize -= FileSize;
		}

		if (f)
		{
			fclose(f);
			f = NULL;
		}

猜你喜欢

转载自blog.csdn.net/u012372584/article/details/84028623