网络编程套接字(二)之UDP服务器简单实现

目录

一、服务端UdpServer

1、udp_server.hpp

1、服务器的初始化

2、服务器的运行

2、udp_server.cc

二、客户端UdpClient

udp_client.cc

三、完整代码


一、服务端UdpServer

1、udp_server.hpp

首先,我们在该文件中,将服务器封装成一个类,而作为一款服务器,必须要有自己的端口号,同时网络服务器需要有对应的IP地址,文件描述符sock_:进行各种各样的数据通信,在类内进行读写操作。然后对外提供初始化和运行的接口。

1、服务器的初始化

我们最开始需要先将它进行初始化。初始化的第一步就是创建套接字,而创建套接字我们需要用到下面的函数。

socket:其作用就是创建套接字。

NAMEsocket - create an endpoint for communicationSYNOPSIS#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol);

参数说明:

~ domain:域,用来表明套接字是进行网络通信还是本地通信,主要使用下面这两种 :AF_UNIX(本地通信)   AF_INET(网络通信)。

~ type:创建套接字时所需的服务类型。其中最常使用的是SOCK_STREAM和SOCK_DGRAM。如:UDP是数据报的网络通信形式,我们采用的就是SOCK_DGRAM(用户数据报服务),TCP是面向字节流式的网络通信,我们采用的就是SOCK_STREAM(叫做流式套接字,提供的是流式服务)。

~ protocol:创建套接字的协议类别。该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。

~ 返回值:套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。

初始化的第二步就是绑定端口号和IP,我们需要用到bind函数

NAMEbind - bind a name to a socketSYNOPSIS#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数说明:

~ sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。

~ addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。

~ addrlen:传入的addr结构体的长度。 

~ 返回值:绑定成功返回0,绑定失败返回-1,同时错误码会被设置。

在绑定时需要将服务器网络相关的属性信息填充到结构体struct addr_in当中,其结构如下:

我们一般需要填充下面三个成员:

sin_family:填充AF_INET。

sin_port:表示服务器端口号,是一个16位的整数。需要注意主机序列和网络序列的转化。

sin_addr:表示服务器IP地址,是一个32位的整数。我们发现这个结构是一个结构体,所以一般是填充其中的成员:是一个32位的整数。

我们所看到的IP地址是点分十进制的,但是真正的IP地址是整数,所以我们在使用是需要将IP地址转换成系统能识别32位的整数,也需要注意主机序列和网络序列的转化,这些操作我们使用函数inet_addr可以一并实现。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);

注:实际上,一款网络服务器不建议指明一个IP,也就是不要显示地绑定IP,因为一个服务器上可能会有多张网卡,所以IP可能不止一个,如果只绑定一个明确的IP,最终的数据可能用别的IP来访问端口号,这就无法访问,所以真实的服务器IP一般采用INADDR_ANY(全0,任意地址)代表任意地址bind 。

所以我们在填充IP地址时最好使用INADDR_ANY。

2、服务器的运行

首先,作为一款服务器,我们必须能够随时给用户提供服务,所以服务器是一个不能够退出的进程,需要使用死循环。然后不断接收从客户端发送过来的请求,进行处理,将结果返回给客户端。

我们一般使用recvform函数接收客户端发送过来的请求:

NAMErecv, recvfrom, recvmsg - receive a message from a socketSYNOPSIS#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);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

参数说明:

sockfd:服务器绑定的套接字,buf:读取到特定缓冲区,len:缓冲区长度。

flags:读取的方式,默认为0,阻塞读取。

src_addr:收到除了消息本身,还得知道是谁发的,输入输出型参数,返回对应的消息内容是从哪一个客户端来的,len:src_addr大小。

返回值:返回-1表示失败,成功返回字节数

服务器收到消息,进行处理后,我们需要将结果发回给客户端,我们一般使用sendto函数:

NAMEsend, sendto, sendmsg - send a message on a socketSYNOPSIS#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);

2、udp_server.cc

服务端进行调用的代码逻辑:构建udpServer的对象,然后进行初始化,在进行启动起来;调用逻辑如下:

因为运行后服务器会自动绑定所有的IP,所以我们只需要绑定端口号即可。

#include <iostream>
#include "udp_server.hpp"
#include <string>
#include <memory>static void usage(const std::string &proc)
{std::cout << "\nusage: " << proc << "  port\n" << std::endl;
}// ./udpserver port
int main(int argc, char *argv[])
{if (argc != 2){usage(argv[0]);exit(0);}uint16_t port = atoi(argv[1]);std::unique_ptr<UdpServer> usvr(new UdpServer(port));usvr->InitServer();usvr->start();return 0;
}

二、客户端UdpClient

udp_client.cc

客户端调用方式:./udpClient server_ip server_port,客户端想连接服务器,必须得知道服务器的IP(公网IP)以及端口号。

客户端的实现方式:创建套接字,发送请求给服务器,接收服务器返回的结果。

需要注意的是:在服务端bind的时候,最重要的不是绑定IP,而是绑定端口号,服务器需要显示地绑定端口号是为了客户端未来能够明确地找到服务器是对应的服务端进程,不能随意改变。

而客户端虽然也需要端口号,但是不重要,自己启动后有端口号就可以了,不需要显示地绑定是哪一个。因为如果不同公司的程序员在写不同软件客户端时,绑定了同一个端口号的话,就会发生冲突,所以我们在客户端不需要程序员自己去绑定端口号,而是由系统随机分配。

三、完整代码

udp_server.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <string>class UdpServer
{
public:UdpServer(const uint16_t &port, std::string ip = ""): ip_(ip), port_(port), sock_(-1){}void InitServer(){// 1.创建套接字sock_ = socket(AF_INET, SOCK_DGRAM, 0);if (sock_ < 0){std::cerr << "创建套接字失败!" << errno << strerror(errno) << std::endl;exit(0);}// 2.进行绑定:绑定ip和端口号// 2.1 服务器的套接字填充结构struct sockaddr_in local_server;bzero(&local_server, sizeof(local_server));local_server.sin_family = AF_INET;local_server.sin_port = htons(port_);                                             // 端口号需要从主机转网络local_server.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str()); // ip地址需要主机转网络,并且要转化成4字节形式int len = sizeof(local_server);if (bind(sock_, (struct sockaddr *)&local_server, len)){std::cout << "绑定失败!" << errno << strerror(errno) << std::endl;exit(1);}std::cout << "服务器初始化完成!" << std::endl;}void start(){std::cout << "服务器运行成功!" << std::endl;char buffer[1024];char result[1024];std::string server_echo;// 服务器不能停下来,除非挂掉——死循环for (;;){struct sockaddr_in src_client; // 服务器收到的客户端来源socklen_t len = sizeof(src_client);ssize_t s = recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&src_client, &len);if (s < 0){std::cout << "接收消息失败!" << std::endl;}buffer[s] = 0;//std::string src_client_ip = inet_ntoa(src_client.sin_addr);//uint16_t src_client_port = ntohs(src_client.sin_port);//std::cout << "[" << src_client_ip << "," << src_client_port << "]: " << buffer << std::endl;// 客户端发过来的信息是指令,服务器将结果返回给客户端FILE *fp = popen(buffer, "r");if (fp == nullptr){std::cout << "popen失败!" << std::endl;continue;}while (fgets(result, sizeof(result), fp) != nullptr){server_echo += result;}// 服务器发回消息sendto(sock_, server_echo.c_str(), server_echo.size(), 0, (struct sockaddr *)&src_client, len);//sendto(sock_, buffer, sizeof(buffer), 0, (struct sockaddr *)&src_client, len);}}private:std::string ip_; // 服务器ip地址uint16_t port_;  // 服务器端口号int sock_;       // 套接字
};

udp_server.cc

#include <iostream>
#include "udp_server.hpp"
#include <string>
#include <memory>static void usage(const std::string &proc)
{std::cout << "\nusage: " << proc << "  port\n" << std::endl;
}// ./udpserver port
int main(int argc, char *argv[])
{if (argc != 2){usage(argv[0]);exit(0);}uint16_t port = atoi(argv[1]);std::unique_ptr<UdpServer> usvr(new UdpServer(port));usvr->InitServer();usvr->start();return 0;
}

udp_client.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cerrno>
#include <unistd.h>
#include <string>
#include <stdio.h>
#include <cstring>static void usage(const std::string &proc)
{std::cout << "\nusage: "<< proc << "  port ip\n"<< std::endl;
}// ./udpclient ip port  带上服务器的ip和端口号
int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(0);}// 1.创建套接字int sock = socket(AF_INET, SOCK_DGRAM, 0);if (sock < 0){std::cerr << "创建套接字失败!" << errno << strerror(errno) << std::endl;exit(1);}// 作为客户端,其不需要进行绑定,由os自动进行绑定std::string message;// 给哪个服务器发送消息,填充服务器信息struct sockaddr_in send_server;bzero(&send_server, sizeof(send_server));send_server.sin_family = AF_INET;send_server.sin_port = htons(atoi(argv[2]));send_server.sin_addr.s_addr = inet_addr(argv[1]);while (true){// 2.从键盘获取信息std::cout << "请输入#";std::getline(std::cin, message);if (message == "quit")break;// 3.发送消息sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&send_server, sizeof(send_server));// 4.接收服务器的消息char buffer[1024];struct sockaddr_in revc_server;bzero(&revc_server, sizeof(revc_server));socklen_t len = sizeof(revc_server);ssize_t s = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&revc_server, &len);std::cout << "服务器说:" << buffer << std::endl;}close(sock);return 0;
}

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

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

相关文章

刷代码随想录有感(32):逆波兰表达式求值

题干&#xff1a; 代码&#xff1a; class Solution { public:stack<long long> st;int evalRPN(vector<string>& tokens) {for(int i 0; i < tokens.size(); i){if(tokens[i] "" || tokens[i] "-" || tokens[i] "*" |…

C++面向对象程序设计-北京大学-郭炜【课程笔记(六)】

C面向对象程序设计-北京大学-郭炜【课程笔记&#xff08;六&#xff09;】 1、可变长数组类的实现2、流插入运算符和流提取运算符的重载2.1、对形如cout << 5 ; 单个"<<"进行重载2.2、对形如cout << 5 << “this” ;连续多个"<<&…

SSL Pinning之双向认证

双向认证处理流程 概述获取证书逆向app 获取证书的KeyStore的 key通过jadx 反编译 app 获取证书&#xff1a;frida hook 证书转换命令行转换portecle 工具使用 charles 配置 p12 格式证书 概述 本篇只介绍怎么解决ssl pinning&#xff0c; 不讲ssl/tls 原理。 为了解决ssl pinn…

ELFK (Filebeat+ELK)日志分析系统

一. 相关介绍 Filebeat&#xff1a;轻量级的开源日志文件数据搜集器。通常在需要采集数据的客户端安装 Filebeat&#xff0c;并指定目录与日志格式&#xff0c;Filebeat 就能快速收集数据&#xff0c;并发送给 logstash 进或是直接发给 Elasticsearch 存储&#xff0c;性能上相…

cesium 添加动态波纹效果 圆形扩散效果 波纹材质

一、扩展材质 /*** 水波纹扩散材质* param {*} options* param {String} options.color 颜色* param {Number} options.duration 持续时间 毫秒* param {Number} options.count 波浪数量* param {Number} options.gradient 渐变曲率*/function CircleWaveMaterialProperty(opt…

CMC学习系列 (9):对侧半球可能支持中风后的恢复

对侧半球可能支持中风后的恢复 0. 引言1. 主要贡献2. 方法2.1 患者信息2.2 行为测试2.3 运动任务/实验范式 3. 结果3.1 对照参与者和卒中患者的相干图3.2 地形图上的CMC标签 4. 讨论5. 总结欢迎来稿 论文地址&#xff1a;https://www.sciencedirect.com/science/article/pii/S2…

golang 使用栈模拟计算器

思路&#xff1a; // Author sunwenbo // 2024/4/12 16:51 package mainimport ("errors""fmt""strconv" )// 使用数组来模拟一个栈的应用 type Stack struct {MaxTop int //表示栈最大可以存放数的个数Top int //表示栈底&#xff…

【方法】如何打开ZIP分卷压缩文件?

ZIP分卷压缩文件&#xff0c;是指在压缩文件时&#xff0c;将文件压缩成若干个ZIP格式的小压缩包&#xff0c;便于储存和传送&#xff0c;这些小压缩包可以被当作一个完整的文件来处理。那ZIP分卷压缩文件要怎么打开呢&#xff1f;不清楚的小伙伴一起来看看吧&#xff01; 首先…

星邦生化设备有限公司将出席2024第13届生物发酵展

参展企业介绍 宁波星邦生化设备有限公司&#xff0c;致力于发酵空气处理、发酵尾气处理及发酵罐节能环保装备的研发制造达30余年。公司拥有20多项自主开发的具有竞争力的国家发明专利技术及核心专有技术。连续三届被中国生物发酵产业协会评为——节能环保重点推荐企业。公司用…

LeetCode 678——有效的括号字符串

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 需要两个栈&#xff0c;一个用来保存左括号所在的位置索引&#xff0c;一个用来保存星号所在的位置索引。 从左往右遍历字符串&#xff0c;如果是左括号或者星号&#xff0c;则将位置索引分别入栈&#xff0c;如…

碧桂园服务:政企联合,助力秭归脐橙销售

4月9日&#xff0c;秭归县农业农村局与碧桂园服务智享楼下心选签订了秭归伦晚脐橙助农合作项目。 秭归县农业农村局现场对接碧桂园服务集团智享心选全国十二个重点区域核心业主代表&#xff0c;并授予他们“秭归伦晚脐橙推广大使”证书&#xff0c;每位代表都变成了秭归伦晚脐橙…

【Next】动态路由、加载 UI 和流式传输

动态路由 动态段作为 params 属性传递给 layout、page、route 和 generateMetadata 函数。 /app/blog/[slug]/page.tsx export default function Page({params}: {params:{slug:string}}) {return <h1>Slug Page -- {params.slug}</h1> };/app/shop/[...slug]/pa…