C++ TCP服务端一对多

TCP是写通讯软件常用的一种通信方式,以前实习就写过这个,现在工作中基本上都是作为C/S模型中的客户端去跟服务端对接的,今天趁项目还没开始忙,把一个完整的服务端一对多模型记录一下(后面有个封装成类的接口,感兴趣的可以自己复制粘贴玩玩);

对于TCP服务端,用代码构建有以下几个步骤:
(1)启动网络库;
(2)绑定服务端套接字;
(3)监听服务端套接字;
(4)服务端套接字接收客户端连接,之后即可自由通信;

对于客户端,步骤就比较简单了:
(1)启动网络库;
(2)连接服务端套接字,连接成功即可自由通信。

服务端代码:

#include 
#include 
#include 
#include 
#include 

void ReceiveAllClients(LPVOID para)
{
	/*这一步很重要,拿到了客户端的SOCKET套接字地址,要先解引用把它的值取出来,
	不要直接对这个指针操作,因为所有的客户端套接字地址都是通过这个指针传进来的*/
	SOCKET newClient = *((SOCKET *)para);
	char msg[200] = {0};
	sockaddr_in clientMsg;
	int len = sizeof(sockaddr);

	getpeername(newClient, (sockaddr *)&clientMsg, &len);/*获取客户端的socket的ip和端口信息,方便查看谁连了上来*/
	std::cout << clientMsg.sin_addr.S_un.S_addr << ":" << clientMsg.sin_port << " has connected!\n";

	while (1)
	{
		memset(msg, '\0', sizeof(msg));

		if (recv(newClient, msg, sizeof(msg), NULL) < 0)
		{
			std::cout << "receive message fail!\n";
			return;
		}

		std::cout << "Client(" << clientMsg.sin_port << "): " << msg << std::endl;
		send(newClient, "收到,请加大力度", 100, NULL);
		/*其实一般项目,服务端收到客户端消息后,都是需要解析这个消息并返回对应报文的,肯定不会这么随意*/
	}
}

int main(int argc, _TCHAR* argv[])
{
	SOCKET server;
	SOCKET client;
	sockaddr_in serverAddr;
	WSADATA wsaData;
	int wsaRet = WSAStartup(MAKEWORD(2, 2), &wsaData);

    /*windows编译器必须要启动这个网络库,否则后面的步骤都会执行失败,但是linux操作系统好像不需要这个*/
	if (wsaRet)
	{
		std::cout << "WSAStartup fail!\n";
		return 0;
	}
	else
	{
		std::cout << "WSAStartup succeed!\n";
	}

	server = socket(AF_INET, SOCK_STREAM, 0);

	//bind
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(23);
	serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	if (bind(server, (sockaddr *)&serverAddr, sizeof(sockaddr)) == SOCKET_ERROR)
	{
		std::cout << "bind fail!\n";
		return 0;
	}
	else
	{
		std::cout << "bind succeed!\n";
	}

	//listen
	if (listen(server, 5) == SOCKET_ERROR)
	{
		std::cout << "listen fail!\n";
		return 0;
	}
	else
	{
		std::cout << "listen succeed!\n";
	}

	//accept
	while (1)
	{
		client = accept(server,NULL, NULL);

		if (client >= 0)
		{
		    /*accept和recv函数都是阻塞函数,这种情况基本上都要开多线程的*/
			_beginthread(ReceiveAllClients, NULL, &client);
		}
		else
		{
			std::cout << "accept fail!\n";
		}
	}

	system("pause");
	return 0;
}


客户端代码:

#include 
#include 
#include 
#include 
#include 
#pragma comment(lib, "ws2_32.lib")

void ReceiveFromServer(LPVOID para)
{
	if (para == nullptr)
	{
		std::cout << "Server is null!\n";
		return;
	}

	char msg[200] = {0};
	SOCKET server = *((SOCKET *)(para));

	while(1)
	{
		memset(msg, '\0', sizeof(msg));
		
		if (recv(server, msg, 200, NULL) < 0)
		{
			std::cout << "recv fail!\n";
			return;
		}
		
		std::cout << "(Server): " << msg << std::endl;
	}
}

int main(int argc, _TCHAR* argv[])
{
	SOCKET server;
	sockaddr_in serverAddr;
	int serverLen = sizeof(serverAddr);
	WSADATA wsaData;
	char msg[200] = {0};

	if (!WSAStartup(MAKEWORD(2, 2), &wsaData))
	{
		std::cout << "WSAStartup succeed!\n";
	}
	else
	{
		std::cout << "WSAStartup fail!\n";
		return 0;
	}

	server = socket(AF_INET, SOCK_STREAM, NULL);
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	serverAddr.sin_port = htons(23);

	if (SOCKET_ERROR == connect(server, (SOCKADDR *)&serverAddr, sizeof(serverAddr)))
	{
		std::cout << "fail!\n";
	}
	else
	{
		_beginthread(ReceiveFromServer, NULL, &server);
		std::cout << "succeed!\n";
	}

	while(1)
	{
		memset(msg, '\0', sizeof(msg));
		std::cin.getline(msg, 100);
		send(server, msg, 100, NULL);
	}

	system("pause");
	return 0;
}


运行结果如下:
C++ TCP服务端一对多_第1张图片

/*************************************/
下面是TCP服务端封装成类的接口形式:
.h文件

#pragma once
#include 

class TcpServer
{
public:
	TcpServer(void);
	virtual ~TcpServer(void);
	BOOL OpenTcp();

protected:
	BOOL WsaStartUp();
	BOOL Bind();
	BOOL Listen();
	BOOL Accept();
	BOOL ReceiveAllClients();

	static void StartAcceptThreadDo(LPVOID para);
	static void StartReceiveThreadDo(LPVOID para);

private:
	SOCKET m_Server;
	SOCKET m_Client;
	sockaddr_in m_ServerAddr;
	WSADATA m_WsaData;
};


.cpp文件


#include 
#include 
#include "TcpServer.h"

#pragma comment(lib, "ws2_32.lib")

TcpServer::TcpServer(void)
{
}


TcpServer::~TcpServer(void)
{
}

BOOL TcpServer::OpenTcp()
{
	if (!WsaStartUp())
	{
		return FALSE;
	}

	if (!Bind())
	{
		return FALSE;
	}

	if (!Listen())
	{
		return FALSE;
	}

	_beginthread(StartAcceptThreadDo, NULL, this);

	return TRUE;
}

BOOL TcpServer::WsaStartUp()
{
	int wsaRet = WSAStartup(MAKEWORD(2, 2), &m_WsaData);

	if (wsaRet)
	{
		std::cout << "WSAStartup fail!\n";
		return FALSE;
	}
	else
	{
		std::cout << "WSAStartup succeed!\n";
	}

	return TRUE;
}

BOOL TcpServer::Bind()
{
	m_Server = socket(AF_INET, SOCK_STREAM, NULL);
	//bind
	m_ServerAddr.sin_family = AF_INET;
	m_ServerAddr.sin_port = htons(23);
	m_ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	if (bind(m_Server, (sockaddr *)&m_ServerAddr, sizeof(sockaddr)) == SOCKET_ERROR)
	{
		std::cout << "bind fail!\n";
		return 0;
	}
	else
	{
		std::cout << "bind succeed!\n";
	}

	return TRUE;
}

BOOL TcpServer::Listen()
{
	//listen
	if (listen(m_Server, 5) == SOCKET_ERROR)
	{
		std::cout << "listen fail!\n";
		return FALSE;
	}
	else
	{
		std::cout << "listen succeed!\n";
	}

	return TRUE;
}

BOOL TcpServer::Accept()
{
	//accept
	while (1)
	{
		m_Client = accept(m_Server,NULL, NULL);

		if (m_Client >= 0)
		{
			_beginthread(StartReceiveThreadDo, NULL, this);
		}
		else
		{
			std::cout << "accept fail!\n";
			return FALSE;
		}
	}

	return TRUE;
}

BOOL TcpServer::ReceiveAllClients()
{
	SOCKET newClient = m_Client;
	char msg[200] = {0};
	sockaddr_in clientMsg;
	int len = sizeof(sockaddr);

	getpeername(newClient, (sockaddr *)&clientMsg, &len);/*获取客户端的socket的ip和端口信息,方便查看谁连了上来*/
	std::cout << clientMsg.sin_addr.S_un.S_addr << ":" << clientMsg.sin_port << " has connected!\n";

	while (1)
	{
		memset(msg, '\0', sizeof(msg));

		if (recv(newClient, msg, sizeof(msg), NULL) < 0)
		{
			std::cout << "receive message fail!\n";
			return FALSE;
		}

		std::cout << "Client(" << clientMsg.sin_port << "): " << msg << std::endl;
		send(newClient, "收到,请加大力度", 100, NULL);
		/*其实一般项目,服务端收到客户端消息后,都是需要解析这个消息并返回对应报文的,肯定不会这么随意*/
	}

	return TRUE;
}

void TcpServer::StartAcceptThreadDo(LPVOID para)
{
	TcpServer *server = static_cast<TcpServer *>(para);
	server->Accept();
}

void TcpServer::StartReceiveThreadDo(LPVOID para)
{
	TcpServer *server = static_cast<TcpServer *>(para);
	server->ReceiveAllClients();
}

这样封装后,主函数里的调用就比较方便了;
main.cpp

#include 
#include 
#include 
#include 
#include "TcpServer.h"

int main(int argc, _TCHAR* argv[])
{
	TcpServer sss666;

	sss666.OpenTcp();/*调一个接口函数,就打开TCP连接了*/

	while (1)/*这个while循环只是代替了后面的业务逻辑代码,其实后面要做什么都无所谓*/
	{
		getchar();
	}
	system("pause");
	return 0;
}

客户端比较简单,就懒得写了,总之最后的运行效果跟之前是一样的。

/*


线
*/
其中有几个重要知识点得说明一下:
(1)TCP的三次握手建立连接,其实跟accept这种函数没关系,哪怕accept执行失败,还是可以建立连接的,accept只是代码层面的逻辑;
(2)同理,recv函数也需要注意一下,哪怕服务端不调用这个函数,也能收到客户端发来的消息,因为在与客户端建立连接后,这个端口就已经被系统一直监视着并接收消息了,recv函数只是从系统接收的消息里去取出这些消息,它同样只是代码层面的应用。

你可能感兴趣的:(C++,TCP,c++,socket)