文章目录
-
-
- [1. UDP网络程序的服务端](#1. UDP网络程序的服务端)
-
- [1.1 如何在UDP中读取数据](#1.1 如何在UDP中读取数据)
- [1.2 如何在UDP中发送数据](#1.2 如何在UDP中发送数据)
- [1.3 谈谈IP地址和Port端口号](#1.3 谈谈IP地址和Port端口号)
- [2. UDP网络程序的客户端](#2. UDP网络程序的客户端)
-
- [2.1 创建UDP套接字](#2.1 创建UDP套接字)
- [2.2 绑定套接字](#2.2 绑定套接字)
- [2.3 获取服务端信息](#2.3 获取服务端信息)
- [2.4 读取和发送数据](#2.4 读取和发送数据)
-
-
序:在上以章中,我们对使用UDP实现网络通信的服务端部分实现了套接字的创建与绑定,深入了解了UDP是面向数据报的,以及了解到在绑定过程对网络接口统一抽象化的struct sockaddr结构体,深入了解了##的作用,而本篇文章将继续了解使用UDP实现网络通信的服务端和客户端细节,深入探寻服务端与客户端的接收和发送。
1. UDP网络程序的服务端
1.1 如何在UDP中读取数据
UDP特殊就特殊在没法使用read和write,这两个接口是面向字节流的,而UDP是面向数据报的,所以UDP要获取数据就要使用recvfrom函数接口
recvfrom函数接口:
需要包含下面两个库
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
该函数的第一个参数表示从指定的套接字中获取数据
第二个参数表示读到的报文的时候所需要的缓冲区,第三个参数表示该缓冲区的长度,这样读到数据后,该数据就会被放在该缓冲区当中
第四个参数设为0,默认使用阻塞方式,当然该参数不止能设置为0,还能设置为其他数字,但在该片文章的讨论中,默认设置为0
问题一:收到一条消息,需不需要知道这条消息是谁发的?为什么要知道?
因为我们收到对应的信息的时候,基本上都需要返回消息给发送者
所以,要知道是谁给服务器发的信息就看看第五个和第六个参数,这两个参数是输出型参数,其中第六个参数是输入输出参数,表示该结构体对象的大小,也就是说,如果有人访问该服务器,那么此时是谁给服务器发的消息,发送端的套接字信息就会保存到第五个参数的指针中,所以,该内存空间是需要我们自己去定义的,因为这是一个输出型参数,但由于这个指针的类型是struct sockaddr,但是我们在进行udp网络通信时,用到的是struct sockaddr_in,所以我们要定义一个sockaddr_in的结构体,然后强转为sockaddr后再传进去,因为网络接口统一抽象化!!!
返回值RETURN_VALUE,如果成功了就会返回收到了多少个字节,失败的话,-1被返回,错误码被设置
class UdpServer{
public:
void Run(func_t func)
{
_is_running = true;
char inbuffer[size];
while(_is_running)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
ssize_t n = recvfrom(_sockfd, inbuffer,sizeof(inbuffer) - 1, 0,
(struct sockaddr*)&client, &len);
if(n < 0)
{
log(Warning, "recvfrom error,errno: %d ,strerror: %s", errno, strerror(errno));
continue;
}
inbuffer[n]=0;
}
}
private:
int _sockfd; //网络文件描述符
std::string _ip; //IP地址, 0.0.0.0 任意地址绑定
uint16_t _port; //表示服务器进程的端口号
bool _is_running;//表示服务器是否在运行
};
1.2 如何在UDP中发送数据
由于UDP是面向数据报,无法使用read和write,所以UDP要发送数据就要使用sendto函数接口
sendto函数接口:
sendto函数的接口和recvfrom一模一样,只要搞懂了recvfrom就会用sendto,要注意的事,该函数的最后两个参数是输入型参数,由于在recvfrom的时候,我们就知道了是谁发数据给我们的
class UdpServer{
public:
void Run(func_t func)
{
_is_running = true;
char inbuffer[size];
while(_is_running)
{
//读数据
//......
inbuffer[n]=0;
// std::cout<<n<<":"<<inbuffer<<std::endl;
//充当一次数据处理
std::string info = inbuffer;
std::string echo_string = func(info);
// std::cout<<echo_string<<std::endl;
sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&client, len);
}
}
private:
int _sockfd; //网络文件描述符
std::string _ip; //IP地址, 0.0.0.0 任意地址绑定
uint16_t _port; //表示服务器进程的端口号
bool _is_running;//表示服务器是否在运行
};
至此我们已经实现了UDP网络通信的服务端的收和发的基本功能。
问题二:如何看我们的服务器成功跑起来了呢?
netstat -naup//使用该命令就能查看
其中n表示,能显示出数字的数据全部显示成数字,不带n的话,有些数据就会显示为字符串
其中p表示,将对应的pid进程信息页表示出来
其中a表示all,将所有相关的信息列出来
其中u表示udp信息
Proto表示用的协议是什么
Recv-Q表示收到的报文的个数
Send-Q表示发出的报文的个数
Local Address本地的地址(IP地址+端口号)
Foreign Address表示远端(只要有这个0.0.0.0或者 * 的就表示,当前主机能接受任何客户端给当前主机发的消息)
State表示状态
从上图我们看到,我们程序已经启动了
1.3 谈谈IP地址和Port端口号
关于IP地址:
公网IP绑定问题
当我们将自己的云服务器的公网IP传进去后,就会报错:
这段代码如果是虚拟机_运行,代码就是可以运行的,但是由于云服务器禁止直接bind公网IP,所以就会报错,一般写服务器的时候,_ 不会直接绑定IP地址,有可能一台主机上有两到三个网卡,有两到三个IP,如果绑定了某个IP,就无法获取到发送到另外两个IP的数据,所以一般bind(IP:0),直接绑定0,即凡是发给我这台主机的数据,我们都要根据端口号向上交付(好处:1. 好写2. 收到的数据是全的,完整的)
关于Port端口号:

当我们将端口号改为80时:
[0,1023]:系统内定的端口号,一般都要有固定的应用层协议使用,http:80,https:443,mysql:3306... ,一般选择绑定的端口是1024+的端口号
服务端的端口号一般是固定,但是客户端的端口号就是系统自由随机分配的,只要保证唯一性就行
127.0.0.1:本地环回地址,通常用来进行客户端,服务端的测试
2. UDP网络程序的客户端
2.1 创建UDP套接字
既然要实现网络通信,就必须先创建套接字
int main()
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
log(Fatal, "create socket error,errno: %d ,strerror: %s", errno, strerror(errno));
return 1;
}
log(Info,"create socket success, sockfd: %d",sockfd);
return 0;
}
2.2 绑定套接字
client要bind吗?肯定是要的,但是不需要用户去显式的bind!!!一般由操作系统自由随机选择
一个端口号只能被一个进程bind,对server是如此,对client,也是如此!!!
其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以了
系统什么时候给我bind?首次发送数据的时候
2.3 获取服务端信息
想要获取服务端的信息,可以通过命令行参数来获取,规定好格式
将对应的数据填充进struct sockaddr_in server中
// ./udpclient serverip serverport
void Usage(const string &proc)
{
cout<<"\n\rUsage:"<< proc<<" serverip serverport\n"<<endl;
return ;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(0);
}
string serverip =argv[1];
uint16_t serverport =stoi(argv[2]);
struct sockaddr_in server;
socklen_t len = sizeof(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());
return 0;
}
2.4 读取和发送数据
发什么,给谁发?
int main(int argc,char* argv[])
{
//......
string message;
char buffer[size];
while(true)
{
cout<<"Please Enter# ";
getline(cin,message);
//1.数据 2.发给谁
ssize_t s = sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,len);
struct sockaddr_in temp;
//bzero(&server, sizeof(temp));
socklen_t lenp = sizeof(temp);
ssize_t n = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&lenp);
if(n > 0)
{
buffer[n]=0;
cout<<buffer<<endl;
}
}
close(sockfd);
return 0;
}
总结:
本篇博客详细介绍了UDP网络编程的核心内容,包括服务端和客户端的实现。服务端重点讲解了如何使用
recvfrom
接收数据和sendto
发送数据,以及如何处理客户端地址信息;客户端则介绍了套接字创建、隐式绑定、服务端信息配置及数据收发流程。此外,还探讨了IP与端口相关知识,如公网IP绑定限制、知名端口范围等。