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

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

我使用的是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通信程序的记录暂时到这里吧,

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值