[C++]服务器与客户端建立连接与检测断开的demo

该程序在IP127.0.0.1以及端口5000环境下测试

有一段时间没有在Windows下用C++进行网络编程了,这段日子都在做QT的网络编程和OpenCV的图像识别。
今天重新写个Windows下C++的,基于TCP的双端连接建立与断开检测的demo,巩固下自己Windows下的网络编程知识点。

在下面的代码共有四个类,一个内部结构体,以下是他们的介绍。

WebException类可以忽略,是一个异常类,用于反馈意外情况。
WebBase类是服务端和客户端的基类,用于初始化共同的基本数据。
Server类是服务端类,用于接收客户端连接。
Client类是客户端类,用于连接服务端。

Server类的内部结构体ClientSocket,用以保存已经连接到服务端的客户端Socket。

由于只是做个简单的相互检测连接与断开的demo,所以整个程序就全在这一个cpp中了。

该demo的主要功能是:

  • 服务器能被连接十次,服务器可以检测客户端断开与否。
  • 客户端连接上服务器后,客户端可以检测到与服务器失联与否。

服务端的整体思路是:

主线程负责检测客户端的连接请求,服务端同意连接获取到客户Socket后,以clock()获取到的值当作客户id,以id为key,将客户Socket保存到cliSockets这个map中;
保存好客户Socket后,开启一条线程用以检测连接是否丢失,如果丢失了(暂不考虑重连),则回收客户的Socket与相应线程资源;
此外,为了了解服务端关闭后,客户端的失联处理,服务端被设置成只能连接十次;
十次后关闭服务端,服务端这次的主动关闭将回收所有存活的客户端Socket和相应的线程资源。
安全回收后,服务端正式关闭。

客户端的整体思路是:

主线程负责检测连接上服务端,每秒尝试一次连接,连接成功获取到服务端Socket后开启一条线程用以检测连接是否丢失,如果丢失了(暂不考虑重连),则回收服务端的Socket与相应线程资源;
如果客户端发现在主动断开与服务端的连接前就已经无法联系服务端,那么将回收服务端Socket和相应的线程资源。
安全回收后,客户端正式关闭。

#pragma comment(lib,"Ws2_32.lib")
#include <iostream>
#include <Windows.h>
#include <thread>
#include <conio.h>
#include <map>
using namespace std;
//自定义的异常类,用来反馈网络异常
class WebException {
public:
	WebException(int error):error(error), errorMsg("未知异常") {}
	WebException(int error,string errorMsg) :error(error), errorMsg(errorMsg) {}
	virtual void what()const {
		switch (error) {
		default:cout << errorMsg << endl;
		}
	}
private:
	int error;
	string errorMsg;
};
class WebBase {
protected:
	//设定监听端口
	WebBase():port(5000){}
	virtual ~WebBase() {
		closesocket(_socket);//关闭套接字
		cout << "套接字关闭完成..." << endl;
		WSACleanup();//清理资源
		cout << "DLL资源已清理..." << endl;
		system("pause");
	}
public:
	virtual void init() {
		wVersionRequested = MAKEWORD(2, 2);//计算版本号
		if (0 != WSAStartup(wVersionRequested, &ws)) 
			throw WebException(0, "初始化DLL失败");
		cout << "初始化DLL完成..." << endl;
		_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		memset(&addr, 0, sizeof(addr));//初始化地址结构为0
		addr.sin_family = AF_INET;//赋值
		addr.sin_port = htons(port);//赋值端口信息
		addr.sin_addr.S_un.S_addr = INADDR_ANY;//表示32位IPv4地址,以网络字节序保存
	}
	WORD wVersionRequested;//版本号
	WSADATA ws;//记录WinSock DLL信息
	SOCKET _socket;//创建套接字
	const u_short port;//端口号
	struct sockaddr_in addr;//地址信息
};
class Server:public WebBase {
public:
	~Server() {
		cout << "当前存活的客户端连接数:" << cliSockets.size() << endl;
		//非强迫地要求所有线程停止活动
		map<clock_t, pair<bool, thread>>::iterator itr = cliThreads.begin();
		while (itr != cliThreads.end()) {
			cliThreads[itr->first].first = false;
			++itr;
		}
		//要求完之后,等待所有线程结束,此处不用资源释放,资源释放在线程中完成
		itr = cliThreads.begin();
		while (itr != cliThreads.end()) {
			cliThreads[itr->first].second.join();
			cliThreads.erase(itr->first);
			itr = cliThreads.begin();
		}
		cout << "服务器已关闭所有客户端连接" << endl;
	}
	void init() {
		cout << "正在部署服务器..." << endl;
		WebBase::init();
		if (SOCKET_ERROR == bind(_socket, (const sockaddr*)(&addr), sizeof(addr)))
			throw WebException(0, "绑定失败");
		cout << "套接字绑定成功..." << endl;
		if(SOCKET_ERROR  == listen(_socket, 5))//设置服务端网络监听,队列为5
			throw WebException(0, " 监听设置失败");
		cout << "正在监听..." << endl;
		char buff[128];
		gethostname(buff, sizeof(buff)); 
		cout << "服务器IP:" << inet_ntoa(*(in_addr*)*(gethostbyname(buff)->h_addr_list)) << endl;
		cout << "服务器等待连接请求中..." << endl;
		int live_num = 10;
		while (live_num--) {
			ClientSocket cliSocket;
			int len = sizeof(cliSocket.addr);
			cliSocket._socket = accept(_socket, (struct sockaddr*)&cliSocket.addr, &len);//接受连接请求
			Sleep(1);
			clock_t id = clock();
			cliSockets[id] = cliSocket;
			cliThreads[id] = pair<bool,thread>(true,thread(&Server::testConRun, this, id));
			cout << "客户(" << id << ")建立起与服务器的连接" << endl;
		}
		shutdown(_socket, 3);
		cout << "已完成十次连接,服务器自动关闭" << endl;
	}
private:
	void testConRun(clock_t id) {
		//每两秒1次连接检测
		int num = 0;
		while (cliThreads[id].first) {
			++num;
			if (num >= 10) {
				num = 0;
				if (SOCKET_ERROR == send(cliSockets[id]._socket, "t", sizeof("t"), 0)) {
					cout << "客户(" << id << ")断开了与服务器的连接" << endl;
					closesocket(cliSockets[id]._socket);
					cout << "关闭了客户(" << id << ")的Socket" << endl;
					cliSockets.erase(id);
					//这里让线程与thread类分离,使得erase掉thread类不影响线程
					//return后,分离了的线程会自己自动销毁
					cliThreads[id].second.detach();
					cliThreads.erase(id);
					return;
				}
			}
			Sleep(200);
		}
		if (!cliThreads[id].first) {
			cout << "服务器断开了与客户(" << id << ")的连接" << endl;
			closesocket(cliSockets[id]._socket);
			cout << "关闭了客户(" << id << ")的Socket" << endl;
			cliSockets.erase(id);
		}
	}
	struct ClientSocket {
		struct sockaddr_in addr;//地址信息
		SOCKET _socket;//创建套接字
	};
	map<clock_t, Server::ClientSocket> cliSockets;
	map<clock_t, pair<bool, thread>> cliThreads;
};
class Client:public WebBase {
public:
	Client():testCon(nullptr),isTestConRun(true){}
	~Client() {
		isTestConRun = false;
		testCon->join();
		closesocket(_socket);
		cout << "关闭了客户端的Socket" << endl;
		testCon.release();
	}
	void init() {
		cout << "正在部署客户端..." << endl;
		WebBase::init();
		addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//表示32位IPv4地址,以网络字节序保存
		while (SOCKET_ERROR == connect(_socket, (const sockaddr*)&addr, sizeof(addr))) {
			cout << "连接服务器失败" << endl;
			Sleep(1000);
		}
		cout << "连接服务器成功,输入任意键以断开链接" << endl;
		testCon.reset(new thread(&Client::testConRun,this));
		_getch();
	}
private:
	void testConRun() {
		//每两秒1次连接检测
		int num = 0;
		while (isTestConRun) {
			++num;
			if (num >= 10) {
				num = 0;
				if (SOCKET_ERROR == send(_socket, "t", sizeof("t"), 0)) {
					cout << "服务器断开了与客户端的链接" << endl;
					return;
				}
			}
			Sleep(200);
		}
	}
	unique_ptr<thread> testCon;
	volatile bool isTestConRun;
};
//启动服务端
void startServer() {
	Server server;
	try {
		server.init();
	}
	catch (WebException& e) {
		e.what();
	}
	catch (...) {
		cout << "未知其他异常";
	}
}
//启动客户端
void startClient() {
	Client client;
	try {
		client.init();
	}
	catch (WebException& e) {
		e.what();
	}
	catch (...) {
		cout << "未知其他异常";
	}
}
int main()
{
	while (true) {
		system("cls");
		cout << "请选择端的类型:" << endl;
		cout << "1.服务端" << endl;
		cout << "2.客户端" << endl;
		cout << "0.退出" << endl;
		cout << "请输入:" << endl;
		switch (_getch()) {
		case '1':system("cls"); startServer();break;
		case '2':system("cls"); startClient();break;
		case '0':return 0;
		}
	}
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值