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

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

在这里插入图片描述

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

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

文章目录

  • Udp Server
    • socket套接字创建
    • 套接字和IP地址、端口号绑定
    • 读取服务器套接字数据--recvfrom
    • 发送数据--stndto
    • 注意事项
  • UDP Client
  • 完整代码
  • 实例测试

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

Udp Server

socket套接字创建

#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已经被占用了。


创建套接字代码:

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地址、端口号绑定。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    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

#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

#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 指向的结构的大小。这个参数在调用时需要正确设置,调用后该变量会被更新为实际的地址长度。

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地址绑定:

local.sin_addr.s_addr=INADDR_ANY;

在这里插入图片描述

UDP Client

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

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

#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

#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

#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

#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;
}

实例测试

在这里插入图片描述


http://www.mrgr.cn/news/53946.html

相关文章:

  • 【AI学习】Mamba学习(十):HiPPO总结
  • Virtuoso Layout无法显示元件,出现pcellEvalFailed错误问题解析
  • Pytorch常用函数汇总【持续更新】
  • 测试教程分享
  • WebGoat SQL Injection (intro) 源码分析
  • SQL 自学:事务处理的COMMIT 和 ROLLBACK 语句的运用
  • 14.归一化——关键的数据预处理方法
  • 【C++ 算法进阶】算法提升四
  • 【C++训练营】现代C++编程(隐藏)
  • 【Mysql】-锁机制-GAP锁
  • 2024年【N2观光车和观光列车司机】及N2观光车和观光列车司机模拟考试题
  • 【Hive】2-Apache Hive概述、架构、组件、数据模型
  • 好的口才是做领导的第一要务
  • SpringMVC一个拦截器和文件上传下载的完整程序代码示例以及IDEA2024部署报错 找不到此 Web 模块的 out\artifacts\..问题
  • CMOS晶体管的串联与并联
  • 【含文档】基于Springboot+Vue的地方特色美食分享管理系统(含源码+数据库+lw)
  • webAPI中的节点操作、高级事件
  • 在 Spring MVC 应用程序中使用 WebMvcTest 注释有什么用处?
  • 学习eNSP后,有哪些具体的就业方向?
  • 「数学::快速幂」矩阵快速幂运算|快速斐波那契数列 / LeetCode 509(C++)
  • 双十一有啥好用的物品可以推荐购买?2024不可错过的必囤好物清单!
  • 填充与步幅
  • oracle10g运维:存数据前
  • 51单片机快速入门之 LCD1602 液晶显示屏2024/10/19
  • C++20中头文件source_location的使用
  • JAVA本地编译运行出现的找不到类名问题