socket 编程入门教程(五)UDP原理:1、设计UDP server类

socket 编程入门教程(五)UDP原理:1、设计UDP server类

人们通常用电话连线来说明TCP协议,而UDP协议,则常常用邮递来做比喻。与TCP有连接的信息传输方式不同,UDP协议被认为是对底层IP协议简单的扩展:协议并不保证每个数据包都会到达目的地,也不保证到达的顺序,而仅仅就是“尽力”的发送每一个数据包。我在这篇教程中有时候使用“数据包”有时候使用“数据报”,广义的说,这两个词意思类似,有代表一个有大小边缘的数据块。但是,用“数据包”的时候,我想强调的是这个数据块中所传送的数据部分;而“数据报”则更强调在数据块中对这段数据的信息和说明部分,比如IP首部,TCP和UDP首部,TCP和UDP报文段这些信息。TCP协议通过同步验证实现了TCP层面上的“数据流”传送,而下层的IP协议,依然是数据报形式的传送,这个我们在前面已经描述过,比如连接握手和断开握手,实际上都是发送的TCP数据报(TCP格式的IP数据报)。UDP格式的IP数据报为IP数据报指定了UDP端口,从而使这样的IP数据报的目的地能够精确到应用程序——没有端口指定的IP数据报目的地只能精确到具有IP地址的主机。另外,与TCP的无边缘保证相反,UDP数据包是有大小的,而其最大限制也即是IP数据包大小的最大限制:65,507字节(这里需要说明两点:1、IP数据包的理论最大值为2^16 – 1,即65,535字节,UDP数据报因为要包含UDP首部的信息,所以比这个值小一点;2、因为MTU的存在,实际传输中的IP数据包会被分封到1500字节以下。)
因为UDP是无连接的,就像一个邮筒,可以接受来自任何人的邮件;也可以发送给任何人的邮件。而每一次接受,都会得到来向的地址;每一次发送,也必须指明去向的地址。我们设计一个类,分别以lastfromSockAddr和destinationSockAddr表示最后一次来向的地址以及(下一次发送的)目的地地址。需要指出的是,因为防火墙的普遍存在,最后一次来向地址变得极其重要!这一点我们将在后面的讨论中看到。

class UDPServerSock: public BaseSock {

private:
    sockaddr_in serverSockAddr;
protected:
    mutable sockaddr_in lastfromSockAddr;
    sockaddr_in destinationSockAddr;
    
char* preBuffer;
    
int preBufferSize;
    mutable 
int preReceivedLength;
public:
    
explicit UDPServerSock(
            unsigned 
short server_port,
            
int pre_buffer_size = 32);
    
virtual ~UDPServerSock();
    
void UDPSetDest(const char* dest_IP,
            
const unsigned short& dest_port);
    
void UDPSetDest(const sockaddr_in& dest_sock_addr);
    
int UDPReceive() const;
    
int UDPSendtoDest(const char* send_data,
            
const int& data_length) const;
};

我们把最后一次来向地址以及预接收缓存中的收到的数据长度设置成mutable是因为我们希望接收UDPReceive()这个方法看起来是不改变对象的。每一次接收,实际上都会刷新lastfromSockAddr,而作为服务器,往往也是通过lastfromSockAddr去决定destinationSockAddr的。

UDPServerSock::UDPServerSock(unsigned short server_port,
                             
int pre_buffer_size):
preBufferSize(pre_buffer_size), preReceivedLength(
0)
{

    preBuffer 
= new char[preBufferSize];
    memset(
&serverSockAddr, 0sizeof(serverSockAddr));
    memset(
&lastfromSockAddr, 0sizeof(lastfromSockAddr));
    memset(
&destinationSockAddr, 0sizeof(destinationSockAddr));

    serverSockAddr.sin_family = AF_INET;
    serverSockAddr.sin_addr.s_addr 
= htonl(INADDR_ANY);
    serverSockAddr.sin_port 
= htons(server_port);
    

    sockFD = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    
if (sockFD < 0) {

        sockClass::error_info(
sock() failed.);
    }

    if (bind(    sockFD,
                (sockaddr
*)&serverSockAddr,
                
sizeof(serverSockAddr)) < 0) {

        sockClass::error_info(
bind() failed.);
    }
}

UDPServerSock::~UDPServerSock()
{

    delete [] preBuffer;
    close(sockFD);
}

构造函数依然使用socket()建立sockFD,然后通过bind()将本机的SockAddr(主要是指定了端口)绑定到这个sockFD上。
我们重载了UDPSetDest()这个方法,可以有两种方式去指定目标地址destinationSockAddr——既可以指定IP地址和端口,也可以直接赋值以sockaddr_in结构。

void UDPServerSock::UDPSetDest(const char* dest_IP,
                               
const unsigned short& dest_port)
{

    destinationSockAddr.sin_family 
= AF_INET;
    destinationSockAddr.sin_addr.s_addr 
= inet_addr(dest_IP);
    destinationSockAddr.sin_port 
= htons(dest_port);
}

void UDPServerSock::UDPSetDest(const sockaddr_in& dest_sock_addr)
{

    destinationSockAddr.sin_family 
= dest_sock_addr.sin_family;
    destinationSockAddr.sin_addr.s_addr 
= dest_sock_addr.sin_addr.s_addr;
    destinationSockAddr.sin_port 
= dest_sock_addr.sin_port;
}

最后是接收和发送。我们知道TCP里面recv()返回0表示连接正常断开,而UDP里面,则仅仅就是表示收到0字节的数据包。可见,数据大小为0,并不代表数据包为空,因为这个数据包实际也是一个数据报,包含着TCP数据报的各种必要信息。

int UDPServerSock::UDPReceive() const
{

    socklen_t
 from_add_len = sizeof(lastfromSockAddr); //use int in win32
    preReceivedLength 
= recvfrom(    sockFD,
                                    preBuffer,
                                    preBufferSize,
                                    
0,
                                    (sockaddr
*)&lastfromSockAddr,
                                    
&from_add_len);
    
if ( preReceivedLength < 0) {

        sockClass::error_info(
recv() failed.);
    }

    return preReceivedLength;
}

int UDPServerSock::UDPSendtoDest(const char* send_data,
                                 
const int& data_length) const
{

    
int send_message_size = sendto(    sockFD,
                                    send_data,
                                    data_length,
                                    
0,
                                    (sockaddr
*)&destinationSockAddr,
                                    
sizeof(destinationSockAddr));
    
if (send_message_size < 0) {

        sockClass::error_info(
send() failed.);
    }
    
if (send_message_size != data_length) {

        sockClass::error_info(
            
send() sent a different number of bytes than expected.);
    }
    
return send_message_size;
}

转载于:https://my.oschina.net/GeorgeSu/blog/264205

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/109831.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 运维架构

    运维架构运维架构1、运维团队配置运维团队分为:应用运维、系统运维、运维开发、监控运维、DBA团队和安全团队团队分工:应用运维:负责支持线上业务,各自会负责对应的业务线,主要职能是保证线上业务稳定性和同开发共

    2022年7月1日
    20
  • 网易云音乐ncm格式如何转换为mp3_ncm文件转换mp3

    网易云音乐ncm格式如何转换为mp3_ncm文件转换mp3ncm格式就是网易云音乐会员身份下载的格式,套路和腾讯一样,会员过期了都听不了,需要你保持一直是会员;此篇文章是给大家安利的是一个在线批量转换ncm格式转为mp3的网站,使用起来非常简单。link:http://ncm.worthsee.com/1.准备好网易云ncm格式音频2.选择音乐,点击开始(shift多选)3.下载全部4….

    2022年9月27日
    0
  • Spring cloud和Dubbo区别

    Spring cloud和Dubbo区别Springboot是创建产品级的Spring应用和服务,简化了配置文件,使用嵌入式web服务器,有很多开箱即用微服务功能,可以和springcloud联合部署。springcloud是微服务工具包,为开发者提供了在分布式系统的配置管理、服务发现、断路器、智能路由、微代理、控制总线等开发工具包。zookeeperDubbo默认不支持rest因此基于它扩展出了自己的DubboX(当当扩展)…

    2022年6月12日
    33
  • 锐龙cpu天梯图_cpu性能天梯排行图最新

    锐龙cpu天梯图_cpu性能天梯排行图最新近段时间,桌面CPU平台有了比较大的变化,AMD今年的Ryzen全面迎来逆袭,此外还拿出了最高16核的RyzenThreadripper顶级处理器。而为应对AMDRyzen的崛起,Intel近日也正式推出了八代CannonLake(10/14nm),不再挤牙膏,全线都带来了多核升级,性能激增,因而备受关注。而“电脑百事”此次CPU天梯图2017年9月版主要是在8月版的基础上,率先加入Inte…

    2022年9月17日
    0
  • 逻辑回归

    逻辑回归

    2021年11月28日
    37
  • Eclipse自动补全功能设置

    Eclipse自动补全功能设置如何设置Eclipse自动补全方法一:按照步骤设置第一步:点击Eclipse导航栏的Window按钮第二步:在Eclipse导航栏的window中找到preferences第三部:进入preferences中找到java→Editor→ContentAssit,在红箭头所指的方框内输入26个英文字母和一些符号“‘<>按照这些操作来进行,您就可以让Eclipse自动补全方法二:使用快捷键Alt+/…

    2022年6月28日
    31

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号