Linux基本的UDP套接字编程

Linux基本的UDP套接字编程

一、概述

​ 在使用TCP编写的应用程序和使用UDP编写的应用程序之间存在一些本质的差异,其原因在于这两个传输层之间的差别:UDP是无连接不可靠的数据报协议,非常不同于TCP提供的面向连接的可靠字节流。
​ 以下给出了典型的UDP客户/服务器的函数调用。客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目的地(即服务器)的地址作为参数。类似的,服务器不接受来自客户端的连接,而是只管调用recvfrom函数,等待来自某个客户的数据到达。recvfrom将接受与所接受的数据报一道返回客户的协议地址,因此服务器可以把响应的发送给正确的客户。
image-20240216222449394

二、UDP相关套接字函数介绍

​ 在学习UDP通信的相关接口时,部分接口与TCP通信相同,在这里就不过多介绍了;可以去看TCP通信这一章;

1.recvfrom函数和sendto函数

// 读取数据
	ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
// 发送数据
	ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

recvfrom参数说明:

  • sockfd:文件描述符。表示从该文件描述符索引的文件当中读取数据。
  • buf:读取数据的存放位置
  • len:期望读取数据的字节数
  • flags:读取的方式。一般设置为0,表示阻塞读取。
  • src_addr:对端网络相关的属性信号,包括协议家族、IP地址、端口号等
  • addrlen:调用时传入期望读取的src_addr结构体的长度,返回时代表实际读取到的src_addr结构体的长度,这里一个输入输出型参数。

返回值说明:

  • 读取成功返回实际读取到的字节数,读取失败返回-1,同时错误码会被设置。

recvfrom的最后参数类似于accept的最后两个参数:返回时其中套接字地址结构的内容告诉我们是谁发送了数据报;

sendto参数说明:

  • sockfd:对应操作的文件描述符。表示将数据写入该文件描述符索引的文件当中。
  • buf:待写入数据的存放位置。
  • len:期望写入数据的字节数。
  • flags:写入的方式。一般设置为0,表示阻塞写入。
  • dest_addr:它是指向一个含有数据报接收者的协议地址,及对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入dest_addr结构体的长度。

返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

​ sendto的最后两个参数类似于connect的最后两个参数:调用时其中的套接字地址结构被我们填入数据报将发往这个协议地址;

注意:
如果recvfrom的src_addr参数是一个空指针,那么相应的长度参数addrlen也必须是一个空指针,表示我们并不关心数据发送者的协议地址;
由于这两个函数提供的第三个参数也是struct sockaddr* 类型的,因此我们在传入结构体地址时需要将struct sockaddr_in*类型进行强转;

2.UDP服务器

#include <iostream>
#include <cstdio>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define NUM 1024
void Usage(std::string proc){
     std::cout << "Usage: " << proc << " port" << std::endl;
}
// ./udp_server port
int main(int argc, char* argv[]){
    if(argc != 2){
        Usage(argv[0]);
        return -1;
    }
    uint16_t port = atoi(argv[1]);//定义一个16位的端口号
    // 1.创建套接字,打开网络文件
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0){
        std::cerr << "socket create error" << errno << std::endl;
        return 1;
    }
    // 2.给该服务器绑定端口号和ip(特殊处理)
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = INADDR_ANY;
    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
         std::cerr << "bind error" << errno << std::endl;
        return 2;
    }
    // 3.提供服务
    bool quit = false;
    char buffer [NUM];
    while(!quit){
        struct sockaddr_in peer;//客户端的服务
        socklen_t len = sizeof(peer);
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);//读取客户端发来的消息
        if(cnt > 0){
            buffer[cnt] = 0;
            FILE* fp = popen(buffer, "r");
            std::string echo_hello;
            char line[1024] = {0};
            while(fgets(line, sizeof(line), fp) != NULL){
                echo_hello += line;
            }
            pclose(fp);
            std::cout << "client# " << buffer << std::endl;
            //echo_hello += "...";
            sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr*)&peer, len);//给客户端一个响应
        }
        else{
            std::cout << "recvfrom false" << std::endl;
        }      
    } 
    return 0;
}

代码详解如下:
image-20240216224316911

3.UDP客户端

#include <iostream>
#include <cstring>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void Usage(std::string proc){
    std::cout << "Usage: \n\t" << proc << " server_ip server_port" << std::endl;
}
int main(int agrc, char* argv[]){
    if(agrc != 3){
        Usage(argv[0]);
        return 0;
    }
    // 1.创建套接字,打开网络文件  
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0){
        std::cerr << "socket error: " << errno << std::endl;
        return 1;
    }
     /*
     客户端需要显示的bind吗?(绑定端口号)
     a.首先,客户端必须要有ip和port
     b.但是,客户端不需要显示的bind!一旦显示bind,就必须明确client要和哪一个port关联
     client指明的端口号,在client端一定会有吗??
       不,有可能被占用,被占用导致client无法使用
     server要的是port必须明确,而且不变,但client只要有就行!
     一般是由操作系统自动给你bind()
     就是client正常发送数据的时候,操作系统会自动给你bind,采用的是随机端口的方式!
     */ 
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    // 2.使用服务
    while(1){
        std::cout << "MyShell $ ";
        char line[1024];
        fgets(line, sizeof(line), stdin);
        // b.你要发给谁??
        sendto(sock, line, strlen(line), 0, (struct sockaddr*)&server, sizeof(server));
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&tmp, &len);
        if(cnt > 0){
            buffer[cnt] = 0;
            std::cout << buffer << std::endl;
        }
        else{
            std::cout << "recvfrom false" << std::endl;
        }
    }
    return 0;
} 

代码的详解如下:
image-20240216224513699

4.UDP通信结果展示

image-20240216233818749

  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Li小李同学Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值