WIFI開(kāi)發(fā)計劃 CooCox Cedar@Wuhan 說(shuō)明: 本來(lái)計劃開(kāi)發(fā)SparkFun的WiFly模塊,后來(lái)發(fā)現Arduino官網(wǎng)出了WIFI模塊,就優(yōu)先開(kāi)發(fā)Arduino WIFI Shield。 官方的WIFI Shield資料如下: *:原理圖 *:PCB文件 *:WIFI庫 *:固件代碼(用于實(shí)現IP棧) 以上所有資料均可從Arduino官網(wǎng)下載,需要說(shuō)明的是WIFI Shield的核心芯片是HDG104,進(jìn)入HD官網(wǎng)下載資料時(shí), 需要注冊賬戶(hù),注冊后,不知為何,始終無(wú)法登陸,所有沒(méi)有下載到任何手冊。如果您有HDG104的手冊,可以分享 一下,非常感謝。 因為換WIFI模塊,需要重新熟悉代碼和資料,現制定一份初步計劃 計劃如下:
PS:如果有任何疑問(wèn),請跟帖或email我,3Q Email: renjun@coocox.com Socket軟件包使用說(shuō)明 綜述 本Socket軟件包作為Arduino WIFI shield入門(mén)指導中的一部分,用于說(shuō)明如何在windows下用TCP/IP Socket編程,如何建立鏈接,綁定端口,收發(fā)數據,深入理解這部分,更利于后期學(xué)習HTTP客戶(hù)端和服務(wù)器代碼。這里用Socket寫(xiě)了一個(gè)簡(jiǎn)單的局域網(wǎng)聊天工具,在不同的機器上分別運行客戶(hù)端和服務(wù)器,然后就像QQ一樣聊天。 軟硬件環(huán)境 操作系統: Win7 開(kāi)發(fā)環(huán)境: VS2008 開(kāi)發(fā)語(yǔ)言:C 注:所有代碼在上述環(huán)境中測試通過(guò),理論上在其它環(huán)境(如VC++6.0,windows XP)可以編譯通過(guò),但未測試 目錄結構 使用時(shí)只需要重點(diǎn)關(guān)注紅色字體標注的文件(夾)
使用方法 A:體驗 1:雙擊打開(kāi)server 目錄下exe文件夾下exe文件,啟動(dòng)聊天服務(wù)器 B:開(kāi)發(fā) 1:確保您已經(jīng)正確安裝VS2008 2:雙擊軟件包中的server.vcproj和client.vcproj,打開(kāi)對應的工程文件 3:編譯和調試server和client代碼 發(fā)布 如果想要將生成的exe文件在其他電腦上運行,只復制exe文件過(guò)去,打開(kāi)時(shí),會(huì )出現下面的錯誤 file:///C:/Users/RENJUN/AppData/Local/youdao/ynote/images/6286383C8E6B40F6B8A017F7F6E13A14/R%5DQEAS%257BW%40)%40%5B8QENKYFPA67.jpg 這是因為缺少對應的dll,所以我們需要同時(shí)復制dll,exe和描述文件過(guò)去,別人才能正常運行 這里以打包c(diǎn)lient為例,來(lái)說(shuō)明如何正確發(fā)布軟件 1:新建一個(gè)文件夾,文件夾名字隨便起,這里我們將文件夾命名為dist,該文件夾用于存放exe文件和對應的依賴(lài)文件 2:打開(kāi)軟件包中Debug文件夾,找到 client .exe和 client .exe.embed.manifest文件 如沒(méi)有這些文件,進(jìn)入VS2008,rebuild一下 3:打開(kāi) client.exe.embed.manifest文件,查看exe依賴(lài)的dll 在我電腦上,client.exe.embed.manifest內容如下: 文件組織方式為標準xml結構,在此重點(diǎn)關(guān)注
知道缺少了哪些dll,只需要將這些dll復制到exe文件夾下即可 4:進(jìn)入VS2008安裝目錄,找到Microsoft.VC90.DebugCRT這個(gè)文件夾,在我的電腦上路徑如下: D:\Program Files\Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86 不同電腦,路徑可能不一樣,但大致結構相同 5:將Microsoft.VC90.DebugCRT文件夾整體拷貝到第1步建立的dist文件夾中 6:現在所有的必須的文件都已經(jīng)復制完畢,dist文件夾下目錄組織結構如下
7:檢查dist目錄下client.exe.embed.manifest和Microsoft.VC90.DebugCRT.manifest版本號(9.0.21022.8)是否一致 8:現在您可以將dist文件夾打包,發(fā)送給朋友了 ^_^ 軟件包下載 所有代碼可以從github網(wǎng)站下載: https://github.com/cedar-renjun/Socket_server_client_chat_Example Socket Http WebClient 說(shuō)明:直接用Socket連接百度web服務(wù)器,然后發(fā)送HTTP Get請求來(lái)獲取百度首頁(yè),VS2008工程在上個(gè)帖子中有鏈接,這次直接發(fā)代碼,使用時(shí),將代碼復制到.c文件中,rebuild生成exe文件,然后點(diǎn)擊運行,就可以看到獲取的html網(wǎng)頁(yè)了 #include #include #include // Need to link with Ws2_32.lib #pragma comment (lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 512 #define DEFAULT_PORT 80 WSADATA wsaData; SOCKET ConnectSocket = INVALID_SOCKET; char *HttpRequst ="GET / HTTP/1.1\r\nConnection: close\r\n\r\n"; char RecvBuf[DEFAULT_BUFLEN]; char *IP = "119.75.217.56"; struct sockaddr_in ServerCfg; char WelcomeInfo[] = { "\t===============WIFI Shield Dirver================\r\n" "\tName: HTTP WebClient Example\r\n" "\tHOST : http://www.baidu.com\r\n" "\tIP : 119.75.217.56\r\n" "\tPORT : 80\r\n" "\t=============== CooCox Team =====================\r\n" }; int main(void) { int tmp = 0; int cnt = 0; int iResult = 0; printf(WelcomeInfo); // Initialize Winsock iResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\r\n", iResult); return 1; } printf("Initial WinSock OK\r\r\n"); // Create a SOCKET for connecting to server ConnectSocket = socket(AF_INET, SOCK_STREAM, 0); if (ConnectSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\r\n", WSAGetLastError()); WSACleanup(); return 1; } printf("Create Socket OK\r\n"); //Connect to Server ServerCfg.sin_family = AF_INET; ServerCfg.sin_port = htons(DEFAULT_PORT); ServerCfg.sin_addr.s_addr = inet_addr(IP); printf("Try to connect server\r\n"); iResult = connect(ConnectSocket, (struct sockaddr *)&ServerCfg, sizeof(struct sockaddr)); if (iResult == SOCKET_ERROR) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; } printf("Connect to server!\r\n"); // Send Message to Server printf("Send HTTP Requst\r\n%s", HttpRequst); iResult = send( ConnectSocket, HttpRequst, strlen(HttpRequst), 0); if (iResult == SOCKET_ERROR) { printf("send failed with error: %d\r\n", WSAGetLastError()); } printf("-----------Receive Respon Message---------\r\n"); // Receive Full response message while(1) { // Important,MUST NOT comment this memset(RecvBuf, '\0', DEFAULT_BUFLEN); iResult = recv( ConnectSocket, RecvBuf, DEFAULT_BUFLEN, 0); if (iResult < 0) { printf("receive failed with error: %d\r\n", WSAGetLastError()); } else if(iResult > 0) { puts(RecvBuf); } else { printf("\r\n----------------------------------------------"); printf("\r\nReceive OK\r\n"); break; } } // clean printf("Now Release resource\r\n"); closesocket(ConnectSocket); WSACleanup(); printf("Closed! Example is over\r\n"); printf("Press Enter to exit\r\n"); getch(); //while(1); } Socket Http WebServer #include #include #include #include #include // Need to link with Ws2_32.lib #pragma comment(lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 2048 #define DEFAULT_PORT 27013 char RecvBuf[DEFAULT_BUFLEN]; char SendBuf[DEFAULT_BUFLEN]; struct sockaddr_in ServerCfg; SOCKET ListenSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; char WelcomeInfo[] = { "\t===============WIFI Shield Dirver================\r\n" "\tName: TCP Socket Http Webserver Example\r\n" "\tPORT : 8080\r\n" "\t=============== CooCox Team =====================\r\n" }; char RespondInfo[] = { "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Connnection: close\r\n" "\r\n" "\r\n" "\r\n" "\r\n" " ""================= WIFI Shield Dirver=============== " "Name: TCP Socket Http Webserver Example " "PORT : 27013 " "===================" "CooCox Team===================== " " |
精簡(jiǎn)版inet_addr 說(shuō)明: 今天在移植代碼的時(shí)候,需要一個(gè)客戶(hù)端WiFiClientConnect函數,該函數的一個(gè)參數為服務(wù)器地址,第二個(gè)為端口。 其中服務(wù)器的地址可以是主機地址(如: www.baidu.com ),也可以是IP地址(如:192.168.2.71)。 官方的WIFI驅動(dòng)是C++重載函數寫(xiě)的,可以根據不同的輸入格式來(lái)調用對應的函數,C中沒(méi)有重載,只有手工解析,解決方法有3個(gè): 1:分別采用2個(gè)函數,比如提供WiFiClientConnectVia 和Host WiFiClientConnectViaIP兩個(gè)函數,供用戶(hù)調用 2:像Arduino WiFi驅動(dòng)接口那樣,使用一個(gè)函數,但增加一個(gè)入口參數,用于指明服務(wù)器地址類(lèi)型 3:符合Arduino 官方WIFI的調用習慣,用一個(gè)WiFiClientConnect函數,用戶(hù)只需輸入HOST或IP即可,解析的問(wèn)題,交給函數內部來(lái)處理如 WiFiConnect( "www.baidu.com", 80);WiFiConnect( "192.168.2.71", 80) 最終為了方便用戶(hù),采用了最后一個(gè)方案,這樣用戶(hù)可以更加方便的使用WIFI 我們需要在內部識別出是否是有效的IP地址,如果是的話(huà),則轉換為網(wǎng)絡(luò )地址,這樣就需要一個(gè)識別和轉換函數IPToNetAddr。 IPToNetAddr將傳入的IP地址解析成網(wǎng)絡(luò )格式,比如將“192.168.2.71”分割成4個(gè)10進(jìn)制的數字,存到數組里面 在Q群?jiǎn)?wèn)了網(wǎng)友,有人說(shuō)inet_addr符合要求,找到對應源碼后,發(fā)現它考慮的情況太多了,支持各種進(jìn)制的數字,而我們這里,只需要支持10進(jìn)制就ok,網(wǎng)上搜了一下,貌似沒(méi)人寫(xiě)過(guò)這函數,所以還是自己動(dòng)手寫(xiě)一個(gè)吧 要求: 1:能識別出輸入IP字符串是否有效,有效則返回0,無(wú)效則返回-1 2:輸入錯誤的IP地址,函數不損壞存放結果的數組 錯誤的IP例子: 1:子項過(guò)大:每個(gè)子項應小于255。下面的IP地址第一個(gè)子項為1921,大于255 1921.108.2.71 2:子項過(guò)多:應有4個(gè)子項,由3個(gè).號分隔,下面的IP地址有5個(gè)子項 192.168.2.1.2 測試代碼如下: int main(void) { int retv = 0; //目標IP地址字符串 char IP_OK[] = "192.168.002.071"; char IP_ERROR_1[] = "1921.108.2.71"; char IP_ERROR_2[] = "192.168.2.1.2"; char IP_ERROR_3[] = "293.168.2.1.2"; char IP_ERROR_4[] = "193.168.2.1.300"; //存儲結果數組 uint8_t result[4] = {0, 0, 0, 0}; retv = IPToNetAddr(IP_OK, result); if(-1 == retv) { printf("Test Failure\r\n"); while(1); } retv = IPToNetAddr(IP_ERROR_1, result); if(0 == retv) { printf("Test Failure\r\n"); while(1); } retv = IPToNetAddr(IP_ERROR_2, result); if(0 == retv) { printf("Test Failure\r\n"); while(1); } retv = IPToNetAddr(IP_ERROR_3, result); if(0 == retv) { printf("Test Failure\r\n"); while(1); } retv = IPToNetAddr(IP_ERROR_3, result); if(0 == retv) { printf("Test Failure\r\n"); while(1); } printf("Test OK, Press any key to exit\r\n"); getchar(); return (0); } 完整代碼如下: CODE: #include "stdafx.h" #include #include //#include #include typedef unsigned char uint8_t; typedef signed char int8_t; typedef unsigned int uint32_t; typedef signed int int32_t; int IPToNetAddr(char * IPStr, uint8_t * NetAddr); int main(void) { int retv = 0; //目標IP地址字符串 char IP_OK[] = "192.168.002.071"; char IP_ERROR_1[] = "1921.108.2.71"; char IP_ERROR_2[] = "192.168.2.1.2"; char IP_ERROR_3[] = "293.168.2.1.2"; char IP_ERROR_4[] = "193.168.2.1.300"; //存儲結果數組 uint8_t result[4] = {0, 0, 0, 0}; retv = IPToNetAddr(IP_OK, result); if(-1 == retv) { printf("Test Failure\r\n"); while(1); } retv = IPToNetAddr(IP_ERROR_1, result); if(0 == retv) { printf("Test Failure\r\n"); while(1); } retv = IPToNetAddr(IP_ERROR_2, result); if(0 == retv) { printf("Test Failure\r\n"); while(1); } retv = IPToNetAddr(IP_ERROR_3, result); if(0 == retv) { printf("Test Failure\r\n"); while(1); } retv = IPToNetAddr(IP_ERROR_4, result); if(0 == retv) { printf("Test Failure\r\n"); while(1); } printf("Test OK, Press any key to exit\r\n"); getchar(); return (0); } int IPToNetAddr(char * IPStr, uint8_t * NetAddr) { uint32_t _IP[4] = {0, 0, 0, 0}; uint8_t cnt = 0; uint8_t idx = 0; char _str = NULL; //檢查IP和接收緩存區是否有效 if(IPStr == NULL || NetAddr == NULL) { return (-1); } while((_str = *IPStr++) != NULL) { if(_str == '.') { //清空計數器,該計數器最大值為3 cnt = 0; if(_IP[idx] > 255) { return (-1); } idx = idx + 1; } else if(_str >= '0' && _str <= '9') //檢查是否為有效字符 '0' --> '9' { if(cnt++ < 3 && idx <= 3) { _IP[idx] = (10 * _IP[idx]) + (_str - '0'); } else { return (-1); } } else { return (-1); } } if(_IP[idx] > 255) { return (-1); } //復制數據到結果緩存區 NetAddr[0] = (uint8_t)_IP[0]; NetAddr[1] = (uint8_t)_IP[1]; NetAddr[2] = (uint8_t)_IP[2]; NetAddr[3] = (uint8_t)_IP[3]; return (0); } |
學(xué)習學(xué)習! |