原文轉自 http://blog.csdn.net/yejiansnake/article/details/2175778
MFC下CSocket編程詳解:
1. 常用的函數和注意事項(詳細的函數接口說明請查看MSDN):
CSocket::Create 初始化(一般寫服務器程序都不要用為好,用下面的 CSocket::Socket 初始化)
CSocket::Socket初始化
CSocket::SetSockOpt 設置socket選項
CSocket::Bind 綁定地址端口
CSocket::Connect 連接
CSocket::Listen 監聽
CSocket::Accept 接收外部連接的socket
CSocket::Send 發送內容
CSocket::Receive 接收內容
CSocket::Close 關閉(不等於delete)
(1) 在使用MFC編寫socket程序時,必須要包含<afxsock.h>都文件。
(2) AfxSocketInit() 這個函數,在使用CSocket前一定要先調用該函數,否則使用CSocket會出錯;並且該函數還有一個重要的使用方式,就是在某個線程下使用 CSocket 前一定要調用,就算主線程調用了該函數,在子線程下使用 CSocket 也要先調用該函數,要不會出錯。
(3) 還要注意的是, Create 方法已經包含了 Bind 方法,如果是以 Create 方法初始化的前提下不能再調用 Bind ,要不一定出錯。
2. 以下是使用例子代碼,通過例子來學習如何使用 CSocket 進行編程, 並且附件上有完整的例子代碼。例子的可以在我的發布資源中找到:MFC下CSocket編程例子 http://download.csdn.net/source/379597
(1) 客戶端主要代碼:
//初始化 AfxSocketInit(); //創建 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 對象, 因為客戶端不需要綁定任何端口和地址, 所以用默認參數即可 if (!aSocket.Create()) { char szMsg[1024] = { 0 }; sprintf(szMsg, "create faild: %d", aSocket.GetLastError()); AfxMessageBox(szMsg); return; } //轉換需要連接的端口內容類型 int nPort = atoi(strPort); //連接指定的地址和端口 if (aSocket.Connect(strIP, nPort)) { char szRecValue[1024] = { 0 }; //發送內容給服務器 aSocket.Send(strText, strText.GetLength()); //接收服務器發送回來的內容(該方法會阻塞, 在此等待有內容接收到才繼續向下執行) aSocket.Receive((void *)szRecValue, 1024); AfxMessageBox(szRecValue); } else { char szMsg[1024] = { 0 }; sprintf(szMsg, "create faild: %d", aSocket.GetLastError()); AfxMessageBox(szMsg); } //關閉 aSocket.Close();
(2) 服務器端代碼:
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創建,因為容易會出現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); //設置Socket的選項, 解決10048錯誤必須的步驟 aSocket.SetSockOpt(SO_REUSEADDR, (void *)&bOptVal, bOptLen, SOL_SOCKET); //監聽 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 }; //接收客戶端內容:阻塞 serverSocket.Receive(szRecvMsg, 256); sprintf(szOutMsg, "Receive Msg: %s ", szRecvMsg); aDlg->GetDlgItemText(IDC_EDIT_LOG, strText); strText += szOutMsg; aDlg->SetDlgItemText(IDC_EDIT_LOG, strText); //發送內容給客戶端 serverSocket.Send("Have Receive The Msg", 50); //關閉 serverSocket.Close(); } } //關閉 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 下的服務器端代碼
//子線程函數 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; //尋址相關結構 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; } //注意,該處非常重要,取值的正確與否決定關閉scoket后端口是否能正常釋放 BOOL bOptVal = TRUE; int bOptLen = sizeof(BOOL); //設置 socket 選項, SOL_SOCKET 和 SO_REUSEADDR 一起使用, 並且后面的參數如上, 關閉scoket后端口便能正常釋放 setsockopt(aSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&bOptVal, bOptLen); //尋址相關結構 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); //綁定: 注意參數的類型轉換 if (bind(aSocket, (sockaddr *)&aSockaddr, sizeof(aSockaddr)) == SOCKET_ERROR) { char szError[256] = { 0 }; sprintf(szError, "Bind Faild: %d", GetLastError()); AfxMessageBox(szError); return 1; } //監聽 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 }; //接收客戶端內容: 阻塞 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); //發送內容給客戶端 send(serverSocket, "Have Receive The Msg", 50, 0); //關閉 closesocket(serverSocket); } } //關閉 closesocket(aSocket); closesocket(serverSocket); aDlg->GetDlgItemText(IDC_EDIT_LOG, strText); strText += "Have Close!"; aDlg->SetDlgItemText(IDC_EDIT_LOG, strText); //當你使用完Winsock接口后,要調用下面的函數對其占用的資源進行釋放 WSACleanup(); return 0; }
3. 總結
(1) MFC進行編程的確比較簡單, 用的代碼比較少, 又容易管理。唯一不好的地方在於很多細節上的東西在資料上不容易查出來, 關聯性非常緊密, 象 AfxSocketInit() 函數就是,函數的實現里包含着很多不容易理解的類, 並且記錄了非常多的環境信息, 比如創建的線程等等, 這樣在主線程調用后子線程沒有調用執行 CSocket 的操作就會出錯。還有就是有些接口的設計非常離奇, 象 CSocket::Create 的接口就是, 實現上還執行了 CSocket::Bind , 非常不容易被發現。並且MSDN上對 CSocket::Bind 的說明又明顯的提示需要顯示執行 CSocket::Bind 操作。
(2) SDK 編程能理解函數的調用順序和代碼的結構就比較容易,省去了MFC下封裝了不知道什么東西的部分,使得代碼的流程容易控制。但是從上面的例子來看非常明顯的並且不是那么容易理解。不僅僅有很多奇怪的結構(微軟的命名一直如此, 無所雲雲), 並且函數相關太過於緊密, 初學者想一下子熟悉使用並不容易, 對開發者來說代碼管理起來非常麻煩。