Linux网络:使用UDP实现网络通信(服务端客户端)
文章目录
- 1. UDP网络程序的服务端
- 1.1 如何在UDP中读取数据
- 1.2 如何在UDP中发送数据
- 1.3 谈谈IP地址和Port端口号
- 2. UDP网络程序的客户端
- 2.1 创建UDP套接字
- 2.2 绑定套接字
- 2.3 获取服务端信息
- 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绑定限制、知名端口范围等。