UDP协议:无连接 不可靠传输 面向数据报传输
本文目的是在linux下能实现简单的UDP通信
搭建流程
服务端:
- 创建套接字
- 绑定地址信息
- 接收消息
- 回复消息
完成这些动作主要依靠系统提供的API来完成
创建套接字:
int socket(int domain, int type, int protocol);
domain:是用什么版本的协议 例如 IPV4 or IPV6
type : 创建什么类型的套接字 :流式套接字 还是 数据报套接字 流式:SOCK_STREAM ,数据报方式 SOCK_DGRAM
protocol: 协议 传输层协议 使用 TCP 还是 DUP等等 这里使用宏加以区分 IPPROTO_TCP 代表TCP ,IPPROTO_UDP 代表UDP
注意: 这里的选项比较多 这里只列出了两种详细内容可以看手册
绑定地址信息:
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
socket :创建好的文件描述符
sockaddr: 这是一个结构体里面用于存放IP地址和端口号, 但是值得注意的是这个接口为了能实现不同版本的协议共用 还有两个子版本的 一个为 sockaddrin(IPV4 ) sockaddrun(PIV6) 在这里面填好IP和端口后需要强转(见代码)
len: 结构体长度, 就是因为为了实现多个传入参数的复用
值得注意 : 这里面的数据应使用网络字节序(大端),详细见代码注释
接收数据 : 从sock的缓冲区中拉出数据放到buf中
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
buf:数据要放的位置
len:要多长
flags: 怎么接收 阻塞还是非阻塞
sockaddr: 对方的IP地址 以及端口号
addrlen: 这个结构体的大小
发送数据:因为不用建立连接, udp现在没有TCP那么笨重(不用建立连接), 想怎么发就怎么发
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
buf :要发发送的数据
len: 发送数据的长度
flags: 阻塞发还是非阻塞 默认为0阻塞
dest: 对端IP地址 以及端口号
addrlen: 发送数据的长度
客户端
- 创建套接字
- 发送数据
- 接收数据
- 关闭套接字
** : 函数都和上面的类似 客户端不推荐手动绑定地址信息 , 因为如果绑了,那么客户端程序有可能只能启动一个 ,操作系统会自动绑定
封装Udp_Socket.hpp
#include<iostream>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
//这个宏函数用于检测返回值是否正确
#define CHECK_RET(q) if((q)==false){return -1;}
using namespace std;
class UdpSocket{
private:
int _sockfd;
public:
bool Socket() //创建套接字
{
// 使用IPV4 数据报套接字 UDP协议
_sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(_sockfd < 0)
{
cerr<<"socket error\n";
return false ;
}
return true ;
}
//绑定地址信息
bool Bind(const string& ip ,const uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family=AF_INET; // 用的什么类型的IP
// htons 会将字符串类型的port转换为一个uint_16的整数的网络字节序版
// 它会自动检测你的电脑是大端还是小端
addr.sin_port = htons(port);
//将点分字符串类型的IP转换为大端的整数
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int len = sizeof(addr);
int ret = bind(_sockfd, (sockaddr*)&addr, len);
if(ret < 0)
{
cerr<<"Bind error"<<endl;
return false ;
}
return true;
}
// 发送数据
bool Send(const string& data ,const string& ip,const u_int16_t& port)
{
sockaddr_in addr;
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = sendto(_sockfd, &data[0], data.size(), 0,(sockaddr*)&addr,sizeof(addr));
if(ret < 0)
{
cerr<<"Send error"<<endl;
return false;
}
return true;
}
//接收数据
bool Recv(string& buf,string& ip, uint16_t& port)
{
sockaddr_in temp_addr;
char temp[4096] = {0};
socklen_t len = sizeof(temp_addr); // 注意 socklen_t 这个数据类型不能写错
int ret = recvfrom(_sockfd, temp, 4095, 0, (sockaddr*)&temp_addr, &len);
if(ret < 0)
{
cerr<<"recvfrom erro"<<endl;
return false;
}
ip = inet_ntoa(temp_addr.sin_addr);
port = ntohs(temp_addr.sin_port);
buf.assign(temp,ret);
return true;
}
//关闭套接字
void Close()
{
close(_sockfd);
}
};
服务器代码
#include "Udp_Socket.hpp"
#include <sstream>
using namespace std;
int main(int argc, char* argv[])
{
if(argc !=3 )
{
std:: cerr<<"输入有误"<<endl;
return -1;
}
uint16_t port;
string ip = argv[1];
stringstream tmp;
tmp<< argv[2];
tmp>>port;
UdpSocket sock;
CHECK_RET(sock.Socket());
CHECK_RET(sock.Bind(ip, port));
while(1)
{
string buf;
string ip;
uint16_t port;
sock.Recv(buf, ip, port);
cout<<"接收到:"<< buf<<endl;
buf.clear();
cin>>buf;
sock.Send(buf, ip ,port);
}
sock.Close();
}
客户端代码
#include "Udp_Socket.hpp"
#include <sstream>
int main(int argc ,char* argv[])
{
if(argc!=3)
{
cerr<< "输入错误"<<endl;
return -1;
}
uint16_t port ;
string ip = argv[1];
stringstream temp ;
temp<<argv[2];
temp>>port;
UdpSocket sock;
CHECK_RET(sock.Socket());
while(1)
{
string buf;
cin>>buf;
sock.Send(buf, ip, port);
buf.clear();
sock.Recv(buf, ip , port);
cout<< "服务器说 "<<buf <<endl;
}
sock.Close();
}
本代码已经调试过可以直接在linux下运行