【Linux网络编程三】Udp套接字编程(简易版服务器)

【Linux网络编程三】Udp套接字编程(简易版服务器)

  • 一.创建套接字
  • 二.绑定网络信息
    • 1.构建通信类型
    • 2.填充网络信息
      • ①网络字节序的port
      • ②string类型的ip地址
    • 3.最终绑定
  • 三.读收消息
    • 1.服务器端接收消息recvfrom
    • 2.服务器端发送消息sendto
    • 3.客户端端发送消息sendto
    • 4.客户端端接收消息recvfrom
  • 四.关于绑定ip与port细节
  • 五.客户端不需要主动绑定
  • 六.客户端/服务器端代码

UDP套接字编程:
网络通信本质是进程之间通信,所以我们需要两个进程来网络通信,假设一个为服务器进程,一个为客户端进程演示。

一.创建套接字

在这里插入图片描述
socket()接口可以用来创建套接字,它总共有三个参数。

第一个参数domain,表示通信的类型,是使用网络通信还是本地通信由用户选择,当传入AF_INET/AF_INET6时表示使用网络通信。
第二个参数type,表示套接字的类型,是TCP呢还是UDP呢。如果传递SOCK_DGRAM表示是UDP,如果传递的SOCK_STREAM表示是TCP.
第三个参数protocol,表示协议,默认为0.

创建套接字成功后会返回一个文件描述符sockfd,也就是创建套接字的本质就是打开一个文件!
在这里插入图片描述

服务器端进程在创建完套接字后,该做什么呢?该套接字(文件)是属于你服务器进程的,然后呢?假设客户端也打开一个套接字(文件),这两个套接字文件都是属于同一个,也就是满足了进程间通信的前提条件:能看到一个共享资源。
而两个进程看到同一份资源后,那么该如何准确的发送给对方呢?因为可能打开这个套接字文件的进程有很多。所以接下来就是两个进程需要知道各自对方能够唯一标识自己的ip地址和端口号等网络信息。这样才能够准确的将数据从客户端进程发送给服务器进程。

 // 1.创建udp套接字,本质就是打开网络套接字文件_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 表示创建失败{lg(Fatal, "socket create error,socket: %d", _sockfd);exit(SOCKET_ERR); // 创建失败之间退出}

二.绑定网络信息

套接字创建成功后,就相当于打开了一个文件,该文件是就两个进程通信的共享资源,不过要想准确通信,还需要知道各自进程的ip地址和端口号,这样往文件里传输数据时,对端才能准确接收到,也就是这个文件需要绑定一些各自进程的网络信息才能准确的传递到对端。

进程之间网络通信需要先绑定端口号,这里创建完套接字后,就需要让该进程的端口号与套接字绑定。这样对端的进程才能找到这个进程
在这里插入图片描述
第一个参数就是创建的套接字,也就是文件对象
第二个参数就是要绑定的该进程的网络信息,包括端口号,ip地址等
第三个参数是网络信息结构体对象的大小
这个网络信息结构体对象要求传的是统一的接口,但是你实际使用什么类型的网络通信,你就定义什么类型,然后传递时强转即可。

 // 在绑定套接信息之前,需要先将对应的结构体对象填充完毕sock_addrstruct sockaddr_in local;                       // 网络通信类型bzero(&local, sizeof(local));                   // 将内容置为0local.sin_family = AF_INET;                     // 网络通信类型local.sin_port = htons(_port);                  // 网络通信中,端口号需要不断发送,所以需要符合网络字节序,主机--->网络字节序local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要将string类型的ip转换成int类型,并且还需要满足网络字节序的要求socklen_t len = sizeof(local);// 以上只是将要绑定的信息填充完毕,套接字(网络文件)而还没有绑定套接信息

1.构建通信类型

比如如果我要网络通信,那么就需要定义一个sockaddr_in结构体对象。
在这里插入图片描述
该结构体对象里有三个需要初始化的参数:
1.sin_family:要使用的通信类型
2.sin_port:该进程的端口号
3.sin_addr:该进程的ip地址

sin_addr这个结构体对象里面只有一个参数,s_addr这个也就是真正的ip地址。

2.填充网络信息

①网络字节序的port

在给套接字绑定网络信息之前,需要将网络信息给构建好,就比如端口号,我们需要将当前进程的端口号填充到sockaddr_in这个结构体对象里。
不过端口号在网络通信中,是要不但的来回发送的,不管是客户端,还是服务器端,两个进程通信就必须知道对方的端口号。
所以端口号是需要发送到网络里的,所以在填充时,必须是网络字节序。
在这里插入图片描述
也就是主机转网络字节序

②string类型的ip地址

用户一般喜欢用string类型的ip地址类型,这样比较好显示。
但是系统里的ip是uint16_t类型的,所以我们在填充初始化时.
【要求1】首先需要将string类型的参数转换成uint16_t类型。
在这里插入图片描述

【要求2】其次ip地址也需要发送到网络里的,所以也必须是网络字节序。
在这里插入图片描述
系统里给了我们相关的接口:inet_addr(char*cp)
在这里插入图片描述

它就是可以将string类型的数据转换成uint16_t类型,并且将主机字节序转换成网络字节序。

3.最终绑定

在这里插入图片描述

  if (bind(_sockfd, (const struct sockaddr *)&local, len) < 0) // 绑定失败{lg(Fatal, "bind sockfd error,errno:%d,err string:%s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind sockfd success,errno:%d,err string:%s", errno, strerror(errno)); // 绑定成功

三.读收消息

1.服务器端接收消息recvfrom

一般客户端进程对服务器进程发送消息,服务器进程接收客户端发送的消息。那么服务器进程如何接收客户端发送的消息呢?
服务器进程是从套接字接收消息,也就是该进程创建的文件里接收。
在这里插入图片描述
不过服务器除了能够接收到消息,还需要知道是谁给它发送的消息,这样它才可以将消息再发送回去。
所以就需要一个sockaddr_in结构体对象,作为输出型参数,将发送端的网络信息存储下来。也就是将客户端的网络信息带出来。

所以recvfrom的功能
1.除了接收到对端发送的消息内容
2.还可以知道对端的网络消息。知道是谁发送过来的。

            struct sockaddr_in client;socklen_t len = sizeof(client);// 服务器接收到消息,它还需要知道谁给它发送的,为了后续将应答返回过去// 利用一个输出型参数,将对方的网络信息填充到里面ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){lg(Warning, "recvfrom sockfd err,errno: %d, err string %s", errno, strerror(errno));continue;}// 读取成功,将网络文件里的消息读取到buffer里buffer[n] = 0; // 字符串形式

2.服务器端发送消息sendto

服务器一般只要用来接收其他客户端的消息,然后加工处理,再将数据发送回去,所以服务器将数据再发送回客户端,也就是往套接字里发送消息,而要发送的客户端网络信息,刚好被存储在输出型参数里。因为客户但是主动发送消息的,服务器一定是先收到客户端发送的消息,然后会将客户端的网络消息存储起来,再根据客户端网络信息发送回去。
在这里插入图片描述

 // 3.将应答发送回去
// 发送给谁呢?服务器知道吗?服务器知道!因为在接收消息时,服务器就用一个输出型参数,将客户端的网络消息保存下来了
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);

因为服务器端在接收客户端发送的消息时,还会保存客户端的网络信息,所以如果服务器想发送消息给客户端是很容易的。

3.客户端端发送消息sendto

客户端将消息发送给服务器端,该怎么发送呢?通过套接字(文件)发送给服务器,发送时需要服务器端的网络信息,比如ip地址端口号等,这样客户端才能准确的发送给该服务器端。
在这里插入图片描述
在这里插入图片描述

//构建服务器端的网络信息std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 将string类型转换成int类型,并且是网络字节序server.sin_port = htons(serverport);socklen_t len = sizeof(server);// 1.创建套接字---本质就是打开网络文件int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket create err" << endl;return 1;}// 创建成功// 2.需要绑定吗?系统会给我自动绑定,首次发送的时候就会绑定char outbuffer[1024];string message;getline(cin, message);//1.发送给服务器sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&server, len);

4.客户端端接收消息recvfrom

客户端要想接收服务器发送回来的消息,只需要读取套接字里的消息即可。利用recvfrom接口读取客户端的套接字。
不过还需要定义一个结构体对象用来保存服务器端的网络信息,虽然客户端已经知道,但是接口要求,所以必须定义。

        struct sockaddr_in temp;socklen_t l=sizeof(temp); //2.接收服务器的应答ssize_t s=recvfrom(sockfd,outbuffer,1023,0,(struct sockaddr*)&temp,&l);

四.关于绑定ip与port细节

【细节1】当服务器端绑定ip时,如果ip地址是’0.0.0.0"则表示任意ip地址绑定。
就表示不管客户端发送时目的ip是多少,只要消息发送到服务器的主机上,那么都可以接收,并将端口号往上发送。

也就是只要是发送到我这台主机上的报文,那么就会忽略到该报文的目的ip地址是多少,只看端口号。
这就表示任意ip地址绑定。相当于一种动态绑定。可以接收到所有发送到我这台主机上的报文。并往上传递。
在这里插入图片描述
【细节2】端口号不是随意绑定的,有些是已经被固定使用的,【0,1023】是属于系统内定的端口号,一般要有固定的应用层协议使用。
所以我们最好使用1023后面的。

【总结】
如果服务器端的ip地址默认是0的话,那么我们只需要知道服务器端的端口号即可。在这里插入图片描述

五.客户端不需要主动绑定

在这里插入图片描述
所以客户端是不需要显示的绑定相关的端口号和ip地址的。操作系统会帮它自动绑定。
本质原因是用户不关心客户端的端口号和ip地址等网络信息,所以不需要显示绑定。

但是服务器端必须主动绑定端口号!为什么呢?
因为用户关心服务器的端口号,必须知道服务器端的端口号。不然无法找到服务器端。
在这里插入图片描述

六.客户端/服务器端代码

【客户端】

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;#include <iostream>
#include <strings.h>
#include <sys/types.h>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
// 客户端
void Usage(std::string proc)
{std::cout << "\n\r./Usage: " << proc << " serverip serverport\n"<< endl;
}
// 启动客户端时要求是: ./Client 服务器ip 服务器port
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 将string类型转换成int类型,并且是网络字节序server.sin_port = htons(serverport);socklen_t len = sizeof(server);// 1.创建套接字---本质就是打开网络文件int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket create err" << endl;return 1;}// 创建成功// 2.需要绑定吗?系统会给我自动绑定,首次发送的时候就会绑定// 3.往服务器的套接字里发送消息--需要知道服务器的ip和端口号,目的ip和目的port,将ip和port填入结构体对象里char outbuffer[1024];string message;while (true){cout<<"Please enter@ ";getline(cin, message);//1.发送给服务器sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&server, len);struct sockaddr_in temp;socklen_t l=sizeof(temp); //2.接收服务器的应答ssize_t s=recvfrom(sockfd,outbuffer,1023,0,(struct sockaddr*)&temp,&l);if(s>0){//接收成功outbuffer[s]=0;cout<<outbuffer<<endl;}}close(sockfd);return 0;
}

【服务器端】

@ -0,0 +1,103 @@
#pragma once#include "Log.hpp"
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
std::string defaultip = "0.0.0.0";
uint16_t defaultport = 8080;
Log lg; // 日志,默认往显示屏打印
typedef std::function<std::string(const std::string&)> func_t;//相当于定义了一个函数指针
//返回值是string类型,函数参数也是string类型,利用函数回调的方法,将服务器端对数据的处理操作进行分离,由上层传递的函数来决定如何处理
enum
{SOCKET_ERR = 1,BIND_ERR
};
class Udpserver
{
public:Udpserver(const uint16_t &port = defaultport, std::string &ip = defaultip) : _sockfd(0), _port(port), _ip(ip){}void Init(){// 1.创建udp套接字,本质就是打开网络套接字文件_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 表示创建失败{lg(Fatal, "socket create error,socket: %d", _sockfd);exit(SOCKET_ERR); // 创建失败之间退出}// 创建成功lg(Info, "socket create success,socket: %d", _sockfd);// 2.绑定服务器的套接信息,比如ip和端口号// 在绑定套接信息之前,需要先将对应的结构体对象填充完毕sock_addrstruct sockaddr_in local;                       // 网络通信类型bzero(&local, sizeof(local));                   // 将内容置为0local.sin_family = AF_INET;                     // 网络通信类型local.sin_port = htons(_port);                  // 网络通信中,端口号需要不断发送,所以需要符合网络字节序,主机--->网络字节序local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要将string类型的ip转换成int类型,并且还需要满足网络字节序的要求socklen_t len = sizeof(local);// 以上只是将要绑定的信息填充完毕,套接字(网络文件)而还没有绑定套接信息if (bind(_sockfd, (const struct sockaddr *)&local, len) < 0) // 绑定失败{lg(Fatal, "bind sockfd error,errno:%d,err string:%s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind sockfd success,errno:%d,err string:%s", errno, strerror(errno)); // 绑定成功}void Run(func_t func) // 服务器是一旦启动不会退出,服务器接收消息,并发送答应{// 1.接收信息char buffer[SIZE];while (true){struct sockaddr_in client;socklen_t len = sizeof(client);// 服务器接收到消息,它还需要知道谁给它发送的,为了后续将应答返回过去// 利用一个输出型参数,将对方的网络信息填充到里面ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr *)&client, &len);if (n < 0){lg(Warning, "recvfrom sockfd err,errno: %d, err string %s", errno, strerror(errno));continue;}// 读取成功,将网络文件里的消息读取到buffer里buffer[n] = 0; // 字符串形式// 2.加工处理// std::string info = buffer;// std::string echo_string  = "server echo# " + info;std::string info=buffer;std::string echo_string=func(info);//将接收的信息由外层函数进行处理// 3.将应答发送回去// 发送给谁呢?服务器知道吗?服务器知道!因为在接收消息时,服务器就用一个输出型参数,将客户端的网络消息保存下来了sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);}}~Udpserver(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;     // 套接字文件描述符std::string _ip; // 我们习惯用string类型的ip地址uint16_t _port;  // 服务器进程的端口号
};

#include "Udpserver.hpp"
#include <memory>
#include <cstdio>
#include <stdlib.h>
// "120.78.126.148" 点分十进制字符串风格的IP地址
std::string handler(const std::string &info)
{ std::string res="get a message: ";res+=info;std::cout<<res<<std::endl;return res;//最后将处理的数据返回回去
}#include "Udpserver.hpp"
#include <memory>
#include <cstdio>void Usage(std::string proc)
{std::cout<<"\n\rUsage: "<<proc<<" port[1024+]\n"<<std::endl;
}
//服务器进程,启动时,按照./Udpserver+port的形式传递
int main(int args,char* argv[])
{if(args!=2){Usage(argv[0]);exit(0);}uint16_t port=std::stoi(argv[1]);std::unique_ptr<Udpserver> svr(new Udpserver(port));//首先创建一个服务器对象指针//智能指针,用一个UdpServer指针来管理类对象svr->Init();//初始化服务器svr->Run(handler);//启动服务器return 0;
} 

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手把手教测试,全网内容最全最深-jmeter-Switch Controller(Switch控制器)

5.1.6.17.Switch Controller(Switch控制器) 主要用于根据参数判断具体执行哪一个取样器。 在Switch Value中设置:数字,就是上图。不常用在Switch Value中设置:${变量名},根据变量名称来决定具体执行哪一个取样器。常用在Switch Value中设置:取样器名称,根据设置的取样器名…

绍兴市越城区人大常委会主任徐荻一行莅临迪捷软件走访慰问

2024年2月2日上午&#xff0c;绍兴市越城区人大常委会主任徐荻一行来到迪捷软件走访慰问高层次人才、迪捷软件董事长康烁&#xff0c;送上节日的问候与新春的祝福。 徐主任一行参观了迪捷软件办公环境&#xff0c;听取了迪捷软件的发展情况和来年规划&#xff0c;亲切询问了康烁…

在openfeign客户端如何获取到服务端抛出的准确异常信息?? openfeign调用(请求/响应)的各个大致过程

在openfeign客户端如何获取到服务端抛出的准确异常信息&#xff1f;&#xff1f; 相关参考背景引入浏览器直接访问Spring的Restful接口&#xff08;最普遍、简单的访问&#xff09;示例结论 openfeign客户端调用的情况调用过程示例场景之一&#xff08;其他场景可类比&#xff…

基于SSM+MySQL的的新闻发布系统设计与实现

目录 项目简介 项目技术栈 项目运行环境 项目截图 代码截取 源码获取 项目简介 新闻发布系统是一款基于Servletjspjdbc的网站应用程序&#xff0c;旨在提供一个全面且高效的新闻发布平台。该系统主要包括后台管理和前台新闻展示两个平台&#xff0c;涵盖了新闻稿件的撰写…

嵌入式软件设计

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

Java设计模式 – 四大类型

设计模式 – 四大类型 创建型模式结构型模式行为型模式J2EE模式 设计模式&#xff08;Design pattern&#xff09;是重构解决方案 根据书Design Patterns – Elements of Reusable Object-Oriented Software&#xff08;中文译名&#xff1a;设计模式 – 可复用的面向对象软件元…

【对象属性拷贝】⭐️按照需要转换的类型反射设置拷贝后对象的属性

背景&#xff1a; 小伙伴们大家好&#xff0c;最近开发的时候遇到一种情况&#xff0c;项目引入了全局序列化器来实现Date&#xff0c;LocalDateTime类型的字段根据时区转换&#xff0c;总体来说接口没什么要改动的&#xff0c;只要原来字段的属性是以上两种就行&#xff0c;但…

react+ts

1.概念 React和TypeScript集合使用的重点集中在 存储数据/状态有关的Hook函数以及组件接口的位置&#xff0c;这些地方最需要数据类型校验 2.使用Vite创建项目 Vite是前端工具链工具&#xff0c;可以帮助我们快速创建一个 reactts 的工程化环境出来 Vite官网&#xff1a;ht…

【C++】文件IO

目录 一、C语言的输入输出二、流的概念三、operator bool四、C文件IO流ifstream和ofstreamostringstream和istringstreamstringstream 一、C语言的输入输出 C语言中我们用到的最频繁的输入输出方式就是 scanf() 和 printf()。 scanf()&#xff1a; 从标准输入设备&#xff08…

GitHub的使用操作

记得看目录哦&#xff01; 1. 创建仓库2. 下载desktop3. 把创建的库克隆到本地4. 文件拷贝到本地仓库![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/7171ac6c4ca14e3b8d22717121f79c9e.png)5. 在网址后面加/compare进行比较6. 给系统添加功能 1. 创建仓库 2. 下载…

微信小程序 安卓/IOS兼容问题

一、背景 在开发微信小程序时&#xff0c;不同的手机型号会出现兼容问题&#xff0c;特此记录一下 二、安卓/IOS兼容问题总结 2.1、new Date()时间转换格式时&#xff0c;IOS不兼容 问题&#xff1a;在安卓中时间格式2024-1-31 10:10:10&#xff0c;但是在iOS中是不支持 &q…

LeetCode383. 赎金信

383. 赎金信 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。 示例 1&#xff1…