UDP协议(用户数据报协议)特点:无连接(通信两端不需要先建立连接),不可靠(有可能数据丢包或者乱序),面向数据报。
应用于传输实时性高于安全性的场景,如 视屏音频传输。
服务端需要先启动等待客户端的请求,客户端需要先知道服务端的地址信息。
Server端:
创建套接字
在内核中创建socket结构体---->网络通信的一个句柄
为套接字绑定地址信息。给创建的socket结构指定源端IP和端口,协议。(服务端的地址信息一般都写死在程序中了)
作用:
发:1.发送数据时指定源端地址信息
收:2.告诉操作系统收到的哪条数据应该交给这个socket处理
接收数据
从指定的socket缓冲区中取出数据,
关闭套接字
Client端:
头文件:
#include //socket接口头文件
#include //地址结构,协议类型头文件
#include //字节序转换接口头文件
int socket(int domain, int type, int protocol);
domain:地址域类型。
AF_INET : ipv4地址域类型 struct sockaddr_in
AF_INET6: ipv6地址域类型 struct sockaddr_in6
AF_UNIX: 本地 地址域类型 struct sockaddr_un
type:套接字类型
SOCK_STREAM:流式套接字—--提供字节流传输 默认协议为TCP
SOCK_DGRAM:数据报套接字---提供数据包传输 默认协议为UDP
protocol:通信所使用的协议类型(常用TCP/UDP)
0:表示套接字类型默认协议
IPPROTO_TCP(0):TCP协议
IPPROTO_UDP(17):UDP协议
返回值:成功返回一个文件描述符—>操作句柄,失败返回-1
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址 .
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数
#include
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
///使用一个接口绑定不同的地址,使用时定义对应类型的结构体,传参时进行类型强转。
sockkfd: 创建套接字返回的描述符
addr: struct sockaddr 地址信息通用结构体
addrlen:指定地址结构长度
返回值:成功返回0,失败-1;
如下图:sockaddr 和sockaddr_in、sockaddr_in6地址信息的长度和内部数据组织形式不同,但是函数接口里使用了统一的通用结构体来接受传入的地址,根据传addrlen(指定地址结构长度)就能解析出来你所传的是哪一种地址结构体。
sockaddr_in结构:----- /usr/include/linux/socket.h
常见操作:
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr(ip)
我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址
sockaddr_in中:
short sin_family;/Address family一般来说AF_INET(地址族)PF_INET(协议族)/
unsigned short sin_port;/Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)/
struct in_addr sin_addr;/IP address in network byte order(Internet address)/
unsigned char sin_zero[8];/Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐/
其中sockaddr结构为:
sa_family_t:地址域类型
sa_data:通用结构体。
常见操作://addr.sin_family=AF_INET;
in_addr结构为:
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数.
Size_t sendto( int sockfd, void *data,int len ,
int flag,struct sockaddr * peer_Addr,socklen_t addrlen);
sockfd:套接字描述符
data:要发送的数据的空间首地址
len:要发送的数据长度
flag:标志位 0表示阻塞发送(缓冲区数据满了则等待)
peer_Addr:获取指定对端的地址信息(发给谁)
addrlen:地址信息长度;
返回值:成功返回实际发送的长度,失败返回-1;
ssize_t recvfrom(int sockfd,void*buf , int len, int flag,
struct sockaddr* peer_addr,socklen_t* addrlen);
sockfd:套接字描述符
buf :缓冲区空间地址,用于存放接收的数据
len:要获取数据的长度
flag: 0 默认为阻塞接收(无数据则阻塞等待)
peer_addr:用于获取发送端的地址信息(谁发的)
addrlen:输入输出型参数---指定想要获取多长的地址;返回实际长度。
返回值:返回实际接收到的数据长度,失败返回-1;
使用UDP协议接收数时每次接收一条完整的数据,不一定将缓冲区中数据获取完。
Int close(int fd);
头文件和UdpSocket类接口:udpsocket.hpp
封装l了一个UdpSocket类,通过实例化对象,调用自己的成员接口可以在外部更加简单的完成客户端与服务端的搭建。
封装使代码的耦合度降低,方便外部调用,且内部数据访问更加安全。
#include
#include
#include
#include
#include
#include
#include
class UdpSocket{
public:
UdpSocket():_sockfd(-1)
{}
bool Socket(){
_sockfd =socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP) ;
if(_sockfd<0){
perror("socket error\n");
return false;
}
return true;
}
bool Bind(const std::string &ip, uint16_t port) {
struct sockaddr_in addr;
addr.sin_family= AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port= htons(port);
int ret = bind(_sockfd,(struct sockaddr*)&addr,
sizeof(struct sockaddr_in));
if(ret<0){
perror("bind error \n");
return false;
}
return true;
}
bool Send(const std::string &data,
const std::string &ip, uint16_t port)
{
struct sockaddr_in peer_addr;
peer_addr.sin_family = AF_INET;
peer_addr.sin_port=htons(port);
peer_addr.sin_addr.s_addr=inet_addr(ip.c_str());
size_t ret = sendto(_sockfd,data.c_str(),data.size(),0,
(struct sockaddr*)&peer_addr,sizeof(struct sockaddr_in));
if(ret<0){
perror("send error \n");
return false;
}
return true;
}
bool Recv(std::string *buf,std::string *ip=NULL, uint16_t *port=NULL)
{
struct sockaddr_in addr;
socklen_t len =sizeof(struct sockaddr_in);
char tmp[4096]={0};
int ret = recvfrom(_sockfd,tmp,4096,0,
(struct sockaddr*)&addr,&len);
if(ret<0){
perror("recvfrom error\n");
return false;
}
buf->assign(tmp, ret);//申请空间并拷贝数据到buf
if(ip!=NULL){
*ip=inet_ntoa(addr.sin_addr);
}
if(port!=NULL){
*ip = ntohs(addr.sin_port);
}
return true;
}
bool Close() {
if(_sockfd != -1) {
close(_sockfd);
}
return true;
}
private:
int _sockfd;
};
服务端:udp_server.c
#include
#include
#include
#include
#include
#include
int main(){
int sockfd= socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sockfd<0){
perror("socket error\n");
return -1;
}
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(9999); //端口号
addr.sin_addr.s_addr =inet_addr("192.168.86.3");//ip
socklen_t addrlen = sizeof(struct sockaddr_in);
int ret= bind(sockfd,(struct sockaddr*)&addr,addrlen);
if(ret <0){
perror("bind error\n");
return -1;
}
while(1){
//接收数据
char buf[1024]={0};
struct sockaddr_in peer_addr;
ret = recvfrom(sockfd, buf, 1023,0,(struct sockaddr*)&peer_addr,&addrlen);
if(ret <0){
perror("recvfrom error\n");
close(sockfd);
return -1;
}
printf("clent say:%s\n",buf);
// 发送数据
memset(buf,0,1024);
printf("server say: ");
fflush(stdout);
fgets(buf,1024,stdin);
ret=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&peer_addr,addrlen);
if(ret <0){
perror("sendto error\n");
close(sockfd);
return -1;
}
}
close(sockfd);
return 0;
}
客户端:udp_client.cpp
#include "udpsocket.hpp"
#define CHECK_RET(q) if((q)==false){return -1;}
int main(){
UdpSocket sock;
CHECK_RET( sock.Socket());
while(1){
std:: string buf;
std::cout<<"clent say:";
std::cin>>buf;
CHECK_RET( sock.Send(buf,"192.168.86.3",9999) );
buf.clear();
CHECK_RET(sock.Recv(&buf,NULL,NULL) );
std:: cout<<"server say: "<<buf<<std::endl;
}
CHECK_RET(sock.Close());
return 0;
}
结果演示:
这里之启动了一个server端,也可同时启动多个server端。