Socket编程模型之简单选择模型

前言

重点:

一、Windows网络编程之OOP思想的应用

二、用多线程实现非阻塞式

三、回调模型应用一个简单的架构。

关于Socket编程模型的资料网上能搜索到很多,我发现资料中的示例代码没有问题的资料真不多。后面我至少写五篇文章,分别详细介绍最主要的五个Socket编程模型。首先介绍简单选择模型。

两种I/O模式

阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序。套接字默认为阻塞模式。可以通过多线程技术进行处理。

非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权。这种模式使用起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误。但功能强大。

如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。每一种模型均适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确,而且综合考虑到程序的扩展性和可移植性等因素,作出自己的选择。

反射式客户端

这种客户端是最好的测试式具。它不加工消息内容,而是校验发送出去的消息是否与接收到的消息一致。主要用途是与反射式服务端一起校验网络的稳定性。

工程代码非常简单,就一个Win32控制台工程。一个主要的代码文件reflect_client.cpp,代码如下:

#include "stdafx.h"

#define SERVER_IP_ADDRESS "127.0.0.1"
#define SOCKET_PORT_NUMBER 5150
#define SOCKET_BUFFER_SIZE 1024

int _tmain(int argc, _TCHAR* argv[])
{
	WSADATA data;
	SOCKET client;
	SOCKADDR_IN server_addr;
	char message[SOCKET_BUFFER_SIZE] = { 0 };
	int iret=0;

	WSAStartup(MAKEWORD(2,2), &data);
	client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	memset(&server_addr, 0, sizeof(SOCKADDR_IN));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP_ADDRESS);
	server_addr.sin_port = htons(SOCKET_PORT_NUMBER);
	connect(client, (struct sockaddr*)&server_addr, sizeof(SOCKADDR_IN));
	while (true)
	{
		strcpy_s(message, "你好");
		printf_s("发:%s\n",message);
		send(client, message, strlen(message), 0);
		ZeroMemory(message, SOCKET_BUFFER_SIZE);
		iret = recv(client, message, SOCKET_BUFFER_SIZE, 0);
		if (iret < 0)
		{
			printf("没有收到响应。\n");
		}
		else
		{
			message[iret] = 0;
			printf("收:%s\n", message);
		}
		Sleep(1000);
	}
	closesocket(client);
	WSACleanup();
	return 0;
}

主要做了一件事,发送一个字符串“你好”到服务端,然后把收到的消息输出。如果接收错误,则输出错误消息。

公共库

新建一个Win32项目,类型是静态库。修改工程属性,把“MFC的使用”改为“在共享DLL中使用MFC”,把“代码生成·运行库”改为“多线程调试DLL(/MDd)”。用向导增加一个类,类名称为iserver_manager,代码如下:

#pragma once
class iserver_manager
{
public:
	virtual bool accept_by_crt() = 0;
	virtual bool accept_by_winapi() = 0;
	virtual void receive() = 0;
	virtual void shutdown() = 0;

public:
	iserver_manager();
	virtual ~iserver_manager();
};

用向导增加一个类,类名称是icallback,代码如下:

#pragma once

#include "../management/iserver_manager.h"

class icallback
{
public:
	virtual void set_manager(iserver_manager* manager) = 0;
	virtual iserver_manager* get_manager() = 0;
	virtual void shutdown() = 0;

public:
	icallback();
	virtual ~icallback();
};


用向导增加一个common_callback类型,继承自icallback,头文件代码如下:

#pragma once

#include "icallback.h"
#include 

class common_callback:
	public icallback
{
private:
	iserver_manager* pmanager;
	HANDLE accept_crt_handle;
	HANDLE accept_winapi_handle;
	HANDLE receive_handle;
	DWORD accept_crt_id;
	DWORD accept_winapi_id;
	DWORD receive_id;

public:
	//icallback
	void set_manager(iserver_manager* pmanager);
	iserver_manager* get_manager();
	void shutdown();

public:
	void start_accept_by_crt();
	void start_accept_by_winapi();
	void start_receive();

public:
	common_callback();
	virtual ~common_callback();
};

common_callback实现文件代码如下:

#include "stdafx.h"
#include "common_callback.h"
#include "../privates.h"

common_callback::common_callback()
{
}


common_callback::~common_callback()
{
}

void common_callback::set_manager(iserver_manager* pmanager)
{
	this->pmanager = pmanager;
}

iserver_manager* common_callback::get_manager()
{
	return pmanager;
}

void common_callback::start_accept_by_crt()
{
	accept_crt_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)accept_crt_thread, this, 0, &accept_crt_id);
}

void common_callback::start_accept_by_winapi()
{
	accept_winapi_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)accept_winapi_thread, this, 0, &accept_winapi_id);
}

void common_callback::start_receive()
{
	receive_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)receive_thread, this, 0, &receive_id);
}

void common_callback::shutdown()
{
	TerminateThread(accept_crt_handle, 0);
	TerminateThread(accept_winapi_handle, 0);
	TerminateThread(receive_handle, 0);
}

DWORD CALLBACK accept_crt_thread(LPARAM param)
{
	((icallback*)param)->get_manager()->accept_by_crt();
	return 0;
}

DWORD CALLBACK accept_winapi_thread(LPARAM param)
{
	((icallback*)param)->get_manager()->accept_by_winapi();
	return 0;
}

DWORD CALLBACK receive_thread(LPARAM param)
{
	((icallback*)param)->get_manager()->receive();
	return 0;
}


反射式服务端

因为简单选择模型使用的是阻塞式I/O,所以新建的Win32控制台工程命名为blocked_server。修改工程属性,把“C++·常规·附加包含目录”改为“$(SolutionDir)common\callback;$(SolutionDir)common\management;%(AdditionalIncludeDirectories)”。使用类向导增加一个类,类名称是blocked_server_manager。头文件的代码如下:

#pragma once

#include 
#include 
#include 
#include 

class blocked_server_manager:
	protected iserver_manager
{
private:
	int iclient_count;
	SOCKET sconnections[FD_SETSIZE];
	std::vector vconnections;
	common_callback callback;
	BOOL brunning;

protected:
	bool accept_by_crt();
	bool accept_by_winapi();
	void receive();

public:
	bool asynchronous_accept_by_crt();
	bool asynchronous_accept_by_winapi();
	void asynchronous_receive();
	void shutdown();

public:
	blocked_server_manager();
	virtual ~blocked_server_manager();
};

int CALLBACK ConditionFunc(LPWSABUF lpCallerId, LPWSABUF lpCallerData, LPQOS lpSQOS, LPQOS lpGQOS, LPWSABUF lpCalleeId, LPWSABUF lpCalleeData, GROUP FAR* g, DWORD_PTR dwCallbackData);

blocked_server_manager类型实现代码如下:

#include "stdafx.h"
#include "blocked_server_manager.h"

#define SOCKET_PORT 5150
#define SOCKET_MESSAGE_SIZE 1024

blocked_server_manager::blocked_server_manager()
{
	callback.set_manager(this);
	iclient_count = 0;
	FD_ZERO(sconnections);
}


blocked_server_manager::~blocked_server_manager()
{
}

bool blocked_server_manager::accept_by_crt()
{
	WSADATA data;
	SOCKET slisten, sconnection;
	SOCKADDR_IN alocal = { 0 }, aconnection = { 0 };
	int iaddr_size = sizeof(SOCKADDR_IN);
	int iret = 0;
	int ibind_port = SOCKET_PORT;
	DWORD_PTR wsaCallbackData = 0;

	WSAStartup(MAKEWORD(2, 2), &data);
	slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	alocal.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	alocal.sin_family = AF_INET;
	alocal.sin_port = htons(ibind_port);
	do
	{
		iret = bind(slisten, (struct sockaddr*)&alocal, sizeof(SOCKADDR_IN));
		if (iret == -1)
		{
			ibind_port++;
			alocal.sin_port = htons(ibind_port);
		}
		else
		{
			printf("服务启动成功,监听端口是:%d\n", ibind_port);
		}
	} while (iret == -1);
	listen(slisten, 3);
	while (brunning)
	{
		sconnection = accept(slisten, (struct sockaddr*)&aconnection, &iaddr_size);
		printf("新连接:%s:%d\n", inet_ntoa(aconnection.sin_addr), ntohs(aconnection.sin_port));
		sconnections[iclient_count++] = sconnection;
	}
	return true;
}

bool blocked_server_manager::accept_by_winapi()
{
	WSADATA data;
	SOCKET slisten, sconnection;
	SOCKADDR_IN alocal = { 0 }, aconnection = { 0 };
	int iaddr_size = sizeof(SOCKADDR_IN);
	int iret = 0;
	int ibind_port = SOCKET_PORT;

	WSAStartup(MAKEWORD(2, 2), &data);
	slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	alocal.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	alocal.sin_family = AF_INET;
	alocal.sin_port = htons(ibind_port);
	do
	{
		iret = bind(slisten, (struct sockaddr*)&alocal, sizeof(SOCKADDR_IN));
		if (iret == -1)
		{
			ibind_port++;
			alocal.sin_port = htons(ibind_port);
		}
		else
		{
			printf("服务启动成功,监听端口是:%d\n", ibind_port);
		}
	} while (iret == -1);
	listen(slisten, 3);
	while (brunning)
	{
		sconnection = WSAAccept(slisten, (struct sockaddr*)&aconnection, &iaddr_size, ConditionFunc, iclient_count);
		if (sconnection == -1)
			continue;
		printf("新连接:%s:%d\n", inet_ntoa(aconnection.sin_addr), ntohs(aconnection.sin_port));
		sconnections[iclient_count++] = sconnection;
	}
	return true;
}

void blocked_server_manager::receive()
{
	int i = 0, iret = 0;
	fd_set selector;
	struct timeval tv = { 1, 0 };
	char message[SOCKET_MESSAGE_SIZE] = { 0 };

	while (brunning)
	{
		FD_ZERO(&selector);
		for (i = 0; i < iclient_count; i++)
		{
			FD_SET(sconnections[i], &selector);
		}
		iret = select(0, &selector, NULL, NULL, &tv);
		if (iret == 0)
			continue;
		for (i = 0; i < iclient_count; i++)
		{
			if (FD_ISSET(sconnections[i], &selector))
			{
				iret = recv(sconnections[i], message, SOCKET_MESSAGE_SIZE, 0);
				if (iret == 0 || iret == SOCKET_ERROR)
				{
					printf("Client :%d closed!\n", sconnections[i]);
					closesocket(sconnections[i]);
					if (i < iclient_count - 1)
					{
						sconnections[i] = sconnections[iclient_count - 1];
					}
					i--;
					iclient_count--;
				}
				else
				{
					message[iret] = 0;
					send(sconnections[i], message, iret, 0);
				}
			}
		}
	}
}

bool blocked_server_manager::asynchronous_accept_by_crt()
{
	brunning = TRUE;
	callback.start_accept_by_crt();
	return true;
}

bool blocked_server_manager::asynchronous_accept_by_winapi()
{
	brunning = TRUE;
	callback.start_accept_by_winapi();
	return true;
}

void blocked_server_manager::asynchronous_receive()
{
	callback.start_receive();
}

void blocked_server_manager::shutdown()
{
	brunning = FALSE;
	callback.shutdown();
	WSACleanup();
}

int CALLBACK ConditionFunc(LPWSABUF lpCallerId, LPWSABUF lpCallerData, LPQOS lpSQOS, LPQOS lpGQOS, LPWSABUF lpCalleeId, LPWSABUF lpCalleeData, GROUP FAR* g, DWORD_PTR dwCallbackData)
{
	if (dwCallbackData > 2)
	{
		printf_s("连接数已达到最大值。\n");
		return CF_REJECT;
	}
	return CF_ACCEPT;
}

accept_by_crt与accept_by_winapi最大的区别在于select函数的调用,一个直接用的是C函数,一个用的是Win32API函数。后者有回调函数,可在选择之前判断是否接受当前网络请求。

blocked_server.cpp代码如下:

#include "stdafx.h"
#include "blocked_server_manager.h"

blocked_server_manager app;

int _tmain(int argc, _TCHAR* argv[])
{
	app.asynchronous_accept_by_winapi();
	app.asynchronous_receive();
	printf_s("按任意键关闭服务器并退出程序。\n");
	system("pause");
	app.shutdown();
	return 0;
}

以上是服务器的启动代码。

运行效果

首选上一张截图吧。

Socket编程模型之简单选择模型_第1张图片


优点是简单,直接上手,缺点是效率不高,在没有客户端的连接的时候CPU空转导致使用率可以达到100%。所有这个方式是不推荐使用的。

你可能感兴趣的:(C/C++,MFC,VC,Socket)