阻塞模式下socket多線程通信

客戶端創建線程負責收發數據,其餘操作封裝在函數中,方便其他應用程序調用。服務端創建了兩個線程,一個用於收發數據,對於接收連接accept部分開一個線程,這樣主窗口就不會因阻塞而掛掉。( 參考資料:《Windows API開發詳解——函數、接口、編程實例》第十四章)

客戶端client.c


/* 頭文件 */
#include <stdio.h>
#include "winsock2.h"
#include <conio.h>
/* 常量 */
#define RECV_BUFFER_SIZE 8192

// 變量定義
SOCKADDR_IN clientService;// 地址
SOCKET ConnectSocket;// socket
WSADATA wsaData;// 庫

HANDLE hThread;



char sendbuf[32] = "get information";// 默認發送的數據



void SendStr(LPSTR sendbuf,SOCKET socket1);

void InitSocket();
void CreateMyThread();
DWORD WINAPI CommunicationThread(
	LPVOID lpParameter
	);



/*************************************
* CommunicationThread
* 功能	用於接收和發送數據的線程
*			爲每一個連接的客戶端創建一個接收發送數據的線程,
*			可以使用多個客戶端同時連接到服務端
* 參數	lpParameter,SOKCET
**************************************/
DWORD WINAPI CommunicationThread(
	LPVOID lpParameter
	)
{

	// 獲得參數sokcet
	SOCKET socket = (SOCKET)lpParameter;
	// 爲接收數據分配空間
	LPSTR szRequest = HeapAlloc(GetProcessHeap(),0,RECV_BUFFER_SIZE);
	int iResult = 0;
	int bytesSent;// 用於保存send的返回值,實際發送的數據的大小
	while(1)
	{
		// 接收數據
		iResult = recv(socket, // socket
			szRequest, // 接收緩存
			RECV_BUFFER_SIZE, // 緩存大小
			0);// 標誌
		if (iResult == 0)// 接收數據失敗,連接已經關閉
		{
			printf("Connection closing...\n");
			HeapFree(GetProcessHeap(), 0 ,szRequest);
			closesocket(socket);
			return 1;
		}
		else if (iResult == SOCKET_ERROR)// 接收數據失敗,socket錯誤
		{
			printf("recv failed: %d\n", WSAGetLastError());
			HeapFree(GetProcessHeap(), 0 ,szRequest);
			closesocket(socket);
			return 1;
		}
		else if (iResult > 0) // 接收數據成功
		{
			// 顯示接收到的數據
			printf("Bytes received: %d\tContent: %s\n",iResult,szRequest);


			// 如果接收到的數據是"download file"
			if (lstrcmpi(szRequest, "start listen") == 0)
			{
				SendStr("COMING",socket);
			}
			// 如果接收到的數據是"get information"
			else if (lstrcmpi(szRequest, "YEAH") == 0)
			{
				SendStr("123",socket);
			}
			else// 收到未知數據
			{
				printf ("unreferenced request\n");
			}
		}
	}
	// 釋放接收數據緩存,關閉socket
	HeapFree(GetProcessHeap(), 0 ,szRequest);
	closesocket(socket);
	return 0;
}


/*************************************
* main
* 功能 socket通信客戶端
**************************************/
void main(int argc, char* argv[])
{

	InitSocket();


	CreateMyThread();
	SendStr("request listen",ConnectSocket);
	getch();
	WaitForSingleObject( 
		hThread,    // handle to mutex
		INFINITE);  // no time-out interval
	WSACleanup();

	return;
}



void InitSocket()
{

	// 初始化socket庫,	保存ws2_32.dll已經加載
	int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != NO_ERROR)
		printf("Error at WSAStartup()\n");

	// 創建socket
	ConnectSocket = socket(AF_INET, // IPv4
		SOCK_STREAM, // 順序的、可靠的、基於連接的、雙向的數據流通信
		IPPROTO_TCP// 使用TCP協議
		);
	if (ConnectSocket == INVALID_SOCKET)
	{
		printf("Error at socket(): %ld\n", WSAGetLastError());
		WSACleanup();
		return;
	}

	// 設置服務端的通信協議、IP地址、端口
	clientService.sin_family = AF_INET;
	clientService.sin_addr.s_addr = inet_addr( "222.31.66.209" );
	clientService.sin_port = htons( 10000 );


	// 連接到服務端
	if ( connect(
		ConnectSocket, // socket
		(SOCKADDR*) &clientService, // 地址
		sizeof(clientService) // 地址的大小
		) == SOCKET_ERROR)
	{
		printf( "Failed to connect(%d)\n",WSAGetLastError() );
		WSACleanup();
		return;
	}




}

void SendStr(LPSTR sendbuf,SOCKET socket1)
{




	int bytesSent;
	// 準備發送數據
	// 如果輸入參數是-d,那麼發送的數據是“download file”否則是"get information"
	// 向服務端發送數據



	bytesSent = send( socket1, // socket
		sendbuf,// 發送的數據 
		lstrlen(sendbuf)+1,// 數據長度
		0 );// 無標誌

	if(bytesSent == SOCKET_ERROR)
	{
		printf( "send error (%d)\n", WSAGetLastError());
		closesocket(socket1);
		return;
	}
	printf( "Bytes Sent: %ld\t Content: %s\n", bytesSent,sendbuf);
}

void CreateMyThread()
{
	// 爲每一個連接創建一個數據發送的接收線程,
	// 使服務端又可以立即接收其他客戶端的連接
	hThread = CreateThread(
		NULL,
		0,
		CommunicationThread, // 線程函數
		(LPVOID)ConnectSocket, // 將socket作爲參數
		0,
		NULL);
	if(!hThread)
	{
		printf("Create Thread error (%d)", GetLastError());

	}


}











服務端 server.c


/* 頭文件 */
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
/* 常量 */
#define DEFAULT_PORT "10000" // 端口
#define MAX_REQUEST 1024 // 接收數據的緩存大小
#define BUF_SIZE 4096 // 發送數據的緩存大小


WSADATA wsaData;
SOCKET ListenSocket = INVALID_SOCKET;// 監聽socket
SOCKET ClientSocket = INVALID_SOCKET;// 連接socket
struct addrinfo *result = NULL,
	hints;
int iResult;// 保存返回結果



void InitSocket();
DWORD WINAPI AcceptThead();
void SendStr(LPSTR sendbuf,SOCKET socket);

/*************************************
* CommunicationThread
* 功能	用於接收和發送數據的線程
*			爲每一個連接的客戶端創建一個接收發送數據的線程,
*			可以使用多個客戶端同時連接到服務端
* 參數	lpParameter,SOKCET
**************************************/
DWORD WINAPI CommunicationThread(
	LPVOID lpParameter
	)
{

	// 獲得參數sokcet
	SOCKET socket = (SOCKET)lpParameter;
	// 爲接收數據分配空間
	LPSTR szRequest = HeapAlloc(GetProcessHeap(),0, MAX_REQUEST);
	int iResult;
	int bytesSent;// 用於保存send的返回值,實際發送的數據的大小
	while(1)
	{
		// 接收數據
		iResult = recv(socket, // socket
			szRequest, // 接收緩存
			MAX_REQUEST, // 緩存大小
			0);// 標誌
		if (iResult == 0)// 接收數據失敗,連接已經關閉
		{
			printf("Connection closing...\n");
			HeapFree(GetProcessHeap(), 0 ,szRequest);
			closesocket(socket);
			return 1;
		}
		else if (iResult == SOCKET_ERROR)// 接收數據失敗,socket錯誤
		{
			printf("recv failed: %d\n", WSAGetLastError());
			HeapFree(GetProcessHeap(), 0 ,szRequest);
			closesocket(socket);
			return 1;
		}
		else if (iResult > 0) // 接收數據成功
		{
			// 顯示接收到的數據
			printf("Bytes received: %d\tContent: %s\n",iResult, szRequest);


			// 如果接收到的數據是"download file"
			if (lstrcmpi(szRequest, "request listen") == 0)
			{
				SendStr("start listen",socket);
			}
			// 如果接收到的數據是"get information"
			else if (lstrcmpi(szRequest, "COMING") == 0)
			{
				// 發送數據
				SendStr("YEAH",socket);
			}
			else if (lstrcmpi(szRequest, "123") == 0)
			{
				SendStr("333",socket);
			}
			else if (lstrcmpi(szRequest, "close") == 0)
			{
				break;
			}

			else// 收到未知數據
			{
				printf ("unreferenced request\n");
			}
		}
	}
	// 釋放接收數據緩存,關閉socket
	HeapFree(GetProcessHeap(), 0 ,szRequest);
	closesocket(socket);
	return 0;
}

/*************************************
* int __cdecl main(void)
* 功能	socket服務端
**************************************/
int __cdecl main(void)
{


	InitSocket();

	AcceptThead();
	// 循環退出,釋放DLL。
	WSACleanup();
	return 0;
}


void InitSocket()
{
	// 初始化Winsock,保證Ws2_32.dll已經加載
	iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != 0)
	{
		printf("WSAStartup failed: %d\n", iResult);
		return 1;
	}
	// 地址
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	// 獲取主機地址,保證網絡協議可用等
	iResult = getaddrinfo(NULL, // 本機
		DEFAULT_PORT, // 端口
		&hints, // 使用的網絡協議,連接類型等
		&result);// 結果
	if ( iResult != 0 )
	{
		printf("getaddrinfo failed: %d\n", iResult);
		WSACleanup();
		return 1;
	}

	// 創建socket,用於監聽
	ListenSocket = socket(
		result->ai_family, // 網絡協議,AF_INET,IPv4
		result->ai_socktype, // 類型,SOCK_STREAM
		result->ai_protocol);// 通信協議,TCP
	if (ListenSocket == INVALID_SOCKET)
	{
		printf("socket failed: %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return 1;
	}
	// 綁定到端口
	iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
	if (iResult == SOCKET_ERROR)
	{
		printf("bind failed: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	printf("bind\n");

	freeaddrinfo(result);// reuslt不再使用
	// 開始監聽
	iResult = listen(ListenSocket, SOMAXCONN);
	printf("start listen......\n");
	if (iResult == SOCKET_ERROR)
	{
		printf("listen failed: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}


	if(!CreateThread(
		NULL,
		0,
		AcceptThead, // 線程函數
		(LPVOID)ClientSocket, // 將socket作爲參數
		0,
		NULL))
	{
		printf("Create Thread error (%d)", GetLastError());
	}


}


DWORD WINAPI AcceptThead()
{


	while (1)
	{
		// 接收客戶端的連接,accept函數會等待,直到連接建立
		printf("ready to accept\n");
		ClientSocket = accept(ListenSocket, NULL, NULL);
		// accept函數返回,說明已經有客戶端連接
		// 返回連接socket
		printf("accept a connetion\n");
		if (ClientSocket == INVALID_SOCKET)
		{
			printf("accept failed: %d\n", WSAGetLastError());
			closesocket(ListenSocket);
			break;// 等待連接錯誤,退出循環
		}
		// 爲每一個連接創建一個數據發送的接收線程,
		// 使服務端又可以立即接收其他客戶端的連接
		if(!CreateThread(
			NULL,
			0,
			CommunicationThread, // 線程函數
			(LPVOID)ClientSocket, // 將socket作爲參數
			0,
			NULL))
		{
			printf("Create Thread error (%d)", GetLastError());
			break;
		}
	}
}




void SendStr(LPSTR sendbuf,SOCKET socket)
{
	int bytesSent;
	// 準備發送數據
	// 如果輸入參數是-d,那麼發送的數據是“download file”否則是"get information"
	// 向服務端發送數據
	bytesSent = send( socket, // socket
		sendbuf,// 發送的數據 
		lstrlen(sendbuf)+1,// 數據長度
		0 );// 無標誌

	if(bytesSent == SOCKET_ERROR)
	{
		printf( "send error (%d)\n", WSAGetLastError());
		closesocket(socket);
		return;
	}
	printf( "Bytes Sent: %ld\tContent: %s\n", bytesSent,sendbuf );
}

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