【Linux网络编程】Socket编程--UDP(第一弹):实现客户端和服务器互相发送消息


🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git

🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈 本科在读菜鸡一枚,指出问题及时改正

文章目录

简单的回显服务器和客户端代码

Udp Server

socket套接字创建

cpp 复制代码
#include<sys/types.h>
#include<sys/socket.h>

int socket(int domain, int type, int protocol);

参数说明:

  • int domain:指定协议族

    AF_INET: IPv4 协议

    AF_INET6: IPv6 协议

    AF_UNIX: 本地通信(也称为 UNIX 域套接字)

  • int type:指定套接字的类型

    SOCK_STREAM: 提供可靠的、面向连接的字节流(TCP)

    SOCK_DGRAM: 提供不可靠的、无连接的数据报(UDP)

    SOCK_RAW: 提供原始套接字,允许直接访问网络层(通常用于网络监测或自定义协议)

  • int protocol:指定所需的协议

  • 返回值:成功时,socket 函数返回一个非负整数,代表新创建的套接字的文件描述符。这个文件描述符可以用于后续的套接字操作(如 bind、listen、accept 等)。

    失败时,返回 -1,并设置 errno 来指示错误原因。

在UDP通信中,将前两个参数设置好之后,最后一个参数设置成0即可。

任何一个UDP服务通信中,都需要有一个int sockfd的文件描述符,按照系统编程中所说,这里打印出来的文件描述符应该是3,因为0,1,2已经被占用了。


创建套接字代码:

cpp 复制代码
void InitServer()
{
    //1.创建套接字  
    _sockfd=::socket(AF_INET,SOCK_DGRAM,0); //调用系统级的方法
    if(_sockfd<0)
    {
        //通信不可能实现,直接退出
        LOG(FATAL,"socket error\n");
        exit(SOCKET_ERROR);
    }

    LOG(DEBUG,"socket creat success, _sockfd:%d\n",_sockfd); //_socked=3
}

套接字和IP地址、端口号绑定

网络通信中,客户端和服务器需要有自己的IP地址和端口号,因此需要将套接字和IP地址、端口号绑定。

cpp 复制代码
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
cpp 复制代码
    void InitServer()
    {
        //1.创建套接字(文件)  
        _sockfd=::socket(AF_INET,SOCK_DGRAM,0); //调用系统级的方法
        if(_sockfd<0)
        {
            //通信不可能实现,直接退出
            LOG(FATAL,"socket error\n");
            exit(SOCKET_ERROR);
        }

        LOG(DEBUG,"socket creat success, _sockfd:%d\n",_sockfd); //_socked=3

        //2.bind
        //(1)先填充本地信息
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family=AF_INET;
        local.sin_port=htons(_localport); //端口号需要从主机转换成网络序列
        local.sin_addr.s_addr=inet_addr(_localip.c_str());             //用户习惯的是字符串,比如"192.xxx.xxx.xxx"
        //但是网络中需要4字节ip,需要的是网络序列ip
        //也就是说这里需要将字符串转换成4字节和网络序列

        //(2)绑定
        int n=::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            //绑定失败,不会网络通信
            LOG(FATAL,"bind error\n");
            exit(BAND_ERROR);
        }
        //绑定成功
        LOG(DEBUG,"socket bind success\n");
    }
  • 定义一个struct sockaddr_in local;结构体用于存储本地地址信息,该对象中有四个字段,如下:

    需要对前三个字段进行设置,sin_family 的值和 socket 函数中的 domain 参数保持一致;sin_por是端口信息,由于是在网络中通信,需要将主机转换成网络序列; local.sin_addr.s_addr=inet_addr(_localip.c_str())是将ip地址从主机序列转换成网络序列,但是ip地址用户习惯于字符串形式,即"192.xxx.xxx.xxx",需要转换成4字节,这里直接使用inet_addr()函数即可。

读取服务器套接字数据--recvfrom

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
   				struct sockaddr *src_addr, socklen_t *addrlen);

参数解释:

  • sockfd套接字描述符
  • buf: 指向存储接收到数据的缓冲区的指针
  • len: 要接收的字节数,表示缓冲区的大小
  • flags: 接收选项的标志
  • src_addr: 可选参数,指向 sockaddr 结构体的指针,用于存储发送方的地址信息。如果不需要该信息,可以传入 NULL
  • addrlen: 可选参数,指向一个 socklen_t 类型的变量,表示 src_addr 指向的结构的大小。调用后,该变量将被更新为实际的地址长度。

发送数据--stndto

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
  • dest_addr:指向目标地址的指针,通常是 sockaddr 结构体的指针,表示数据将要发送到的地址。如果目标是 UDP 套接字,必须指定目标地址。
  • addrlen:指向一个 socklen_t 类型的变量,表示 dest_addr 指向的结构的大小。这个参数在调用时需要正确设置,调用后该变量会被更新为实际的地址长度。

cpp 复制代码
void Start()
{
    _isrunning=true;
    char inbuffer[1024];
    while (_isrunning)
    {
        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);
        
        ssize_t n=recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&peer,&len);
        if(n>0)
        {
            inbuffer[n]=0;
            std::string echo_string="[udp_server echo] #";
            echo_string+=inbuffer;

            sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);
        }
    }
    
}

注意事项

云服务器上禁止绑定自己的公网:

可以绑定内网,但是都不到信息,因为不会在公网公布:

在云服务上,绑定IP地址一般绑定为0,这样云服务器绑定了任意IP:

服务器端进程任意IP地址绑定:

cpp 复制代码
local.sin_addr.s_addr=INADDR_ANY;

UDP Client

和服务器有所不同, 客户端的进程很多,但是端口号只能和一个进程绑定,可能出现两个进程绑定同一个端口号,会出现冲突无法运行。为了解决这一问题,客户端的端口号一般不让用户设定,而是让客户端操作所在的操作系统随机选择一个端口号。客户端的端口号具体是多少不重要,只要能标记和别的进程不一样即可。

客户端需要绑定自己的IP地址和端口,但是不需要显示绑定自己的IP地址和端口。客户端在首次向服务器发送数据的时候,系统会自动给客户端绑定它自己的IP和端口。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 客户端需要先知道服务器ip地址和端口号

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
        exit(0);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }

    // client 不需要显示绑定自己的IP和端口,但是需要绑定自己的IP和端口

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    while (1)
    {
        std::string line;
        std::cout << "Please Enter# ";
        std::getline(std::cin, line);

        int n = sendto(sockfd, line.c_str(), line.size(), 0, (sockaddr *)&server, sizeof(server));
        if (n > 0)
        {
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            char buffer[1024];
            int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&temp, &len);
            if (m > 0)
            {
                buffer[m] = 0;
                std::cout << buffer << std::endl;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }
    }

    ::close(sockfd);
    return 0;
}

完整代码

UdpServer.hpp

cpp 复制代码
#pragma once

#include<iostream>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"nocopy.hpp"
#include"Log.hpp"

using namespace log_ns;

static const int gsockfd=-1;
static const uint16_t glocalport=8888;

enum
{
    SOCKET_ERROR=1,
    BAND_ERROR
};

class UdpServer:public nocopy
{
public:
    UdpServer(uint16_t localport=glocalport)
        :_sockfd(gsockfd)
        ,_localport(localport)
        ,_isrunning(false)
    {}

    void InitServer()
    {
        //1.创建套接字(文件)  
        _sockfd=::socket(AF_INET,SOCK_DGRAM,0); //调用系统级的方法
        if(_sockfd<0)
        {
            //通信不可能实现,直接退出
            LOG(FATAL,"socket error\n");
            exit(SOCKET_ERROR);
        }

        LOG(DEBUG,"socket creat success, _sockfd:%d\n",_sockfd); //_socked=3

        //2.bind
        //(1)先填充本地信息
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family=AF_INET;
        local.sin_port=htons(_localport); //端口号需要从主机转换成网络序列
        /*
        local.sin_addr.s_addr=inet_addr(_localip.c_str());             //用户习惯的是字符串,比如"192.xxx.xxx.xxx"
        //但是网络中需要4字节ip,需要的是网络序列ip
        //也就是说这里需要将字符串转换成4字节和网络序列
        */
       local.sin_addr.s_addr=INADDR_ANY;

        //(2)绑定
        int n=::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            //绑定失败,不会网络通信
            LOG(FATAL,"bind error\n");
            exit(BAND_ERROR);
        }
        //绑定成功
        LOG(DEBUG,"socket bind success\n");
    }

    void Start()
    {
        _isrunning=true;
        char inbuffer[1024];
        while (_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len=sizeof(peer);
            
            ssize_t n=recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&peer,&len);
            if(n>0)
            {
                inbuffer[n]=0;
                std::cout<<"client say# "<<inbuffer<<std::endl;
                std::string echo_string="[udp_server echo] #";
                echo_string+=inbuffer;

                sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);
            }
            else
            {
                std::cout<<"recvfrom : error "<<std::endl;
            }
        }
        
    }

    ~UdpServer()
    {
        if(_sockfd>gsockfd)::close(_sockfd);
    }

private:
    int _sockfd;    
    uint16_t _localport; //服务器的端口号
    bool _isrunning;
};

UdpServerMain.cc

cpp 复制代码
#include"UdpServer.hpp"
#include<memory>


int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;
        exit(0);
    }
    
    uint16_t port = std::stoi(argv[1]);

    EnableScreen();
    std::unique_ptr<UdpServer> usvr=std::make_unique<UdpServer>(port); //C++14标准
    usvr->InitServer();
    usvr->Start();

    return 0;
}

UdpClientMain.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 客户端需要先知道服务器ip地址和端口号

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
        exit(0);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }

    // client 不需要显示绑定自己的IP和端口,但是需要绑定自己的IP和端口

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    while (1)
    {
        std::string line;
        std::cout << "Please Enter# ";
        std::getline(std::cin, line);

        int n = sendto(sockfd, line.c_str(), line.size(), 0, (sockaddr *)&server, sizeof(server));
        if (n > 0)
        {
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            char buffer[1024];
            int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&temp, &len);
            if (m > 0)
            {
                buffer[m] = 0;
                std::cout << buffer << std::endl;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }
    }

    ::close(sockfd);
    return 0;
}

实例测试

相关推荐
转世成为计算机大神25 分钟前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
黑客Ash26 分钟前
【D01】网络安全概论
网络·安全·web安全·php
->yjy27 分钟前
计算机网络(第一章)
网络·计算机网络·php
sinat_3842410929 分钟前
使用 npm 安装 Electron 作为开发依赖
服务器
机器视觉知识推荐、就业指导34 分钟前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
宅小海44 分钟前
scala String
大数据·开发语言·scala
朝九晚五ฺ44 分钟前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
qq_327342731 小时前
Java实现离线身份证号码OCR识别
java·开发语言
锅包肉的九珍1 小时前
Scala的Array数组
开发语言·后端·scala
自由的dream1 小时前
Linux的桌面
linux