C/C++ Linux网络编程2 - Socket编程与简单UDP服务器客户端

上篇文章:Linux网络编程 - 1网络编程基础-CSDN博客

代码仓库:橘子真甜 (yzc-YZC) - Gitee.com

目录

[一. Socket编程API与结构体](#一. Socket编程API与结构体)

[1.1 Socketaddr结构体](#1.1 Socketaddr结构体)

[二. UDP网络编程API⭐](#二. UDP网络编程API⭐)

[2.1 socket](#2.1 socket)

[2.2 bind](#2.2 bind)

[2.3 sendto](#2.3 sendto)

[2.4 recvfrom](#2.4 recvfrom)

[三. UDP服务端代码](#三. UDP服务端代码)

[3.1 udpserver.hpp](#3.1 udpserver.hpp)

[3.2 udpserver.cc](#3.2 udpserver.cc)

[四. UDP客户端代码](#四. UDP客户端代码)

[4.1 udpclient.hpp](#4.1 udpclient.hpp)

[4.2 udpclient.cc](#4.2 udpclient.cc)

[五. 测试](#五. 测试)


一. Socket编程API与结构体

socket是插座的意思,插就是应用程序,座就是网络socket。通信双方都通过socket进行通信。

1.1 Socketaddr结构体

有三套结构体,sockaddr sockaddr_in sockaddr_un

sockaddr:原始套接字,可以通过传输层控制底层结构,常用于网络中的监听,抓包等功能。结构如下:

sockaddr_in:网络套接字,常用于跨主机之间的网络通信。内部**集合了主机的IP和端口,在大部分网络编程API中,需要强制转化为原始套接字。**结构如下:

Unix域间套接字:用于主机内部的通信,类似于管道和共享内存。结构如下:

在使用这些结构体的时候,通过将 sockaddr_in sockaddr_un转化为 sockaddr即可实现相应类型的通信。

其实就是一种多态的思想,soackaddr就是基类,剩下两种是派生类。

二. UDP网络编程API⭐

UDP的特点是不保证可靠传输,面向数据报,无连接。

所以:

我们每一次接收数据的时候,应该将一个完整的报文读取完毕。

由于无连接,发送数据的时候需要带上自己的ip和端口等信息

收数据的时候也要定义对象来获取对方的ip和端口等信息

2.1 socket

cpp 复制代码
//头文件
#include <sys/types.h>
#inclyde <sys/socket.h>

//函数原型,用于创建一个用于网络通信的套接字fd
int socket(int domin, int type, int protocol)


//参数说明
//domin : 域,表示是需要网络通信还是主机内部通信

//type  : 通信的类型
//SOCK_STREAM    是流式套接字通信,即TCP通信
//SOCK_DGRAM     是报文套接字通信,即UDP通信

// protocol,
// 分为tcpprotocol udpprotoclo 但是我们一般设置为0,因为type参数选择之后,这个就会自动固定

//返回值,返回一个用于通信的文件fd,其实socket与open是非常类似的

Linux中一切皆文件,打开网络与打开普通文件是一样的。读写网络数据与读写文件数据也是非常类似的

2.2 bind

用于绑定sockfd与IP端口

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

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
sockfd : 使用socket获取的文件描述符
addr : 传入的结构体,该结构体是你需要通信的方式,内部包含了IP和端口等信息
addrlen : 传入结构体的长度,需要注意类型

返回值:绑定成功返回0,失败返回-1

//注意,我们定义下面三种结构体的时候需要带上头文件#include <arpa/inet.h>
struct sockaddr s;
s.sin_family = AF_INET    表示协议家族,我们需要传入协议,如AF_INET表示使用网络通信
s.sin_port                表示绑定端口号
s.sin_addr                存储了IP号码
s.sin_addr.s_addr         即可获取IP地址

2.3 sendto

这个接口常用于UDP服务器/客户端之间的发送信息

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

用于发送信息到网络中
//函数原型
ssize_t sendto(int sockfd, void *buf, size_t len, int flags, struct sockaddr* dest_addr, socklen_t *addrlen);
//将  len长度的buf数据通过sockfd这个套接字发送到网络中
flags为0表示阻塞

//输入形参数
dest_addr 对方接收数据的IP地址和端口
addrlen   对方接收数据结构体长度

2.4 recvfrom

用于UDP通信时候接收数据

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

用于接收来自网络的信息
//函数原型
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen)

//参数
sockfd     接收信息的文件描述符
bur        接收信息写入的缓冲区    len 接收信息的长度
flags      接收信息的方式,一般默认设置为0    表示阻塞方式,没有信息阻塞等待

//下面两个参数是输入输出形参数,是为了获取对方的IP信息与端口号码,协议方式
src_addr    对方IP的信息
addrlen     该信息的长度,注意这个参数在定义的时候不需要初始化为src_addr的长度 addrlen = sizeof(src_addr)

三. UDP服务端代码

这个服务器用于接收客户端的代码,然后打印输出。

3.1 udpserver.hpp

服务器的代码框架如下:

cpp 复制代码
#pragma once
#include <iostream>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

static const std::string defaultIp = "0.0.0.0";
class udpServer
{
public:
    udpServer(uint16_t port, const std::string &ip = defaultIp)
        : _sockfd(-1), _ip(ip), _port(port) {}

    void init() {}

    void run() {}

private:
    int _sockfd; // 套接字

    std::string _ip; // 服务器ip
    uint16_t _port;  // 绑定的端口
};

需要变量来存储服务器的 通信fd,ip和端口。

init用于初始化服务器:包含socket,bind

run:用于服务器的运行,一个服务器一般是一个死循环执行代码的。

一般来说,一台主机是有很多ip的,为了能够保证接收数据不丢失,服务器绑定的IP一般都是"0.0.0.0"表示服务器可以接收底层任意ip地址的网络数据

init代码:

cpp 复制代码
  void init()
        {
            // 1.socket创建套接字,udp需要使用 报文
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd < 0)
            {
                perror("socket");
                exit(errno);
            }
            printf("socket success\n");

            // 2.绑定fd和端口
            // 首先要设置好通信结构体
            struct sockaddr_in serveraddr;
            memset(&serveraddr, 0, sizeof(serveraddr));
            serveraddr.sin_family = AF_INET;
            serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要转化为大端数据
            serveraddr.sin_port = htons(_port);                  // 需要转化为大端数据
            socklen_t len = sizeof(serveraddr);                  // 设置bind第三个参数

            int n = bind(_sockfd, (struct sockaddr *)&serveraddr, len);
            if (n < 0)
            {
                perror("bind");
                exit(errno);
            }
            printf("bind success\n");
            // UDP在绑定好之后,就能运行了
        }

run:

服务器运行的时间是很长的,所以我们需要使用一个死循环来读取数据。

cpp 复制代码
        void run()
        {
            char buffer[1024];
            while (true)
            {
                // 收数据,要定义对象获取对方的ip和端口
                struct sockaddr_in clientaddr;
                memset(&clientaddr, 0, sizeof(clientaddr));
                socklen_t clientaddrlen = sizeof(clientaddr);

                // recvfrom接收数据
                int count = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&clientaddr, &clientaddrlen);

                if (count > 0)
                {
                    // 通过设置好的sockaddr获取ip,端口等信息
                    std::string clientip = inet_ntoa(clientaddr.sin_addr);
                    uint16_t clientport = ntohs(clientaddr.sin_port);
                    // 打印收到数据的信息
                    printf("client[%s][%d] --> server:%s\n", clientip.c_str(), clientport, buffer);
                }
                // 清空 buffer
                memset(buffer, 0, sizeof(buffer));
            }
        }

总代码如下:

cpp 复制代码
namespace YZC
{
    static const std::string defaultIp = "0.0.0.0";
    class udpServer
    {
    public:
        udpServer(uint16_t port, const std::string &ip = defaultIp)
            : _sockfd(-1), _ip(ip), _port(port) {}

  void init()
        {
            // 1.socket创建套接字,udp需要使用 报文
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd < 0)
            {
                perror("socket");
                exit(errno);
            }
            printf("socket success\n");

            // 2.绑定fd和端口
            // 首先要设置好通信结构体
            struct sockaddr_in serveraddr;
            memset(&serveraddr, 0, sizeof(serveraddr));
            serveraddr.sin_family = AF_INET;
            serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要转化为大端数据
            serveraddr.sin_port = htons(_port);                  // 需要转化为大端数据
            socklen_t len = sizeof(serveraddr);                  // 设置bind第三个参数

            int n = bind(_sockfd, (struct sockaddr *)&serveraddr, len);
            if (n < 0)
            {
                perror("bind");
                exit(errno);
            }
            printf("bind success\n");
            // UDP在绑定好之后,就能运行了
        }

        void run()
        {
            char buffer[1024];
            while (true)
            {
                // 收数据,要定义对象获取对方的ip和端口
                struct sockaddr_in clientaddr;
                memset(&clientaddr, 0, sizeof(clientaddr));
                socklen_t clientaddrlen = sizeof(clientaddr);

                // recvfrom接收数据
                int count = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&clientaddr, &clientaddrlen);

                if (count > 0)
                {
                    // 通过设置好的sockaddr获取ip,端口等信息
                    std::string clientip = inet_ntoa(clientaddr.sin_addr);
                    uint16_t clientport = ntohs(clientaddr.sin_port);
                    // 打印收到数据的信息
                    printf("client[%s][%d] --> server:%s\n", clientip.c_str(), clientport, buffer);
                }
                // 清空 buffer
                memset(buffer, 0, sizeof(buffer));
            }
        }

    private:
        int _sockfd; // 套接字

        std::string _ip; // 服务器ip
        uint16_t _port;  // 绑定的端口
    };
}

3.2 udpserver.cc

这个代码包含main函数,用于服务器的启动和测试

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

// 设置使用方法
void Usage(const char *s)
{
    printf("Usage:\r\n%s serverport\n", s);
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(-1);
    }

    // 使用智能指针管理服务器,可以自动销毁。防止内存泄漏
    std::unique_ptr<YZC::udpServer> usptr(new YZC::udpServer(atoi(argv[1])));
    usptr->init();
    usptr->run();
    return 0;
}

四. UDP客户端代码

这个客户端用于向服务端发送数据

思考一下:服务端为何需要bind绑定fd和端口?

因为,如果没有指明端口,其他客户端如何连接服务器?

思考一下:客户端需要显示绑定fd和端口吗?

不需要,因为通信是由客户端发起的,服务端被动接收。此时服务端无需知道客户端具体的ip和端口信息

服务端可以直接通过recvfrom获取我的ip/端口等信息

4.1 udpclient.hpp

框架与server类似

cpp 复制代码
namespace YZC
{
    class udpClient
    {
    public:
        udpClient(std::string &ip, uint16_t port)
            : _ip(ip), _port(port) {}

        void init()
        {
        }

        void start()
        {
        }

    private:
        int _sockfd; // 监听套接字

        std::string _ip; // 目的ip
        uint16_t _port;  // 目的端口
    };
}

init : 上面讨论过了,只需要socket,无需显示bind。如果没有bind,在发送数据的时候内核会自动帮我们找到没有使用的port进行bind

cpp 复制代码
        void init()
        {
            // 1.socket创建套接字,udp需要使用 报文
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd < 0)
            {
                perror("socket");
                exit(errno);
            }
            printf("client socket success\n");

            // bind由OS自行决定
        }

start:

用于发送数据

cpp 复制代码
  void start()
        {
            // 发送数据,需要指定对方的ip和端口
            struct sockaddr_in serveraddr;
            memset(&serveraddr, 0, sizeof(serveraddr));

            // 设置ip/端口/通信等信息。注意转化为大端数据
            serveraddr.sin_family = AF_INET;
            serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str());
            serveraddr.sin_port = htons(_port);

            socklen_t clientaddrlen = sizeof(serveraddr);

            // 可以发送数据
            char buffer[1024];
            while (true)
            {
                memset(buffer, 0, sizeof(buffer));
                std::cout << "请输入你想要向server发送的数据;";
                std::cin >> buffer;
                int count = sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&serveraddr, clientaddrlen);
                if (count <= 0)
                {
                    printf("数据发送异常,client quit\n");
                    close(_sockfd);
                    exit(-1);
                }
                printf("数据发送成功\n");
            }
        }

总代码如下:

cpp 复制代码
#pragma once
#include <iostream>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <cstring>

namespace YZC
{
    class udpClient
    {
    public:
        udpClient(std::string &ip, uint16_t port)
            : _ip(ip), _port(port) {}

        void init()
        {
            // 1.socket创建套接字,udp需要使用 报文
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd < 0)
            {
                perror("socket");
                exit(errno);
            }
            printf("client socket success\n");

            // bind由OS自行决定
        }

  void start()
        {
            // 发送数据,需要指定对方的ip和端口
            struct sockaddr_in serveraddr;
            memset(&serveraddr, 0, sizeof(serveraddr));

            // 设置ip/端口/通信等信息。注意转化为大端数据
            serveraddr.sin_family = AF_INET;
            serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str());
            serveraddr.sin_port = htons(_port);

            socklen_t clientaddrlen = sizeof(serveraddr);

            // 可以发送数据
            char buffer[1024];
            while (true)
            {
                memset(buffer, 0, sizeof(buffer));
                std::cout << "请输入你想要向server发送的数据;";
                std::cin >> buffer;
                int count = sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&serveraddr, clientaddrlen);
                if (count <= 0)
                {
                    printf("数据发送异常,client quit\n");
                    close(_sockfd);
                    exit(-1);
                }
                printf("数据发送成功\n");
            }
        }

    private:
        int _sockfd; // 监听套接字

        std::string _ip; // ip
        uint16_t _port;  // 绑定的端口
    };
}

4.2 udpclient.cc

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

// 设置使用方法
void Usage(const char *s)
{
    printf("Usage:\r\n%s serverip serverport\n", s);
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(-1);
    }

    // 使用智能指针管理服务器,可以自动销毁。防止内存泄漏
    std::unique_ptr<YZC::udpClient> ucptr(new YZC::udpClient(argv[1], atoi(argv[2])));
    ucptr->init();
    ucptr->start();
    return 0;
}

五. 测试

使用上面的代码来测试服务端server与客户端client 能否通信

这里首先了解一下本地回环地址 127.0.0.1。使用命令 ifconfig查看网卡信息

下面的数据是我在自己的云服务器输入ifconfig获取的。

cpp 复制代码
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.52.126  netmask 255.255.192.0  broadcast 172.17.63.255
        inet6 fe80::216:3eff:fe0a:5e1e  prefixlen 64  scopeid 0x20<link>
        ether 00:16:3e:0a:5e:1e  txqueuelen 1000  (Ethernet)
        RX packets 1944787  bytes 1196104294 (1.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1537251  bytes 634615558 (605.2 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 746248  bytes 164466741 (156.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 746248  bytes 164466741 (156.8 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

可以看第二段的第二行,本地回环地址是 127.0.0.1

首先要启动服务器

然后启动客户端

运行结果如下:

可以看到,我们的数据被客户端正常发送了。服务端也接收到来正确的数据

这样一来就能保证 server - client之间的通信,当然这份代码是比较简单的。

相关推荐
wWYy.4 小时前
Linux发送数据包的过程
linux·网络
小嵌同学4 小时前
Linux 内核中的 Per-CPU 机制详解
linux·运维·arm开发·嵌入式
云飞云共享云桌面4 小时前
自动化设备厂 10 人研发团队,如何共用一台服务器做 SolidWorks 建模?
运维·服务器·网络·3d·自动化·制造
筝筝ba4 小时前
Configure IP Address Via Different User Roles And Verify(1)
服务器
QH139292318804 小时前
NVIDIA H200 H300服务器
运维·服务器·开发语言·网络·信息与通信
xixiaoyunya4 小时前
中小微企业轻量组网方案的技术选型与实践:从传统专线到软定义隧道的成本与效能分析
服务器·php·apache
Hello-FPGA4 小时前
嵌入式LINUX 设备 ip配置后怎么保存才能掉电不消失?
linux·运维·服务器
奥莱维5 小时前
KNX酒店方案_KNX专用线与高端酒店技术逻辑
java·服务器·前端·数据库
工业一体机老司机5 小时前
Linux工业一体机自启动服务配置:systemd服务单元编写与开机优化实战
java·linux·服务器
zhixingheyi_tian5 小时前
Linux 之 ssh
linux·服务器·ssh