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

【Linux】UDP网络套接字编程

什么是UDP协议

  • UDP协议叫做用户数据报协议
  • UDP是传输层协议
  • 无连接、不可靠传输、面向数据报

实现UDP网络套接字的原理

(1)客户端

  • 创建套接字
  • 为套接字绑定地址信息(为了避免端口冲突,建议不要自己绑定,让操作系统自己去决定合适的端口绑定)
  • 发送信息
  • 接受信息
  • 关闭套接字

(2)服务端

  • 创建套接字
  • 为套接字绑定地址信息(对于服务端地址必须绑定,不然导致客户端不能正常向服务端发送信息)
  • 接受信息
  • 发送信息
  • 关闭套接字

注意: 由于UDP协议的性质就是无连接的,这也就决定了UDP传输协议传输的数据不能保证数据安全到达,他只管发送。但是UDP正式由于这些性质也是的UDP协议传输具有较低的延迟,客户端只管发送数据,而服务端只管接收数据就行,常用与对实时性要求高于安全性要求的程序中,如直播、视频传输;

接受和发送函数

这里我们要介绍两个在UDP编程中,套机字中封装的两个库函数一个用于接收数据、另外一个用于发送数据。
(1)接收数据

ssize_t recvfrom(sockfd,char* buf,int len ,int flag,struct sockaddr* saddr,socklen_t *slen);
参数:
	1、sockfd:套接字操作句柄
	2、buf:从接收缓冲区取出数据存入buf中
	3、len:要接受的数据长度
	4、flag:选项参数。为0==阻塞接收(缓冲区中没有数据时一直等待)
	5、saddr:源端地址信息,表示这条数据是谁发的
	6、slen:是一个输入输出型参数,用于指定想要获取的地址信息长度以及实际获取的地址长度
返回值:
	>0 实际接收的数据长度
	-1 接受错误	

(2)发送数据

ssize_t sendto(sockfd,char* data,int len,int flag,struct sockaddr* daddr,socklen_t dlen);
参数:
	1、sockfd:套接字操作句柄
	2、data:要发送的数据
	3、len:要发送数据的长度
	4、flag:选项参数 =0 表示阻塞阻塞发送,MSG_DNTWAIT-非阻塞发送
	5、daddr:对端地址信息
	6、dlen:对端地址信息结构的长度
返回值:
	成功 >0 	实际发送数据长度
	失败 <0	发送错误

注意: 这里的接受和发送并非就是直接发送给对方的进程直接处理,其实在通信时建立的套接字其实在内存中也就是一个结构体,而在套接字结构体中有两个缓冲区一个是接收缓冲区另一个是发送缓冲区。而这里的两个库函数接受和发送数据其实操作的对象就是这两个缓冲区,发送数据就是把数据发送到发送缓冲区,而接收数据其实就是把数据放入到接收缓冲区中,最后操作系统等到何时的时间将数据从缓冲区中拿去发送或处理。

UDPSocket.hpp

第一步先封装一个UDP编程套接字类

/*
  简单的封装一个socket类来实现,udp编程;
*/
#include <iostream>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string>
#include<unistd.h>
#include<stdio.h>
using namespace std;

class udpsocket
{
  private:
    int _sockfd;//套接字操作句柄
  private:
      //先封装一个地址结构
      void MakeAddr(struct sockaddr_in &addr,const string &ip,const uint16_t port)
      {
        addr.sin_family = AF_INET;//使用IPv4地址结构
        addr.sin_port=htons(port);
        addr.sin_addr.s_addr = inet_addr(ip.c_str());
      }
    public:
      udpsocket():_sockfd(-1)
      {  }
      //1、创建套接字
      bool Socket()
      {
        _sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
        if(_sockfd<0)
        {
          perror("create socket error");
          return false;
        }
      return true;
      }  

      //3.为套接字绑定地址信息
      bool Bind(const string &ip,const uint16_t port)
      {
        struct sockaddr_in addr;
        MakeAddr(addr,ip,port);
        socklen_t len = sizeof(struct sockaddr_in);
        int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
        if(ret<0)
        {
          perror("bind error");
          return false;
        }
        return true;
      }
      //4.发送数据
      bool Send(const string &buf,string &dip,uint16_t dport)
      {
        struct sockaddr_in addr;
        MakeAddr(addr,dip,dport);
        socklen_t len = sizeof(struct sockaddr_in);
        int ret = sendto(_sockfd,&buf[0],buf.size(),0,(struct sockaddr*)&addr,len);
        if(ret < 0)
        {
          perror("sendto error");
          return false;
        }
        return true;
      }

      //5.接受数据
      bool Recv(string &buf,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,4095,0,(struct sockaddr*)&addr,&len);
        if(ret < 0)
        {
          perror("recv error");
          return false;
        }
        buf.assign(tmp,ret);//这个函数的意识就是将tem中ret长度的字符拷贝到buf中去
        if(ip != NULL)
        {
          *ip = inet_ntoa(addr.sin_addr);
        }

        if(port != NULL)
        {
          *port = ntohs(addr.sin_port);
        }
        return true;
      }

      //6.关闭套接字
      bool Close()
      {
        close(_sockfd);
          return true;
      }
};

Udp_client客户

#include<iostream>
#include<string>
#include<stdlib.h>
#include"Udpsocket.hpp"

int main(int argc,char* argv[])
{

  if(argc != 3)
  {
    printf("缺少---ip----port\n");
    return -1;
  }

  //这两个地址信息是服务端的地址信息,表示数据要从客户端发给谁
  string srv_ip = argv[1];
  uint16_t srv_port = atoi(argv[2]);
  udpsocket sock;
  if(sock.Socket() == false)
  {
    return -1;
  }

  //一般客户端的地址信息并不需要用户自己自动绑定,
  //因为操作系统会检测,若没有绑定则操作系统会为该
  //客户端选择一个合适的ip地址和端口进行绑定;
  
  while(1)
  {
      cout<<"client say:";
      fflush(stdout);
      string buf;
      cin>>buf;
      sock.Send(buf,srv_ip,srv_port);
      buf.clear();
      sock.Recv(buf);
      cout<<"server say:"<<buf<<endl;
  }
  sock.Close();
  cout<<"hello eorld"<<endl;
  return 0;

}

Udp_server服务端

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

int main(int argc,char* argv[])
{
  if(argc!=3)
  {
    printf("缺少---ip----port----error\n");
    return -1;
  }
  //从参数中得到地址和端口信息
  uint16_t port = atoi(argv[2]);
  char* ip = argv[1];

  //设置地址结构信息
  struct sockaddr_in addr;
  addr.sin_family=AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = inet_addr(ip);
  //in_addr_t inet_addr(cosnt char*ip);讲一个点分十进制的IP地址转换为网络字节序的IP地址;
  socklen_t len = sizeof(struct sockaddr_in);

  //第一步创建套接字
  int sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);//创建一个ipv4类型且使用流式传输的字节流套接字
  if(sockfd < 0)
  {
    perror("create socket error");
    return -1;
  }

  //第二步为套接字绑定地址信息
  int ret = bind(sockfd,(struct sockaddr*)&addr,len);
  if(ret < 0)
  {
    perror("bind socket error");
    return -1;
  }

  //第三步作为服务端,可定是被动接受请求的一端,先接受数据
  while(1)
  {
    char buf[1024] = {0};
    struct sockaddr_in cliaddr;//用于服务端保存客户端的地址信息
    ret = recvfrom(sockfd,buf,1023,0,(struct sockaddr*)&cliaddr,&len);//len在这里是一个输入输出型参数,表示想要接收到对端地址的长度以及实际接收到的对端地址长度;
    if(ret < 0)
    {
      perror("recv error");
      return -1;
    }
    printf("client say:%s\n",buf);
    printf("server say:");
    fflush(stdout);
    memset(buf,0x00,1024);
    scanf("%s",buf);
    //第四部及时收到客户端发送的数据时,回复客户端发送是数据
    ret = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&cliaddr,len);
    if(ret < 0)
    {
      perror("send error");
      return -1;
    }
  }
  //第五步关闭套接字
  close(sockfd);
  return 0;
}

结果展示

在这里插入图片描述

相关文章:

  • 【数据结构:树】——搜索二叉树-K模型(非递归和递归)
  • 【C++】——STL关联式容器认识以及使用
  • TCP三次握手和四次挥手详解
  • 【Linux】进程控制
  • 【Linux】进程程序替换——exec函数簇
  • 【Linux】入门基础命令(2)
  • 【Linux】权限管理和粘滞位理解
  • linux下inode节点理解
  • C语言函数
  • C语言数组
  • C语言表达式
  • C语言初识指针
  • C语言结构体
  • C语言深度剖析数据在内存中的存储
  • 深入了解指针
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • Docker容器管理
  • HomeBrew常规使用教程
  • JavaScript 基础知识 - 入门篇(一)
  • Java应用性能调优
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • MySQL用户中的%到底包不包括localhost?
  • Promise面试题,控制异步流程
  • python 装饰器(一)
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 后端_ThinkPHP5
  • 记一次用 NodeJs 实现模拟登录的思路
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 驱动程序原理
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 用Visual Studio开发以太坊智能合约
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • 转载:[译] 内容加速黑科技趣谈
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​configparser --- 配置文件解析器​
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • # 学号 2017-2018-20172309 《程序设计与数据结构》实验三报告
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (二)springcloud实战之config配置中心
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (求助)用傲游上csdn博客时标签栏和网址栏一直显示袁萌 的头像
  • (全注解开发)学习Spring-MVC的第三天
  • (三)Honghu Cloud云架构一定时调度平台
  • (三分钟)速览传统边缘检测算子
  • (已解决)什么是vue导航守卫
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • (转)可以带来幸福的一本书
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • .Net Core与存储过程(一)