UDP聊天程序学习笔记

来源:互联网 发布:python 计算技术指标 编辑:程序博客网 时间:2024/05/19 17:49

近日看了孙鑫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 

试试
BOOL bNewBehavior = FALSE;
DWORD dwBytesReturned;
WSAIoctl(m_Sckt, SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL);
其中 m_Sckt 是UDP SOCKET

程序代码

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;//定义日历时间结构体tstruct tm * tm;//定义时间结构体变量tmt= 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;//定义地址结构体变量addrSrvaddrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//设置客户端地址为127.0.0.1addrSrv.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 functionsprintf("设置成功!\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);//将从键盘输入的数据存入sendBuftime();    printf("%s",ncBuf);printf("%s \n",dataBuf);strcat(sendBuf,dataBuf);sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrSrv,len);//将从键盘输入的数据存入sendBufprintf("请等待对方回话... \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;//定义日历时间结构体tstruct tm * tm;//定义时间结构体变量tmt= 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;//定义地址结构体变量addrSrvaddrSrv.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;//定义地址结构体变量addrClientint 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]={" "};//初始化sendBufstrcat(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);//将从键盘输入的数据存入dataBuftime();printf("%s",ncBuf);printf("%s \n",dataBuf);strcat(sendBuf,dataBuf);//将dataBuf中的值放入sendBufsendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len);//将从键盘输入的数据存入sendBufprintf("请等待对方回话... \n");}closesocket(sockSrv);//关闭套接字,释放为程序调用的资源WSACleanup();//WSACleanup()终止调用system("pause");//等待用户关闭界面}

执行结果


原创粉丝点击