1. 程式人生 > >C++封裝的基於WinSock2的TCP服務端、客戶端

C++封裝的基於WinSock2的TCP服務端、客戶端

  無聊研究Winsock套接字程式設計,用原生的C語言介面寫出來的程式碼看著難受,於是自己簡單用C++封裝一下,把思路過程理清,方便自己後續翻看和新手學習。

  只寫好了TCP通訊服務端,有空把客戶端流程也封裝一下。

  先上主函式:

// main.cpp : 異想家sandeepin poi!
#include "stdafx.h"
#include <iostream>
extern int JTCPserver();
extern int JTCPclient();
int main()
{
	JTCPserver(); // 兩者選一
//	JTCPclient();
	return 0;
}
  JTCPserver.cpp內容:
// JTCPserver.cpp : 蔣方正封裝的TCP服務端

#include "stdafx.h"
#include <iostream>
#include <string>
#include <WinSock2.h> // WinSocket 
#include <WS2tcpip.h> // IP地址轉換用到inet_pton
#pragma comment(lib,"ws2_32.lib")  
using namespace std;

// 【1】初始化WinSock
bool initWinSock();
// 【2】建立socket
bool createSocket(SOCKET &listenScok);
// 【3】socket繫結本機地址資訊
bool bindIPandPort(SOCKET &listenScok, const string ip, const unsigned short port);
// 【4】偵聽socket,接收客戶端請求
bool listenSocket(SOCKET &listenScok);
// 【5】等待客戶端連線-阻塞
bool waitClientConnect(SOCKET &listenScok, SOCKET &clientSock);
// 【6】接收資料-阻塞
bool receiveData(SOCKET &clientSock, string &data);
// 【7】停止套接字的接收、傳送
bool shutdownSocket(SOCKET &clientSock);
// 【8】傳送資訊
bool sendData(SOCKET &clientSock, const string &data);

int JTCPserver()
{		
	SOCKET listenScok;	// 服務端Socket
	SOCKET clientSock;	// 客戶端Socket
	string data;		// 收到的資料

	// 【1】初始化WinSock
	initWinSock();
	// 【2】建立socket
	createSocket(listenScok);
	// 【3】socket繫結本機地址資訊
	bindIPandPort(listenScok, "127.0.0.1", 1994);
	// 【4】偵聽socket,接收客戶端請求
	listenSocket(listenScok);

	// 坐等客戶端連線
	bool isClientSockConnect = false;	// 是否有客戶端連進來
	bool isReceiveData = false;			// 是否接收資料成功

	while (true)
	{ 
		if (!isClientSockConnect)
		{
			// 【5】等待客戶端連線
			isClientSockConnect = waitClientConnect(listenScok, clientSock);
		}
		else
		{
			if(!isReceiveData)
			{
				// 【6】接收資料-阻塞
				isReceiveData = receiveData(clientSock, data);
				// 如果接收資料失敗則斷開
				if(!isReceiveData) 
				{
					// 【7】停止套接字的接收、傳送
					shutdownSocket(clientSock);
					cout << "等待客戶端再連線..." << endl;
					isClientSockConnect = false; // 可以重連了
				}
			}
			if(isReceiveData && data != "jfzpoi"  && data != "@end#")
			{
				isReceiveData = false;
			}
			if(isReceiveData && data == "jfzpoi")
			{
				// 【8】傳送資訊(收的資料為jfzpoi)
				sendData(clientSock, "sandeepin!\r\n");
				isReceiveData = false;
			}
			if (isReceiveData && data == "@end#")
			{
				// 【9】關閉相關
				int err;
//				err = shutdown(listenScok, 2);
//				if (err == SOCKET_ERROR)
//				{
//					cout << "關閉失敗!" << endl;
//				}
				// 關閉套接字,釋放資源
				err = closesocket(listenScok);
				if (err == SOCKET_ERROR)
				{
					cout << "關閉socket失敗!" << endl;
				}
				// 停止使用WinSock庫,釋放對應資源
				if (WSACleanup() != 0)
				{
					cout << "WSA清空失敗!" << endl;
				}
				cout << "關完了,坐等關機!poi" << endl;
				return 0;
			}
		}
	}
}

// 【1】初始化WinSock
bool initWinSock()
{
	WORD verision = MAKEWORD(2, 2);
	WSADATA lpData;
	int intEr = WSAStartup(verision, &lpData); // 指定winsock版本並初始化
	if (intEr != 0)
	{
		cout << "WinSock初始化失敗!" << endl;
		return false;
	}
	cout << "WinSock初始化成功!" << endl;
	return true;
}

// 【2】建立socket
bool createSocket(SOCKET &listenScok)
{
	// 建立偵聽socket  
	listenScok = socket(AF_INET, SOCK_STREAM, 0);
	if (listenScok == INVALID_SOCKET)
	{
		cout << "socket建立失敗!" << endl;
		return false;
	}
	cout << "socket建立成功!" << endl;
	return true;
}

// 【3】socket繫結本機地址資訊
bool bindIPandPort(SOCKET &listenScok, const string ip, const unsigned short port)
{
	// 製作sockaddr_in結構體
	// 在bind函式,connect函式裡提到了套接字程式設計網路地址資訊結構體const struct sockaddr和const struct sockaddr_i
	sockaddr_in hostAddr;
	hostAddr.sin_family = AF_INET;
	hostAddr.sin_port = htons(port);//轉換成網路位元組序  
									//hostAddr.sin_addr.S_un.S_addr = inet_addr(SERVERIP);//轉換成網路位元組序  
									//cout << "net IP:" << hostAddr.sin_addr.S_un.S_addr << endl;  
									/*
									inet_addr()版本太低,被棄用使用inet_pton(協議族,字串IP地址,void目標in_addr*)
									標頭檔案:WS2tcpip.h
									*/
	in_addr addr;
	inet_pton(AF_INET, ip.c_str(), (void*)&addr);
	hostAddr.sin_addr = addr;
	cout << "ip(網路位元組序):" << addr.S_un.S_addr << endl;
	cout << "ip(常規形式):" << ip.c_str() << endl;

	// 偵聽套接字listenSock繫結本機地址資訊  
	int err = bind(listenScok, (struct sockaddr*)&hostAddr, sizeof(sockaddr));
	if (err != 0)
	{
		cout << "本地IP繫結失敗!" << endl;
		return false;
	}
	return true;
}

// 【4】偵聽socket,接收客戶端請求
bool listenSocket(SOCKET &listenScok)
{
	// 設定套接字為偵聽狀態,準備接收客戶機程序傳送來的連線請求
	int err = listen(listenScok, 3);
	if (err != 0)
	{
		cout << "socket監聽失敗!" << endl;
		return false;
	}
	cout << "監聽客戶端連線中……" << endl;
	return true;
}

// 【5】等待客戶端連線-阻塞
bool waitClientConnect(SOCKET &listenScok, SOCKET &clientSock)
{
	sockaddr_in clientAddr;
	int len = sizeof(struct sockaddr); // 必須指定長度,否則會導致accept返回10014錯誤
									   // accept會迴圈等待客戶端連線
	clientSock = accept(listenScok, (struct sockaddr*)&clientAddr, &len);
	cout << "客戶端Socket編號:" << clientSock << endl;
	if (clientSock == INVALID_SOCKET)
	{
		cout << "客戶端連線失敗!" << endl;
		cout << WSAGetLastError() << endl;
		return false;
	}
	return true;
}

// 【6】接收資料-阻塞
bool receiveData(SOCKET &clientSock, string &data)
{
	static int cnt = 1; // 接收資料編號-靜態
						// 通過已建立連線的套接字,接收資料 設定緩衝1024位元組
	char buf[1024] = "\0";
	// flags操作方式(0正常資料,MSG_PEED系統緩衝區的資料複製到所提供的接收緩衝區內,系統緩衝區資料未刪除,MSG_OOB處理帶外資料,通常用引數0即可)
	int buflen = recv(clientSock, buf, 1024, 0);
	if (buflen == SOCKET_ERROR)
	{
		cout << "接收失敗!" << endl;
		return false;
	}
	// 一切正常則顯示接收資料
	data = string(buf);
	cout << "收到第" << cnt++ << "次,內容為:\n" << buf << endl;
	return true;
}

// 【7】停止套接字的接收、傳送
bool shutdownSocket(SOCKET &clientSock)
{
	//(收完就關)停止套接字的接收、傳送功能(0禁止接收,1禁止傳送,2禁制接收發送)
	int err = shutdown(clientSock, 2);
	if (err == SOCKET_ERROR)
	{
		cout << "關閉Socket失敗!" << endl;
		return false;
	}
	return true;
}

// 【8】傳送資訊
bool sendData(SOCKET &clientSock, const string &data)
{

	int err = send(clientSock, data.c_str(), data.size(), 0);
	if (err == SOCKET_ERROR)
	{
		cout << "傳送失敗!" << endl;
		return false;
	}
	cout << "傳送資料為:\n" << data << endl;
	return true;
}
  JTCPclient.cpp內容:
// JTCPclient.cpp : 蔣方正封裝的TCP客戶端程式碼

#include "stdafx.h"  
#include <iostream>
#include <WinSock2.h>  
#include <WS2tcpip.h>  
#pragma comment(lib,"ws2_32.lib")
using namespace std;

// 【1】初始化WinSock
bool initWinSockC();
// 【2】建立socket
bool createSocketC(SOCKET &listenScok);
// 【3】連線到伺服器
bool connectSocketC(SOCKET &conSock, const string ip, const unsigned short port);
// 【4】傳送資料
bool sendDataC(SOCKET &clientSock, const string &data);
// 【5】接收資料
bool receiveDataC(SOCKET &clientSock, string &data);

int JTCPclient()
{
	SOCKET clientSock;	// 客戶端Socket
	string data;		// 收到的資料

	bool isCreateSocket = false;	// 是否建立了Socket
	bool isConnectSocket = false;	// 是否連上了伺服器
	bool isSendDataOK = false;		// 是否傳送成功資料
	bool isReceiveDataOK = false;	// 是否接收成功資料

	// 【1】初始化WinSock
	if(initWinSockC())
	{
		while (true)
		{
			if(!isCreateSocket)
			{
				// 【2】建立socket
				createSocketC(clientSock);
				isCreateSocket = true;
			}
			else
			{
				if(!isConnectSocket)
				{
					// 【3】連線到伺服器
					connectSocketC(clientSock, "127.0.0.1", 1994);
					isConnectSocket = true;
				}
				else
				{
					if(!isSendDataOK)
					{
						// 【4】傳送資料
						isSendDataOK = sendDataC(clientSock, "jfz hello\r\n");
					}
					else
					{
						if(!isReceiveDataOK)
						{
							// 【5】接收資料
							isReceiveDataOK = receiveDataC(clientSock, data);
						}
						else
						{
							if(data == "@end#")
							{
								WSACleanup();
								return 0;
							}
							isReceiveDataOK = false;
							isSendDataOK = false;
						}
					}
					
				}
			}
		}
	}
	return 0;
}

// 【1】初始化WinSock
bool initWinSockC()
{
	WORD verision = MAKEWORD(2, 2);
	WSADATA lpData;
	int intEr = WSAStartup(verision, &lpData); // 指定winsock版本並初始化
	if (intEr != 0)
	{
		std::cout << "WinSock初始化失敗!" << endl;
		return false;
	}
	std::cout << "WinSock初始化成功!" << endl;
	return true;
}

// 【2】建立socket
bool createSocketC(SOCKET &listenScok)
{
	// 建立偵聽socket  
	listenScok = socket(AF_INET, SOCK_STREAM, 0);
	if (listenScok == INVALID_SOCKET)
	{
		cout << "socket建立失敗!" << endl;
		return false;
	}
	cout << "socket建立成功!" << endl;
	return true;
}

// 【3】連線到伺服器
bool connectSocketC(SOCKET &conSock, const string ip, const unsigned short port)
{
	// 建立地址結構體
	sockaddr_in hostAddr;
	hostAddr.sin_family = AF_INET;
	hostAddr.sin_port = htons(port);//轉換成網路位元組序  
									//hostAddr.sin_addr.S_un.S_addr = inet_addr(SERVERIP);//轉換成網路位元組序  
									//cout << "net IP:" << hostAddr.sin_addr.S_un.S_addr << endl;  
									/*
									inet_addr()版本太低,被棄用使用inet_pton(協議族,字串IP地址,void目標in_addr*)
									標頭檔案:WS2tcpip.h
									*/
	in_addr addr;
	inet_pton(AF_INET, ip.c_str(), (void*)&addr);
	hostAddr.sin_addr = addr;
	cout << "ip(網路位元組序):" << addr.S_un.S_addr << endl;
	cout << "ip(常規形式):" << ip.c_str() << endl;

	// 向伺服器提出連線請求
	int err = connect(conSock, (sockaddr*)&hostAddr, sizeof(sockaddr));
	if (err == INVALID_SOCKET)
	{
		cout << "連線伺服器失敗!" << endl;
		return false;
	}
	return true;
}

// 【4】傳送資料
bool sendDataC(SOCKET &clientSock, const string &data)
{
	int err = send(clientSock, data.c_str(), data.size(), 0);
	if (err == SOCKET_ERROR)
	{
		cout << "傳送失敗!" << endl;
		return false;
	}
	cout << "傳送資料為:\n" << data.c_str() << endl;
	return true;
}

// 【5】接收資料
bool receiveDataC(SOCKET &clientSock, string &data)
{
	static int cnt = 1; // 接收資料編號-靜態
						// 通過已建立連線的套接字,接收資料 設定緩衝1024位元組
	char buf[1024] = "\0";
	// flags操作方式(0正常資料,MSG_PEED系統緩衝區的資料複製到所提供的接收緩衝區內,系統緩衝區資料未刪除,MSG_OOB處理帶外資料,通常用引數0即可)
	int buflen = recv(clientSock, buf, 1024, 0);
	if (buflen == SOCKET_ERROR)
	{
		cout << "接收失敗!" << endl;
		return false;
	}
	// 一切正常則顯示接收資料
	data = string(buf);
	cout << "收到第" << cnt++ << "次,內容為:\n" << buf << endl;
	return true;
}