socket套接字编程---UDP通信流程和代码编写


网络中通信的两端主机:客户端,服务端
客户端:是通信中主动发起请求的一端。
服务端:通信中针对请求提供服务的一端,也是被动接受请求的一端。
C/S –>客户端服务器架构 ; B/S -->浏览器服务器架构

1.UDP通信流程和接口介绍

UDP协议(用户数据报协议)特点:无连接(通信两端不需要先建立连接),不可靠(有可能数据丢包或者乱序),面向数据报。
应用于传输实时性高于安全性的场景,如 视屏音频传输。

1.1UDP通信流程:

服务端需要先启动等待客户端的请求,客户端需要先知道服务端的地址信息。

Server端:

  1. 创建套接字
    在内核中创建socket结构体---->网络通信的一个句柄

  2. 为套接字绑定地址信息。给创建的socket结构指定源端IP和端口,协议。(服务端的地址信息一般都写死在程序中了)
    作用:
    发:1.发送数据时指定源端地址信息
    收:2.告诉操作系统收到的哪条数据应该交给这个socket处理

    在这里插入图片描述

  3. 接收数据
    从指定的socket缓冲区中取出数据,

  4. 发送数据
    将数据放到指定的socket的发送缓冲区中。
    在这里插入图片描述

  5. 关闭套接字

Client端:

  1. 创建套接字
  2. 为套接字绑定地址信息(客户端通常不主动绑定地址
    反例eg:多个客户端的情况,如果第一个客户端绑定了固定的端口,其他的客户端则无法绑定这个端口(一个端口只能被一个进程占用)。
  3. 发送数据 (发送数据时,检测到若自己没绑定地址信息,则系统会自动的选择合适的地址信息(IP+PORT)进行绑定。)(服务端的地址一般会提前告诉客户端,比如QQ客户端里面就写有其服务端的地址信息,不用我们自己写。)
  4. 接收数据
  5. 关闭套接字.

1.2接口介绍:

头文件:

#include <sys/socket.h>  //socket接口头文件
#include <netinet/in.h>  //地址结构,协议类型头文件
#include <arpa/inet.h>   //字节序转换接口头文件
1.2.1创建套接字
int socket(int domain, int type, int protocol);
domain:地址域类型。 
	AF_INET : ipv4地址域类型  struct sockaddr_in
	AF_INET6: ipv6地址域类型  struct  sockaddr_in6
	AF_UNIX: 本地 地址域类型    struct  sockaddr_un	
type:套接字类型
		SOCK_STREAM:流式套接字—--提供字节流传输  默认协议为TCP
		SOCK_DGRAM:数据报套接字---提供数据包传输   默认协议为UDP
protocol:通信所使用的协议类型(常用TCP/UDP)
		0:表示套接字类型默认协议
		IPPROTO_TCP(0:TCP协议
		IPPROTO_UDP(17:UDP协议
返回值:成功返回一个文件描述符—>操作句柄,失败返回-1

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址 .
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数
在这里插入图片描述

1.2.2为套接字绑定地址信息
#include <sys/types.h>        
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);	
///使用一个接口绑定不同的地址,使用时定义对应类型的结构体,传参时进行类型强转。

sockkfd: 创建套接字返回的描述符
addr:  struct sockaddr 地址信息通用结构体
addrlen:指定地址结构长度
返回值:成功返回0,失败-1

如下图:sockaddr 和sockaddr_in、sockaddr_in6地址信息的长度和内部数据组织形式不同,但是函数接口里使用了统一的通用结构体来接受传入的地址,根据传addrlen(指定地址结构长度)就能解析出来你所传的是哪一种地址结构体。
在这里插入图片描述

sockaddr_in结构:----- /usr/include/linux/socket.h
常见操作:
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr(ip)
在这里插入图片描述
我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址
sockaddr_in中:
short sin_family;/Address family一般来说AF_INET(地址族)PF_INET(协议族)/

unsigned short sin_port;/Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)/

struct in_addr sin_addr;/IP address in network byte order(Internet address)/

unsigned char sin_zero[8];/Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐/

其中sockaddr结构为:
在这里插入图片描述
sa_family_t:地址域类型
sa_data:通用结构体。
常见操作://addr.sin_family=AF_INET;
in_addr结构为:

在这里插入图片描述

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数.

1.2.3发送数据
Size_t sendto( int sockfd, void *data,int len , 
				int flag,struct sockaddr * peer_Addr,socklen_t addrlen);

sockfd:套接字描述符
data:要发送的数据的空间首地址
len:要发送的数据长度
flag:标志位  0表示阻塞发送(缓冲区数据满了则等待)
peer_Addr:获取指定对端的地址信息(发给谁)
addrlen:地址信息长度; 
返回值:成功返回实际发送的长度,失败返回-1
1.2.4接收数据
ssize_t recvfrom(int sockfd,void*buf , int len, int flag,
				struct sockaddr* peer_addr,socklen_t* addrlen);

sockfd:套接字描述符
buf	:缓冲区空间地址,用于存放接收的数据
	len:要获取数据的长度
flag: 0 默认为阻塞接收(无数据则阻塞等待)
peer_addr:用于获取发送端的地址信息(谁发的)
addrlen:输入输出型参数---指定想要获取多长的地址;返回实际长度。
返回值:返回实际接收到的数据长度,失败返回-1

使用UDP协议接收数时每次接收一条完整的数据,不一定将缓冲区中数据获取完。

1.2.5 关闭套接字 释放资源
 Int close(int fd);

2.UDP通信代码实现

头文件和UdpSocket类接口:udpsocket.hpp
封装l了一个UdpSocket类,通过实例化对象,调用自己的成员接口可以在外部更加简单的完成客户端与服务端的搭建。
封装使代码的耦合度降低,方便外部调用,且内部数据访问更加安全。

#include <cstdio>
#include <iostream>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

class UdpSocket{
  public:
    UdpSocket():_sockfd(-1)
    {}

    bool Socket(){
      _sockfd =socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP) ;
      if(_sockfd<0){
        perror("socket error\n");
        return false;
      }
      return true;
    }

    bool Bind(const std::string &ip, uint16_t port) {
      struct sockaddr_in addr;
      addr.sin_family= AF_INET;
      addr.sin_addr.s_addr = inet_addr(ip.c_str());
      addr.sin_port= htons(port);
      int ret = bind(_sockfd,(struct sockaddr*)&addr,
                    sizeof(struct sockaddr_in));
      if(ret<0){
        perror("bind error \n");
        return false;
      }
      return true;
    }

    bool Send(const std::string &data, 
        const std::string &ip, uint16_t port)
    {
      struct sockaddr_in peer_addr;
      peer_addr.sin_family = AF_INET;
      peer_addr.sin_port=htons(port); 
      peer_addr.sin_addr.s_addr=inet_addr(ip.c_str());
      size_t ret = sendto(_sockfd,data.c_str(),data.size(),0,
                (struct sockaddr*)&peer_addr,sizeof(struct sockaddr_in));
      if(ret<0){
        perror("send error \n");
        return false;
      }
      return true;
    }

    bool Recv(std::string *buf,std::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,4096,0,
                      (struct sockaddr*)&addr,&len);
      if(ret<0){
        perror("recvfrom error\n");
        return false;
      }
      buf->assign(tmp, ret);//申请空间并拷贝数据到buf
      if(ip!=NULL){
        *ip=inet_ntoa(addr.sin_addr);
      }
      if(port!=NULL){
      *ip = ntohs(addr.sin_port);
      }
      return true;
    }

    bool Close() {
      if(_sockfd != -1) {
        close(_sockfd);
      }
      return true;
    }

  private:
    int _sockfd;

};

服务端:udp_server.c

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

int  main(){
  int sockfd= socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
  if(sockfd<0){
    perror("socket error\n");
    return -1;
  }
  
  struct sockaddr_in addr;
  addr.sin_family=AF_INET;
  addr.sin_port=htons(9999);  //端口号
  addr.sin_addr.s_addr =inet_addr("192.168.86.3");//ip
  socklen_t addrlen = sizeof(struct sockaddr_in);
  int ret= bind(sockfd,(struct sockaddr*)&addr,addrlen);
  if(ret <0){
    perror("bind error\n");
    return -1;
  }

  while(1){
    //接收数据
    char buf[1024]={0};
    struct sockaddr_in peer_addr;
    ret = recvfrom(sockfd, buf, 1023,0,(struct sockaddr*)&peer_addr,&addrlen);
    if(ret <0){
      perror("recvfrom error\n");
      close(sockfd);
      return -1;
    }
    printf("clent say:%s\n",buf);

    // 发送数据
    memset(buf,0,1024);
    printf("server say: ");
    fflush(stdout);
    fgets(buf,1024,stdin);
    ret=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&peer_addr,addrlen);
    if(ret <0){
      perror("sendto error\n");
      close(sockfd);
      return -1;
    }
  }


  close(sockfd);
  return  0;
}

客户端:udp_client.cpp

#include "udpsocket.hpp"

#define CHECK_RET(q) if((q)==false){return -1;}


int main(){
  UdpSocket sock;
  CHECK_RET( sock.Socket());
  while(1){
     std:: string buf;
     std::cout<<"clent say:";
     std::cin>>buf;
         
    CHECK_RET( sock.Send(buf,"192.168.86.3",9999) );
    
    buf.clear();
   CHECK_RET(sock.Recv(&buf,NULL,NULL) );
   std:: cout<<"server say: "<<buf<<std::endl;
  }
  CHECK_RET(sock.Close());
  return 0;
}

在这里插入图片描述

结果演示:
这里之启动了一个server端,也可同时启动多个server端。

在这里插入图片描述

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值