linux socket编程之udp(实现客户端和服务端消息的发送和接收)

news2025/4/20 22:15:08

目录

一.创建socket套接字(服务器端)

二.bind将prot与端口号进行绑定(服务器端)

2.1填充sockaddr_in结构

2.2bind绑定端口

三.直接通信(服务器端)

3.1接收客户端发送的消息

3.2给客户端发送消息

四.客户端通信

4.1创建socket套接字

4.2客户端bind问题

4.3直接通信即可

4.3.1构建目标主机的socket信息

4.3.2给服务端发送消息

4.3.3.接收服务端发送过来的消息

五.效果展示

5.1使用127.0.0.1本地环回测试

5.2使用公网ip测试

六.代码演示

6.1UdpServer.hpp

6.2UdpClient.cc

6.3InetAddr.hpp

6.4LockGuard.hpp

6.5Log.hpp

6.6main.cc

6.7makefile


一.创建socket套接字(服务器端)

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

domain:选择你要使用的网络层协议 一般是ipv4,也就是AF_INET

type:选择你要使用的应用层协议,这里我们选择udp,也就是SOCK_DGRAM

protocol:这里我们先设置成0

成功返回文件描述符,失败返回-1

//1.创建socket套接字
_socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(_socketfd < 0)
{
    LOG(FATAL, "SOCKET ERROR, %s, %d\n", strerror(errno), errno);
    exit(SOCKET_ERROR);
}

二.bind将prot与端口号进行绑定(服务器端)

2.1填充sockaddr_in结构

uint16_t htons(uint16_t hostshort);//将端口号从主机序列转成网络序列
in_addr_t inet_addr(const char *cp);//将ip从主机序列转成网络序列 + 字符串风格ip转成点分十进制ip
uint16_t ntohs(uint16_t netshort);//将端口号从网络序列转成主机序列
char *inet_ntoa(struct in_addr in);//将ip从网络序列转成主机序列 + 点分十进制ip转成字符串风格ip

网络通信:struct sockaddr_in

本地通信:sockaddr_un

16位地址类型表明了他们是网络通信还是本地通信 

16位地址类型:sin_family

16位端口号:sin_port

32位ip地址:sin_addr.s_addr

//填充sockaddr_in结构
struct sockaddr_in local;
local.sin_family = AF_INET;//表明是网络通信
local.sin_port = htons(_prot);//将主机序列转成网络序列
local.sin_addr.s_addr = inet_addr("0.0.0.0");//将字符串类型的点分十进制ip转成四字节ip,并转成网络序列

2.2bind绑定端口

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockfd:要绑定的socket套接字的文件描述符

struct sockaddr *:包含ip地址+端口号的结构体(类型不一样需要进行强转)

socklen_t addrlen:sockaddr_in结构体的大小

//bind绑定端口号
int n = bind(_socketfd,(sockaddr *)&local,sizeof(local));//需要将sockaddr_in强转成sockaddr
if(n < 0)
{
    LOG(FATAL, "BIND ERROR, %s, %d\n", strerror(errno), errno);
    exit(BIND_ERROR);
}

三.直接通信(服务器端)

3.1接收客户端发送的消息

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

sockfd:套接字的文件描述符

buf:缓存区

len:缓存区的大小,单位是字节

flags:暂时设置为0

src_addr:数据来自于哪                                

addrlen:struct sockaddr结构体的大小

//接收客户端发送来的消息
char buffer[1024];
sockaddr_in peer;
socklen_t len = sizeof(peer);
int n = recvfrom(_socketfd,buffer,sizeof(buffer) - 1,0,(sockaddr*)&peer,&len);  

3.2给客户端发送消息

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

sockfd:套接字的文件描述符

buf:缓存区

len:缓存区的大小,单位是字节

flags:设置为0

src_addr:要将数据发送给谁

addrlen:struct sockaddr结构体的大小

//处理客户端发送的消息,并且将结果返回给客户端
buffer[n] = 0;
sendto(_socketfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);

四.客户端通信

4.1创建socket套接字

// 1. 创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
    std::cerr << "socket error" << std::endl;
}

4.2客户端bind问题

客户端不需要显示的bind,os会自动帮你绑定端口号!!!

试想一下,你的手机上有抖音和微信两个客户端小程序,如果抖音客户端bind了8080这个端口,微信也想要bind8080这个端口,那么这时候就会出现一个问题,一个端口号被两个进程竞争!!!结果就是,抖音和微信不可能同时启动。

所以解决方法就是:udp client首次发送数据的时候,OS会自己自动随机的给client进行bind

4.3直接通信即可

4.3.1构建目标主机的socket信息

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());

4.3.2给服务端发送消息

//给服务端发送消息
std::cout << "Please Enter# ";
std::getline(std::cin, message);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));

4.3.3.接收服务端发送过来的消息

//接收服务端发送过来的消息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buffer[1024];
ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
if(n > 0)
{
    buffer[n] = 0;
    std::cout << "server echo# " << buffer << std::endl;
}

五.效果展示

5.1使用127.0.0.1本地环回测试

5.2使用公网ip测试

六.代码演示

6.1UdpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"


enum 
{
    SOCKET_ERROR = 1,//创建套接字失败
    BIND_ERROR,//bind绑定端口失败
    USAGE_ERROR//启动udp服务失败
};

int default_socketfd = -1;
class UdpServer
{
public:
    UdpServer(uint16_t prot): _socketfd(default_socketfd),_prot(prot)
    {}   
    ~UdpServer()
    {}
    void Init()
    {
        //1.创建socket套接字
        _socketfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_socketfd < 0)
        {
            LOG(FATAL, "SOCKET ERROR, %s, %d\n", strerror(errno), errno);
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "socket create success, sockfd: %d\n", _socketfd);
        //2.bind 将socket套接字和端口号进行绑定

        //填充sockaddr_in结构
        struct sockaddr_in local;
        local.sin_family = AF_INET;//表明是网络通信
        local.sin_port = htons(_prot);//将主机序列转成网络序列
        local.sin_addr.s_addr = inet_addr("0.0.0.0");//将字符串类型的点分十进制ip转成四字节ip,并转成网络序列
        //local.sin_addr.s_addr = INADDR_ANY;

        //bind绑定端口号
        int n = bind(_socketfd,(sockaddr *)&local,sizeof(local));//需要将sockaddr_in强转成sockaddr
        if(n < 0)
        {
            LOG(FATAL, "BIND ERROR, %s, %d\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        LOG(INFO, "socket bind success\n");
    }

    void Stat()
    {
        //3.直接开始通信
        while(true)
        {
            //接收客户端发送来的消息
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);           
            ssize_t n = recvfrom(_socketfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);  
            if(n > 0)
            {
                InetAddr addr(peer);
                LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);
                //处理客户端发送的消息,并且将结果返回给客户端
                buffer[n] = 0;
                sendto(_socketfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
            }
        }
    }

private:
    uint16_t _prot;
    int _socketfd;
};

6.2UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#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 << " serverip serverport\n"<< std::endl;
}

// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
    }
    // 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
    // a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!
    // b. 什么时候bind呢?首次发送数据的时候

    
    // 构建目标主机的socket信息
    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());

    std::string message;
    // 2. 直接通信即可
    while(true)
    {
        //给服务端发送消息
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);
        std::cout<<message<<std::endl;
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
        
        //接收服务端发送过来的消息
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        char buffer[1024];
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    return 0;
}

6.3InetAddr.hpp

#pragma once

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

class InetAddr
{
private:
    void GetAddress(std::string *ip, uint16_t *port)
    {
        *port = ntohs(_addr.sin_port);
        *ip = inet_ntoa(_addr.sin_addr);
    }

public:
    InetAddr(const struct sockaddr_in &addr) : _addr(addr)
    {
        GetAddress(&_ip, &_port);
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

6.4LockGuard.hpp

#include <iostream>
#include <pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        pthread_mutex_lock(_mutex); // 构造加锁
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);// 析构释放锁
    }
private:
    pthread_mutex_t *_mutex;
};

6.5Log.hpp

#pragma once
#include <cstdio>
#include <iostream>
#include <string>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <fstream>

#include "LockGuard.hpp"


bool IsSave = false;//是否向文件中写入
const std::string logname = "log.txt";//日志信息写入的文件路径

// 日志是有等级的
enum Level
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

// 将日志的登记由整形转换为字符串
std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "Debug";
    case INFO:
        return "Info";
    case WARNING:
        return "Warning";
    case ERROR:
        return "Error";
    case FATAL:
        return "Fatal";
    default:
        return "Unknown";
    }
}

// 获取时间
std::string GetTimeString()
{
    time_t curr_time = time(nullptr);
    struct tm *format_time = localtime(&curr_time);
    if (format_time == nullptr)
        return "None"; // 没有获取成功

    char time_buffer[1024];
    snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900, // 这里的year是减去1900之后的值,需要加上1900
             format_time->tm_mon + 1,     // 这里的mon是介于0-11之间的,需要加上1
             format_time->tm_mday,
             format_time->tm_hour,
             format_time->tm_min,
             format_time->tm_sec);
    return time_buffer;
}


//将日志信息写入到文件中
void SaveFile(const std::string &filename, const std::string &message)
{
    std::ofstream out(filename, std::ios::app);
    if (!out.is_open())
    {
        return;
    }
    out << message;
    out.close();
}

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定义锁 支持多线程
// 日志是有格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{

    std::string levelstr = LevelToString(level);
    std::string timestr = GetTimeString();
    pid_t selfid = getpid();

    char buffer[1024];

    va_list arg;//定义一个void* 指针
    va_start(arg, format);//初始化指针,将指针指向可变参数列表开始的位置
    vsnprintf(buffer, sizeof(buffer), format, arg);//将可变参数列表写入到buffer中
    va_end(arg);//将指针置空

    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +
                          "[" + std::to_string(selfid) + "]" +
                          "[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer + "\n";

    LockGuard lockguard(&lock);
    if (!issave)
    {
        std::cout << message;//将日志信息打印到显示器中
    }
    else
    {
        SaveFile(logname, message);//将日志信息写入到文件
    }
}

// C99新特性__VA_ARGS__
#define LOG(level, format, ...)  do{ LogMessage(__FILE__, __LINE__,IsSave,level, format, ##__VA_ARGS__); }while(0)
#define EnableFile()    do{ IsSave = true; }while(0)
#define EnableScreen()  do{ IsSave = false; }while(0) 

6.6main.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}

// ./udpserver port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERROR);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);
    usvr->Init();
    usvr->Stat();
    return 0;
}

6.7makefile

.PHONY:all
all:udpserver udpclient
udpclient:UdpClient.cc
	g++ -o udpclient UdpClient.cc -std=c++14
udpserver:main.cc
	g++ -o udpserver main.cc -std=c++14
.PHONY:clean
clean:
	rm -f udpserver

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2339024.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络 实验四 静态路由的配置与应用

一、实验目的 掌握路由器基础工作原理及静态路由协议机制熟练使用华为ENSP网络模拟器进行拓扑设计与设备配置建立系统化的网络故障排除思维通过实践验证静态路由在中小型网络中的部署优势 二、实验环境 硬件配置&#xff1a;标准PC终端软件工具&#xff1a;华为企业网络模拟…

[每周一更]-(第140期):sync.Pool 使用详解:性能优化的利器

文章目录 一、什么是 sync.Pool&#xff1f;二、sync.Pool 的基本作用三、sync.Pool 的主要方法四、sync.Pool 的内部工作原理五、sync.Pool 适用场景六、使用示例示例 1&#xff1a;基本使用输出示例&#xff1a;示例 2&#xff1a;并发使用 七、一个基于 sync.Pool 的 **Benc…

3.QT-信号和槽|自定义槽函数|自定义信号}自定义的语法}带参数的信号和槽(C++)

信号和槽 Linux信号 Signal 系统内部的通知机制. 进程间通信的方式. 信号源&#xff1a;谁发的信号.信号的类型&#xff1a;哪种类别的信号信号的处理方式&#xff1a;注册信号处理函数&#xff0c;在信号被触发的时候自动调用执行. Qt中的信号和Linux中的信号&#xff0c;虽…

健康养生之道

在快节奏的现代生活中&#xff0c;健康养生不再是中老年人的专属话题&#xff0c;越来越多的人开始意识到&#xff0c;合理的养生方式是保持良好身体状态和生活质量的关键。​ 饮食养生是健康的基石。遵循 “食物多样、谷类为主” 的原则&#xff0c;保证每天摄入足够的蔬菜、…

Spark-SQL核心编程3

数据加载与保存 通用方式&#xff1a; SparkSQL 提供了通用的保存数据和数据加载的方式。这里的通用指的是使用相同的API&#xff0c;根据不同的参数读取和保存不同格式的数据&#xff0c;SparkSQL 默认读取和保存的文件格式为parquet 数据加载方法&#xff1a; spark.read.lo…

TVM计算图分割--Collage

1 背景 为满足高效部署的需要&#xff0c;整合大量优化的tensor代数库和运行时做为后端成为必要之举。现在的深度学习后端可以分为两类&#xff1a;1&#xff09;算子库(operator kernel libraries)&#xff0c;为每个DL算子单独提供高效地低阶kernel实现。这些库一般也支持算…

MCGS昆仑通太屏笔记

4.3寸&#xff1a;4013ef/e1 7寸&#xff1a;7032kw 特点&#xff1a; 如果是使用组态屏进行调试使用&#xff0c;选择com1如果是实际项目使用&#xff0c;选择com2 操作步骤&#xff1a; 先创建设备窗口&#xff0c;再创建用户界面 在设备窗口界面&#xff0c;依次设置如下…

服务治理-搭建Nacos注册中心

运行nacos.sql文件。 将准备好的nacos目录和nacos.tar包上传。 192.168.59.101是我的虚拟机ip&#xff0c;8848是我们设置的访问端口号。

网络--socket编程(2)

Socket 编程 TCP TCP 网络程序 和刚才 UDP 类似 . 实现一个简单的英译汉的功能 TCP socket API 详解 下面介绍程序中用到的 socket API, 这些函数都在 sys/socket.h 中。 socket(): • socket() 打开一个网络通讯端口 , 如果成功的话 , 就像 open() 一样返回一个…

结合建筑业务讲述TOGAF标准处理哪种架构

TOGAF标准处理哪种架构 内容介绍业务架构业务策略&#xff0c;治理&#xff0c;组织和关键业务流程数据架构组织的逻辑和物理数据资产以及数据管理资源的结构应用架构待部署的各个应用程序&#xff0c;它们之间的交互以及与组织核心业务流程的关系的蓝图技术架构支持业务&#…

C++入门小馆: 深入string类(一)

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…

NHANES指标推荐:WWI

文章题目&#xff1a;Weight-adjusted waist circumference index with hepatic steatosis and fibrosis in adult females: a cross-sectional, nationally representative study (NHANES 2017-2020) DOI&#xff1a;10.1186/s12876-025-03706-4 中文标题&#xff1a;体重调整…

2025.04.18|【Map】地图绘图技巧全解

Add circles Add circles on a Leaflet map Change tile Several background tiles are offered by leaflet. Learn how to load them, and check the possibilities. 文章目录 Add circlesChange tile 2025.04.18【Map】| 地图绘图技巧全解1. 准备工作2. 地理区域着色图&…

PR第一课

目录 1.新建 2.PR内部设置 3.导入素材 4.关于素材窗口 5.关于编辑窗口 6.序列的创建 7.视频、图片、音乐 7.1 带有透明通道的素材 8.导出作品 8.1 打开方法 8.2 导出时&#xff0c;需要修改的参数 1.新建 2.PR内部设置 随意点开 编辑->首选项 中的任意内容&a…

Vue+Notification 自定义消息通知组件 支持数据分页 实时更新

效果图&#xff1a; message.vue 消息组件 子组件 <template><div class"custom-notification"><div class"content"><span click"gotoMessageList(currentMessage.split()[1])">{{ currentMessage.split()[0] }}</…

不规则曲面上两点距离求取

背景 在CT中求皮肤上两点间的弧长。由于人体表面并不是规则的曲面&#xff0c;不可能用圆的弧长求取方法来计算出两点间的弧长。 而在不规则的曲面上求两点的距离&#xff0c;都可以用类似测地线距离求取的方式来求取&#xff08;积分&#xff09;&#xff0c;而转化为搜索路…

性能比拼: Elixir vs Go

本内容是对知名性能评测博主 Anton Putra Elixir vs Go (Golang) Performance (Latency - Throughput - Saturation - Availability) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 对比 Elixir 和 Go 简介 许多人长期以来一直要求我对比 Elixir 和 Go。在本视频…

【Linux网络与网络编程】11.数据链路层mac帧协议ARP协议

前面在介绍网络层时我们提出来过一个问题&#xff1a;主机是怎么把数据交给路由器的&#xff1f;那里我们说这是由数据链路层来做的。 网络上的报文在物理结构上是以mac帧的形式流动的&#xff0c;但在逻辑上是以IP流动的&#xff0c;IP的流动是需要mac帧支持的。 数据链路层解…

lottie深入玩法

A、json文件和图片资源分开 delete 是json资源名字 /res/lottie/delete_anim_images是图片资源文件夹路径 JSON 中引用的图片名&#xff0c;必须与实际图片文件名一致 B、json文件和图片资源分开&#xff0c;并且图片加载不固定 比如我有7张图片&#xff0c;分别命名1~7&…

热门与冷门并存,25西电—电子工程学院(考研录取情况)

1、电子工程学院各个方向 2、电子工程学院近三年复试分数线对比 学长、学姐分析 由表可看出&#xff1a; 1、电子科学与技术25年相较于24年上升20分 2、信息与通信工程、控制科学与工程、新一代电子信息技术&#xff08;专硕&#xff09;25年相较于24年下降25分 3、25vs24推…