概述
首先,我們知道,UDP是無連接配接不可靠的資料報協定,有很多場合比較适合使用UDP協定。使用UDP編寫的一些常見的應用程式有:DNS(域名系統)、NFS(網絡檔案系統)和SNMP(簡單網絡管理協定)。
下圖為典型的UDP用戶端/伺服器程式的函數調用。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2csITRU90d4EzY650MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLykDO2ATOwUTMzITNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
基于UDP協定的網絡通信流程
用戶端流程:
- 建立套接字。
- 為套接字綁定位址(ip + port)資訊。通常用戶端不推薦使用者手動綁定位址資訊。
- 發送資料(如果socket還沒有綁定位址,這時候作業系統會選擇一個合适的位址端口進行綁定)。
- 接收資料。
-
關閉套接字。
服務端流程:
- 建立套接字。通過套接字使程序與網卡建立聯系。
- 為套接字綁定位址資訊(ip + port)。
- 接收資料。
- 發送資料。
- 關閉套接字。
接口介紹
建立套接字:
int socket(int domain, int type, int protocol);
參數:
domin:位址域。
AF_INET:IPV4網絡協定位址域。
AF_INET6:IPV6網絡協定位址域。
type:套接字類型。
SOCK_STREAM:流式套接字,預設協定TCP,不支援UDP。
SOCK_DGRAM:資料報套接字,預設協定UDP,不支援TCP。
protocol:協定類型。
0:試用套接字預設協定。
6/IPPROTO_TCP:TCP協定。
17/IPPROTO_UDP:UDP協定。
傳回值:套接字操作句柄(檔案描述符),失敗傳回-1。
為套接字綁定位址資訊:
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
參數:
sockfd:建立套接字傳回的描述符。
addr:位址資訊。
addrlen:位址資訊長度。
傳回值:成功傳回0,失敗傳回-1。
接受資料:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
參數:
sockfd:操作句柄,套接字描述符。
buf:用buf存儲接收的資料。
len:想要接收的資料長度。
flags:
0:預設阻塞接受。
saddr:發送端的位址資訊。
addrlen:位址資訊長度(輸入輸出型參數),不但要指定想要接收多長還要儲存實際接受了多長。
傳回值:實際接收的資料長度,失敗傳回-1。
發送資料:
ssize_t sendto(
int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen
);
參數:
socket:套接字描述符。
buf:要發送的資料。
len:要發送的資料長度。
flags:
0:預設阻塞發送。
dest_addr:目的端位址資訊,辨別資料要發送到哪裡去。
addrlen:位址資訊長度。
傳回值:實際發送的資料長度,失敗傳回-1。
關閉套接字:
int close(int fd);
參數:
fd:套接字描述符。
傳回值:成功傳回0,失敗傳回-1。
封裝UdpSocket類
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string>
#include <arpa/inet.h>
#include <unistd.h>
using std::cout;
using std::endl;
// 緩沖大小
#define BUF_SIZE 1024
// UDP socket類
class UdpSocket{
public:
// 構造函數
UdpSocket()
: _sockfd(-1)
{}
// 建立套接字
bool Socket(){
// 建立套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(_sockfd < 0){
// 套接字建立失敗
perror("socket error");
return false;
}
return true;
}
// 綁定位址資訊
bool Bind(std::string& ip, uint16_t port){
// IPv4位址結構
struct sockaddr_in addr;
addr.sin_family = AF_INET;
// 點分十進制IP轉二進制形式
inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);
// 端口号,主機位元組序轉網絡位元組序
addr.sin_port = htons(port);
// 位址空間長度
socklen_t len = sizeof(struct sockaddr_in);
// 綁定位址資訊
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if(ret < 0){
// 綁定失敗
perror("bind error");
return false;
}
return true;
}
// 接收
bool Recv(std::string& buf, struct sockaddr_in* saddr){
// 清空緩沖區
buf.clear();
buf.resize(BUF_SIZE);
// 位址空間大小
socklen_t len = sizeof(struct sockaddr_in);
// 阻塞接收
int ret = recvfrom(_sockfd, &buf[0], BUF_SIZE, 0,
(struct sockaddr*)saddr, &len);
if(ret < 0){
// 接收失敗
perror("recvfrom error");
return false;
}
return true;
}
// 發送
bool Send(std::string& buf, struct sockaddr_in* daddr){
// 位址資訊長度
socklen_t len = sizeof(struct sockaddr_in);
// 阻塞發送
int ret = sendto(_sockfd, buf.c_str(), buf.size(), 0,
(struct sockaddr*)daddr, len);
if(ret < 0){
// 發送失敗
perror("sendto error");
return false;
}
return true;
}
// 關閉
bool Close(){
// 關閉套接字
int ret = close(_sockfd);
if(ret < 0){
// 套接字關閉失敗
perror("close error");
return false;
}
return true;
}
private:
// 套接字描述符
int _sockfd;
};
字典伺服器
下面,我們使用前面封裝的UdpSocket類實作一個字典伺服器,下面我們封裝一個字典伺服器:
UDP用戶端和伺服器的實作
UDP伺服器
#include "udp_socket.h"
int main(int argc, char* argv[]){
bool ret;
if(argc != 3){
cout << "./udp_server ip port" << endl;
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
UdpSocket sock;
ret = sock.Socket();
if(!ret){
return -1;
}
ret = sock.Bind(ip, port);
if(!ret){
return -1;
}
while(1){
std::string buf;
struct sockaddr_in cli_addr;
ret = sock.Recv(buf, &cli_addr);
if(!ret){
return -1;
}
cout << "client say: " << buf << endl;
cout << "server say: " << endl;
fflush(stdout);
std::cin >> buf;
ret = sock.Send(buf, &cli_addr);
if(!ret){
return -1;
}
}
ret = sock.Close();
if(!ret){
return -1;
}
return 0;
}
UDP用戶端
#include "udp_socket.h"
int main(int argc, char* argv[]){
bool ret;
if(argc != 3){
cout << "./udp_client ip port" << endl;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
UdpSocket sock;
ret = sock.Socket();
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &ser_addr.sin_addr);
while(1){
std::string buf;
cout << "client say: ";
fflush(stdout);
std::cin >> buf;
ret = sock.Send(buf, &ser_addr);
if(!ret){
return -1;
}
ret = sock.Recv(buf, &ser_addr);
if(!ret){
return -1;
}
cout << "server say: " << buf << endl;
}
ret = sock.Close();
if(!ret){
return -1;
}
return 0;
}