您的位置:首页 > 其它

UDP通信程序设计(学习记录)

2019-03-27 01:23 190 查看

因为作为新手还是不太清楚,稍稍记录一下遇到的情况。

我使用的是Windows平台。
首先需要编写udp的通信程序,就要了解其需要用到什么函数。
一般来说,在Windows上用winsock编写udp程序需要使用winsock2.h来创建套接字,并实现收发等功能。
然而一开始发现编译并不能通过,发现是没在链接器里加上-lws2_32的语句。加上,这样就可以链接ws2_32.lib库了。

但在创建套接字之前,需要调用winsock的库。不然会返回错误。
通过WSAStartup()函数对Windows Sockets API初始化。
每个winsock应用必须加载winsock DLL的相应版本,如MAKEWORD(2,2)表示winsock的2.2版本,也是无连接类型所必须的。
当然在程序退出前应该调用函数WSAcleanup()释放使用。

WSADATA wdata;
if(WSAStartup(MAKEWORD(2,2),&wdata)!=0){
cout<<"网络环境初始化失败"<<endl;
return -1;
}

调用库后,便可以用如下函数创建套接字了。

SOCKET socket(int domain, int type, int protocol)

SOCKET sS=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sS==INVALID_SOCKET){
printf("Failed socket() %d .\n",WSAGetLastError());
return -1;
}

对于现在所需要的通信方式,需要将其传输层协议指定为UDP,其中第二个参数代表套接字类型,我们设为

SOCK_DGRAM
表示无连接的数据报方式(如UDP)
与之相对的是
SOCK_STREAM
表示基于连接的字节流方式(如TCP)
第一个参数表示协议簇,第三个参数表示协议号。

对于服务器,我们需要把本地的IP地址和端口号绑定到刚刚创建的套接字上。我们使用

int bind(SOCKET socket, struct sockaddr * address, int addr_len)
函数;在这之前,我们需要把相应的数据放入sockaddr_in中才能对socket进行绑定。

SOCKADDR_IN si;
si.sin_family=AF_INET;
si.sin_port=htons(PORT);
si.sin_addr.S_un.S_addr=htonl(INADDR_ANY);

其中port是端口,需要我们自行设置,而地址则可以使用

INADDR_ANY
来进行绑定,
INADDR_ANY
表示0.0.0.0,可以表示本机所有的地址。

绑定完成后,就要创建监听接收部分了。
个人这里用到一个

int recvfrom(SOCKET s,void *buf,int len,unsigned int flags, struct sockaddr *from,int *fromlen)
函数用来监听。
参数5、6不是必须的参数,但接收这些信息可以为下面的应答提供地址。

收到信息后,服务器需要应答客户端的请求,于是又用到了一个

int sendto ( socket s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen)
函数,其整体结构和recvfrom相似。

这样,服务器的基本功能就完成了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<winsock2.h>
#include<time.h>
using namespace std;

#define MSD(X,t) memset(X,t,sizeof(X))
#define PORT 8080

int main(){
int cnt=0;
WSADATA wdata;
if(WSAStartup(MAKEWORD(2,2),&wdata)!=0){
cout<<"网络环境初始化失败"<<endl;
return -1;
}

SOCKET sS=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sS==INVALID_SOCKET){
printf("Failed socket() %d .\n",WSAGetLastError());
return -1;
}SOCKADDR_IN si;
si.sin_family=AF_INET;
si.sin_port=htons(PORT);
si.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
if(bind(sS,(sockaddr *)&si,sizeof(sockaddr)) == -1){
cout<<"bind()error."<<endl;
return -1;
}
cout<<"现在服务器在端口 8080 运行, 等待中. \n"<<endl;
char szbuf[10];
MSD(szbuf,0);
while(1){
SOCKADDR_IN sC;
memset(&sC, 0, sizeof(sockaddr));
int sCLen = sizeof(sockaddr);
int ret = recvfrom(sS, szbuf, sizeof(szbuf), 0, (sockaddr *) &sC, &sCLen);
if(ret == -1){
printf("接收失败!");
return -1;
}cnt++;
printf("No.%d 收到信息:%s 从 IP[%s],端口[%d]\n", cnt, szbuf,
inet_ntoa(sC.sin_addr), ntohs(sC.sin_port));
sendto(sS,"拟好!",sizeof("拟好!"), 0, (sockaddr *)&sC, sCLen);
printf("回复至 IP[%s],端口[%d]\n", inet_ntoa(sC.sin_addr), ntohs(sC.sin_port));

}
closesocket(sS);
WSACleanup();

return 0;
}

对于UDP的客户端,我们并不需要对套接字进行绑定了,只需要创建好套接字地址,就能直接用sendto()对服务器进行数据发送了。同时发送完毕后也会监听服务器的回应。

但是单只是这样,万一服务器回传丢包,那么客户端就因为阻塞函数recvfrom的原因不会停止了。

那么我们可以用

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
为套接字设置一个超时。

那么recvfrom就会返回一个变化的时间,我们就可以停下没有意义的监听了。
在关闭前,我们需要关闭套接字和WSA库。以免造成资源浪费。

实际上UDP也是能用connect()函数的,不过本人对这部分了解不足,具体的运作并不清楚。

因为调整源代码改地址和数据太过麻烦,稍稍往客户端添加了一些内容。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<winsock2.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
using namespace std;

#define MSD(X,t) memset(X,t,sizeof(X))
#pragma comment (lib,"ws2_32.lib")
#define PORT 8080

int main(){
WSADATA wdata;

if(WSAStartup(MAKEWORD(2,2),&wdata)!=0){
cout<<"网络环境初始化失败"<<endl;
return -1;
}

puts("输入地址:");
string s2;
cin>>s2;
char*s3=new char [s2.length()+1];
s2.copy(s3,s2.length(),0);
s3[s2.length()]='\0';
SOCKADDR_IN si;
si.sin_family=AF_INET;
si.sin_port=htons(PORT);
si.sin_addr.S_un.S_addr=inet_addr(s3);
int n;
string s1;
printf("请输入发送内容:\n");
cin>>s1;
puts("个数?:");
scanf("%d",&n);
char* szbuff=new char[s1.length()+1];
s1.copy(szbuff,s1.length(),0);
szbuff[s1.length()]='\0';
SOCKET sC=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sC==INVALID_SOCKET){
printf("Failed socket() %d .\n",WSAGetLastError());
return -1;
}
int nNetTimeout=20;//20ms  recv timeout
setsockopt(sC,SOL_SOCKET,SO_RCVTIMEO,(char*)&nNetTimeout,sizeof(int));//获知端口不可达
int cnt=0;
for(int i=1;i<=n;i++){
int dwSend = sendto(sC, szbuff, sizeof(szbuff), 0, (sockaddr *)&si, sizeof(sockaddr));
if(dwSend<=0){
cout<<"发送失败"<<endl;
cnt++;
}

}
printf("成功发送数据包:\"%s\" %d个\n", szbuff, n-cnt);

char szRbuff[4096];
MSD(szRbuff,0);
SOCKADDR_IN addrS = {0};
int addrSLen = sizeof(sockaddr);
for(int i=1;i<=n-cnt;i++){
int timeout=recvfrom(sC, szRbuff, sizeof(sockaddr), 0, (sockaddr *)&addrS, &addrSLen);
if(timeout<=0) printf("timeout:\"%s\"%d\n", inet_ntoa(addrS.sin_addr),i);
else printf("从服务器[%s]收到信息:%s\n", inet_ntoa(addrS.sin_addr), szRbuff);
}

closesocket(sC);
WSACleanup();
system("pause");

return 0;
}

那么对UDP通信程序的记录暂时到这里吧,

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