近日看了孫鑫VC++視頻,14及15課,瞭解了網絡編程相關內容,並從網上找了相關聊天程序實現的代碼進行學習。
UDP通信實例源文件下載地址:http://download.csdn.net/detail/fengdongjingquan/6460787
背景知識
Socket 通信原理(Android客戶端和服務器以TCP&&UDP方式互通)
MFC相關背景知識:MFC的運行機制 以及 MFC中的DC、CDC、HDC、句柄、設備上下文
關於項目轉換:VC 6.0工程向VS 2010轉換的問題
網絡編程基礎:WS網絡開發——第1篇
相關問題及解答
疑問及解答:http://bbs.csdn.net/topics/330194207
udp 服務端如何知道客戶端socket?
如果是tcp,通過accept得到一個socket,然後向客戶端發信息是就用這個socket,但是udp沒有accept,當我要分發消息到多個客戶端時要如何做?
你說的 udp服務端如何知道客戶端socket,應該就是怎麼知道客戶端的IP地址和UDP端口。一般來說有兩種方式:
一種是客戶端發消息顯示顯式地告訴服務器IP地址和端口,消息內容就包括IP地址和UDP端口。
另外一種就是隱式的,服務器從收到的包的頭部中得到包的源IP地址和端口。
通常udp服務端根本不需要知道客戶端的socket,它直接建立一個socket用於發送即可,udp通信的關鍵只在於IP和端口。多個客戶端如果需要點到點分發,必須給服務端socket循環設置每個客戶端的IP併發出,但更常用的是廣播分發,服務端socket設定一個224.X.X.X的廣播地址並始終向它發送,每個客戶端建立的socket只需要綁定這個廣播地址便可以收到。
孫鑫程序的源碼:此部分由於編譯環境的改變(VC 6.0--->VS 2010)對代碼進行了一些改動。從網上也看到了對於孫鑫編程序習慣的一些批評,由於初學,只是以能運行出結果爲主。
關於代碼轉換:http://bbs.csdn.net/topics/380020531
由於未加載相應的庫可能會出現類似於“error LNK2019: 無法解析的外部符號_WinMain@16,該符號在函數 ___tmainCRTStartup 中被引用”這樣的錯誤。需要加載相應的庫,在VS 2010中加載方式爲:
#pragma comment(lib,"ws2_32.lib")
同時,在調試過程中也可能出現10054錯誤:
解決辦法:http://blog.csdn.net/ccnucjp8136/article/details/4515002
http://bbs.csdn.net/topics/90240718 --->#14
程序代碼
UdpClient:
#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6666);
sendto(sockClient,"Hello everybody!",strlen("Hello everybody!")+1,0,
(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
closesocket(sockClient);
WSACleanup();
}
UdpSrv:
#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6666);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
char recvBuf[100]={0};
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
printf_s("%s\n",recvBuf);
closesocket(sockSrv);
WSACleanup();
}
運行的時候不知道爲什麼不能同時對兩個項目進行編譯執行(不能像視頻中的那樣分別選擇兩個項目,先啓動服務器端再啓動客戶端)。可以通過編譯生成的Debug文件下的.exe文件,先執行UdpSrv.exe,再找到UdpClient項目下Debug中的UdpClient.exe,這樣分別執行即可看到視頻中的結果。即,執行完客戶端程序後,服務器顯示“hello”。
另外,在CSDN上面找到一個類似的程序,而且有詳細的註釋說明,一併貼上來。
資源地址:http://download.csdn.net/detail/sh167779/4245364
UdpClient:
#include <Winsock2.h>
#include <stdio.h>
#include <time.h>//聲明應用的頭文件
#pragma comment(lib,"ws2_32.lib")//引用ws2_32.lib庫。VC6.0不用此命令!!!
void time()//時間子函數,用於獲取當前時間並顯示出來
{
time_t t;//定義日曆時間結構體t
struct tm * tm;//定義時間結構體變量tm
t= time(NULL);//獲取1900年至現在的秒數
tm=localtime(&t);//將秒數轉換爲當前時間,默認爲ANSI C標準時間格式
printf("%04d年%02d月%02d日 %02d:%02d:%02d ",tm->tm_year+1900,
tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
} //將ANSI C標準時間格式轉換爲中國常用的表示方式並顯示出來
void main()//主函數,實現通信功能
{//加載套接字庫
WORD wVersionRequested;//wVersionRequested參數用於指定準備加載的Winsock庫的版本。
//高位字節指定所需要的Winsock庫的副版本,而低位字節則是主版本。
WSADATA wsaData;//參數是指向WSADATA結構的指針,WSAStartup用其加載的庫版本有關的信息填在這個結構中。
int err;
wVersionRequested = MAKEWORD( 1, 1 ); // Winsock版本(1.1)
err = WSAStartup( wVersionRequested, &wsaData ); //加載套接字庫;進行套接字庫版本的協商
if ( err != 0 ) {
return;
}//如果不能找到合適的Winsock,程序退出
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {//判斷版本是否是1.1.若不是則WSACleanup()終止調用
WSACleanup();//WSAData的wVersion成員中將包含你的應用程序應該使用的版本,
//它是DLL所支持的最高版本與請求版本中較小的那個。
//對於每一個WSAStartup的成功調用(成功加載WinSock DLL後),
//在最後都對應一個WSACleanUp調用,以便釋放爲該應用程序分配的資源。
return; //未找到合適版本的Winsock就返回
}
printf("歡迎使用本軟件! \n");
SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);//該函數接收三個參數。
//第一個參數af指定地址族,對於TCP/IP協議的套接字,它只能是AF_INET(也可寫成PF_INET)。
//第二個參數指定Socket類型,對於1.1版本的Socket,它只支持兩種類型的套接字,SOCK_STREAM指定產生流式套接字,SOCK_DGRAM產生數據報套接字。
//第三個參數是與特定的地址家族相關的協議,如果指定爲0,那麼它就會根據地址格式和套接字類別,自動爲你選擇一個合適的協議。
SOCKADDR_IN addrSrv;//定義地址結構體變量addrSrv
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//設置客戶端地址爲127.0.0.1
addrSrv.sin_family=AF_INET;//指定該地址家族,在這裏必須設爲AF_INET。
addrSrv.sin_port=htons(5500); //(u_short)從主機字節序轉換爲網絡字節序 ,注意要用1024以上的端口號。
char sayBuf[]={"說:\n "};
char ncBuf[20];//定義暱稱數組
char dataBuf[80];//定義說話內容數組
char sendBuf[100];//定義發送數組
char recvBuf[100];//定義接收數組
char tempBuf[200];//定義臨時數組
printf("請設置你的暱稱:");
gets(ncBuf);
strcat(ncBuf,sayBuf);//StrCat function
//Appends one string to another.
//Note Do not use. See Remarks for alternative functions
printf("設置成功!\n");
int i;
i=strlen(ncBuf)+1;//獲取ncBuf的長度
int len=sizeof(SOCKADDR);//定義整形變量len,存放SOCKADDR的長度
while(1)//死循環,使程序一直處於監聽狀態
{
char sendBuf[100]={" "};
strcat(sendBuf,ncBuf);
printf("請輸入聊天內容或按z鍵終止聊天: ");
gets(dataBuf);//將從鍵盤輸入的數據存入sendBuf
time();
printf("%s",ncBuf);
printf("%s \n",dataBuf);
strcat(sendBuf,dataBuf);
sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrSrv,len);//將從鍵盤輸入的數據存入sendBuf
printf("請等待對方回話... \n");
recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv,&len);//接受數據
if('z'==recvBuf[0])
{
sendto(sockClient,"通話已終止,請退出!",strlen("通話已終止,請退出!")+1,0,(SOCKADDR*)&addrSrv,len);
printf("通話已終止,請退出... \n");
break;
}//判斷收到的數據是否是z,若是則發送回一個z,並終止程序。
sprintf(tempBuf,"(%s) %s ",inet_ntoa(addrSrv.sin_addr),recvBuf);//inrt_ntoa將in_addr結構體類型的參數化成點分十進制的IP地址字符串
//將recvBuf、IP地址字符串和"%s 說: \n %s"格式化到tempBuf中。
time();//調用時間子函數,顯示當前時間
printf("主機");
printf(" %s\n",tempBuf);//顯示tempBuf中的內容
}
closesocket(sockClient);//關閉套接字,釋放爲程序調用的資源
WSACleanup();//WSACleanup()終止調用
system("pause");//等待用戶關閉界面
}
UdpSrv:
#include <Winsock2.h>
#include <stdio.h>
#include <time.h>//聲明應用的頭文件
#pragma comment(lib,"ws2_32.lib")//引用ws2_32.lib庫。VC6.0不用此命令!!!
void time()//時間子函數,用於獲取當前時間並顯示出來
{
time_t t;//定義日曆時間結構體t
struct tm * tm;//定義時間結構體變量tm
t= time(NULL);//獲取1900年至現在的秒數
tm=localtime(&t);//將秒數轉換爲當前時間,默認爲ANSI C標準時間格式
printf("%04d年%02d月%02d日 %02d:%02d:%02d ",tm->tm_year+1900,
tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
} //將ANSI C標準時間格式轉換爲中國常用的表示方式並顯示出來
void main()//主函數,實現通信功能
{//加載套接字庫
WORD wVersionRequested;//wVersionRequested參數用於指定準備加載的Winsock庫的版本。
//高位字節指定所需要的Winsock庫的副版本,而低位字節則是主版本。
WSADATA wsaData;//參數是指向WSADATA結構的指針,WSAStartup用其加載的庫版本有關的信息填在這個結構中。
int err;
wVersionRequested = MAKEWORD( 1, 1 ); // Winsock版本(1.1)
err = WSAStartup( wVersionRequested, &wsaData ); //加載套接字庫;進行套接字庫版本的協商
if ( err != 0 ) {
return;
}//如果不能找到合適的Winsock,程序退出
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {//判斷版本是否是1.1.若不是則WSACleanup()終止調用
WSACleanup();//WSAData的wVersion成員中將包含你的應用程序應該使用的版本,
//它是DLL所支持的最高版本與請求版本中較小的那個。
//對於每一個WSAStartup的成功調用(成功加載WinSock DLL後),
//在最後都對應一個WSACleanUp調用,以便釋放爲該應用程序分配的資源。
return; //未找到合適版本的Winsock就返回
}
printf("歡迎使用本軟件! \n");
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);//該函數接收三個參數。
//第一個參數af指定地址族,對於TCP/IP協議的套接字,它只能是AF_INET(也可寫成PF_INET)。
//第二個參數指定Socket類型,對於1.1版本的Socket,它只支持兩種類型的套接字,SOCK_STREAM指定產生流式套接字,SOCK_DGRAM產生數據報套接字。
//第三個參數是與特定的地址家族相關的協議,如果指定爲0,那麼它就會根據地址格式和套接字類別,自動爲你選擇一個合適的協議。
SOCKADDR_IN addrSrv;//定義地址結構體變量addrSrv
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//(u_long)從主機字節序轉換爲網絡字節序(INADDR_ANY爲0)
addrSrv.sin_family=AF_INET;//指定該地址家族,在這裏必須設爲AF_INET。
addrSrv.sin_port=htons(5500); //(u_short)從主機字節序轉換爲網絡字節序 ,注意要用1024以上的端口號。
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//綁定到本地地址和端口上。
SOCKADDR_IN addrClient;//定義地址結構體變量addrClient
int len=sizeof(SOCKADDR);//定義整形變量len,存放SOCKADDR的長度
char sayBuf[]={"說:\n "};
char ncBuf[20];//定義暱稱數組
char dataBuf[80];//定義說話內容數組
char sendBuf[100];//定義發送收數組
char recvBuf[100];//定義接收數組
char tempBuf[200];//定義臨時數組
printf("請設置你的暱稱:");
gets(ncBuf);
strcat(ncBuf,sayBuf);
printf("設置成功!\n");
int i;
i=strlen(ncBuf)+1;//獲取ncBuf的長度
while(1)//死循環,使程序一直處於監聽狀態
{
char sendBuf[100]={" "};//初始化sendBuf
strcat(sendBuf,ncBuf);//將ncBuf中的值放入sendBuf
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);//接受數據
if('z'==recvBuf[0])
{
sendto(sockSrv,"通話已終止,請退出!",strlen("通話已終止,請退出!")+1,0,(SOCKADDR*)&addrClient,len);
printf("通話已終止,請退出... \n");
break;
}//判斷收到的數據是否是q,若是則發送回一個q,並終止程序。
sprintf(tempBuf,"(%s) %s",inet_ntoa(addrClient.sin_addr),recvBuf);//inrt_ntoa將in_addr結構體類型的參數化成點分十進制的IP地址字符串
//將recvBuf、IP地址字符串和"%s 說: \n %s"格式化到tempBuf中。
time();//調用時間子函數,顯示當前時間
printf(" %s\n",tempBuf);//顯示tempBuf中的內容
printf("請輸入聊天內容或按z鍵終止聊天: ");
gets(dataBuf);//將從鍵盤輸入的數據存入dataBuf
time();
printf("%s",ncBuf);
printf("%s \n",dataBuf);
strcat(sendBuf,dataBuf);//將dataBuf中的值放入sendBuf
sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len);//將從鍵盤輸入的數據存入sendBuf
printf("請等待對方回話... \n");
}
closesocket(sockSrv);//關閉套接字,釋放爲程序調用的資源
WSACleanup();//WSACleanup()終止調用
system("pause");//等待用戶關閉界面
}
執行結果