当前位置: 首页 > news >正文

Linux_实现UDP网络通信

目录

1、实现服务器的逻辑

1.1 socket

1.2 bind

1.3 recvfrom

1.4 sendto

1.5 服务器代码

2、实现客户端的逻辑 

2.1 客户端代码

3、实现通信

结语 


前言:

        在Linux下,实现传输层协议为UDP的套接字进行网络通信,网络层协议为IPv4,需要用到的接口有以下4个:socket、bind、recvfrom、sendto。具体实现方法:在云服务器上创建一个服务器进程和一个客户端进程,让客户端向服务器发送消息,并且服务器收到消息后可以反馈给对方。

        示意图如下:

1、实现服务器的逻辑

        按照以下函数的调用顺序,即可实现服务器方的UDP通信。

1.1 socket

        首先明确使用IPv4协议和UDP协议后,先调用接口socket,让其返回一个网络文件描述符给到我们,socket函数介绍如下:

#include <sys/types.h>          
#include <sys/socket.h>int socket(int domain, int type, int protocol);
//domain表示网络协议族,AF_INET为IPv4,AF_INET6为IPv6
//type表示传输层协议,SOCK_STREAM为TCP,SOCK_DGRAM为UDP
//protocol表示指定特定的协议,一般前两个参数的协议足矣,这里填0即可//调用成功返回一个类型文件描述符的网络描述符,失败返回-1

1.2 bind

        定义一个struct sockaddr_in类型的变量,该变量的作用是为调用bind接口做准备,该变量里面有3个信息需要填写,分别是:1、传输层协议,2、为该进程设置的端口号,3、该主机的IP地址。其中,端口号和IP地址需要对其转换成大端字节序,因为网络规定传输的数据采用大端字节序传输,这里介绍两个接口可以帮助我们直接将端口号和IP转换成大端字节序,接口介绍如下:

#include <arpa/inet.h>uint16_t htons(uint16_t hostshort);//常用来转换端口号
//将主机字节序转换成网络字节序并返回#include <arpa/inet.h>  unsigned long inet_addr(const char *cp);//常用来转换IP地址
//如果cp指向的是IP地址的字符串形式,那么会将其转换为网络字节序的IP地址
//并且以无符号的长整型返回

        待struct sockaddr_in类型的变量的字段填写完毕后,下一步就是进行绑定操作,绑定的目的是将socket返回的网络描述符与struct sockaddr_in类型的变量进行绑定,即可以通过网络描述符来找到对应的ip地址以及端口号,简单来说,通过网络描述符就能找到对应主机的对应进程


        接着就是调用bind接口进行绑定了,bind接口介绍如下:

int bind(int socket, const struct sockaddr *address,socklen_t address_len);
//socket表示要绑定的网络描述符
//address表示指向struct sockaddr类型的变量
//address_len表示address指向变量的大小//调用成功返回0,失败返回-1

1.3 recvfrom

         recvfrom接口有点类似文件操作中的read接口,都带有接收的意思。recvfrom接口用于从网络描述符中读取对方主机发送的数据,并且还可以将对方主机的地址信息(IP和端口号)给记录下来,该接口的介绍如下:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,  struct sockaddr *src_addr, socklen_t *addrlen);
//sockfd表示读取的网络描述符
//buf表示存放读取数据的目标缓冲区
//len表示期望读取内容的大小
//flag表示设置该函数的模式,比如阻塞或非阻塞,通常设为0表阻塞
//src_addr是个输出型参数,用于保存发送方的地址信息
//addrlen表示src_addr指向变量的大小//成功返回接收的字节数,若sockfd关闭返回0,失败返回-1

1.4 sendto

        如果说recvfrom接口类似read接口,那么sendto就好比write接口,他能够往网络文件描述符内写入数据,即发送方就是调用sendto接口向接收方发送数据,sendto和recvfrom相互搭配实现网络通信。sendto介绍如下:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,  const struct sockaddr *dest_addr, socklen_t addrlen);
//sockfd表示要发送数据的文件描述符
//buf表示发送缓冲区
//len表示要发送数据的长度
//flag表示设置该函数的模式,比如阻塞或非阻塞,通常设为0表阻塞
//dest_addr指向的结构体里包含接收方的IP和端口号,依靠他们才能找到接收方
//addrlen表示dest_addr指向结构体的大小

1.5 服务器代码

        将服务器封装成一个类,并把服务器的端口号、ip地址、网络描述符作为该类的成员变量,这样就可以对上述功能逻辑进行分层了,服务器类代码如下:

#pragma once#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 <cstdio>
#include <unistd.h>
using namespace std;uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;class UdpServer{
public:UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip){}void Init(){// 1. 创建udp socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0); printf("socket create success, sockfd: %d\n", sockfd_);// 2. bind socketstruct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_); local.sin_addr.s_addr = inet_addr(ip_.c_str()); bind(sockfd_, (const struct sockaddr *)&local, sizeof(local));printf("bind success, errno: %d, \err string: %s\n", errno, strerror(errno));}void Run() // 对代码进行分层{char inbuffer[size];while(true){struct sockaddr_in client;socklen_t len = sizeof(client);//服务器先接收消息ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1,0, (struct sockaddr*)&client, &len);cout<<"客户端说:"<<inbuffer<<endl;inbuffer[n] = 0;std::string info = inbuffer;std::string echo_string = "服务器的回答:"+info;//再反馈消息sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);}}~UdpServer(){if(sockfd_>0) close(sockfd_);//关闭描述符}
private:int sockfd_;     // 网路文件描述符std::string ip_; // ip地址uint16_t port_;  // 服务器端口号
};

         该服务器的逻辑是先接收客户端发送的消息,然后利用recvfrom函数保存客户端的地址信息,再使用sendto函数对客户端进行信息的反馈。

2、实现客户端的逻辑 

        客户端逻辑和服务器逻辑几乎一样,第一步必须调用socket创建网络描述符,但是第二步客户端不需要进行bind绑定,因为服务器之所以需要绑定是因为服务器必须手动自定义一个端口号,目的就是要让该端口号可见,以便让客户端知道该端口号,这样客户端才能通过该端口号定位服务器。而客户端不需要自定义端口号,因为客户端的主要任务是给服务器发送信息,这个过程服务器是不需要知道客户端的端口号也可以接收客户端的信息,因此客户端的端口号只需要保证其唯一性即可,即交给操作系统来生成,当首次发送数据的时候操作系统就会为客户端生成端口号。

2.1 客户端代码

        客户端代码如下:

#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>using namespace std;void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n"<< std::endl;
}// ./udpclient serverip serverport
int main(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]);struct sockaddr_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());//转换网络字节序// 1. socket拿到网络描述符int sockfd = socket(AF_INET, SOCK_DGRAM, 0);socklen_t len = sizeof(server);string message;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, message);//2. 向服务器发送信息sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);//3. 打印来自服务器的信息recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);cout << buffer << endl;}close(sockfd);//关闭文件描述符return 0;
}

3、实现通信

        实现通信的前提是让服务器以进程的形式跑起来,然后再让客户端也以进程的形式跑起来,因为网络通信的本质就是进程间通信,而上述代码中客户端本身就是在main函数中执行的,所以此时客户端可以直接运行,但是服务器还只是个类,因此现在只需要用服务器类实现一个main函数,即可完成两个进程的运行。

        服务器进程代码如下: 

#include "UDPser.hpp"
#include <memory>
#include <cstdio>void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}// ./udpserver port
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}//从命令行参数拿到端口号uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> svr(new UdpServer(port));svr->Init();svr->Run();return 0;
}

        运行结果:

        从结果可以看到目前可以正常的进行客户端与服务器之间的通信。

结语 

        以上就是关于实现UDP网络通信的讲解,实现UDP的核心在于对套接字的理解以及相关接口的逻辑使用,其实只需要记住只要涉及到网络通信,那么socket和bind函数是必须在最开始就调用的。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!! 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 详解 @RequestHeader 注解在 Spring Boot 中的使用
  • 学生党如何挑选高性价比蓝牙耳机?四款天花板级蓝牙耳机推荐
  • 在C++里如何释放内存的时候不调用对象的析构函数?
  • Final Draft for Mac v13.1.0激活版:专业剧本写作软件
  • 【Python】基础学习技能提升代码样例2:小功能块
  • UE5C++中,NewObject<>()和CreateDefaultSubobject<>()的区别
  • 网络通信---UDP
  • C语言 写一个函数days,实现某日在本年中是第几天计算。
  • c++中grpc简单使用---函数介绍及其代码演示
  • 如何处理selenium Webdriver中的文本框?
  • Linux环境docker部署Firefox结合内网穿透远程使用浏览器测试
  • SpringBoot 日志
  • C:图案打印
  • C++——QT:保姆级教程,从下载到安装到用QT写出第一个程序
  • Android串口开发及读取完整数据的解决方法
  • [译]如何构建服务器端web组件,为何要构建?
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 07.Android之多媒体问题
  • Android 控件背景颜色处理
  • Docker: 容器互访的三种方式
  • dva中组件的懒加载
  • eclipse的离线汉化
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • HTTP那些事
  • Python - 闭包Closure
  • Redux系列x:源码分析
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • Theano - 导数
  • Vue 动态创建 component
  • Vue官网教程学习过程中值得记录的一些事情
  • Windows Containers 大冒险: 容器网络
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 阿里研究院入选中国企业智库系统影响力榜
  • 从输入URL到页面加载发生了什么
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 两列自适应布局方案整理
  • 盘点那些不知名却常用的 Git 操作
  • 学习使用ExpressJS 4.0中的新Router
  • 源码安装memcached和php memcache扩展
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • 【干货分享】dos命令大全
  • HanLP分词命名实体提取详解
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​插件化DPI在商用WIFI中的价值
  • #systemverilog# 之 event region 和 timeslot 仿真调度(十)高层次视角看仿真调度事件的发生
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • (Python第六天)文件处理
  • (第二周)效能测试
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (附源码)计算机毕业设计大学生兼职系统
  • (六)vue-router+UI组件库
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (十八)SpringBoot之发送QQ邮件
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (原創) 未来三学期想要修的课 (日記)