Winsock編程_第4篇_套接字阻塞模式2

         最近受一位編程牛人的影響,本身決定從今天開始將Windows Sockets 網絡編程的內容以日誌的形式記錄下來,其一是爲本身保留一份筆記,其二是加深本身對winsock編程的理解,其三是和喜歡代碼的朋友一塊兒分享編程的樂趣,我不知道本身能不能一直堅持下去,可是我會盡本身最大的努力作到每週更新1-2篇,在此也但願更多的朋友爲我提出建議,若是你也喜歡編程,在閱讀代碼的過程當中有什麼疑問,請與本人進行探討。
 

       每次我會以一段程序代碼的分析來闡述其中的知識點,由於閱讀大量優秀代碼能夠快速提高自身的編程水平。 ios

本程序基於C/S架構,實現客戶端和服務器之間簡單的TCP/IP通訊。完成客戶端和服務器之間的相互問候。

服務器運行過程以下:
1.服務器啓動後,等待客戶端的鏈接請求。
2.當收到客戶端的請求後,在界面上顯示該客戶端的IP地址和端口,以及「Hello,Server!」問候語。
3.服務器向該客戶端應答「Hello,Clinet!」問候語。
4.服務器退出。
客戶端運行過程以下:
1.客戶端啓動後,向服務器發起鏈接請求。
2.當鏈接請求被接受後,客戶端向服務器發送「Hello,Server!」問候語。
3.等待服務器的應答。
4.當客戶端收到服務器的「Hello,Clinet!」應答後,客戶端退出。


接下來開始設計服務器:
1.啓動後顯示「服務器初始化成功」「等待客戶端的鏈接...」(此時服務器完成了套接字的初始化工做,而且綁定到端口2012,開始監聽來自該端口的請求信息)
2.接受客戶端請求(打印輸出客戶端的端口號以及客戶端的IP地址)
3.接收數據(打印輸出Hello,Server!)
4.退出(服務器回覆客戶端Hello Clinet,退出程序)
5.錯誤處理 
服務器的實現:
編程

服務器工做流程圖 windows

Server Code: api

// Server.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include <iostream>
#include <winsock2.h>
#pragma comment(lib, "wsock32.lib")
using namespace std;

#define SERVER_EXIT_OK				0	//服務器正常退出
#define SERVER_DLL_REEOR			1	//調用Windows sockets DLL失敗
#define SERVER_API_ERROR			2	//調用Windows sockets API失敗
#define	SERVERPORT					2012//服務器TCP端口
#define MAX_NUM_BUF					64	//緩衝區最大尺寸

//變量
char	bufRecv[MAX_NUM_BUF];			//讀緩衝區
char	bufSend[MAX_NUM_BUF];			//寫緩衝區
SOCKET	sServer;						//服務器監聽套接字
SOCKET	sClient;						//接受客戶端套接字
BOOL	bConning;						//與客戶端的鏈接狀態

//函數
void	InitMember(void);				//初始化成員變量 
int		ExitClient(int nExit);			//客戶端退出
BOOL	RecvLine(SOCKET s, char* buf);	//讀取一行數據
BOOL	SendLine(SOCKET s, char* buf);	//發送一行數據
int		HandleSocketError(char *str);	//對Windows sockets API調用錯誤處理
void	ShowSocketMsg(char* str);		//顯示錯誤信息

//主函數
int main(int argc, char* argv[])
{

	InitMember();					//初始化變量
	
	WORD	wVersionRequested;		//應用程序須要Windows sockets DLL的版本
	WSADATA	wsaData;				//Windows sockets DLL版本信息
	int		retVal;					//調用Windows sockets API返回值		
	//初始化Windows Sockets DLL
	wVersionRequested = MAKEWORD(1,1);
	retVal = WSAStartup(wVersionRequested, &wsaData);
	if ( 0 != retVal ) 
	{
		ShowSocketMsg("初始化動態連接庫失敗!");
		return SERVER_DLL_REEOR;
	}	
	//確保WinSock DLL支持1.1
	if ( LOBYTE( wsaData.wVersion ) != 1 ||	HIBYTE( wsaData.wVersion ) != 1)
	{
		ShowSocketMsg("沒有發現一個能使用的動態連接庫!");
		WSACleanup( );
		return SERVER_DLL_REEOR; 
	}
	
	
	//建立套接字
	sServer= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
	if(INVALID_SOCKET == sServer)
	{
		return HandleSocketError("Failed socket()!");
	}
	
	//服務器套接字地址 
	SOCKADDR_IN addrServ;
	addrServ.sin_family = AF_INET;
	addrServ.sin_port = htons(SERVERPORT);
	addrServ.sin_addr.s_addr = INADDR_ANY;		
	//綁定套接字
	retVal = bind(sServer, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));
	if(SOCKET_ERROR == retVal)
	{
		closesocket(sServer);						//關閉套接字
		return HandleSocketError("綁定套接字失敗!");	//錯誤處理
	}
	
	//開始監聽 
	retVal = listen(sServer, 1);
	if(SOCKET_ERROR == retVal)
	{
		closesocket(sServer);						//關閉套接字
		return HandleSocketError("監聽失敗!");//錯誤處理
	}
	

	//等待客戶端的鏈接	
	cout << "服務器初始化成功!" << endl;
	cout << "等待客戶端的鏈接..." << endl;



	//接受客戶端請求
	sockaddr_in addrClient;
	int addrClientlen = sizeof(addrClient);
	sClient = accept(sServer,(sockaddr FAR*)&addrClient, &addrClientlen);
	if(INVALID_SOCKET == sClient)
	{
		closesocket(sServer);							//關閉套接字
		return HandleSocketError("接收客戶端請求失敗!");	//錯誤處理
	}else{
		bConning = TRUE;								//客戶端請求成功
	}
	
	//顯示客戶端的IP和端口
	char *pClientIP = inet_ntoa(addrClient.sin_addr);
	u_short  clientPort = ntohs(addrClient.sin_port);	
	cout<<"接收到一個來自客戶端的請求."<<endl;
	cout<<"客戶端IP: "<<pClientIP<<endl;
	cout<<"端口: "<<clientPort<<endl;


	//接收客戶端數據
	if (!RecvLine(sClient, bufRecv))
	{
		return	ExitClient(SERVER_API_ERROR);//退出
	}	
	//顯示客戶端數據
	cout << bufRecv<<endl;


	//向客戶端發送數據
	strcpy_s(bufSend, "Hello,Client!\n");
	if (!SendLine(sClient, bufSend))
	{
		return	ExitClient(SERVER_API_ERROR);
	}
	

	//顯示退出信息
	cout << "服務器正在退出..." << endl;
	
	//退出
	return ExitClient(SERVER_EXIT_OK);
}



/*
 *	初始化成員變量
 */
void	InitMember(void)
{
	//初始化讀和寫緩衝區
	memset(bufRecv, 0, MAX_NUM_BUF);
	memset(bufSend, 0, MAX_NUM_BUF);

	//初始化
	sServer = INVALID_SOCKET;
	sClient = INVALID_SOCKET;

	//沒有鏈接狀態
	bConning = FALSE;
}


/*
 *	退出
 */
int		ExitClient(int nExit)
{
	closesocket(sServer);	//關閉監聽套接字
	closesocket(sClient);	//關閉鏈接客戶端套接接
	WSACleanup();			//卸載Windows sockets DLL 清理內存
	return nExit;			//退出
}
/*
 *	讀一行數據
 */
BOOL	RecvLine(SOCKET s, char* buf)
{
	BOOL	retVal = TRUE;			//返回值
	BOOL	bLineEnd = FALSE;		//行結束
	int		nReadLen = 0;			//讀入字節數
	int		nDataLen = 0;			//數據長度
	while (!bLineEnd && bConning)	//與客戶端鏈接 沒有換行
	{
		nReadLen = recv(s, buf + nDataLen, 1, 0);//每次接收一個字節
		
		//錯誤處理
		if (SOCKET_ERROR == nReadLen)
		{
			int nErrCode = WSAGetLastError();//錯誤代碼
			if (WSAENOTCONN == nErrCode)
			{
				ShowSocketMsg("套接字未鏈接!");
				
			}else if(WSAESHUTDOWN == nErrCode)
			{
				ShowSocketMsg("套接字已關閉!");
				
			}else if (WSAETIMEDOUT == nErrCode)
			{
				ShowSocketMsg("鏈接已斷開!");							
			}else if (WSAECONNRESET == nErrCode)
			{
				ShowSocketMsg("一個現存的遠程主機上運行的客戶端被強制關閉!");
			}else{}	
			
			retVal = FALSE;	//讀數據失敗
			break;			//跳出循環						
		}
		
		
		if (0 == nReadLen)//客戶端關閉
		{
			retVal = FALSE;	//讀數據失敗
			break ;			//跳出循環			
		}
		
		//讀入數據
		if ('\n' == *(buf + nDataLen))	//換行符
		{
			bLineEnd = TRUE;			//接收數據結束
		}else{
			nDataLen += nReadLen;		//增長數據長度
		}	
	}
	
	return retVal;
}

/*
 *	//發送一行數據
 */
BOOL	SendLine(SOCKET s, char* str)
{
	int retVal;//返回值
	retVal = send(s, str, strlen(str), 0);//一次發送

	//錯誤處理
	if (SOCKET_ERROR == retVal)
	{
		int nErrCode = WSAGetLastError();//錯誤代碼
		if (WSAENOTCONN == nErrCode)
		{
			ShowSocketMsg("套接字未鏈接");
			
		}else if(WSAESHUTDOWN == nErrCode)
		{
			ShowSocketMsg("套接字已關閉!");
			
		}else if (WSAETIMEDOUT == nErrCode)
		{
			ShowSocketMsg("鏈接已斷開!");
		}else{}	
		
		return FALSE;	//發送失敗
	}
	
	return TRUE;		//發送成功
}
	
/*
 *	錯誤處理
 */
int		HandleSocketError(char *str)
{
	ShowSocketMsg(str);		//顯示錯誤消息	
	WSACleanup();			//卸載Windows socket DLL	
	return SERVER_API_ERROR;//退出應用程序
}

/*
 *	顯示錯誤
 */
void	ShowSocketMsg(char* str)
{
	MessageBox(NULL, str, "SERVER ERROR", MB_OK);
}
Clinet Code:
// Client.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"

#include <windows.h>
#include <winsock.h>
#include <iostream>
#pragma comment(lib, "wsock32.lib")
using namespace std;


#define CLIENT_EXIT_OK				0	//客戶端正常退出
#define CLIENT_DLL_REEOR			1	//調用Windows socket dll失敗
#define CLIENT_API_ERROR			2	//調用Windows socket api失敗
#define MAX_NUM_BUF					64	//緩衝區的最大長度
#define	SERVERPORT					2012//服務器TCP端口


//變量
char	bufRecv[MAX_NUM_BUF];			//讀緩衝區
char	bufSend[MAX_NUM_BUF];			//寫緩衝區
SOCKET	sHost;							//socket
BOOL	bConning;						//鏈接服務器狀態

//函數
void	InitMember(void);				//初始化變量
int		ExitClient(int nExit);		//退出
BOOL	RecvLine(SOCKET s, char* buf);	//讀取一行數據
void	ShowErrorMsg(void);			//顯示錯誤信息

//主函數
int main()
{

	//初始化變量
	InitMember();

	WORD	wVersionRequested;		//應用程序須要Windows sockets DLL的版本
	WSADATA	wsaData;		//Windows sockets DLL版本信息
	int		retVal;		//調用Windows sockets API返回值

	//初始化Windows Sockets DLL
	wVersionRequested = MAKEWORD(1,1);	
	retVal = WSAStartup(wVersionRequested,(LPWSADATA)&wsaData);
	if ( 0 != retVal ) 
	{
		MessageBox(NULL, "初始化動態連接庫失敗!", "ERROR", MB_OK);
		return CLIENT_DLL_REEOR;
	}


	//建立Windows socket
	sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(INVALID_SOCKET == sHost)
	{
		ShowErrorMsg();			//顯示錯誤信息
		WSACleanup();			//釋放資源
		return CLIENT_API_ERROR;//退出
	}

	//準備鏈接服務器
	cout << "客戶端初始化成功!" << endl;
	cout<<"準備鏈接到服務器..."<<endl;

	
	
	//獲取主機的信息
	LPHOSTENT hostEntry;
	char hostname[MAX_NUM_BUF];
	gethostname(hostname,MAX_NUM_BUF);			//獲取主機名稱
	hostEntry = gethostbyname(hostname);		//獲取主機信息
	if(!hostEntry)
	{
		ShowErrorMsg();						//顯示錯誤信息
		return ExitClient(CLIENT_API_ERROR);	//退出
	}
	//設置sockaddr_in
	SOCKADDR_IN addrServ;
	addrServ.sin_family = AF_INET;
	addrServ.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);
	addrServ.sin_port = htons(SERVERPORT);
	//鏈接服務器
	retVal=connect(sHost,(LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));
	if(SOCKET_ERROR == retVal)
	{
		ShowErrorMsg();						//顯示錯誤信息
		return ExitClient(CLIENT_API_ERROR);	//退出
	}else{
		bConning = TRUE;					//鏈接服務器成功
	}     
	//鏈接服務器成功
	cout<<"鏈接服務器成功!"<<endl;


	//向服務器發送數據
	strcpy_s(bufSend, "Hello,Server!\n");
	retVal = send(sHost, bufSend, strlen(bufSend), 0);
	if (SOCKET_ERROR == retVal)
	{
		ShowErrorMsg();						//顯示錯誤信息
		return ExitClient(CLIENT_API_ERROR);	//退出
	}

	//從服務器接收數據
	if (!RecvLine(sHost, bufRecv))
	{
		ShowErrorMsg();						//顯示錯誤信息
		return ExitClient(CLIENT_API_ERROR);	//退出
	}
	//顯示服務器的應答
	cout<<bufRecv<<endl;

	//退出
	return ExitClient(CLIENT_EXIT_OK);
}


/*
 *	顯示錯誤信息
 */
void	ShowErrorMsg(void)
{
	int nErrCode = WSAGetLastError();//獲取錯誤代碼

	HLOCAL hlocal = NULL;  
	
	//獲取錯誤的文本字符串
	BOOL fOk = FormatMessage(
		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
		NULL, nErrCode, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
		(PTSTR)&hlocal, 0, NULL);
	
	//顯示錯誤信息
	if (hlocal != NULL)
	{
		MessageBox(NULL, (char*)LocalLock(hlocal), "CLIENT ERROR", MB_OK);	
		LocalFree(hlocal);
	}
}


/*
 *	初始化成員變量
 */
void	InitMember(void)
{
	//初始化讀和寫緩衝區
	memset(bufRecv, 0, MAX_NUM_BUF);
	memset(bufSend, 0, MAX_NUM_BUF);
	//初始化
	sHost = INVALID_SOCKET;
	//沒有鏈接狀態
	bConning = FALSE;
}

/*
 *	退出
 */
int		ExitClient(int nExit)
{
	closesocket(sHost);		//關閉套接字
	WSACleanup();			//卸載Windows sockets DLL 清理內存

		//顯示退出信息
	cout << "客戶端正在退出..." << endl;
	Sleep(20000);
	return nExit;	//退出
}


/*
 *	讀取一行數據
 */
BOOL	RecvLine(SOCKET s, char* buf)
{
	BOOL	retVal = TRUE;			//返回值
	BOOL	bLineEnd = FALSE;		//行結束
	int		nReadLen = 0;			//讀入字節數
	int		nDataLen = 0;			//數據長度
	while (!bLineEnd && bConning)	//與客戶端鏈接 沒有換行
	{
		nReadLen = recv(s, buf + nDataLen, 1, 0);//每次接收一個字節		
		//錯誤處理
		if (SOCKET_ERROR == nReadLen)
		{			
			retVal= FALSE;	//讀數據失敗
			break;			//跳出循環						
		}		
		
		if (0 == nReadLen)//客戶端關閉
		{
			retVal = FALSE;	//讀數據失敗
			break ;			//跳出循環			
		}
		
		//讀入數據
		if ('\n' == *(buf + nDataLen))	//換行符
		{
			bLineEnd = TRUE;			//接收數據結束
		}else{
			nDataLen += nReadLen;		//增長數據長度
		}	
	}
	
	return retVal;
}
程序運行結果:


轉載請註明出處,謝謝!
服務器

相關文章
相關標籤/搜索