1 实验类型
验证型实验
2 实验目的
1. 进一步理解Winsock API 的调用方法
2. 了解UDP 协议的工作原理
3. 掌握UDP 服务端程序和客户端程序的编写流程
4. 熟悉程序的调试方法。
3 背景知识
1. Winsock 编程模型
Winsock 编程的主要模型分为流套接字编程模型和数据报套接字编程模型两类,主要区
别在于:前者提供双向的、有序的、无重复并且无记录边界的数据流服务,即采用有连接的
数据传输服务,保证数据可靠到达;后者也支持双向数据流,但不能保证数据的可靠、有序
和无重复,它保留了记录边界,是一种无连接、不可靠的数据传输模型。
2. 数据报套接字编程模型
数据报套接字使用UDP 协议进行数据的传输,是一种无连接的数据传输模型,编程过程
相对简单,采用客户/服务器(C/S)结构进行设计。
在数据报套接字编程模型中,客户端发送数据(也称发送端),服务器端接收数据(也
称接收端)。实际上,由于数据报套接字编程模型也支持双向数据传递,因此,服务器端和
客户端的概念已经比较模糊。为了说明数据报套接字编程模型的工作原理,这里仍然沿用这
两个概念。
数据报套接字的服务进程和客户进程不需要在通信前建立连接,仅需要创建各自的套接
字,因此程序设计过程相当简单,简述如下:
接收端:1、创建数据报套接字;2、绑定本机地址和端口;3、等候接收数据;4、使用
完成后关闭套接字。
发送端:1、创建数据报套接字;2、向指定地址和端口发送数据;3、使用完成后关闭
套接字。
3. 数据报套接字编程使用的函数
1) 创建套接字函数socket()
SOCKET socket(int af,int type,int protocol);
由于采用数据报套接字进行数据传输,因此type 参数必须设置为SOCK_DGRAM,
protocol 参数必须设置为IPPROTO_UDP
2) 绑定本地地址到所创建的套接字函数bind()
int bind(SOCKET s,const struct sockaddr* name,int namelen);
在实际编程时可以省略该函数,系统会自动绑定
3) 接收数据函数recvfrom()
int recvfrom(SOCKET s,char* buf,int len,int flags,
struct sockaddr* from,int* fromlen);
4) 发送数据函数sendto()
int sendto(SOCKET s,const char* buf,int len,int flags,
const struct sockaddr* to,int* tolen);
5) 关闭套接字函数closesocket()
int closesocket(SOCKET s);
1. 数据报套接字编程模型时序和流程
为便于理解数据报套接字模型下的编程过程,用时序图表述如下(请注意,时序图不同于程序流程图,它只是对完成一次通信过程进行原理性描述的手段。
4 实验内容
1、认真理解数据报套接字编程模型,仔细阅读并调试运行UDPserve.cpp 程序和
UTPClient.cpp 程序源代码,分析在服务端和客户端分别使用了哪些Winsock API 函数,写
入实验报告;
2、修改UDPServer 和UDPClient 程序,设计一个简单的UDP 通信程序,并达到以下要
求:
○1 双方能相互发送数据,并显示接收到的数据。
○2 当收到对方的数据为“bye”时,能退出程序。
代码如下:
1.服务器端代码:
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#define BUFFER_SIZE 1024
int main(){
WSADATA WSAData;
char receBuf[BUFFER_SIZE];
char Response[]="";
if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0){
printf("初始化失败");
exit(1);
}
SOCKET sockServer=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sockServer == INVALID_SOCKET)
{
printf("Failed socket() \n");
return 0;
}
SOCKADDR_IN addr_Server; //服务器的地址等信息
addr_Server.sin_family=AF_INET;
addr_Server.sin_port=htons(4567);
addr_Server.sin_addr.S_un.S_addr=INADDR_ANY;
if(bind(sockServer,(SOCKADDR*)&addr_Server,sizeof(addr_Server))==SOCKET_ERROR ){//服务器与本地地址绑定
printf("Failed socket() %d \n", WSAGetLastError());
return 0;
}
SOCKADDR_IN addr_Clt;
int fromlen = sizeof(SOCKADDR);
while(true){
int last= recvfrom(sockServer, receBuf, 1024, 0, (SOCKADDR*) &addr_Clt, &fromlen);
if(last>0){ //判断接收到的数据是否为空
receBuf[last]='\0';//给字符数组加一个'\0',表示结束了。不然输出有乱码
if(strcmp(receBuf,"bye")==0){
cout<<" 客户端不跟我聊天了..."<return 0;
}else{
printf("接收到数据(%s):%s\n", inet_ntoa(addr_Clt.sin_addr), receBuf);
}
}
cout<<"回复客户端消息:";
cin>>Response; //给客户端回复消息
sendto(sockServer,Response, strlen(Response), 0, (SOCKADDR*)&addr_Clt, sizeof(SOCKADDR));
}
closesocket(sockServer);
WSACleanup();
return 0;
}
(提示:若提示stack around the variable “XX” was corrupted.,中文翻译就是“在变量XX周围的堆栈已损坏”。把 project->配置属性->c/c++->代码生成->基本运行时检查 为 默认值 就不会报本异常)
如图:
2.客户端代码:
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib")
# define BUFFER_SIZE 1024 //缓冲区大小
int main(){
SOCKET sock_Client; //客户端用于通信的Socket
WSADATA WSAData;
char receBuf[BUFFER_SIZE]; //发送数据的缓冲区
char sendBuf[BUFFER_SIZE]; //接受数据的缓冲区
if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0){
printf("初始化失败!");
return -1;
} //初始化
sock_Client=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);//创建客户端用于通信的Socket
SOCKADDR_IN addr_server; //服务器的地址数据结构
addr_server.sin_family=AF_INET;
addr_server.sin_port=htons(4567);//端口号为4567
addr_server.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); //127.0.0.1为本电脑IP地址
SOCKADDR_IN sock;
int len=sizeof(sock);
while(true){
cout<<"请输入要传送的数据:";
cin>>sendBuf;
sendto(sock_Client,sendBuf,strlen(sendBuf),0,(SOCKADDR*)&addr_server,sizeof(SOCKADDR));
//int last=recv(sock_Client, receBuf, strlen(receBuf), 0); // (调用recv和recvfrom都可以)
int last=recvfrom(sock_Client,receBuf,strlen(receBuf),0,(SOCKADDR*)&sock,&len);
if(last>0){
receBuf[last]='\0'; //给字符数组加一个'\0',表示结束了。不然输出有乱码
if(strcmp(receBuf,"bye")==0){
cout<<" 服务器不跟我聊天了..."<//当服务器发来bye时,关闭socket
closesocket(sock_Client);
break;
}else{
printf("接收到数据:%s\n", receBuf);
}
}
}
closesocket(sock_Client);
WSACleanup();
return 0;
}
/*
sockaddr和sockaddr_in的区别:
sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息。是一种通用的套接字地址。
而sockaddr_in 是internet环境下套接字的地址形式。
所以在网络编程中我们会对sockaddr_in结构体进行操作。
使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。
*/