C++ 一台服务器对应多个客户端之间的通讯

SocketBox.dll封装套接字,利用控制台程序完成服务器与客户端的通讯.

环境:VS2019

一.SocketBox.dll

//SocketBox.h
#pragma once
#ifndef SOCKET_H
#define SOCKET_H

#ifdef SOCKET_EXPORTS
#define _DLL_API _declspec(dllexport)
#else
#define _DLL_API _declspec(dllimport)
#endif


//Server InterFace
class ServerBox
{
public:
	ServerBox() {};
	virtual ~ServerBox() {};

	virtual bool StartServer(const int iPort = 6666) = 0;
	virtual void Stop() = 0;
};



//Client InterFace
class ClientBox
{
public:
	ClientBox() {};
	virtual ~ClientBox() {};

	virtual bool GetHostIp(char* ipaddress) = 0;
	virtual bool ConnectServer(LPCSTR lpstrIP, const int iPort = 6666) = 0;
	virtual void Stop() = 0;
	virtual void SendData(char* srcData) = 0;
};


extern "C"
{
	void _DLL_API CreateServer(ServerBox** ppServer);
	void _DLL_API ReleaseServer(ServerBox* pServer);
	void _DLL_API CreateClient(ClientBox** ppClient, const int iClientIndex = 0);
	void _DLL_API ReleaseClient(ClientBox* pClient);
}



#endif
//SocketBox.cpp
#pragma once
#include "SocketBox.h"


#define MAXCLIENTNUM 20




//Server
class ServerBoxEx : public ServerBox
{
public:
	ServerBoxEx() 
	{
		memset(m_cMsgBuffer, 0, sizeof(char) * 1944);
		memset((void*)&m_addr, 0, sizeof(sockaddr_in));
		memset((void*)&m_client_addr, 0, sizeof(sockaddr_in));
		m_hSocket = NULL;
		m_iServerPort = 0;
		m_bStopServer = false;
	};
	~ServerBoxEx() {};

	bool StartServer(const int iPort = 6666);
	
	void Stop();

private:
	char m_cMsgBuffer[1944];
	bool m_bStopServer;
	int m_iServerPort;
	WSADATA m_wsaData;
	SOCKET m_server;//服務器套接字
	sockaddr_in m_addr;//本地IP地址
	fd_set m_fdsock;
	fd_set m_fdread;
	sockaddr_in m_client_addr;//客戶端IP地址
	SOCKET  m_newsock;//臨時連接的套接字變量


	//服務器線程
	HANDLE m_hSocket;
	static UINT _stdcall ServerSocketThread(PVOID p);
};



//Client
class ClientBoxEx : public ClientBox
{
public:
	ClientBoxEx() 
	{
		m_iLinkStatus = -1;
	};
	~ClientBoxEx() {};

	bool GetHostIp(char* ipaddress);
	bool ConnectServer(LPCSTR lpstrIP, const int iPort = 6666);
	void Stop();
	void SendData(char* srcData);

private:
	int m_iLinkStatus;
	SOCKET m_client;
	sockaddr_in m_server_addr;
	WSADATA m_wsaData;

};
//socket.cpp
#include "pch.h"
#include "Socket.h"
#include "ToolFunction.h"



UINT _stdcall ServerBoxEx::ServerSocketThread(PVOID p)
{
	ServerBoxEx* ps = static_cast<ServerBoxEx*>(p);

	//設置select等待時間
	timeval tSelectTime;
	tSelectTime.tv_sec = GetPrivateProfileInt(_T("Config"), _T("SelectSecTime"), 0, _T("./SocketConfig.ini"));
	tSelectTime.tv_usec = GetPrivateProfileInt(_T("Config"), _T("SelectUsecTime"), 0, _T("./SocketConfig.ini"));

	//準備獲取客戶端
	FD_ZERO(&ps->m_fdsock);//初始化fdsock
	FD_SET(ps->m_server, &ps->m_fdsock);//將監聽套接字添加到套接字集合中
	int iaddr_len = sizeof(sockaddr_in);
	while (true)
	{
		if (ps->m_bStopServer)
		{
			break;
		}
		FD_ZERO(&ps->m_fdread);//初始化read
		ps->m_fdread = ps->m_fdsock;
		int iSelect = select(0, &ps->m_fdread, NULL, NULL, &tSelectTime);//接收到連接或者發來的消息請求的時候都會返回,此外一直阻塞,除非有指定timeval的時間
		if (iSelect > 0)
		{
			for (unsigned int i = 0; i < ps->m_fdsock.fd_count; i++)
			{
				bool bIsSet = FD_ISSET(ps->m_fdsock.fd_array[i], &ps->m_fdread);
				if (bIsSet)
				{
					//接收請求
					if (ps->m_fdsock.fd_array[i] == ps->m_server)
					{
						//最大fd_set只能添加FD_SETSIZE個套接字,超過直接關閉服務器
						int iSetClientNum = GetPrivateProfileInt(_T("Config"), _T("ServerConfig"), FD_SETSIZE, _T("./SocketConfig.ini"));
						if (iSetClientNum > FD_SETSIZE)
						{
							iSetClientNum = FD_SETSIZE;
						}
						if (ps->m_fdsock.fd_count > iSetClientNum)
						{
							ListOutLog(SOCKET_LOG, _T("Server is over max number,accept Client fail!"));
							goto END;
						}
						//accept為非阻塞模式,且接收的連接請求
						ps->m_newsock = accept(ps->m_server, (sockaddr*)&ps->m_client_addr, &iaddr_len);
						if (ps->m_newsock != INVALID_SOCKET)
						{
							TCHAR tcIP[20] = { 0 };
							CharToTchar(inet_ntoa(ps->m_client_addr.sin_addr), tcIP);
							ListOutLog(SOCKET_LOG, _T("IP: %s has linked!."), tcIP);
							FD_SET(ps->m_newsock, &ps->m_fdsock);//將新的連接套接字添加到fd_sock中
						}
						else
						{
							goto END;
						}
					}
					else
					{
						//接收消息
						memset(ps->m_cMsgBuffer, 0, sizeof(char) * 1944);
						int size = recv(ps->m_fdsock.fd_array[i], ps->m_cMsgBuffer, sizeof(ps->m_cMsgBuffer), 0);
						getpeername(ps->m_fdsock.fd_array[i], (sockaddr*)&ps->m_client_addr, &iaddr_len);//獲取對方的IP地址
						TCHAR tcIP[20] = { 0 };
						CharToTchar(inet_ntoa(ps->m_client_addr.sin_addr), tcIP);
						if (size <= 0)
						{
							ListOutLog(SOCKET_LOG, _T("Client [IP:%s] has been closed!"), tcIP);
							closesocket(ps->m_fdsock.fd_array[i]);
							FD_CLR(ps->m_fdsock.fd_array[i], &ps->m_fdsock);
						}
						else
						{
							TCHAR tcMsg[1944] = { 0 };
							CharToTchar(ps->m_cMsgBuffer, tcMsg);
							ListOutLog(SOCKET_LOG, _T("Get message from [IP:%s]: %s"), tcIP, tcMsg);

						}

					}
				}
			}
		}
		//else
		//{
		//	break;
		//}
	}
	

END:
	for (unsigned int i = 0; i < ps->m_fdsock.fd_count; i++)
	{
		closesocket(ps->m_fdsock.fd_array[i]);
	}
	//關閉服務端套接字
	closesocket(ps->m_server);
	WSACleanup();
	ListOutLog(SOCKET_LOG, _T("Prepare to close server."));
	return 0;
}

bool ServerBoxEx::StartServer(const int iPort)
{
	m_iServerPort = iPort;

	//初始化winsock2.dll
	int iWinSock = WSAStartup(MAKEWORD(2, 2), &m_wsaData);
	if (iWinSock != 0)
	{
		ListOutLog(SOCKET_LOG, _T("init WinSock2.dll fail!"));
		return false;
	}
	ListOutLog(SOCKET_LOG, _T("init WinSock2.dll success!."));

	//創建套接字
	m_server = socket(AF_INET, SOCK_STREAM, 0);
	if (m_server == SOCKET_ERROR)
	{
		ListOutLog(SOCKET_LOG, _T("Create socket fail!Error code:%d."), WSAGetLastError());
		WSACleanup();
		return false;
	}
	ListOutLog(SOCKET_LOG, _T("Create socket success!."));

	//設定套接字為非阻塞模式
	unsigned long ul = 1;
	int iIoct = ioctlsocket(m_server, FIONBIO, &ul);
	if (iIoct == SOCKET_ERROR)
	{
		ListOutLog(SOCKET_LOG, _T("Set ioctlsocket fail!Error code:%d."), WSAGetLastError());
		closesocket(m_server);
		WSACleanup();
		return false;
	}


	//填寫本地IP
	int iaddr_len = sizeof(sockaddr_in);
	memset((void*)&m_addr, 0, iaddr_len);
	m_addr.sin_family = AF_INET;
	m_addr.sin_port = htons(m_iServerPort);//主機字節端口轉網絡字節端口
	m_addr.sin_addr.s_addr = htonl(INADDR_ANY);//允許將本機任何IP地址轉為網絡字節地址

	//服務器套接字與本地IP相綁定
	int iBind = bind(m_server, (sockaddr*)&m_addr, sizeof(m_addr));
	if (iBind != 0)
	{
		ListOutLog(SOCKET_LOG, _T("bind local IP with socket fail !Error code:%d."), WSAGetLastError());
		closesocket(m_server);
		WSACleanup();
		return false;
	}

	//監聽
	int iListen = listen(m_server, 0);
	if (iListen != 0)
	{
		ListOutLog(SOCKET_LOG, _T("listening fail!Error code:%d."), WSAGetLastError());
		closesocket(m_server);
		WSACleanup();
		return false;
	}
	ListOutLog(SOCKET_LOG, _T("bind local IP with socket success and listen..."));



	m_hSocket = (HANDLE)_beginthreadex(NULL,0, ServerSocketThread,this,0,NULL);
	if (m_hSocket != NULL)
	{
		return true;
	}
	else
	{
		ListOutLog(SOCKET_LOG, _T("server thread fail!Error code:%d."), WSAGetLastError());
		closesocket(m_server);
		WSACleanup();
		return false;
	}
	return false;
}
void ServerBoxEx::Stop()
{
	if (m_hSocket != NULL)
	{
		m_bStopServer = true;
		DWORD dwTime = WaitForSingleObject(m_hSocket, INFINITE);
		CloseHandle(m_hSocket);
		m_hSocket = NULL;
		ListOutLog(SOCKET_LOG, _T("Stop current server success."));
	}
	m_bStopServer = false;
}


bool ClientBoxEx::GetHostIp(char* ipaddress)
{
	int err;
	int nStatus = 0;
	bool bRet = true;
	char szhn[256] = { 0 };
	WORD wVersionRequested;
	HOSTENT* host = { 0 };
	WSADATA wsaData;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);

	if (err != 0)    bRet = false;

	nStatus = gethostname(szhn, sizeof(szhn));

	if (nStatus == SOCKET_ERROR) bRet = false;

	host = gethostbyname(szhn);

	char* ipTemp = NULL;
	if (host != NULL)
	{
		ipTemp = inet_ntoa(*(IN_ADDR*)host->h_addr_list[0]);
	}

	strcpy_s(ipaddress, 30, ipTemp);

	WSACleanup();

	return bRet;
}
bool ClientBoxEx::ConnectServer(LPCSTR lpstrIP, const int iPort)
{
	int addr_len = sizeof(sockaddr_in);
	
	int iWinSock = WSAStartup(MAKEWORD(2, 2), &m_wsaData);
	if (iWinSock != 0)
	{
		ListOutLog(SOCKETClient_LOG, _T("init WinSock2.dll fail!"));
		return false;
	}
	ListOutLog(SOCKETClient_LOG, _T("init WinSock2.dll success!."));
	
	//創建套接字
	m_client = socket(AF_INET, SOCK_STREAM, 0);
	if (m_client < 0)
	{
		ListOutLog(SOCKETClient_LOG, _T("Create socket fail!Error code:%d."), WSAGetLastError());
		WSACleanup();
		return false;
	}
	ListOutLog(SOCKETClient_LOG, _T("Create socket success!."));

	memset((void*)&m_server_addr, 0, addr_len);
	m_server_addr.sin_family = AF_INET;
	m_server_addr.sin_port = htons(iPort);
	in_addr a;
	inet_pton(AF_INET, lpstrIP, &a);
	m_server_addr.sin_addr.s_addr = a.S_un.S_addr;


	TCHAR lpctstrIP[20] = { 0 };
	CharToTchar((char*)lpstrIP, lpctstrIP);
	//與服務器建立連接
	m_iLinkStatus = connect(m_client, (sockaddr*)&m_server_addr, addr_len);
	if (m_iLinkStatus != 0)
	{
		ListOutLog(SOCKETClient_LOG, _T("IP: %s Connect server fail!Error code:%d."), lpctstrIP, WSAGetLastError());
		closesocket(m_client);
		WSACleanup();
		return false;
	}
	ListOutLog(SOCKETClient_LOG, _T("IP: %s Connect server success!."), lpctstrIP);

	return true;
}
void ClientBoxEx::Stop()
{
	closesocket(m_client);
	WSACleanup();
}
void ClientBoxEx::SendData(char* srcData)
{
	send(m_client, srcData, strlen(srcData), 0);
}





void _DLL_API CreateServer(ServerBox** ppServer)
{
	*ppServer = new ServerBoxEx;
}
void _DLL_API ReleaseServer(ServerBox* pServer)
{
	if (pServer != NULL)
	{
		delete pServer;
		pServer = NULL;
	}
}
void _DLL_API CreateClient(ClientBox** ppClient, const int iClientIndex)
{
	*ppClient = new ClientBoxEx;
}
void _DLL_API ReleaseClient(ClientBox* pClient)
{
	if (pClient != NULL)
	{
		delete pClient;
		pClient = NULL;
	}
}

二.Server服务器Demo

#include <iostream>
#include <Windows.h>
#include "../../Build/include/SocketBox.h"

#pragma comment(lib,"../../Build/lib/x64/Debug/SocketBox.lib")

int main()
{
	ServerBox* sb;
	CreateServer(&sb);
	if (sb->StartServer(6666))
	{
		std::cout << "服務器啟動成功!" << std::endl;
		std::cout << "--------------" << std::endl;
		std::cout << "按任意鍵關閉服務器" << std::endl;
	}

	system("pause");
	sb->Stop();
	
	ReleaseServer(sb);
	return 0;
}

三.客户端Demo

#include <iostream>
#include <Windows.h>
#include "../../Build/include/SocketBox.h"
#pragma comment(lib,"../../Build/lib/x64/Debug/SocketBox.lib")


int main()
{
    char cMsg[1024] = "1";
    char cLocalIP[100] = { 0 };
	ClientBox* cb;
	CreateClient(&cb);
    cb->GetHostIp(cLocalIP);
    if (cb->ConnectServer(cLocalIP, 6666))
    {
        std::cout << "服務器連接成功!" << std::endl;
        std::cout << "--------------" << std::endl;
        std::cout << "按請輸入要發送的消息" << std::endl;
        std::cin >> cMsg;
        cb->SendData(cMsg);
        std::cout << "按請輸入要發送的消息" << std::endl;
        std::cin >> cMsg;
        cb->SendData(cMsg);
    }
    std::cout << "按任意鍵關閉客戶端" << std::endl;
    system("pause");
    cb->Stop();
    ReleaseClient(cb);
	return 0;
}

可以实现多个客户端连接并给服务端发送消息

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
在C++语言中,使用TCP协议发送数据包给个客户可以通过以下步骤实现1. 创建一个服务器套接字(socket),使用`socket()`函数。指定地址族(通常为AF_INET)和套接字类型(通常为SOCK_STREAM)。 2. 使用`bind()`函数将服务器套接字与特定的IP地址和口号绑定。 3. 使用`listen()`函数开始监听传入的连接请求。 4. 使用`accept()`函数接受客户的连接请求,并创建一个新的套接字用于与该客户通信。 5. 在一个循环中,使用`recv()`函数从客户接收数据,并使用`send()`函数将数据发送给客户。 以下是一个简单的示例代码: ```cpp #include <iostream> #include <cstring> #include <arpa/inet.h> #include <sys/socket.h> #include <unistd.h> int main() { int serverSocket, clientSocket; struct sockaddr_in serverAddress, clientAddress; char buffer[1024]; // 创建服务器套接字 serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket == -1) { std::cerr << "Failed to create socket" << std::endl; return -1; } // 设置服务器地址和口 serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = INADDR_ANY; serverAddress.sin_port = htons(12345); // 绑定服务器套接字 if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0) { std::cerr << "Failed to bind socket" << std::endl; return -1; } // 开始监听连接请求 if (listen(serverSocket, 5) < 0) { std::cerr << "Failed to listen socket" << std::endl; return -1; } // 接受客户连接请求并处理 while (true) { socklen_t clientAddressLength = sizeof(clientAddress); clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressLength); if (clientSocket < 0) { std::cerr << "Failed to accept connection" << std::endl; return -1; } // 与客户通信 while (true) { memset(buffer, 0, sizeof(buffer)); int bytesRead = recv(clientSocket, buffer, sizeof(buffer) - 1, 0); if (bytesRead < 0) { std::cerr << "Failed to receive data" << std::endl; break; } if (bytesRead == 0) { std::cout << "Client disconnected" << std::endl; break; } // 在这里处理接收到的数据 std::cout << "Received data: " << buffer << std::endl; // 发送数据给客户 const char* response = "Hello from server"; if (send(clientSocket, response, strlen(response), 0) < 0) { std::cerr << "Failed to send data" << std::endl; break; } } // 关闭与客户的套接字 close(clientSocket); } // 关闭服务器套接字 close(serverSocket); return 0; } ``` 请注意,这只是一个简单的示例,没有处理多个客户的并发请求。如果需要处理多个客户并发请求,您可能需要使用多线程或多进程来处理每个客户连接。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

圆圆的枫树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值