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

回顾

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

一个多线程服务器

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

//ServerTest.h
#pragma once		//和#ifdef效果一样
#include <WinSock2.h>
#include <vector>
#include <thread>
#include <mutex>

#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<Sthread*> m_Vecthread;	//保存线程数据的一个vector
	int m_CsocketCount = 0;//线程编号
};

ServerTest.cpp

#include "ServerTest.h"
#include <iostream>

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 <WinSock2.h>
    #include <iostream>
    #include <thread>
    
    #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;
}

先启动服务器,在启动多个客户端,然后效果如下
测试用例图

总结

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

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++实现socket通讯需要使用操作系统提供的socket API。下面是一个简单的例子,展示了如何创建一个TCP服务器和一个TCP客户端进行通讯。 服务器端代码: ```cpp #include <iostream> #include <winsock2.h> using namespace std; int main() { // 初始化Winsock库 WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { cout << "WSAStartup failed: " << result << endl; return 1; } // 创建socket SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listenSocket == INVALID_SOCKET) { cout << "socket failed: " << WSAGetLastError() << endl; WSACleanup(); return 1; } // 绑定socket到本地IP和端口 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.s_addr = INADDR_ANY; result = bind(listenSocket, (sockaddr*)&addr, sizeof(addr)); if (result == SOCKET_ERROR) { cout << "bind failed: " << WSAGetLastError() << endl; closesocket(listenSocket); WSACleanup(); return 1; } // 监听socket result = listen(listenSocket, SOMAXCONN); if (result == SOCKET_ERROR) { cout << "listen failed: " << WSAGetLastError() << endl; closesocket(listenSocket); WSACleanup(); return 1; } // 接受客户端连接 sockaddr_in clientAddr; int clientAddrSize = sizeof(clientAddr); SOCKET clientSocket = accept(listenSocket, (sockaddr*)&clientAddr, &clientAddrSize); if (clientSocket == INVALID_SOCKET) { cout << "accept failed: " << WSAGetLastError() << endl; closesocket(listenSocket); WSACleanup(); return 1; } // 从客户端接收数据 char buffer[1024]; result = recv(clientSocket, buffer, sizeof(buffer), 0); if (result == SOCKET_ERROR) { cout << "recv failed: " << WSAGetLastError() << endl; closesocket(clientSocket); closesocket(listenSocket); WSACleanup(); return 1; } // 处理客户端发送的数据 cout << "Received from client: " << buffer << endl; // 关闭socket closesocket(clientSocket); closesocket(listenSocket); WSACleanup(); return 0; } ``` 客户端代码: ```cpp #include <iostream> #include <winsock2.h> using namespace std; int main() { // 初始化Winsock库 WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { cout << "WSAStartup failed: " << result << endl; return 1; } // 创建socket SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (clientSocket == INVALID_SOCKET) { cout << "socket failed: " << WSAGetLastError() << endl; WSACleanup(); return 1; } // 连接服务器 sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(12345); serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); result = connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)); if (result == SOCKET_ERROR) { cout << "connect failed: " << WSAGetLastError() << endl; closesocket(clientSocket); WSACleanup(); return 1; } // 发送数据到服务器 const char* message = "Hello, server!"; result = send(clientSocket, message, strlen(message), 0); if (result == SOCKET_ERROR) { cout << "send failed: " << WSAGetLastError() << endl; closesocket(clientSocket); WSACleanup(); return 1; } // 关闭socket closesocket(clientSocket); WSACleanup(); return 0; } ``` 在Windows环境下编译运行上述代码,即可实现简单socket通讯

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值