用C++实现简单的一对多Socket通讯(二)

回顾

上一篇文章实现了一个最简单的socket连接,但只能进行一对一通讯,所以本篇文章将会将功能扩展下,实现一对多的通讯方式。上篇实现代码中,感谢@rf_versace指出服务器接受数据的容器太小,容易溢出,这点之前没有想到过,目前解决的方法就是设置大一点,一般来说消息都会有序列化和反序列化,关于序列化和反序列化之后再写一篇文章来说下。

一个多线程服务器

和上一篇文章一样,我们初始化socket,设置IP、端口,绑定监听,然后接下来就不一样了,我们不再是阻塞等待客户端->建立连接->接受消息->断开客户端->阻塞等待客户端的形式了,因为我们想与已经连接的客户端保持通讯,所以我们创建一个多线程管理函数,它专门用来分配已连接的socket到一个收发消息的线程,这样客户端就不需要等待上一个socket断开才能连接服务器,从而实现一对多的通讯。

//ServerTest.h
#pragma once		//和#ifdef效果一样
#include 
#include 
#include 
#include 

#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll

typedef struct serverThread
{
	std::thread *t1 = nullptr;
	bool isRuning = false;
	int threadID = -1;
	SOCKET csocket = -1;
}Sthread;

class SocketServerTest
{
public:
	SocketServerTest();
	~SocketServerTest();

	bool CreateSocket();//创建socket
	bool BandSocket(const char* ip, const unsigned short prot);//绑定本地地址和端口号
	bool ListenSocket();//初始化监听并设置最大连接等待数量
	void AcceptSocketManager();//接受请求连接的请求,并返回句柄
	void AddClientSocket(SOCKET &sClient);//循环检查客户端连接
	void ThreadClientRecv(Sthread *sthread);//客户端线程接受数据
	void CloseMySocket();//关闭连接
	void OutputMessage(const char *outstr);//输出文字

private:
	SOCKET m_nServerSocket;//绑定本地地址和端口号的套接口
	std::vector m_Vecthread;	//保存线程数据的一个vector
	int m_CsocketCount = 0;//线程编号
};

ServerTest.cpp

#include "ServerTest.h"
#include 

SocketServerTest::SocketServerTest():m_nServerSocket(-1)
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		OutputMessage("Socket版本错误");
}

SocketServerTest::~SocketServerTest()
{
	CloseMySocket();
}

void SocketServerTest::CloseMySocket()
{
	//退出所有线程
	for (auto it : m_Vecthread)
	{
		it->isRuning = false;
	}

	if (m_nServerSocket != -1)
		closesocket(m_nServerSocket);	//关闭socket连接

	m_nServerSocket = -1;
	WSACleanup();	//终止ws2_32.lib的使用
}

bool SocketServerTest::CreateSocket()
{
	if (m_nServerSocket == -1)
	{
		m_nServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	//设定TCP协议接口;失败返回INVALID_SOCKET
		if (m_nServerSocket != INVALID_SOCKET) 
		{
			OutputMessage("服务器启动成功");
			return true;
		}
	}
	return false;
}

bool SocketServerTest::BandSocket(const char* ip, const unsigned short prot)
{
	int nRet = -1;
	if (m_nServerSocket != -1)
	{
		sockaddr_in Serveraddr;
		memset(&Serveraddr, 0, sizeof(sockaddr_in*));
		Serveraddr.sin_family = AF_INET;
		Serveraddr.sin_addr.s_addr = inet_addr(ip);
		Serveraddr.sin_port = htons(prot);
		nRet = bind(m_nServerSocket, (sockaddr *)&Serveraddr, sizeof(Serveraddr));	//绑定服务器地址和端口号;成功,返回0,否则为SOCKET_ERROR
	}

	if (nRet == 0)
	{
		OutputMessage("绑定IP和端口成功");
		return true;
	}

	OutputMessage("绑定IP和端口失败");
	return false;
}

bool SocketServerTest::ListenSocket()
{
	int nRet = -1;
	if (m_nServerSocket != -1)
	{
		nRet = listen(m_nServerSocket, 5);//设定接受连接的套接字,以及设定连接队列长度;成功返回0,失败返回-1
	}
	if (nRet == SOCKET_ERROR)
	{
		OutputMessage("监听绑定失败");
		return false;
	}
		
	OutputMessage("监听绑定成功");
	return true;
}

void SocketServerTest::AcceptSocketManager()
{
	while (m_nServerSocket != -1)
	{
		sockaddr_in nClientSocket;//如果要保存客户端的IP和端口号,就存在本地
		int nSizeClient = sizeof(nClientSocket);
		SOCKET sClient = accept(m_nServerSocket, (sockaddr*)&nClientSocket, &nSizeClient);//接受客户端连接,阻塞状态;失败返回-1
		if (sClient == SOCKET_ERROR)
		{
			OutputMessage("当前与客户端连接失败");
			return;
		}
		else
		{
			AddClientSocket(sClient);
		}
		Sleep(25);
	}
}

void SocketServerTest::AddClientSocket(SOCKET& sClient)
{
	Sthread *it = new Sthread();
	it->threadID = ++m_CsocketCount;
	it->isRuning = true;
	it->csocket = sClient;
	std::thread t(&SocketServerTest::ThreadClientRecv, this, it);
	t.detach();
	it->t1 = &t;
	m_Vecthread.push_back(it);
	char str[50];
	sprintf_s(str, "%dthread connect is success", it->threadID);
	OutputMessage(str);

	char mess[] = "sercer:与服务器连接成功!";
	send(sClient, mess, sizeof(mess), 0);//发送消息给客户端
}

void SocketServerTest::ThreadClientRecv(Sthread *sthread)
{
	while (sthread->isRuning == true)
	{
		// 从客户端接收数据
		char buff[65535];
		int nRecv = recv(sthread->csocket, buff, 65535, 0);//从客户端接受消息
		if (nRecv > 0)
		{
			char str[50];
			sprintf_s(str, "%dthread send message", sthread->threadID);
			OutputMessage(str);
			OutputMessage(buff);
			char mess[] = "server:收到了你的消息。";
			send(sthread->csocket, mess, sizeof(mess), 0);
		}
		else
		{
			char str[50];
			sprintf_s(str, "ID%d is exit", sthread->threadID);
			OutputMessage(str);
			sthread->isRuning = false;
		}
	}
	return;
}

void SocketServerTest::OutputMessage(const char *outstr)
{
	std::cout << outstr << std::endl;
}

一个多线程客户端

相较于服务器,客户端这边改动有点打,我将接受服务器消息的函数设置成了一个单独的线程,发送服务器消息的函数也被我提出来了。不过总体思路和上篇的没多大区别。

    //multipartiteClientSocket.h
    #pragma once
    #include 
    #include 
    #include 
    
    #pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll
    
    typedef struct thread1
    {
    	std::thread *t1 = nullptr;
    	bool isRuning = false;
    } Mythread;
    
    class MultipartiteClientSocketTest
    {
    public:
    	MultipartiteClientSocketTest();
    	~MultipartiteClientSocketTest();
    
    	bool CreateSocket();
    	void CloseSocket();
    
    	bool Myconnect(const char* ip, const unsigned short prot);
    	void Mysend();
    	void Myrecv();
    	bool InitMyrecv();
    
    	void OutputMessage(const char* outstr);
    
    private:
    	char m_message[256];
    
    	SOCKET m_nLocalSocket;
    	Mythread recvThread;
    };
multipartiteClientSocket.cpp
#include "multipartiteClientSocket.h"

MultipartiteClientSocketTest::MultipartiteClientSocketTest()
{
	m_nLocalSocket = -1;
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		OutputMessage("Socket版本加载失败");
}

MultipartiteClientSocketTest::~MultipartiteClientSocketTest()
{
	CloseSocket();
}

void MultipartiteClientSocketTest::CloseSocket()
{
	if (m_nLocalSocket != -1)
		closesocket(m_nLocalSocket);	//关闭socket连接

	m_nLocalSocket = -1;
	WSACleanup();	//终止ws2_32.lib的使用
}

//创建一个socket
bool MultipartiteClientSocketTest::CreateSocket()
{
	if (m_nLocalSocket == -1)
	{
		m_nLocalSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (m_nLocalSocket != INVALID_SOCKET)
		{
			OutputMessage("客服端socket启动成功");
			return true;
		}
		else
		{
			OutputMessage("客服端socket启动失败");
			return false;
		}
	}

	OutputMessage("客服端socket已启动");
	return true;
}

bool MultipartiteClientSocketTest::Myconnect(const char* ip, const unsigned short prot)
{
	int nRet = SOCKET_ERROR;
	if (m_nLocalSocket != -1)
	{
		sockaddr_in m_nServeraddr;
		memset(&m_nServeraddr, 0, sizeof(m_nServeraddr));
		m_nServeraddr.sin_family = AF_INET;
		m_nServeraddr.sin_port = htons(prot);
		m_nServeraddr.sin_addr.s_addr = inet_addr(ip);
		nRet = connect(m_nLocalSocket, (sockaddr*)&m_nServeraddr, sizeof(m_nServeraddr));//成功返回0。否则返回SOCKET_ERROR

		if (nRet == SOCKET_ERROR)
		{
			OutputMessage("服务器连接失败!");
			return false;
		}

		OutputMessage("服务器连接成功!");
		InitMyrecv();

		return true;
	}

	return false;
}

bool MultipartiteClientSocketTest::InitMyrecv()
{
	if (m_nLocalSocket == -1)
		return false;

	if (recvThread.t1 == nullptr)
	{
		recvThread.isRuning = true;
		std::thread t(&MultipartiteClientSocketTest::Myrecv, this);
		t.detach();
		recvThread.t1 = &t;
	}
	else
	{
		OutputMessage("recvThread is failed!");
		return false;
	}

	Mysend();

	return true;
}

void MultipartiteClientSocketTest::Myrecv()
{
	if (m_nLocalSocket != -1)
	{
		int resultRecv = -1;
		while (recvThread.isRuning == true)
		{
			resultRecv = recv(m_nLocalSocket, m_message, sizeof(m_message), 0);
			if (resultRecv > 0)
			{
				//输出消息
				OutputMessage(m_message);
				memset(m_message, '\0', sizeof(m_message));
			}
			else
			{
				//这几种错误码,认为连接是正常的,继续接收
				if ((resultRecv < 0) && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
				{
					continue;//继续接收数据
				}
				OutputMessage("与服务器连接中断!");
				break;//跳出接收循环
			}
		}
	}
	else
	{
		OutputMessage("当前与服务器未连接!");
	}
	recvThread.t1 = nullptr;
	return;
}

void MultipartiteClientSocketTest::Mysend()
{
	if (m_nLocalSocket != -1)
	{
		char tempstr[256];
		while (std::cin >> tempstr)
		{
			send(m_nLocalSocket, tempstr, sizeof(tempstr), 0);
		}
	}
	else
	{
		OutputMessage("当前与服务器未连接");
	}
	return;
}

void MultipartiteClientSocketTest::OutputMessage(const char * outstr)
{
	std::cout << outstr << std::endl;
}

先启动服务器,在启动多个客户端,然后效果如下
用C++实现简单的一对多Socket通讯(二)_第1张图片

总结

虽然解决了一对多的问题,但是当有大量客户端连接服务器的时候,你就需要开这么多线程来对应客户端连接,而如此多的线程必定会大大降低效率,所以这种方式是不科学的。那我们能不能在一个或几个线程里来处理这么多客户端连接呢?答案肯定是:能。也就是大家所说的IO多路复用。因为在windows的环境,所以下一篇会写一个select模型的socket通讯。

你可能感兴趣的:(Socket从头开始)