MFC下CSocket編程詳解
轉(zhuǎn)自:
http://blog.csdn.net/yejiansnake/archive/2008/03/13/2175778.aspx
MFC下CSocket編程詳解:
1. 常用的函數(shù)和注意事項(詳細(xì)的函數(shù)接口說明請查看MSDN):
CSocket::Create 初始化(一般寫服務(wù)器程序都不要用為好,用下面的 CSocket::Socket 初始化)
CSocket::Socket初始化
CSocket::SetSockOpt 設(shè)置socket選項
CSocket::Bind 綁定地址端口
CSocket::Connect 連接
CSocket::Listen 監(jiān)聽
CSocket::Accept 接收外部連接的socket
CSocket::Send 發(fā)送內(nèi)容
CSocket::Receive 接收內(nèi)容
CSocket::Close 關(guān)閉(不等于delete)
1) 在使用MFC編寫socket程序時,必須要包含<afxsock.h>都文件。
2) AfxSocketInit() 這個函數(shù),在使用CSocket前一定要先調(diào)用該函數(shù),否則使用CSocket會出錯;并且該函數(shù)還有一個重要的使用方式,
就是在某個線程下使用 CSocket 前一定要調(diào)用,就算主線程調(diào)用了該函數(shù),在子線程下使用 CSocket 也要先調(diào)用該函數(shù),要不會出錯。
3) 還要注意的是, Create 方法已經(jīng)包含了 Bind 方法,如果是以 Create 方法初始化的前提下不能再調(diào)用 Bind ,要不一定出錯。
2. 以下是使用例子代碼,通過例子來學(xué)習(xí)如何使用 CSocket 進(jìn)行編程, 并且附件上有完整的例子代碼。例子的可以在我的發(fā)布資源中找到:MFC下CSocket編程例子 http://download.csdn.net/source/379597
1) 客戶端主要代碼:
//初始化
AfxSocketInit();
//創(chuàng)建 CSocket 對象
CSocket aSocket;

CString strIP;
CString strPort;
CString strText;

this->GetDlgItem(IDC_EDIT_IP)->GetWindowText(strIP);
this->GetDlgItem(IDC_EDIT_PORT)->GetWindowText(strPort);
this->GetDlgItem(IDC_EDIT_TEXT)->GetWindowText(strText);

//初始化 CSocket 對象, 因為客戶端不需要綁定任何端口和地址, 所以用默認(rèn)參數(shù)即可
if(!aSocket.Create())

...{

char szMsg[1024] = ...{0};

sprintf(szMsg, "create faild: %d", aSocket.GetLastError());

AfxMessageBox(szMsg);
return;
}

//轉(zhuǎn)換需要連接的端口內(nèi)容類型
int nPort = atoi(strPort);
//連接指定的地址和端口
if(aSocket.Connect(strIP, nPort))

...{

char szRecValue[1024] = ...{0};

//發(fā)送內(nèi)容給服務(wù)器
aSocket.Send(strText, strText.GetLength());
//接收服務(wù)器發(fā)送回來的內(nèi)容(該方法會阻塞, 在此等待有內(nèi)容接收到才繼續(xù)向下執(zhí)行)
aSocket.Receive((void *)szRecValue, 1024);

AfxMessageBox(szRecValue);
}
else

...{

char szMsg[1024] = ...{0};
sprintf(szMsg, "create faild: %d", aSocket.GetLastError());
AfxMessageBox(szMsg);
}

//關(guān)閉
aSocket.Close();

2)服務(wù)器端代碼:
unsigned int StartServer(LPVOID lParam)

...{
//初始化Winscok
if (!AfxSocketInit())

...{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return 1;
}

m_exit = false;

CServerDlg *aDlg = (CServerDlg *)lParam;

CString strPort;
aDlg->GetDlgItemText(IDC_EDIT_PORT, strPort);
UINT nPort = atoi(strPort);
//socket------------------------------------------------
CSocket aSocket, serverSocket;
//最好不要使用aSocket.Create創(chuàng)建,因為容易會出現(xiàn)10048錯誤
if (!aSocket.Socket())

...{

char szError[256] = ...{0};
sprintf(szError, "Create Faild: %d", GetLastError());
AfxMessageBox(szError);
return 1;
}

BOOL bOptVal = TRUE;
int bOptLen = sizeof(BOOL);
//設(shè)置Socket的選項, 解決10048錯誤必須的步驟
aSocket.SetSockOpt(SO_REUSEADDR, (
void *)&bOptVal, bOptLen, SOL_SOCKET);
//監(jiān)聽
if(!aSocket.Listen(10))

...{

char szError[256] = ...{0};
sprintf(szError, "Listen Faild: %d", GetLastError());
AfxMessageBox(szError);
return 1;
}
CString strText;
aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
strText += "Server Start! ";
aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

while(!m_exit)

...{
//接收外部連接
if(!aSocket.Accept(serverSocket))

...{
continue;
}
else

...{

char szRecvMsg[256] = ...{0};

char szOutMsg[256] = ...{0};
//接收客戶端內(nèi)容:阻塞
serverSocket.Receive(szRecvMsg, 256);

sprintf(szOutMsg, "Receive Msg: %s ", szRecvMsg);
aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
strText += szOutMsg;
aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);
//發(fā)送內(nèi)容給客戶端
serverSocket.Send("Have Receive The Msg", 50);

//關(guān)閉
serverSocket.Close();
}
}
//關(guān)閉
aSocket.Close();
serverSocket.Close();
aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
strText += "Have Close!";
aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

return 0;
}
//綁定端口
if (!aSocket.Bind(nPort))

...{

char szError[256] = ...{0};
sprintf(szError, "Bind Faild: %d", GetLastError());
AfxMessageBox(szError);
return 1;
}
3) SDK 下的服務(wù)器端代碼
//子線程函數(shù)
unsigned int StartServer(LPVOID lParam)

...{
//初始化Winsock, AfxSocketInit() 也是封裝了這些語句, 不過 AfxSocketInit() 所做的事比這里多些

WSADATA wsaData;
//Winsock 的版本, 建議用1.1 ,兼容性好
WORD wVersionRequested = MAKEWORD(1, 1);
int nResult = WSAStartup(wVersionRequested, &wsaData);
if (nResult != 0)

...{
return 1;
}

//-----------------------------------------------------

m_exit = false;

CServerDlg *aDlg = (CServerDlg *)lParam;

CString strPort;
aDlg->GetDlgItemText(IDC_EDIT_PORT, strPort);
UINT nPort = atoi(strPort);
//socket------------------------------------------------
//接口對象
SOCKET aSocket, serverSocket;

//尋址相關(guān)結(jié)構(gòu)
sockaddr_in serverSockaddr;
memset(&serverSockaddr, 0, sizeof(serverSockaddr));


aSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (aSocket == INVALID_SOCKET)

...{

char szError[256] = ...{0};
sprintf(szError, "Create Faild: %d", GetLastError());
AfxMessageBox(szError);
return 1;
}

//注意,該處非常重要,取值的正確與否決定關(guān)閉scoket后端口是否能正常釋放
BOOL bOptVal = TRUE;
int bOptLen = sizeof(BOOL);
//設(shè)置 socket 選項, SOL_SOCKET 和 SO_REUSEADDR 一起使用, 并且后面的參數(shù)如上,
關(guān)閉scoket后端口便能正常釋放
setsockopt(aSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&bOptVal, bOptLen);
//尋址相關(guān)結(jié)構(gòu)
sockaddr_in aSockaddr;
memset(&aSockaddr,0,sizeof(aSockaddr));

aSockaddr.sin_family = AF_INET;

aSockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
aSockaddr.sin_port = htons((u_short)nPort);
//綁定: 注意參數(shù)的類型轉(zhuǎn)換
if(bind(aSocket,(sockaddr *)&aSockaddr, sizeof(aSockaddr)) == SOCKET_ERROR)

...{

char szError[256] = ...{0};
sprintf(szError, "Bind Faild: %d", GetLastError());
AfxMessageBox(szError);
return 1;
}

//監(jiān)聽
if(listen(aSocket, 10) == SOCKET_ERROR)

...{

char szError[256] = ...{0};
sprintf(szError, "Listen Faild: %d", GetLastError());
AfxMessageBox(szError);
return 1;
}
CString strText;

aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);

strText += "Server Start! ";

aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

while(!m_exit)

...{
//接收外部連接, 非阻塞
serverSocket = accept(aSocket, (sockaddr *)&serverSockaddr, 0);
if(serverSocket == INVALID_SOCKET)

...{
continue;
}
else

...{

char szRecvMsg[256] = ...{0};

char szOutMsg[256] = ...{0};
//接收客戶端內(nèi)容: 阻塞
recv(serverSocket, szRecvMsg, 256, 0);

sprintf(szOutMsg, "Receive Msg: %s ", szRecvMsg);
aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
strText += szOutMsg;

aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

//發(fā)送內(nèi)容給客戶端
send(serverSocket, "Have Receive The Msg", 50, 0);

//關(guān)閉
closesocket(serverSocket);
}
}
//關(guān)閉
closesocket(aSocket);
closesocket(serverSocket);

aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
strText += "Have Close!";

aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

//當(dāng)你使用完Winsock接口后,要調(diào)用下面的函數(shù)對其占用的資源進(jìn)行釋放
WSACleanup();

return 0;
}

3. 總結(jié)
1) MFC進(jìn)行編程的確比較簡單, 用的代碼比較少, 又容易管理。唯一不好的地方在于很多細(xì)節(jié)上的東西在資料上不容易查出來, 關(guān)聯(lián)性非常緊密, 象 AfxSocketInit() 函數(shù)就是,函數(shù)的實現(xiàn)里包含著很多不容易理解的類, 并且記錄了非常多的環(huán)境信息, 比如創(chuàng)建的線程等等, 這樣在主線程調(diào)用后子線程沒有調(diào)用執(zhí)行 CSocket 的操作就會出錯。還有就是有些接口的設(shè)計非常離奇, 象 CSocket::Create 的接口就是, 實現(xiàn)上還執(zhí)行了 CSocket::Bind , 非常不容易被發(fā)現(xiàn)。并且MSDN上對 CSocket::Bind 的說明又明顯的提示需要顯示執(zhí)行 CSocket::Bind 操作。
2) SDK 編程能理解函數(shù)的調(diào)用順序和代碼的結(jié)構(gòu)就比較容易,省去了MFC下封裝了不知道什么東西的部分,使得代碼的流程容易控制。但是從上面的例子來看非常明顯的并且不是那么容易理解。不僅僅有很多奇怪的結(jié)構(gòu)(微軟的命名一直如此, 無所云云), 并且函數(shù)相關(guān)太過于緊密, 初學(xué)者想一下子熟悉使用并不容易, 對開發(fā)者來說代碼管理起來非常麻煩。
發(fā)表于 @ 2008年03月13日 09:01:00|評論(5
)|編輯
posted on 2009-04-15 11:10
Alex-Lee 閱讀(6255)
評論(3) 編輯 收藏 引用