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

C/C++ Linux网络编程2 - Socket编程与简单UDP服务器客户端

上篇文章:Linux网络编程 - 1网络编程基础-CSDN博客

代码仓库:橘子真甜 (yzc-YZC) - Gitee.com

目录

一. Socket编程API与结构体

1.1 Socketaddr结构体

二. UDP网络编程API⭐

2.1 socket

2.2 bind

2.3 sendto

2.4 recvfrom

三. UDP服务端代码

3.1 udpserver.hpp

3.2 udpserver.cc

四. UDP客户端代码

4.1 udpclient.hpp

4.2 udpclient.cc

五. 测试


一. Socket编程API与结构体

          socket是插座的意思,插就是应用程序,座就是网络socket。通信双方都通过socket进行通信。      

1.1 Socketaddr结构体

        有三套结构体,sockaddr sockaddr_in sockaddr_un

        sockaddr:原始套接字,可以通过传输层控制底层结构,常用于网络中的监听,抓包等功能。结构如下:

        sockaddr_in:网络套接字,常用于跨主机之间的网络通信。内部集合了主机的IP和端口,在大部分网络编程API中,需要强制转化为原始套接字。结构如下:

Unix域间套接字:用于主机内部的通信,类似于管道和共享内存。结构如下:

        在使用这些结构体的时候,通过将 sockaddr_in sockaddr_un转化为 sockaddr即可实现相应类型的通信。

        其实就是一种多态的思想,soackaddr就是基类,剩下两种是派生类。

二. UDP网络编程API⭐

        UDP的特点是不保证可靠传输,面向数据报,无连接。

所以:

我们每一次接收数据的时候,应该将一个完整的报文读取完毕。

由于无连接,发送数据的时候需要带上自己的ip和端口等信息

收数据的时候也要定义对象来获取对方的ip和端口等信息

2.1 socket

//头文件
#include <sys/types.h>
#inclyde <sys/socket.h>//函数原型,用于创建一个用于网络通信的套接字fd
int socket(int domin, int type, int protocol)//参数说明
//domin : 域,表示是需要网络通信还是主机内部通信//type  : 通信的类型
//SOCK_STREAM    是流式套接字通信,即TCP通信
//SOCK_DGRAM     是报文套接字通信,即UDP通信// protocol,
// 分为tcpprotocol udpprotoclo 但是我们一般设置为0,因为type参数选择之后,这个就会自动固定//返回值,返回一个用于通信的文件fd,其实socket与open是非常类似的

        Linux中一切皆文件,打开网络与打开普通文件是一样的。读写网络数据与读写文件数据也是非常类似的

2.2 bind

        用于绑定sockfd与IP端口

#include <sys/types.h>
#inckude <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
sockfd : 使用socket获取的文件描述符
addr : 传入的结构体,该结构体是你需要通信的方式,内部包含了IP和端口等信息
addrlen : 传入结构体的长度,需要注意类型返回值:绑定成功返回0,失败返回-1//注意,我们定义下面三种结构体的时候需要带上头文件#include <arpa/inet.h>
struct sockaddr s;
s.sin_family = AF_INET    表示协议家族,我们需要传入协议,如AF_INET表示使用网络通信
s.sin_port                表示绑定端口号
s.sin_addr                存储了IP号码
s.sin_addr.s_addr         即可获取IP地址

2.3 sendto

        这个接口常用于UDP服务器/客户端之间的发送信息

#include <sys/types.h>
#include <sys/socket.h>用于发送信息到网络中
//函数原型
ssize_t sendto(int sockfd, void *buf, size_t len, int flags, struct sockaddr* dest_addr, socklen_t *addrlen);
//将  len长度的buf数据通过sockfd这个套接字发送到网络中
flags为0表示阻塞//输入形参数
dest_addr 对方接收数据的IP地址和端口
addrlen   对方接收数据结构体长度

2.4 recvfrom

        用于UDP通信时候接收数据

#include <sys/types.h>
#include <sys/socket.h>用于接收来自网络的信息
//函数原型
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen)//参数
sockfd     接收信息的文件描述符
bur        接收信息写入的缓冲区    len 接收信息的长度
flags      接收信息的方式,一般默认设置为0    表示阻塞方式,没有信息阻塞等待//下面两个参数是输入输出形参数,是为了获取对方的IP信息与端口号码,协议方式
src_addr    对方IP的信息
addrlen     该信息的长度,注意这个参数在定义的时候不需要初始化为src_addr的长度 addrlen = sizeof(src_addr)

三. UDP服务端代码

        这个服务器用于接收客户端的代码,然后打印输出。

3.1 udpserver.hpp

        服务器的代码框架如下:

#pragma once
#include <iostream>#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>static const std::string defaultIp = "0.0.0.0";
class udpServer
{
public:udpServer(uint16_t port, const std::string &ip = defaultIp): _sockfd(-1), _ip(ip), _port(port) {}void init() {}void run() {}private:int _sockfd; // 套接字std::string _ip; // 服务器ipuint16_t _port;  // 绑定的端口
};

需要变量来存储服务器的 通信fd,ip和端口。        

init用于初始化服务器:包含socket,bind

run:用于服务器的运行,一个服务器一般是一个死循环执行代码的。

        一般来说,一台主机是有很多ip的,为了能够保证接收数据不丢失,服务器绑定的IP一般都是"0.0.0.0"表示服务器可以接收底层任意ip地址的网络数据

init代码:

  void init(){// 1.socket创建套接字,udp需要使用 报文_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){perror("socket");exit(errno);}printf("socket success\n");// 2.绑定fd和端口// 首先要设置好通信结构体struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要转化为大端数据serveraddr.sin_port = htons(_port);                  // 需要转化为大端数据socklen_t len = sizeof(serveraddr);                  // 设置bind第三个参数int n = bind(_sockfd, (struct sockaddr *)&serveraddr, len);if (n < 0){perror("bind");exit(errno);}printf("bind success\n");// UDP在绑定好之后,就能运行了}

run:

        服务器运行的时间是很长的,所以我们需要使用一个死循环来读取数据。

        void run(){char buffer[1024];while (true){// 收数据,要定义对象获取对方的ip和端口struct sockaddr_in clientaddr;memset(&clientaddr, 0, sizeof(clientaddr));socklen_t clientaddrlen = sizeof(clientaddr);// recvfrom接收数据int count = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&clientaddr, &clientaddrlen);if (count > 0){// 通过设置好的sockaddr获取ip,端口等信息std::string clientip = inet_ntoa(clientaddr.sin_addr);uint16_t clientport = ntohs(clientaddr.sin_port);// 打印收到数据的信息printf("client[%s][%d] --> server:%s\n", clientip.c_str(), clientport, buffer);}// 清空 buffermemset(buffer, 0, sizeof(buffer));}}

总代码如下:

namespace YZC
{static const std::string defaultIp = "0.0.0.0";class udpServer{public:udpServer(uint16_t port, const std::string &ip = defaultIp): _sockfd(-1), _ip(ip), _port(port) {}void init(){// 1.socket创建套接字,udp需要使用 报文_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){perror("socket");exit(errno);}printf("socket success\n");// 2.绑定fd和端口// 首先要设置好通信结构体struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要转化为大端数据serveraddr.sin_port = htons(_port);                  // 需要转化为大端数据socklen_t len = sizeof(serveraddr);                  // 设置bind第三个参数int n = bind(_sockfd, (struct sockaddr *)&serveraddr, len);if (n < 0){perror("bind");exit(errno);}printf("bind success\n");// UDP在绑定好之后,就能运行了}void run(){char buffer[1024];while (true){// 收数据,要定义对象获取对方的ip和端口struct sockaddr_in clientaddr;memset(&clientaddr, 0, sizeof(clientaddr));socklen_t clientaddrlen = sizeof(clientaddr);// recvfrom接收数据int count = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&clientaddr, &clientaddrlen);if (count > 0){// 通过设置好的sockaddr获取ip,端口等信息std::string clientip = inet_ntoa(clientaddr.sin_addr);uint16_t clientport = ntohs(clientaddr.sin_port);// 打印收到数据的信息printf("client[%s][%d] --> server:%s\n", clientip.c_str(), clientport, buffer);}// 清空 buffermemset(buffer, 0, sizeof(buffer));}}private:int _sockfd; // 套接字std::string _ip; // 服务器ipuint16_t _port;  // 绑定的端口};
}

3.2 udpserver.cc

        这个代码包含main函数,用于服务器的启动和测试

#include <iostream>
#include <memory>
#include "udpserver.hpp"// 设置使用方法
void Usage(const char *s)
{printf("Usage:\r\n%s serverport\n", s);
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(-1);}// 使用智能指针管理服务器,可以自动销毁。防止内存泄漏std::unique_ptr<YZC::udpServer> usptr(new YZC::udpServer(atoi(argv[1])));usptr->init();usptr->run();return 0;
}

四. UDP客户端代码

        这个客户端用于向服务端发送数据

思考一下:服务端为何需要bind绑定fd和端口?

        因为,如果没有指明端口,其他客户端如何连接服务器?

思考一下:客户端需要显示绑定fd和端口吗?

        不需要,因为通信是由客户端发起的,服务端被动接收。此时服务端无需知道客户端具体的ip和端口信息

        服务端可以直接通过recvfrom获取我的ip/端口等信息

        

4.1 udpclient.hpp

        框架与server类似

namespace YZC
{class udpClient{public:udpClient(std::string &ip, uint16_t port): _ip(ip), _port(port) {}void init(){}void start(){}private:int _sockfd; // 监听套接字std::string _ip; // 目的ipuint16_t _port;  // 目的端口};
}

init : 上面讨论过了,只需要socket,无需显示bind。如果没有bind,在发送数据的时候内核会自动帮我们找到没有使用的port进行bind

        void init(){// 1.socket创建套接字,udp需要使用 报文_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){perror("socket");exit(errno);}printf("client socket success\n");// bind由OS自行决定}

start:

        用于发送数据

  void start(){// 发送数据,需要指定对方的ip和端口struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));// 设置ip/端口/通信等信息。注意转化为大端数据serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str());serveraddr.sin_port = htons(_port);socklen_t clientaddrlen = sizeof(serveraddr);// 可以发送数据char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer));std::cout << "请输入你想要向server发送的数据;";std::cin >> buffer;int count = sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&serveraddr, clientaddrlen);if (count <= 0){printf("数据发送异常,client quit\n");close(_sockfd);exit(-1);}printf("数据发送成功\n");}}

总代码如下:

#pragma once
#include <iostream>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>#include <cstring>namespace YZC
{class udpClient{public:udpClient(std::string &ip, uint16_t port): _ip(ip), _port(port) {}void init(){// 1.socket创建套接字,udp需要使用 报文_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){perror("socket");exit(errno);}printf("client socket success\n");// bind由OS自行决定}void start(){// 发送数据,需要指定对方的ip和端口struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));// 设置ip/端口/通信等信息。注意转化为大端数据serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str());serveraddr.sin_port = htons(_port);socklen_t clientaddrlen = sizeof(serveraddr);// 可以发送数据char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer));std::cout << "请输入你想要向server发送的数据;";std::cin >> buffer;int count = sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&serveraddr, clientaddrlen);if (count <= 0){printf("数据发送异常,client quit\n");close(_sockfd);exit(-1);}printf("数据发送成功\n");}}private:int _sockfd; // 监听套接字std::string _ip; // ipuint16_t _port;  // 绑定的端口};
}

4.2 udpclient.cc

#include <iostream>
#include <memory>
#include "udpclient.hpp"// 设置使用方法
void Usage(const char *s)
{printf("Usage:\r\n%s serverip serverport\n", s);
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(-1);}// 使用智能指针管理服务器,可以自动销毁。防止内存泄漏std::unique_ptr<YZC::udpClient> ucptr(new YZC::udpClient(argv[1], atoi(argv[2])));ucptr->init();ucptr->start();return 0;
}

五. 测试

        使用上面的代码来测试服务端server与客户端client 能否通信

这里首先了解一下本地回环地址 127.0.0.1。使用命令 ifconfig查看网卡信息

下面的数据是我在自己的云服务器输入ifconfig获取的。

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500inet 172.17.52.126  netmask 255.255.192.0  broadcast 172.17.63.255inet6 fe80::216:3eff:fe0a:5e1e  prefixlen 64  scopeid 0x20<link>ether 00:16:3e:0a:5e:1e  txqueuelen 1000  (Ethernet)RX packets 1944787  bytes 1196104294 (1.1 GiB)RX errors 0  dropped 0  overruns 0  frame 0TX packets 1537251  bytes 634615558 (605.2 MiB)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536inet 127.0.0.1  netmask 255.0.0.0inet6 ::1  prefixlen 128  scopeid 0x10<host>loop  txqueuelen 1000  (Local Loopback)RX packets 746248  bytes 164466741 (156.8 MiB)RX errors 0  dropped 0  overruns 0  frame 0TX packets 746248  bytes 164466741 (156.8 MiB)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

可以看第二段的第二行,本地回环地址是 127.0.0.1

首先要启动服务器

然后启动客户端

运行结果如下:

        可以看到,我们的数据被客户端正常发送了。服务端也接收到来正确的数据

这样一来就能保证 server - client之间的通信,当然这份代码是比较简单的。

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

相关文章:

  • 工业场景漏油硬件检测方法及原理
  • 工业设计就业网站在线优化工具
  • 瑞美吉泮Rimegepant说明书深度解析:用法用量,真实世界疗效
  • 做电影网站主机放哪比较好网站页面设计服务
  • 每日两题day41
  • 网站设计与开发期末考试题建筑模拟器2022下载
  • 访问日志查询功能
  • vite创建vue2项目
  • 【MATLAB例程】二维平面的TOA定位,几何精度因子GDOP和克拉美罗下界CRLB计算与输出
  • 怎么创一个网站赚钱免费入驻的外贸平台
  • 云边云科技SD-WAN解决方案 — 构建安全、高效、智能的云网基石
  • 20251112给荣品RD-RK3588开发板跑Rockchip的原厂Android13系统时适配AP6275P模块的BT蓝牙部分【使用原厂的DTS】
  • MyBatis 专题深度细化解析
  • a做爰视频免费观费网站asp网站如何迁移
  • 网站推广平台wordpress怎么加属性
  • 文创做的好的网站推荐微信公众号属于网站建设
  • 1. Cockpit 管理服务器;2. Linux 软件包管理
  • 【剑斩OFFER】算法的暴力美学——山脉数组的蜂顶索引
  • 关键词挖掘工具有哪些兰州seo优化
  • LeetCode 热题 100——哈希——最长连续序列
  • c语言反编译软件|详细解析c语言反编译工具的使用及其重要性
  • 模板网站更改青海制作网站的公司
  • 牛客:栈的压入、弹出序列
  • 深入解析UDP服务器核心开发机制
  • 阜阳做网站的公司网站开发前端跟后端的区别
  • MongoDB知识点与技巧总结
  • 企业网站 设计国外免费建站网站不用下载
  • LeetCode算法学习之数组中的第K个最大元素
  • 应急调度系统让每一次救援都精准到位
  • RL机器人人库使用简介