| 80年代初,美國政府的高級研究工程機構(gòu)(ARPA)給加利福尼亞大學(xué)Berkeley分校提供了資金,為實現(xiàn)UNIX操作系統(tǒng)下的TCP/IP協(xié)議而開發(fā)了一個API(Application Programming Interface),稱為Socket接口(套接字)。Socket接口是TCP/IP網(wǎng)絡(luò)最為通用的API。 90年代初,由Microsoft聯(lián)合了其他幾家公司共同制定了一套Windows下的網(wǎng)絡(luò)編程接口,即Windows Sockets規(guī)范。Windows Sockets規(guī)范是一套開放的、支持多種協(xié)議的Windows網(wǎng)絡(luò)編程接口,并已成為Windows網(wǎng)絡(luò)編程的事實上的標(biāo)準(zhǔn)。目前,在實際應(yīng)用中的Windows Sockets規(guī)范主要有1.1版和2.0版。2.0版可以支持多協(xié)議,有良好的向后兼容性,任何使用1.1版的源代碼,二進制文件,應(yīng)用程序都可以不加修改在2.0規(guī)范下使用。 Socket實際上在計算機中提供了一個通信端口,可以通過這個端口與任何一個具有Socket接口的計算機通信。應(yīng)用程序在網(wǎng)絡(luò)上傳輸/接收的信息都通過這個Socket接口來實現(xiàn)的。在應(yīng)用開發(fā)中可以像使用文件句柄一樣來對Socket句柄進行讀/寫操作。目前可以使用兩種套接口,即流式套接字(SOCK_STREAM)和數(shù)據(jù)報套接字(SOCK_DGRAM)。流式套接字提供了一個面向連接的、可靠的、數(shù)據(jù)無錯的、無重復(fù)發(fā)送的及按發(fā)送順序接收數(shù)據(jù)的服務(wù);數(shù)據(jù)報套接字提供不可靠的、無連接的數(shù)據(jù)報傳輸服務(wù)。 套接字可分為阻塞套接字和非阻塞套接字。阻塞套接字是指執(zhí)行此套接字的網(wǎng)絡(luò)調(diào)用時,直到成功才返回,否則一直阻塞在此網(wǎng)絡(luò)調(diào)用上;而非阻塞套接字是指執(zhí)行此套接字的網(wǎng)絡(luò)調(diào)用時,不管是否執(zhí)行成功,都立即返回。實際上非阻塞套接字是用得最多的。 C/S模型,即客戶機/服務(wù)器模型,是一種非對稱式編程模式。對于這種模式而言,其中一部分需要作為服務(wù)端,用來響應(yīng)并為客戶提供固定的服務(wù);另一部分則作為客戶端用來向服務(wù)端提出請求或要求某種服務(wù)。在實際應(yīng)用中,程序可以同時包含客戶端和服務(wù)端。 Microsoft Visual C++提供了十分完整的Windows Sockets庫函數(shù),并且對這些庫函數(shù)進行了一系列封裝,繼而產(chǎn)生了CAsynSocket、CSocket、CSocketFile等類,它們封裝著有關(guān)Socket的各種功能,使網(wǎng)絡(luò)編程變得更加簡單。但是為了更好理解Winsock的通信原理,這里將介紹怎樣使用底層的API函數(shù)來實現(xiàn)網(wǎng)絡(luò)通訊。 面向連接協(xié)議的通信過程如下:服務(wù)端和客戶端都必須建立通信套接字,而服務(wù)端套接字應(yīng)先進入監(jiān)聽狀態(tài),然后客戶端套接字發(fā)出連接請求,服務(wù)端套接字收到連接請求后,建立一個新套接字與客戶端套接字進行通信,原來負(fù)責(zé)監(jiān)聽的套接字仍進行監(jiān)聽,如果再收到其它客戶端套接字的連接請求,則再建立一個新套接字與之通信。通信完畢后斷開連接,關(guān)閉相應(yīng)套接字。 (1) 初始化通信端口??梢栽诔绦蛳?qū)е刑砑覹indows Sockets支持,或者直接添加代碼: | #include <afxsock.h> if (!AfxSocketInit()) { ????AfxMessageBox("Windows 通信端口初始化失敗!"); } | (2) 初始化Windows Sockets DLL。目前Winsock有兩個版本,版本號分別為1.1和2.2,對應(yīng)參數(shù)為0x101和0x202。 | WSADATA wsaData; if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0) { ????AfxMessageBox("加載Windows Sockets DLL失敗!"); ????WSACleanup(); } | (3) 創(chuàng)建流式套接字。 | 套接字族: | | ? | AF_UNIX: | UNIX內(nèi)部協(xié)議族 | | AF_INET: | Iternet協(xié)議 | | AF_NS: | XeroxNs協(xié)議 | | AF_IMPLINK: | IMP鏈接層 | | | 套接字類型: | | ? | SOCK_STREAM: | 流式套接字 | | SOCK_DGRAM: | 數(shù)據(jù)報套接字 | | SOCK_RAW: | 原始套接字 | | SOCK_SEQPACKET: | 定序分組套接字 | | | ? | | SOCKET m_Socket; m_Socket = INVALID_SOCKET; if ((m_Socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { ????AfxMessageBox("創(chuàng)建套接字失敗!"); } | (4) 服務(wù)端綁定端口。端口號范圍:1024到65535,低于1024的端口對應(yīng)著因特網(wǎng)上的一些常見服務(wù)。 struct sockaddr { ????u_short sa_family;???????????????// 地址族地址族 address family ????address family char sa_data[14]; // 14字節(jié)的協(xié)議地址 up to 14 bytes of direct address }; typedef struct sockaddr SOCKADDR; typedef struct sockaddr *PSOCKADDR; typedef struct sockaddr FAR *LPSOCKADDR; struct sockaddr_in { ????short sin_family; ???????// 地址族 ????u_short sin_port;????????// 端口號 ????struct in_addr sin_addr; // IP地址 ????char sin_zero[8]; ???????// 填充0 }; typedef struct sockaddr_in SOCKADDR_IN; typedef struct sockaddr_in *PSOCKADDR_IN; typedef struct sockaddr_in FAR *LPSOCKADDR_IN; 字節(jié)順序轉(zhuǎn)換函數(shù): ????htons():"Host to Network Short" ????htonl():"Host to Network long" ????ntohs():"Network to Host Short" ????ntohl():"Network to Host Long" | SOCKADDR_IN m_saAddr; u_short ????m_nPort = 20048; ???????????????// 端口號 ZeroMemory(&m_saAddr, sizeof(m_saAddr)); m_saAddr.sin_family ?????= AF_INET; m_saAddr.sin_port ???????= htons(m_nPort); ?// 如果此值為0,系統(tǒng)將隨機選擇一個未被使用的端口號 m_saAddr.sin_addr.s_addr = INADDR_ANY; ?????// 填入本機IP地址 if (bind(m_Socket, (LPSOCKADDR) &m_saAddr, sizeof(m_saAddr)) == SOCKET_ERROR) { ????AfxMessageBox("綁定端口失敗!"); } | (5) 服務(wù)端監(jiān)聽端口。 | #define MAX_BACKLOG 5 if (listen(m_Socket, MAX_BACKLOG) == SOCKET_ERROR) { ????AfxMessageBox("監(jiān)聽失敗!"); } | (6) 客戶端請求連接。 | DWORD m_dwServerIP; char m_sServerIP[] = "127.0.0.1"; // 主機IP地址 u_short m_nServerPort = 20048; ???// 主機端口號 if ((m_dwServerIP = inet_addr(m_sServerIP)) == INADDR_NONE) { ????AfxMessageBox("無法獲取主機IP!"); ????return; } m_saAddr.sin_family = AF_INET; m_saAddr.sin_port = htons(m_nServerPort); m_saAddr.sin_addr.s_addr = m_dwServerIP; if (connect(m_Socket, (LPSOCKADDR) &m_saAddr, sizeof(m_saAddr))) { ????AfxMessageBox("連接服務(wù)器失敗!"); } | (7) 注冊網(wǎng)絡(luò)事件。 | 網(wǎng)絡(luò)事件定義: | | ? | FD_READ: | 網(wǎng)絡(luò)數(shù)據(jù)包到達(dá) | | FD_WRITE: | 發(fā)送網(wǎng)絡(luò)數(shù)據(jù) | | FD_OOB: | OOB數(shù)據(jù)到達(dá) | | FD_ACCEPT: | 收到連接請求 | | FD_CONNECT: | 已建立連接 | | FD_CLOSE: | 斷開連接 | | FD_QOS: | 服務(wù)質(zhì)量(QoS)發(fā)生變化 | | FD_GROUP_QOS: | 保留事件 | | FD_ROUTING_INTERFACE_CHANGE: | 指定地址的路由接口發(fā)生變化 | | FD_ADDRESS_LIST_CHANGE: | 本地地址變化 | | | ? | | #define WM_NETWORK_EVENT WM_USER + 101 if (WSAAsyncSelect(m_Socket, m_hWnd, WM_NETWORK_EVENT, FD_ACCEPT | FD_READ | FD_CLOSE) == SOCKET_ERROR) { ????AfxMessageBox("注冊網(wǎng)絡(luò)事件失敗!"); } | (8) 處理網(wǎng)絡(luò)事件。 | afx_msg LRESULT OnNetworkEvent(WPARAM wParam, LPARAM lParam); ON_MESSAGE(WM_NETWORK_EVEN, OnNetworkEvent) LRESULT OnNetworkEvent(WPARAM wParam, LPARAM lParam) { ????switch (WSAGETSELECTEVENT(lParam)) ????{ ????case FD_ACCEPT: ????????// 接受連接請求 ????????break; ????case FD_READ: ????????// 接收數(shù)據(jù) ????????break; ????case FD_CLOSE: ????????// 斷開連接 ????????break; ????} ????return 0L; } | (9) 服務(wù)端接受連接請求。(采用上敘方法處理這里的網(wǎng)絡(luò)事件,只是不需要處理FD_ACCEPT事件。) | #define WM_CLIENT_EVENT WM_USER + 102 SOCKET m_hClientSocket[MAX_BACKLOG]; SOCKADDR_IN m_saClientAddr[MAX_BACKLOG]; for (int i = 0; i < MAX_BACKLOG; i++) { ????ZeroMemory(&m_saClientAddr[i], sizeof(m_saClientAddr[i])); ????m_hClientSocket[i] = INVALID_SOCKET; } BOOL Accept(void) { ????CString sClientIP; ????int nLength = sizeof(SOCKADDR); ????for (int i = 0; i < MAX_BACKLOG; i++) ????{ ????????if (m_hClientSocket[i] == INVALID_SOCKET) ????????{ ????????????m_hClientSocket[i] = socket(PF_INET, SOCK_STREAM, 0); ????????????m_hClientSocket[i] = accept(m_Socket, (LPSOCKADDR) &m_saClientAddr[i], (LPINT) &nLength); ????????????WSAAsyncSelect(m_hClientSocket[i], m_hWnd, WM_CLIENT_EVENT, FD_READ | FD_CLOSE); ????????????// 獲取客戶端IP ????????????sClientIP.Format("%d.%d.%d.%d", ????????????????m_saClientAddr[i].sin_addr.S_un.S_un_b.s_b1, ????????????????m_saClientAddr[i].sin_addr.S_un.S_un_b.s_b2, ????????????????m_saClientAddr[i].sin_addr.S_un.S_un_b.s_b3, ????????????????m_saClientAddr[i].sin_addr.S_un.S_un_b.s_b4); ????????????AfxMessageBox(sClientIP); ????????????return TRUE; ????????} ????} ????AfxMessageBox("連接資源不足!"); ????return FALSE; } | (10) 客戶端斷開連接。 | void ClientClose(WPARAM wParam) { ????for (int i = 0; i < MAX_BACKLOG; i++) ????{ ????????if (m_ClientSocket[i] == wParam) ????????{ ????????????closesocket(m_hClientSocket[i]); ????????????m_hClientSocket[i] = INVALID_SOCKET; ????????} ????} } | (11) 讀取客戶端數(shù)據(jù)。 | BOOL ClientRead(WPARAM wParam) { ????int nBytesRead; ????int nBufferLength; ????int nEnd; ????int nSpaceRemaining; ????char chIncomingDataBuffer[4096]; ????nEnd = 0; ????nBufferLength = sizeof(chIncomingDataBuffer); ????nSpaceRemaining = sizeof(chIncomingDataBuffer); ????nSpaceRemaining -= nEnd; ????for (int i = 0; i < MAX_BACKLOG; i++) ????{ ????????if (m_hClientSocket[i] == wParam) ????????{ ????????????nBytesRead = recv(m_hClientSocket[i], (LPSTR) (chIncomingDataBuffer + nEnd), nSpaceRemaining, 0); ????????????nEnd += nBytesRead; ????????????if (nBytesRead == SOCKET_ERROR) ????????????{ ????????????????AfxMessageBox("讀取數(shù)據(jù)出錯!") ????????????????return FALSE; ????????????} ????????????chIncomingDataBuffer[nEnd] = '/0'; ????????????if (lstrlen(chIncomingDataBuffer) != 0) ????????????{ ????????????????AfxMessageBox(chIncomingDataBuffer); ????????????} ????????} ????} ????return TRUE; } | (12) 讀取服務(wù)端數(shù)據(jù)。 | BOOL Read(void) { ????int nBytesRead; ????int nBufferLength; ????int nEnd; ????int nSpaceRemaining; ????char chIncomingDataBuffer[4096]; ????nEnd = 0; ????nBufferLength = sizeof(chIncomingDataBuffer); ????nSpaceRemaining = sizeof(chIncomingDataBuffer); ????nSpaceRemaining -= nEnd; ????nBytesRead = recv(m_Socket, (LPSTR) (chIncomingDataBuffer + nEnd), nSpaceRemaining, 0); ????nEnd += nBytesRead; ??? if (nBytesRead == SOCKET_ERROR) ????{ ????????AfxMessageBox("讀取數(shù)據(jù)出錯!") ????????return FALSE; ????} ????chIncomingDataBuffer[nEnd] = '/0'; ????if (lstrlen(chIncomingDataBuffer) != 0) ????{ ????????AfxMessageBox(chIncomingDataBuffer); ????} ????return TRUE; } | (13) 發(fā)送數(shù)據(jù)。 | BOOL Send(SOCKET Socket, CString sSendData) { ????if (Socket == INVALID_SOCKET) ????{ ????????AfxMessageBox("套接字不可用!"); ????????return FALSE; ????} ????if (send(Socket, sSendData, sSendData.GetLength(), 0) == SOCKET_ERROR) ????{ ????????AfxMessageBox("發(fā)送數(shù)據(jù)失敗!"); ????????return FALSE; ????} ????return TRUE; } | (14) 關(guān)閉套接字。 | if (m_Socket != INVALID_SOCKET) { ????closesocket(m_Socket); } m_Socket = INVALID_SOCKET; WSACleanup(); | 希望本文能夠?qū)W(wǎng)絡(luò)編程的初學(xué)者有所幫助。文中沒有把服務(wù)端和客戶端徹底分開,有些代碼是它們共有的部分,而有的代碼則專屬于服務(wù)端或客戶端,閱讀時請注意相應(yīng)的文字說明。由于時間匆忙,文中給出的代碼都沒有經(jīng)過調(diào)試,難免有錯誤和不足之處,敬請大家原諒,同時也真心希望您能指出和糾正。 |