根據前面一節的說明,服務端套接字應該按照如下順序建立:
- 初始化
- 創建套接字
- 綁定本地地址
- 進入偵聽狀態
- 處理接受循環
下面首先創建一個例子來演示服務端套接字的實現,并在以后的各節中優化這個設計。
這個設計實現的功能如下:允許客戶端(實際上就是telnet程序)登陸,并對客戶端的輸入回顯。
4.1 準備工程
為了實現這個demo,我打算使用Visual Studio 6.0提供的對話框模板來實現,請按照下述步驟準備工程:
- 啟動Visual C++ 6.0
- 選擇File/New/Project
- 選擇MFC AppWizard(exe)
- 在Project name中輸入demo1, Next
- 選擇Dialog Base, Next
- 在Windows Sockets前打鉤
- 其他都保持缺省值,點擊Next完成向導
- 在工程中加入兩個文件:sockutil.h和sockutil.cpp:這兩個文件將保存公共的函數,避免以后重復編寫。
這樣創建的工程會自動加入WinSock2支持,具體的內容包括:
- 在stdafx.h中加入#include <afxsock.h>,該頭文件包含如下語句載入對應LIB:
#pragma comment(lib, "wsock32.lib")
- 在InitInstance中加入如下代碼:
if(!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
- 在資源中定義字符串資源IDP_SOCKETS_INIT_FAILED:Windows通訊端口初始化失敗
如果大家創建工程時沒有加入對應的sock支持,我們可以參照上述列表手工加入。
對于前文所述的sockutil.h文件,加入如下初始代碼:
#if !defined(__SOCKET_UTILITY_HEADER__)
#define __SOCKET_UTILITY_HEADER__
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#endif
對于sockutil.cpp文件,則加入如下初始代碼:
#include "stdafx.h"
#include "sockutil.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
4.2 準備錯誤處理方式
在編制每個程序之前,我習慣是為錯誤處理定制一個統一的處理方式。在以后的例子中,我會按照如下的方式處理錯誤:在一個log文件中記錄錯誤發生的位置(文件、行號),錯誤號和字符串解釋。為了實現這個目的,我在sockutil.cpp定義如下的函數:
bool ErrorHandle(LPCTSTR expression, bool bFalse, LPCTSTR file, UINT line)
{
if(!bFalse)
{//沒有錯誤,直接返回
return false;
}
FILE * fp;
fp = fopen("demo.log","at");
if(NULL == fp)
{//如果文件打開失敗,放棄記錄
return true;
}
//獲得錯誤碼
DWORD ErrorCode = GetLastError();
//格式化成字符串格式
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
ErrorCode ,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);
//輸出到文件
fprintf(fp,_T("file=%s,line=%u"n%d:%s"n"),file,line,ErrorCode, (LPCTSTR)lpMsgBuf);
// 釋放空間,該空間由FormatMessage分配
LocalFree( lpMsgBuf );
//關閉文件
fclose(fp);
return true;
}
在sockutil.h中,添加如下代碼:
bool ErrorHandle(LPCTSTR expression, bool bFalse, LPCTSTR file, UINT line);
#define ERRORHANDLE(expression) ErrorHandle(#expression,(expression),__FILE__,__LINE__)
4.3 核心代碼
根據前文,設計該服務端套接字主函數如下:
void sockmain(LPCTSTR ip, UINT port)
{
SOCKET hSocket;
hSocket = socket(AF_INET,SOCK_STREAM,0);
if(ERRORHANDLE(hSocket == INVALID_SOCKET))
{
return;
}
sockaddr_in addr;
InitializeAddress(inet_addr(ip), port, addr);
if(ERRORHANDLE(SOCKET_ERROR == bind(hSocket, (const sockaddr*) & addr, sizeof(addr))))
{
closesocket(hSocket);
return;
}
if(ERRORHANDLE(SOCKET_ERROR == listen(hSocket,5)))
{
closesocket(hSocket);
return;
}
SOCKET hClient;
int size;
char buffer[2048];
int length;
size = sizeof(addr);
while(INVALID_SOCKET != (hClient = accept(hSocket,(sockaddr*)&addr, & size)))
{
size = sizeof(addr);
while((length = recv(hClient, buffer, sizeof(buffer),0)) > 0)
{
SendData(hClient,buffer, length);
}
closesocket(hClient);
}
closesocket(hSocket);
return;
}
其中,InitializeAddress定義如下:
void InitializeAddress(DWORD ip, UINT port, sockaddr_in & addr)
{
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr= ip;
addr.sin_port = htons(port);
}
SendData定義如下:
int SendData(SOCKET hSocket, const char * data, int length)
{
int result;
int pos = 0;
while(pos < length)
{
result = send(hSocket, data + pos, length - pos , 0);
if(result > 0 )
{
pos += result;
}else{
return result;
}
}
return length;
}
4.4 啟動該任務
為了啟動服務端套接字,我們可以在對話框資源的OK按鈕上雙擊,然后在OnOK中添加如下代碼
sockmain("0.0.0.0",2000);
當我們啟動該工程,并點擊OK按鈕,就可以通過telnet來測試是否有回顯功能了。
posted on 2008-07-03 15:19
SIMONE 閱讀(636)
評論(0) 編輯 收藏 所屬分類:
C++