C++ TCP服務端一對多

TCP是寫通訊軟件常用的一種通信方式,以前實習就寫過這個,現在工作中基本上都是作爲C/S模型中的客戶端去跟服務端對接的,今天趁項目還沒開始忙,把一個完整的服務端一對多模型記錄一下(後面有個封裝成類的接口,感興趣的可以自己複製粘貼玩玩);

對於TCP服務端,用代碼構建有以下幾個步驟:
(1)啓動網絡庫;
(2)綁定服務端套接字;
(3)監聽服務端套接字;
(4)服務端套接字接收客戶端連接,之後即可自由通信;

對於客戶端,步驟就比較簡單了:
(1)啓動網絡庫;
(2)連接服務端套接字,連接成功即可自由通信。

服務端代碼:

#include <WinSock2.h>
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include <process.h>

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


運行結果如下:
在這裏插入圖片描述

/*************************************/
下面是TCP服務端封裝成類的接口形式:
.h文件

#pragma once
#include <Windows.h>

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 <iostream>
#include <process.h>
#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 <WinSock2.h>
#include <tchar.h>
#include <iostream>
#include <process.h>
#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函數只是從系統接收的消息裏去取出這些消息,它同樣只是代碼層面的應用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章