人们通常用电话连线来说明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, 0, sizeof(serverSockAddr));
- memset(&lastfromSockAddr, 0, sizeof(lastfromSockAddr));
- memset(&destinationSockAddr, 0, sizeof(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;
- }
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。