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");//等待用户关闭界面
}

执行结果


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