UDP
1.含义:udp协议是传输层的一种协议。(User Datagram Protocol用户数据报协议)
2.特点:无连接、不可靠、面向数据报。
无连接:不用向服务端建立连接
不可靠:数据传输的过程是一个不可靠的--数据可能会丢失
面向数据报:数据报的传输方式
3.流程:
4.实现:
(1)首先封装一个UdpSocket类来实现其基本的功能
//传输层基于UDP协议的网络通信
// 1.创建套接字
// 2.绑定地址信息
// 3.发送数据
// 4.接收数据
// 5.关闭套接字
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string>
using namespace std;
#define CHECK_RET(q) if((q) == false){return -1;}
class UdpSocket{
public:
//将_sockfd的初始值给-1,
UdpSocket():_sockfd(-1){}
//1.创建套接字
bool Socket(){
//socket(int domain, int type, int proto);
// domain:地址域的类型--ipv4/ipv6
// type :套接字类型--数据报/字节流
// proto :协议的类型--udp/tcp
// 返回值:
// 成功--0;失败--<-1;
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(_sockfd < 0){
cerr << "socket error\n";
return false;
}
return true;
}
//2.绑定地址信息
bool Bind(string& ip, u_int16_t port){
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(&ip[0]);
addr.sin_port = htons(port);
socklen_t len = sizeof(addr);
//bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
// sockfd :套接字描述符
// addr :地址信息
// addrlen :地址结构的大小--字节
// 返回值:
// 成功--0; 失败--》-1;
int ret = bind(_sockfd, (sockaddr*)&addr, len);
if(ret < 0){
cerr << "bind error\n";
return false;
}
return true;
}
//3.发送数据
bool Send(string& buf, string& ip, u_int16_t& port){
//sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
// sockfd :套接字描述符
// buf :发送的字符串的首地址
// len :发送的数据的长度
// flags :标志位,0--阻塞发送
// dest_addr :目的端地址
// addrlen :地址大小
// 返回值:
// 成功--返回已发送的字符数目;失败--》-1
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(&ip[0]);
addr.sin_port = htons(port);
socklen_t len = sizeof(addr);
int ret = sendto(_sockfd, &buf[0], buf.size(), 0, (sockaddr*)&addr, len);
if(ret < 0){
cerr << "send error\n";
return false;
}
return true;
}
//4.接收数据
bool Recv(string& buf, string& ip, uint16_t& port){
//recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
// sockfd :套接字描述符
// buf :接收到接收缓冲区
// len :要接收的数据大小
// flags :标志位,0--阻塞接收
// src_addr :源端地址
// addrlen :地址大小;源端地址非空--就是源端地址的大小;源端地址空--他就是NULL
// 返回值:
// > 0 :接收到的字节数
// ==0 :表示连接已经断开
// -1 :接收错误
char tmp[1024] = {0};
sockaddr_in addr;
socklen_t len = sizeof(addr);
int ret = recvfrom(_sockfd, tmp, 1024, 0, (sockaddr*)&addr, &len);
if(ret < 0 ){
cerr << "recvfrom error\n";
return false;
}else if(ret == 0){
cout << "peer shutdown\n";
return false;
}
ip = inet_ntoa(addr.sin_addr);
port = ntohs(addr.sin_port);
buf.assign(tmp,ret);
return true;
}
//5.关闭套接字
bool Close(){
if(_sockfd < 0){
cerr << "close error\n";
return false;
}
close(_sockfd);
return true;
}
~UdpSocket(){
Close();
}
private:
int _sockfd; //套接字描述符--文件描述符(网络通信的操作句柄)
};
(2)服务端程序
//传输层基于UDP协议的服务端程序
// 1.创建套接字
// 2.绑定地址信息
// 3.接收客户端发送的数据
// 4.发送数据
// 5.关闭套接字
#include "udp_socket.hpp"
int main(int argc, char* argv[]){
//判断参数是否足够
if(argc != 3){
cout << "./udp_srv 192.168.136.146 9000\n";
return -1;
}
string ip = argv[1];
uint16_t port = atoi(argv[2]);
UdpSocket sock;
//1.创建套接字
CHECK_RET(sock.Socket());
//2.绑定地址信息
CHECK_RET(sock.Bind(ip, port));
while(1){
//3.循环收发数据
string buf;
CHECK_RET(sock.Recv(buf, ip, port));
cout << "client say:" << buf << endl;
buf.clear();
cin >> buf;
//4.发送数据
CHECK_RET(sock.Send(buf, ip, port));
}
sock.Close();
return 0;
}
(3)客户端程序
//传输层基于UDP协议的客户端
#include "udp_socket.hpp"
int main(int argc, char* argv[]){
//判断参数是否足够
if(argc != 3){
cout << "./udp_cli serverip serverport";
return -1;
}
string ip = argv[1];
uint16_t port = atoi(argv[2]);
UdpSocket sock;
//1.创建套接字
CHECK_RET(sock.Socket());
//2.绑定地址信息--可以不用手动绑定
//CHECK_RET(sock.Bind("192.168.136.148", 8000));
//进入循环收发数据
while(1){
string buf;
cin >> buf;
CHECK_RET(sock.Send(buf, ip, port));
buf.clear();
//buf.clear(); //发完以后缓冲区需要清空吗????????
CHECK_RET(sock.Recv(buf, ip, port));
cout << "server say:" << buf << endl;
}
sock.Close();
return 0;
}
5.UDP协议的报头信息
16bitUDP长度:报头+数据;也就是说udp所能发送的数据长度最大是64k-8(2^16 - 8);
16位的校验和:int sum = 0;sum +=~data[i];