每次我會以一段程序代碼的分析來闡述其中的知識點,由於閱讀大量優秀代碼能夠快速提高自身的編程水平。 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; }程序運行結果:
轉載請註明出處,謝謝!
服務器