您的位置:首页 > 理论基础 > 计算机网络

UDP聊天程序学习笔记

2013-10-23 21:44 369 查看
近日看了孙鑫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;//定义日历时间结构体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");//等待用户关闭界面
}

执行结果

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息