当前位置: 首页 > news >正文

linux: udp服务器与客户端 CS 基于ipv4的地址结构体

一. UDP服务器创建

  在UDP服务器端,主要要做以下几件事:

        1.  服务器进程打开套接字文件

        2. 主动绑定当前主机的地址结构体, 我们这里主要使用的是ipv4的地址结构体,

作为服务器的端口号是要主动暴露的,而绑定的当前主机的ip地址则是可以当前主机的ip地址,但是一个主机可能有多张网卡(对应多个ip),所以我们的地址结构体中绑定本地的ip可以用 0,也就是表示绑定本主机的任意一个ip地址,这样当socket文件未来接受信息的时候可以接受来自本主机的所有网卡发来的信息。

         3. 在用bind 主动把本机的地址结构体绑入socket文件中

            

  type类型

                   

                      

            

       

        4. 以上步骤作为,我们的服务器就可以开始负责,收发数据了,套接字文件的读和写都有各自的缓冲区,也就是所谓的双工的,你可以多线程的读和写。   

1.1 地址结构体的初始化

 ipv4地址结构体   具体可以参考我之前的文章---网络套接字编程

原生的:

      既然提到了地址结构体的初始化,那么就要介绍网络套接字编程的套路,首先网络编程为了统一你使用的接口,对于sokcet文件它的创建接口只有一个统一的地址结构体:

   也就是sockaddr结构体,它就是一个中间载体而已,他的大小也就尽可能让其他的地址结结构体的设计靠近它,入上面的ipv4的地址结构体.

   1. 创建地址结构体,2.初始化协议族 3. 初始化端口 4. 初始化ip地址  然后在用绑入这就是本机地址结构体的绑定,   

这个过程中 对于我们在网络字节序和本机字节序的转换可以参考下面的: 

ip地址的点分字符串

        我们通常使用的ipv4地址 如: ”192.1.1.1“ 点分字符串的类型,一共4个点分割了4个8位一共是32位的uint32_t 类型的ip地址,所以我们通常传入的ip地址大多是点分字符串的,其次我们需要把点分字符串转网络字节序,以及网络字节序转我们看见的点分字符串,显然上面的htonl() 这个接口就是表示32位的主机转网络的,我们在这个场景是用不了的,我们我们的诉求是点分字符串转网络。

        所以有这么一些接口帮我封装好了,把主机序列的ip地址提前处理,然后在转网络字节序。 

1.2 关于ip地址的 转换位网络字节序的系统调用介绍

// 创建地址结构体sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(_port);// _ip :string _ip : "192.1.11.1"// 1.  in_addr_t inet_addr(const char *cp);addr.sin_addr.s_addr = inet_addr(_ip.c_str());// 把点分字符串 传进去// 2. int inet_aton(const char *cp, struct in_addr *inp);inet_aton(_ip.c_str(),&addr.sin_addr);// 3. int inet_pton(int af, const char *src, void *dst);inet_pton(AF_INET,_ip.c_str(),&addr.sin_addr);

     之前提到过关于存放ip地址的32位的其实做了一层封装的结构体,其实在我们的sockaddr_in结构体中的ipv4地址结构体是: sin_addr 内部就封装的32位的ip地址。

      所以用上面的三个结果可以帮你把点分式的ip地址转为网络序的ip地址并放入地址结构体中

ip地址网络序转字符串 谈线程安全问题 

第一个接口 仅仅用于ipv4的 : 

       char *inet_ntoa(struct in_addr in);

   这是一个老接口非常常用,先谈它的用法 :  作用就是传入 sin_addr 这个变量,然后返回了一共char*字符串指向的字符指针,这里就一个问题了,这个char* 指向的字符串应该是"192.2.2.2" 这样的点分式字符串,那这个指针肯定是动态内存的,又或者静态的,因为这个字符串是在我们传入的inet_ntoa 函数内部得到的。  

        我们查阅文档

: The  inet_ntoa() function converts the Internet host address in, given in network byte order, to a string in IPv4 dotted-decimal notation.  The string is returned in
a statically allocated buffer, which subsequent calls will overwrite.

  所以它是静态buffer,此后你在通过它调用得到是ip字符串也是同一个静态的buffer,那么也就意味着有线程安全问题

  

    验证: 关于这个char*得到的ip字符串的问题 

#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdio>int main()
{// 创建地址结构体sockaddr_in addr1;sockaddr_in addr2;addr1.sin_addr.s_addr = 0xffffff; // 32位addr2.sin_addr.s_addr = 0;char *str2 = inet_ntoa(addr2.sin_addr);char *str1 = inet_ntoa(addr1.sin_addr);printf("%p\n", str1);printf("%p\n", str2);printf("%s\n", str1);printf("%s\n", str2);return 0;
}

输出结果:

   首先 0x7这个地址空间应该是在进程的共享库,但是在我们调用这个函数动态库指向的静态区的 ,这个有点绕,之后我在些共享库博客时候会提及的。 总之可以理解为它是一共静态的变量,重复使用会覆盖使用。

线程安全问题:

      如下代码,简单来说就是,这是一份共享资源的静态缓冲区,那么它可能会存在线程安全问题,APUE, 明确提出inet_ntoa不是线程安全的函数,( Advanced Programming in the UNIX Environment 的缩写,中文对应《UNIX 环境高级编程))

        经过我们如下验证哈,发现没有线程问题哈,这跟具体的品牌系统有关,我这是centos7的可能加锁了,但是为了通用,为了安全其实还有一组系统调用接口更为使用广泛。


// 线程安全问题
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *Func1(void *p)
{struct sockaddr_in *addr = (struct sockaddr_in *)p;while (1){char *ptr = inet_ntoa(addr->sin_addr);printf("addr1: %s\n", ptr);usleep(100000);}return NULL;
}
void *Func2(void *p)
{struct sockaddr_in *addr = (struct sockaddr_in *)p;while (1){char *ptr = inet_ntoa(addr->sin_addr);printf("addr2: %s\n", ptr);usleep(100000);}return NULL;
}
int main()
{pthread_t tid1 = 0;struct sockaddr_in addr1;struct sockaddr_in addr2;addr1.sin_addr.s_addr = 0;addr2.sin_addr.s_addr = 0xffffffff;pthread_create(&tid1, NULL, Func1, &addr1);pthread_t tid2 = 0;pthread_create(&tid2, NULL, Func2, &addr2);pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

 const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);

 const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

  这个思路就是 要求你主动传入一共缓冲区也就是字符串地址:

  • af:地址族(AF_INET 对应 IPv4,AF_INET6 对应 IPv6);
  • src:指向二进制网络地址的指针(IPv4 用 struct in_addr*,IPv6 用 struct in6_addr*);
  • dst:存储转换后字符串的缓冲区(需用户自行分配,避免覆盖问题);
  • sizedst 缓冲区大小(IPv4 建议 ≥ INET_ADDRSTRLEN(16 字节),IPv6 建议 ≥ INET6_ADDRSTRLEN(46 字节))。
#include <arpa/inet.h>
#include <stdio.h>int main() {struct sockaddr_in addr = {.sin_addr.s_addr = 0xffffffff};char ip_buf[INET_ADDRSTRLEN];  // IPv4专用缓冲区宏// 传入缓冲区大小,用socklen_t接收,用法和size_t无差异if (inet_ntop(AF_INET, &addr.sin_addr, ip_buf, sizeof(ip_buf))) {printf("%s\n", ip_buf);}return 0;
}

统一记忆使用ip地址转换

// 接口使用 ip地址 使用
#include <arpa/inet.h>
#include <stdio.h>int main()
{struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));std::string ip = "192.1.1.1";inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);char ip_buf[16]; // IPv4专用缓冲区宏// ip地址的传入// 传入缓冲区大小,用socklen_t接收,用法和size_t无差异if (inet_ntop(AF_INET, &addr.sin_addr, ip_buf, sizeof(ip_buf))){printf("%s\n", ip_buf);}return 0;
}

输出:

二. udp服务读和写

   首先要深刻理解,我们的网络路文件也就是socket文件,这是我们进行读写的文件,依旧理解它是一套设计在内存的文件,我们当然也能用read和write,但是对于udp这样的我们读写套接字文件的时候要指名我们要读取来自谁的地址结构体的套记者文件和获取对方的信息,所以使用得用: 偏原生的接口

     这相当于read读取信息,传入套接字文件fd,

buffer: 是你要传入的buferr数组,来接受信息,如char buffer[1024]

length 对应buffer的长度

flags: 表示以什么样的形式读取: 0默认阻塞读取

restrict:  然后是传入接受对方的地址结构体信息,这样还是用的通用地址结构体记得强转

socklen_t : 记得传入这个表示你这边的地址结构体缓冲区的大小

     这相当于write读取信息,传入套接字文件fd,

buffer: 是你要传入的buferr数组,来接受信息,如char buffer[1024]

length 对应buffer的长度

flags: 表示以什么样的形式读取: 0默认阻塞读取

dest_addr:  然后是传入对方的地址结构体信息,这样还是用的通用地址结构体记得强转

socklen_t : 记得传入这个表示你这边的地址结构体缓冲区的大小

返回值: 

   二者都是成功返回0,否则-1同时设置errno

三 . udp服务器代码 

   我的思路是把服务器当成一个类来写会更爽一些。如下代码,这里服务器的简单小功能就是对客户端传递过来的commad命令进行执行,然后在返回执行后的结果,然后这里的执行借用popen 就不需要自己手搓一个了。

server.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <cerrno>
#include "log.h"typedef std::function<std::string(const std::string &)> func_t;enum exiterr
{SOCK_CREATE = 1,SOCK_BIND
};class udpServer
{
public:udpServer(const std::string &ip = "0", const uint16_t port = 8080): _ip(ip),_port(port),_isrunning(false),_sock_fd(0){}void Init(){// 创建地址结构体sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(_port);// 3. int inet_pton(int af, const char *src, void *dst);inet_pton(AF_INET, _ip.c_str(), &addr.sin_addr);socklen_t server_len = sizeof(addr);_sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (_sock_fd < 0){mylog(Fatal, "socket create errno:%d strerror:%s", errno, strerror(errno));exit(SOCK_CREATE);}mylog(Info, "socket create fd:%d", _sock_fd);if (bind(_sock_fd, (sockaddr *)&addr, server_len) < 0){mylog(Fatal, "bind error\n");exit(SOCK_BIND);}mylog(Info, "bind success");}void Run(func_t func){// 运行 首先启动运行bool类型 其次 收recv 然后执行命令 再发 sendto_isrunning = false;while (_isrunning){// 创建对方的地址结构体缓冲区 以及消息缓冲区sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);char buffer[1024];ssize_t n = recvfrom(_sock_fd, buffer, sizeof(buffer), 0, (sockaddr *)&client_addr, &client_addr_len);if (n < 0){mylog(Warning, "recvfrom");continue; // 这个信息不至于退出  不要读取信息即可}buffer[n] = 0;std::string command(buffer);std::string perform_str = func(command);// 发回去 给对方sendto(_sock_fd, perform_str.c_str(), perform_str.size(), 0, (sockaddr *)&client_addr, client_addr_len);}}~udpServer(){if (_sock_fd >= 0){close(_sock_fd);}}private:int _sock_fd;std::string _ip; // 本机ipuint16_t _port;  // 当前进程的端口号bool _isrunning;static Log mylog;
};
Log udpServer::mylog;

main.cc

#include <iostream>
#include <string>
#include "server.hpp"
#include <stdio.h>
#include<memory>void Usage(const char *proc)
{printf("Usage:%s: port\n", proc);
}std::string func(const std::string &command)
{FILE *fd = popen(command.c_str(), "r");if (fd == nullptr){return "error";}char buffer[1024];std::string ret;while (true){int n = fread(buffer, 1, 1023, fd);if (n == 0){break;}buffer[n] = 0;ret += buffer;};pclose(fd);return ret;
}int main(int argc, char *arg[])
{if (argc < 2){Usage(arg[0]);exit(1);}uint16_t port = std::stoi(arg[1]);// // 传入端口 测试 没问题// std::cout << func("ls -li") << std::endl;std::unique_ptr<udpServer> server(new udpServer("0",port));server->Init();server->Run(func);return 0;
}

四. udp客户端 

  udp的客户端思路更为简单,首先udp协议是不连接的,数据也是数据报的,也就意味着客户端发信息,发就是了,只要有了对方的ip地址和端口那就拿着sendto就直接发,注意我这里再次强调了udp的不面向连接使得它有一个要求就是,必须要主动写地址结构体而不能像tcp一样面向连接已经在socket文件中写好了对方的地址结构体,而udp非常需要,还有就是客户端不需要自己主动绑定,因为客户端可以被千千万万个用户使用对应的端口和ip地址都是不一样的不需要自己去绑定,让系统帮你绑定即可。

4.1 实现思路

  udp客户端的实现是,是

1. 创建套接字文件用于通信 socket

2. 初始化对方的地址结构体

3. 初始化消息buffer

4. 用sendto系统调用直接发信息!

客户端最好也是一共无限循环,一个是读取recvfrom 读取已经你的请求 ,另一个是发请求。

          注意我这里的简单的linux客户端实现的作用主要就是发指令给服务端让服务端去执行然后返回执行结果回显给客户端。

   

参考代码: 

#include <iostream>
#include <string>
#include "server.hpp"
#include <stdio.h>
#include <memory>void Usage(const char *proc)
{printf("Usage:%s: port\n", proc);
}std::string func(const std::string &command)
{FILE *fd = popen(command.c_str(), "r");if (fd == nullptr){return "error";}char buffer[1024];std::string ret;while (true){int n = fread(buffer, 1, 1023, fd);if (n == 0){break;}buffer[n] = 0;ret += buffer;};pclose(fd);return ret;
}int main(int argc, char *arg[])
{if (argc < 2){Usage(arg[0]);exit(1);}uint16_t port = std::stoi(arg[1]);// 传入端口 测试 没问题//std::cout << func("ls -li") << std::endl;std::unique_ptr<udpServer> server(new udpServer("0", port));server->Init();server->Run(func);return 0;
}

4.2 效果

服务端:

客户端

http://www.dtcms.com/a/609174.html

相关文章:

  • 做食品网站需要什么条件手机靓号网站建设
  • 运筹说145期:从快递到自动驾驶:启发式算法的智慧幕后
  • 如何选择合适的养老服务机器人
  • 微博评论数据采集:基于Requests的智能爬虫实战
  • 数据挖掘概述
  • 51c自动驾驶~合集43
  • Go语言反编译:深入分析与技术探索 | 从原理到实践,全面解析Go反编译的实现和应用
  • ASP.NET Core 10
  • 2025新加坡金融科技节:看AI驱动的金融转型策略与“中国方案”
  • 站群seo技巧济南企业网站设计
  • 网站类游戏网站开发犀牛云做网站推广怎么样
  • 嵌入式网络编程实战:从Socket基础到高并发优化
  • 基于UDP协议的英汉翻译服务系统:从网络通信到字典查询的完整机制
  • 在ec2上部署indexTTS和尝试部署sparkTTS模型
  • IP种子技术:构建全球P2P网络实时监测方案
  • Kali远程桌面+cpolar:网络安全攻防的跨域协作新范式
  • 网络安全学习困扰及解决建议
  • 黑马点评学习笔记11(Redission)
  • 计算机网络复习日报18
  • 网站开发合同知识产权wordpress gettheid
  • Redis 全体系深度解析(架构原理、性能模型、使用场景、持久化机制、过期策略与最佳实践)
  • 百度世界 2025 核心看点:文心 5.0、萝卜快跑、惠博星数字人、伐谋智能体齐亮相!
  • 【百度拥抱开源】介绍ERNIE-4.5-VL-28B-A3B-Thinking:多模态AI的重大突破
  • HarmonyOS分布式输入法开发:实现多设备无缝输入体验
  • 基于GIS的智慧旅游调度指挥平台
  • 网站怎么做才美观WordPress moe acg
  • C/C++ Linux网络编程4 - 解决TCP服务器并发的方式
  • AI取名大师 | uni-app + Wot UI 跟随设备自动切换明暗主题
  • 镜像站更新
  • 《uni-app跨平台开发完全指南》- 07 - 数据绑定与事件处理