UDP聊天程序學習筆記

近日看了孫鑫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");//等待用戶關閉界面
}

執行結果


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章