socket编程-----udp通信程序编写的基本流程


前言

传输层协议的区别:
tcp:传输控制协议–面向连接,可靠传输,面向字节流
udp:用户数据报协议—无连接,不可靠,面向数据报
tcp保证可靠传输,但是传输速度没有udp快
tcp应用于安全性要求高的场景/udp应用于实时性要求高的场景

一、通信程序的编写

通信中总是两端主机进行通信,并且主动发起请求的一方称为客户端,被动首先接收请求的一方称为服务端
客户端:主动发起请求的一方----用户的一端
服务端:被动接收请求的一方----向用户提供服务的一端

网络通信程序的编写都使用的是套接字接口----socket接口

二、udp通信流程

1.udp流程图

在这里插入图片描述

2.套接字接口

1.创建套接字
int socket(int domain,int type,int protocol)

  • domain:地址域–确定本次socket通信使用哪种协议版本的地址结构–不同的协议版本有不同的地址结构–AF_INET IPV4
  • type:套接字类型(流式套接字-SOCK_STREAM–TCP所使用的/数据报套接字-SOCK_DGRAM—UDP所使用的)
  • protocol:协议类型(通常就是传输层协议的选择 IPPROTO_TCP/IPPROTO__UDP),默认为0-流式默认tcp/数据报默认udp
  • 返回值:文件描述符–非负整数–套接字所有其他接口的操作句柄;失败返回-1;

2.为套接字绑定地址信息
int bind(int sockfd,const struct sockaddr *addr,socklen_t len)

  • sockfd:创建套接字返回的操作句柄
  • addr:要绑定的地址信息
  • len:实际地址结构长度
  • ipv4通信使用的是struct scokaddr_in 地址信息
  • 返回值:成功返回0,失败返回-1
    在这里插入图片描述

3.接收数据
ssize_t recvfrom(int sockfd,char* buf, int len,int flag, struct sockaddr *peer_addr,socklen_t addlen);

  • sockfd:socket操作句柄
  • buf:一块缓冲区,用于接收从接收缓冲区中取出的数据
  • len:想要接收的数据长度
  • flag:操作选项标志,默认为0,表示阻塞操作
  • peer_addr:发送方的地址信息
  • addrlen:想要获取的地址信息长度以及返回实际长度
  • 返回值:成功返回实际接收到的数据字节长度;失败返回-1;

4.发送数据
ssize_t sendto(int sockfd,char *data,int len ,int flag,struct sockaddr *peer_addr,socklen_t addlen);

  • sockfd:socket操作句柄
  • data:要发送的数据首地址
  • len:想要发送的数据长度
  • flag:操作选项标志,默认为0,表示阻塞操作
  • peer_addr:接受方的地址信息
  • addrlen:地址信息长度
  • 返回值:成功返回实际发送的数据字节长度;失败返回-1;

5.关闭套接字
int close(fd);

3.字节序转换接口

  • uint16_t htons(uint16_t data)/uint32_t htonl(uint32_t data);主机字节序到网络字节序的整型数据转换
  • uint16_t ntohs(uint16_t data)/uint32_t ntohl(uint32_t data);网络字节序到主机字节序的整型数据转换
  • in_addr_t inet_addr(char *ip);字符串IPV4的IP地址转换为网络字节序的整数IP地址
  • char* inet_ntoa(struct in_addr nip);网络字节序整数IP地址转换为字符串的IPV4的IP地址
  • const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);将网络字节序的整数IP地址,转换为字符串IP地址—兼容ipv6
  • int inet_pton(int af,const char* src,void *dst);将字符串的IP地址转换成网络字节序的整数IP地址

三.实现代码

udpsocket.hpp

#include <cstdio>
#include <string>
#include <unistd.h>
#include <netinet/in.h>//包含地址结构信息
#include <arpa/inet.h>//字节序转换接口
#include <sys/socket.h>//套接字接口信息

class UdpSocket {
    public:
        UdpSocket():_sockfd(-1){}
        bool Socket() {//创建套接字
            //socket(地址域, 套接字类型, 协议类型)
            _sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            if (_sockfd < 0) {
                perror("socket error");
                return false;
            }
            return true;
        }
        // 为套接字绑定地址信息
        bool Bind(const std::string &ip,  uint16_t port) {
            //定义IPV4地址结构 struct sockaddr_in
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);//htons将主机字节序短整型数据转换为网络字节序数据
            addr.sin_addr.s_addr = inet_addr(ip.c_str());//将字符串IP地址转换为网络字节序
            //bind(描述符, 地址信息, 地址信息长度)
            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;
        } 
        //接收数据,获取发送端地址信息
        //Recv(&buf, &ip, &port)
        //Recv(&buf);--不给予后两个参数,就会使用默认参数 = Recv(&buf, NULL, NULL);
        bool Recv(std::string *buf,  std::string *ip=NULL,  uint16_t *port=NULL) {
            //recvfrom(套接字句柄,接收缓冲区,数据长度,标志, 源端地址,地址长度)
            struct sockaddr_in peer_addr;
            socklen_t len = sizeof(struct sockaddr_in);
            char tmp[4096] = {0};
            int ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr*)&peer_addr, &len);
            if (ret < 0) {
                perror("recvfrom error");
                return false;
            }
            buf->assign(tmp, ret); // assign从指定字符串中截取指定长度的数据到buf中
            if (port != NULL) {//根据地址判断用户是否想要获取地址信息
                *port = ntohs(peer_addr.sin_port);//网络字节序到主机字节序的转换
            }
            if (ip != NULL) {
                *ip = inet_ntoa(peer_addr.sin_addr);//网络字节序到字符串IP地址的转换
            }
            return true;
        }
        // 发送数据
        bool Send(const std::string &data, const std::string &ip, const uint16_t port) {
            //sendto(套接字句柄,数据首地址,数据长度,标志,对端地址信息,地址信息长度)
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());
            socklen_t len = sizeof(struct sockaddr_in);
            int ret = sendto(_sockfd, data.c_str(), data.size(), 0, 
                    (struct sockaddr*)&addr, len);
            if (ret < 0) {
                perror("sendto error");
                return false;
            }
            return true;
        }
        bool Close(){
            if (_sockfd > 0) {
                close(_sockfd);
                _sockfd = -1;
            }
            return true;
        }// 关闭套接字
    private:
        int _sockfd;
};

udp_cli.cpp

#include <iostream>
#include <string>
#include "udpsocket.hpp"

#define CHECK_RET(q) if((q)==false){return -1;}
int main (int argc, char *argv[])
{
    //客户端参数获取的IP地址是服务端绑定的地址,也就是客户端发送数据的目标地址
    //不是为了自己绑定的
    if (argc != 3) {
        std::cout << "Usage: ./udp_cli ip port\n";
        return -1;
    }
    std::string srv_ip = argv[1];
    uint16_t srv_port = std::stoi(argv[2]);

    UdpSocket cli_sock;
    //创建套接字
    CHECK_RET(cli_sock.Socket());
    //绑定地址(不推荐)
    while(1) {
        //发送数据
        std::cout << "client say:";
        std::string buf;
        std::cin >> buf;
        if (buf == "quit") {
            break;
        }
        CHECK_RET(cli_sock.Send(buf, srv_ip, srv_port));
        //接收数据
        buf.clear();
        CHECK_RET(cli_sock.Recv(&buf));//默认参数可以不用赋予
        std::cout << "server say: " << buf << std::endl;
    }
    //关闭套接字
    cli_sock.Close();
    //...
    
    return 0;
}

udp_src.cpp

#include <iostream>
#include <string>
#include "udpsocket.hpp"

#define CHECK_RET(q) if((q)==false){return false;}
int main (int argc, char *argv[])
{
    //argc 表示程序运行参数的个数
    //./udp_srv 192.168.2.2 9000
    if (argc != 3) {
        std::cout << "Usage: ./udp_srv ip port\n";
        return -1;
    }
    uint16_t port = std::stoi(argv[2]);
    std::string ip = argv[1];

    UdpSocket srv_sock;
    //创建套接字
    CHECK_RET(srv_sock.Socket());
    //绑定地址信息
    CHECK_RET(srv_sock.Bind(ip, port));
    while(1) {
        //接收数据
        std::string buf;
        std::string peer_ip;
        uint16_t peer_port;
        CHECK_RET(srv_sock.Recv(&buf, &peer_ip, &peer_port));//接收对端数据以及地址信息
        std::cout << "client["<<peer_ip<<":"<<peer_port<<"] say: "<<buf<<std::endl; 
        //发送数据
        buf.clear();
        std::cout << "server say: ";
        std::cin >> buf;
        CHECK_RET(srv_sock.Send(buf, peer_ip, peer_port));//谁发送了数据就给谁回复
    }
    //关闭套接字
    srv_sock.Close();
    return 0;
}

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值