【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;
}
结果展示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值