【Linux】socket网络编程之UDP
socket网络编程UDP
一、必备接口
1、接收数据
recvfrom函数用于从套接字接收数据
#include<sys/socket.h>ssize_trecvfrom(int sockfd,void*buf,size_t len,int flags,structsockaddr*src_addr,socklen_t*addrlen);返回值:返回正数表示成功接收到的数据字节数,返回0表示对方已经关闭了连接(对于TCP),返回-1表示发生错误sockfd:指定从哪个套接字接收数据buf:指向一个缓冲区,用于存储接收到的数据len:指定buf的最大长度flags:设置接收数据的标志,无标志一般设为0,在这里可以设置非阻塞模式src_addr:用于存储发送方的地址信息,输出型参数addrlen:用于指定src_addr结构体的长度,输入输出型参数,在调用 recvfrom 函数之前,需要将其初始化为 src_addr 结构体的最大长度,函数返回时,addrlen 会被更新为实际存储在 src_addr 中的地址信息的长度
2、发送数据
sendto函数用于在无连接的套接字(如 UDP 套接字)上发送数据
#include<sys/socket.h>ssize_tsendto(int sockfd,constvoid*buf,size_t len,int flags,conststructsockaddr*dest_addr,socklen_t addrlen);返回值:返回正数表示成功发送的数据字节数,返回-1表示发送过程中出现错误sockfd:指定通过哪个套接字发送数据buf:指向要发送的数据的缓冲区len:表示要发送的数据的长度(以字节为单位),即 buf 缓冲区中实际要发送的数据的字节数flags:设置发送数据的标志,无标志一般设为0,在这里可以设置非阻塞模式dest_addr:该结构体包含了目标地址的信息addrlen:表示dest_addr结构体的长度(以字节为单位)
二、实现UDP网络编程
1、服务器
(一)UdpServer.hpp
#pragmaonce#include<iostream>#include<string>#include<strings.h>#include<cstring>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<functional>#include<unistd.h>// 下面两行都是代表着定义一个类似函数指针的变量func_t,参数和返回值都是string// using func_t = std::function<std::string(const std::string&)>;typedef std::function<std::string(const std::string&)> func_t;//枚举错误类型enum{ SOCKET_ERR=1, BIND_ERR };//定义端口号和ip地址uint16_t defaultport =8080; std::string defaultip ="0.0.0.0";//size用来定义缓冲区大小constint size =1024;classUdpServer{public:UdpServer(constuint16_t&port = defaultport,const std::string &ip = defaultip):sockfd_(0),port_(port),ip_(ip),isrunning_(false){}voidInit(){// 创建udp socket sockfd_ =socket(AF_INET, SOCK_DGRAM,0);if(sockfd_ <0){exit(SOCKET_ERR);}// 定义一个local存储本地地址structsockaddr_in local;//bzero的作用就是将local结构体清零bzero(&local,sizeof(local));//将local的前两个字节也就是它的地址族为AF_INET local.sin_family = AF_INET;//保证我们的端口号是网络字节序列,因为该端口号是要给对方发送的 local.sin_port =htons(port_);//inet_addr函数将ip_转换为in_addr_t类型的IP地址,并存储在local.sin_addr.s_addr local.sin_addr.s_addr =inet_addr(ip_.c_str());//绑定sockfd_到本地地址local上,失败则退出if(bind(sockfd_,(conststructsockaddr*)&local,sizeof(local))<0){exit(BIND_ERR);}}voidRun(func_t func)// 对代码进行分层{//设置服务器开始运行标志 isrunning_ =true;//定义缓冲区存放接收到的数据char inbuffer[size];while(isrunning_){//定义client结构体存储客户端的地址信息structsockaddr_in client; socklen_t len =sizeof(client);//获取客户端信息 ssize_t n =recvfrom(sockfd_, inbuffer,sizeof(inbuffer)-1,0,(structsockaddr*)&client,&len);if(n <0){continue;}//在最后添加字符串描述符'\0' inbuffer[n]=0;//将所有的信息通过func函数处理为echo_string std::string info = inbuffer; std::string echo_string =func(info);//将处理完后的数据发送到客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(),0,(const sockaddr*)&client, len);}}//析构,sockfd_本质上是一个文件描述符一样的东西,用close可以关闭~UdpServer(){if(sockfd_>0)close(sockfd_);}private:int sockfd_;// 网路文件描述符 std::string ip_;// 任意地址bind 0uint16_t port_;// 表明服务器进程的端口号bool isrunning_;// 服务器运行状态};(二)main.cpp
#include"UdpServer.hpp"#include<memory>#include<cstdio>// 端口号一般是可以随便定义的,但是[0,1023]范围内的端口号一般都是有固定的应用层协议的//所以我们都用1024+的端口号//输入小于1024的端口号就提示重输voidUsage(std::string proc){ std::cout <<"\n\rUsage: "<< proc <<" port[1024+]\n"<< std::endl;}//该函数用于处理接收到的消息,将消息添加到固定的前缀 "Server get a message: " 后面//并将结果输出到控制台 std::string Handler(const std::string &str){ std::string res ="Server get a message: "; res += str; std::cout << res << std::endl;return res;}//该函数用于执行客户端发送的命令,并将命令执行的结果返回给 UDP 服务器//从而实现服务器与客户端之间的交互,让客户端可以在服务器端执行命令并获取结果 std::string ExcuteCommand(const std::string &cmd){ FILE *fp =popen(cmd.c_str(),"r");if(nullptr== fp){perror("popen");return"error";} std::string result;char buffer[4096];while(true){char*ok =fgets(buffer,sizeof(buffer), fp);if(ok ==nullptr)break; result += buffer;}pclose(fp);return result;}// ./udpserver portintmain(int argc,char*argv[]){//在命令行中只带一个参,多带参和少带参都要执行Usageif(argc !=2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);//智能指针管理 UdpServer 对象的生命周期,确保对象在不再使用时自动释放内存 std::unique_ptr<UdpServer>svr(newUdpServer(port));//初始化并启动服务器 svr->Init(); svr->Run(ExcuteCommand);return0;}2、客户端
UdpClient.cpp
#include<iostream>#include<cstdlib>#include<unistd.h>#include<strings.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>usingnamespace std;voidUsage(std::string proc){ std::cout <<"\n\rUsage: "<< proc <<" serverip serverport\n"<< std::endl;}intmain(int argc,char*argv[]){//命令行参数不为三个就打印提示信息if(argc !=3){Usage(argv[0]);exit(0);}//获取服务器端口号和服务器ip std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//下面似曾相识呢,和上面服务器的解释一致structsockaddr_in server;bzero(&server,sizeof(server)); server.sin_family = AF_INET; server.sin_port =htons(serverport); server.sin_addr.s_addr =inet_addr(serverip.c_str()); socklen_t len =sizeof(server);int sockfd =socket(AF_INET, SOCK_DGRAM,0);if(sockfd <0){ cout <<"socker error"<< endl;return1;}//客户端套接字实际上也需要绑定,但通常不需要用户显式地进行绑定//操作系统会在客户端首次发送数据时自动为其分配一个可用的端口号进行绑定 string message;char buffer[1024];while(true){ cout <<"Please Enter@ ";getline(cin, message);//将消息发送到服务器sendto(sockfd, message.c_str(), message.size(),0,(structsockaddr*)&server, len);//接收信息并打印structsockaddr_in temp; socklen_t len =sizeof(temp); ssize_t s =recvfrom(sockfd, buffer,1023,0,(structsockaddr*)&temp,&len);if(s >0){ buffer[s]=0; cout << buffer << endl;}}close(sockfd);return0;}最终我们实现的就是一个客户端可以通过指令操控服务器,然后将服务器应该输出的结果打印到客户端

今日分享就到这里了~