【Linux】套接字编程

目录

套接字

IP + PORT

TCP和UDP的介绍

TCP

UDP

网络字节序

转换接口

UDP服务器的编写

服务器的初始化 

socket

bind

sockaddr 结构

服务器的运行

数据的收发

业务处理

客户端的编写

运行效果

拓展 


套接字

🌸首先,我们先思考一个问题,数据从 A 主机发送到 B 主机是网络通信的最终目的吗

🌸显然不是的,我们进行网络通信是为了二者能通过某种协同方式,共同完成一个任务。因此,数据传输到 B 主机的传输层后并不能就此结束,还要向上交付给应用层。

🌸同时,我们还应该注意到,客户端与服务端本质上都是运行起来的服务,即二者都是正在运行的进程

🌸因此,网络通信的本质是进程间通信。

IP + PORT

🌸所以在网络通信的过程中必定经历这两个步骤。

  • 先将数据通过OS,将数据发送到目标主机。
  • 再将本主机收到的数据,推送给自己上层的指定进程。

🌸我们知道通过 IP 地址定位一台主机,而在网络中我们使用 port端口号(2字节)来定位主机上的进程。

🌸这时候我们突然想起来,之前在系统中不是使用了 pid 作为进程的唯一标识符吗?那这里为什么不继续使用 pid 标识进程呢?

🌸我们需要知道的是,pid 是属于操作系统部分的概念,若直接在网络中使用 pid 则会增加操作系统和网络直接的耦合度,当数据需要修改时则牵一发而动全身。

🌸因此,使用 IP + PORT就可以定位到互联网中的唯一进程,即网络通信的本质是通过 IP 和 PORT 构建进程的唯一性,基于网络的进程间通信。

🌸而通过IP和PORT来标志进程唯一性的方案就叫做套接字通信(socket)。

TCP和UDP的介绍

🌸在传输层我们有两个十分常见的传输协议,分别是 TCP UDP 协议,下面就分别介绍一下二者的区别。

TCP

🌸TCP是一种有连接可靠传输面向字节流的一种传输协议。

🌸面向字节流就好比家里的自来水,你要用多少就接收多少,而还未使用的数据就以流的形式存放在缓冲区中。

🌸而可靠传输体现在 TCP 需要保证对方收到对应消息,若未收到就会进行重发。

UDP

🌸UDP则是无连接不可靠传输面向数据报的传输协议。

🌸面向数据报的形式就像是我们收快递那样,一次至少接收一个完整的快递,不能收半个快递。

🌸需要注意的是,可靠性是一个中性词,并没有谁好谁坏,因为 UDP 在传输过程中并不关心对方是狗收到对应的报文,所以传输的过程中若丢失了对应的数据报就是真的丢失了。

🌸TCP 保证可靠性自然需要做更多的工作来维护,因而使用成本较高,而 UDP 并不保证,因此使用起来比较简单,二者并无谁优谁劣

网络字节序

🌸我们都知道多字节的数据在内存中存放具有大小端之分,不同主机间的存储方式也不同,那么在网络通信过程中该如何解决这个问题呢?

🌸TCP/IP 协议规定,网络数据流统一采用大端字节序,因此若当前发送的主机是小端机就需要先将数据转成大端,再进行通信。

转换接口

🌸对于整数的转换,有以下的接口,函数的名字和作用很好记,h 代表 host 即当前主机,n 代表 net 即网络序列,后面的 s16 位整数,l32 位整数。如 htons 就是将 16 位整数由当前主机序列转化为网络序列。

UDP服务器的编写

🌸接下来我们一起来学习一下 UDP 服务器是如何编写的吧。

🌸为了方便管理,这里直接将服务器封装起来了,对于一个服务器对象需要将其初始化,接着才能让它运行起来,因此一开始的类中,便需要以下几个成员函数。

namespace Alpaca
{class UdpServer{public:UdpServer(){}~UdpServer(){}void InitServer(){}void Start(){}};
}

🌸而在主函数中,只需要创建一个服务器的对象就能让他运行起来了。

int main(int argc, char *argv[])
{unique_ptr<UdpServer> usvr(new UdpServer());usvr->InitServer();usvr->Start();return 0;
}

服务器的初始化 

socket

🌸首先介绍的便是 socket 函数,其用于创建套接字打开网络文件。

🌸其中第一个参数用于选择通信的协议族,有以下几种可以选择,一般我们进行网络通信填 AF_INET 即可。

🌸其二的 type 参数用于指定通信语义,还记得我们上面讲的 TCP 是面向字节流的一种协议,而 UDP 则是面向数据报的一种协议,因此若是使用 TCP 协议直接使用 SOCK_STREAM 即可,而使用 UDP 则填入 SOCK_DGRAM

🌸最后一个参数默认为 0 即可。

🌸而 socket 函数的返回值是一个文件描述符,就像我们在文件系统那样,需要先存起来,之后还会用到。

_sock = socket(AF_INET, SOCK_DGRAM, 0);    //用成员先存起来

🌸因为返回的值表示为文件描述符,所以当返回值 < 0 时则说明创建套接字失败,便不能进行接下来的操作,直接结束进程。

if (_sock < 0)
{std::cout << "create socket error: " << strerror(errno) << std::endl;exit(SOCKET_ERR);
}

🌸这里返回的错误码另外定义就行,这里我是使用枚举来定义的。

enum
{USAGR_ERR = 1,SOCKET_ERR,BIND_ERR
};

🌸如此,我们便成功创建了套接字。 

bind

🌸成功创建套接字后,我们需要将套接字与 IP 和端口进行绑定,使用的便是 bind 函数。

🌸第一个参数自然就是先前创建的文件描述符,而第二个参数则需要接下来着重介绍了。

sockaddr 结构

🌸sockaddr 这个结构就类似于使用C语言的方法简单实现了一个多态的处理方式,当头部的地址类型为 AF_INET 就以 struct addr_in 的方式解析结构体,若是 AF_UNIX 则使用 struct addr_un 的方式解析。

🌸而 AF_INETAF_UNIX 在上方 socket 函数就介绍过了,即我们需要进行网络通信时则填充 struct addr_in,而要本地通信则填充 struct addr_un,强转后传给 bind 函数即可。

🌸接下来我们便需要对 sockaddr_in 结构体进行填充,需要注意的是,这里填入数据需要以网络字节序的形式,同时我们也有对应的接口协助我们进行转换,端口的转换使用 htons ,而 IP 则可以使用 inet_adddr 进行转换。

🌸但由于这里我使用的是云服务器,因此并不需要绑定 IP 地址,因此这里填入的便是 INADDR_ANY,若是使用虚拟机便需要绑定对应的 IP 地址。

struct sockaddr_in local;
bzero(&local, sizeof local);    //清空操作可选可不选
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;if (bind(_sock, (sockaddr*)&local, sizeof(local)))    //传参时需要强转
{std::cout << "bind socket error: " << strerror(errno) << std::endl;exit(BIND_ERR);
}

🌸同时,绑定的端口我们可以自己默认设置,或者使用命令行参数进行传入。

🌸这里我将从命令行参数里面提取对应的端口,然后通过构造函数构造出对应的服务器对象。

void usage(string proc)    //使用提示
{cout << "Usage:\n\t" << proc << " port" << endl;
}int main(int argc, char* argv[])
{if (argc != 2){usage(argv[0]);exit(USAGR_ERR);}uint16_t port = atoi(argv[1]);unique_ptr<UdpServer> usvr(new UdpServer(port));usvr->InitServer();usvr->Start();return 0;
}

🌸由此,我们便完成了 UDP 网络通信的前期准备,接下来只要根据业务运行服务器即可。 

服务器的运行

🌸完成了服务器的初始化,接下来就是服务器运行函数的实现了。下面我们就简单地实现一个收发操作吧。

数据的收发

🌸对于数据的接收,我们使用的是 recvfrom 这个函数,使用的情况与文件操作中的读取操作并无太大区别,但值得注意的是后面两个参数为输入输出型参数,用于接收发送者的相关信息,我们便能够从中提取出对应的 IP端口

🌸同样的,为了读取的数据接下来使用,在读取时要给 \0 留一个位置,读取完对应的内容再加上,同时,我们服务器提供的服务是时刻运行的,因此还需要持续不断的循环。

void Start()
{char buffer[1024];while (true){// 接收信息struct sockaddr_in peer;socklen_t len = sizeof(peer);int n = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&peer, &len);if (n > 0)buffer[n] = '\0';elsecontinue;}
}

🌸而发送信息则使用 sendto 这个函数,最后两个函数代表我们要将这个消息发送给谁。

sendto(_sock, buffer, strlen(buffer), 0, (sockaddr *)&peer, sizeof(peer));

🌸我们便可以将接收到的数据打印出来,再发回给客户端,完成一个简单的交互。

🌸而客户端的相关数据就存在返回回来的 sockaddr_in 结构体中,经过转换就能够直接输出了。

void Start()
{char buffer[1024];while (true){// 接收信息struct sockaddr_in peer;socklen_t len = sizeof(peer);int n = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&peer, &len);if (n > 0)buffer[n] = '\0';elsecontinue;// 提取客户端信息std::string clientip = inet_ntoa(peer.sin_addr);uint16_t port = ntohs(peer.sin_port);std::cout << "get message from " << clientip << "-" << port << ": " << buffer << std::endl;// 发送信息sendto(_sock, buffer, strlen(buffer), 0, (sockaddr*)&peer, sizeof(peer));}
}

业务处理

🌸客户端向服务器发送数据,一定需要服务器提供某种服务,自然不是简单的收发操作。

🌸因此,我们再外部定义一个函数,将其作为服务器类中的一个成员,当需要使用服务时就回调对应的函数。

🌸为了方便,我们事先使用了包装器,对函数类型进行包装。

using func_t = std::function<std::string(std::string)>;

🌸这里我们可以简单写一个服务用于将小写字符转成大写。 

std::string transString(std::string request)
{for (auto& c : request){if (islower(c))c = toupper(c);}return request;
}

🌸接着在构造的时候传入类中即可。

unique_ptr<UdpServer> usvr(new UdpServer(transString, port));

🌸最后,当我们收到对应的数据时,先对其进行处理,经过服务后再发回客户端

void Start()
{char buffer[1024];while (true){// 接收信息struct sockaddr_in peer;socklen_t len = sizeof(peer);int n = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&peer, &len);if (n > 0)buffer[n] = '\0';elsecontinue;// 提取客户端信息std::string clientip = inet_ntoa(peer.sin_addr);uint16_t port = ntohs(peer.sin_port);std::cout << "get message from " << clientip << "-" << port << ": " << buffer << std::endl;std::string resp = _service(buffer);// 发送信息sendto(_sock, resp.c_str(), resp.size(), 0, (sockaddr*)&peer, sizeof(peer));}
}

客户端的编写

🌸完成了服务器的编写,客户端的流程也有异曲同工之处,由于服务器已经封装过了,这里的客户端我们便直接在主函数中写了。

🌸首先确定的一点便是,客户端一定是知道服务器 IP 和端口号的,因此我们可以在客户端启动的时候从命令行里获取。

void usage(std::string proc)
{cout << "Usage:\n\t" << proc << " serverip serverport" << endl;
}int main(int argc, char* argv[])
{if (argc != 3){usage(argv[0]);return USAGR_ERR;}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);
}

🌸接着,便是开始网络通信的前期准备,即创建套接字,和上面讲过的方式并无差别,这里就直接跳过了。

int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{std::cerr << "create socket error: " << strerror(errno) << std::endl;exit(SOCKET_ERR);
}

🌸值得注意的一点来了,虽然客户端也要进行 bind ,但并不需要我们自己 bind ,也不要自己 bind操作系统会自动给我们 bind

🌸主要原因是,如果明确写死了端口号便可能与其他客户端的端口发生冲突,因此由 OS 为客户端分配空闲的端口。

🌸既然不用绑定端口,那么接下来我们就可以进行数据发送的准备工作了,在上面因为我们是直接拿接收下来的发送方的信息作为对象发送数据。

🌸这里需要先将服务端的信息填充进结构体。

sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());

🌸接着便能进入循环的服务中,我们可以通过命令行获取要发送的信息,再使用一个缓冲区接收服务端发送回的数据,当收到数据时就将对应的数据输出即可。

while (true)
{std::string message;cout << "please Enter# ";getline(cin, message);sendto(sock, message.c_str(), message.size(), 0, (sockaddr*)&server, sizeof(server));char buffer[1024];struct sockaddr_in tmp;socklen_t len = sizeof(tmp);int n = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&tmp, &len);if (n > 0){buffer[n] = '\0';cout << "server echo# " << buffer << std::endl;}
}

运行效果

🌸将服务器和客户端运行起来,尝试向服务器发送信息,便成功收到其回应,同时当我们输入的信息有小写的字符时便会将其转换成大写。

拓展 

🌸好了,这下我们搭建的服务器已经初具雏形了,接着可以往几个方向进行拓展,比如增加多线程的模块,分配一个线程专门进行数据的接收,而另一个线程则进行数据的发送。同时,可以将发送过信息的 IP 与端口加入注册表中,用 hash 进行维护,一旦接收到信息就向其中所有用户进行广播,便可以构成一个小型的聊天组。

🌸好了,我们今天的内容到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。

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

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

相关文章

【深度学习】pytorch快速得到mobilenet_v2 pth 和onnx

在linux执行这个程序&#xff1a; import torch import torch.onnx from torchvision import transforms, models from PIL import Image import os# Load MobileNetV2 model model models.mobilenet_v2(pretrainedTrue) model.eval()# Download an example image from the P…

韦东山linux驱动开发学习【常更】

1.linux目录简单介绍 2.直接运行需要在$path路径下

windows 安裝字體Font

或者直接Copy到C:\Windows\fonts 目錄下

用三智者交易策略澳福加减仓轻松盈利,就是这么厉害

就是这么厉害&#xff0c; 用三智者交易策略&#xff0c;澳福通过加减仓就可以在交易市场中轻松盈利。各位投资者都知道三智者交易策略的两个重要的原则。当市场超过外部极限时&#xff0c;在向上分形的高点和向下分形的低点&#xff0c;就会跟随外部方向/分形点。 fpmarkets澳…

如何使用Docker部署Apache+Superset数据平台并远程访问?

大数据可视化BI分析工具Apache Superset实现公网远程访问 文章目录 大数据可视化BI分析工具Apache Superset实现公网远程访问前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网…

LLM大模型 (chatgpt) 在搜索和推荐上的应用

目录 1 大模型在搜索的应用1.1 召回1.1.1 倒排索引1.1.2 倒排索引存在的问题1.1.3 大模型在搜索召回的应用 (实体倒排索引&#xff09; 1.2 排序1.2.1 大模型在搜索排序应用&#xff08;融入LLM实体排序&#xff09; 2 大模型在推荐的应用2.1 学术界关于大模型在推荐的研究2.2 …

采用Nexus搭建Maven私服

采用Nexus搭建Maven私服 1.采用docker安装 1.创建数据目录挂载的目录&#xff1a; /usr/local/springcloud_1113/nexus3/nexus-data2.查询并拉取镜像docker search nexus3docker pull sonatype/nexus33.查看拉取的镜像docker images4.创建docker容器&#xff1a;可能出现启动…

为关键信息基础设施安全助力!持安科技加入关保联盟

近日&#xff0c;中关村华安关键信息基础设施安全保护联盟发布了其新一批的会员单位&#xff0c;零信任办公安全代表企业持安科技成功加入&#xff0c;与联盟企业共同为关键信息基础设施提供各类支撑和保障。 中关村华安关键信息基础设施安全保护联盟由北京市科学技术委员会、中…

软件测试面试时问你的项目经验,你知道该怎么说吗?

很简单&#xff0c;我来给你们一个公式 0 自我介绍&#xff0c;名字 学历 荣誉。 1 简述项目背景&#xff0c;你身处这个项目是做什么的。 不要太细&#xff0c;试着引导一下面试官让他提问。这样&#xff0c;请问您对此有什么疑问吗&#xff1f; 2 简述 你在项目中的角色&…

Dubbo快速实践

文章目录 架构相关概念集群和分布式架构演进 Dubbo概述Dubbo快速入门前置准备配置服务接口配置Provider配置Consumer Dubbo基本使用总结 本文参考https://www.bilibili.com/video/BV1VE411q7dX 架构相关概念 集群和分布式 集群&#xff1a;很多“人”一起 &#xff0c;干一样…

Visual Studio Code 从英文界面切换中文

1、先安装中文的插件&#xff0c;直接安装。 2、点击右下角的 change language restart&#xff0c; 让软件重启即可以完成了。

Kafka学习笔记(二)

目录 第3章 Kafka架构深入3.3 Kafka消费者3.3.1 消费方式3.3.2 分区分配策略3.3.3 offset的维护 3.4 Kafka高效读写数据3.5 Zookeeper在Kafka中的作用3.6 Kafka事务3.6.1 Producer事务3.6.2 Consumer事务&#xff08;精准一次性消费&#xff09; 第4章 Kafka API4.1 Producer A…

异常控制流——(中断、陷阱、故障、终止、进程等操作系统干货)

异常 异常控制流 控制流&#xff1a; 假设从处理机上电运行一直到断电关机的这段时间内&#xff0c;程序计数器的值是下图序列&#xff0c;其中ak表示某一条指令Ik的地址。 **控制转移&#xff1a;**每一次从ak到ak1的过渡 **平滑&#xff1a;**Ik和Ik1在内存中是相邻的&am…

怎样备份电脑文件比较安全

域智盾软件是一款功能强大的电脑监控软件&#xff0c;它不仅具备实时屏幕监控、行为审计等功能&#xff0c;还能够对电脑文件进行备份和管理。下面将介绍域智盾软件如何备份电脑文件&#xff0c;以确保数据安全。 1、开启文档备份功能 部署后台&#xff0c;然后点击文档安全&a…

李宏毅2023机器学习作业HW05解析和代码分享

ML2023Spring - HW5 相关信息&#xff1a; 课程主页 课程视频 Sample code HW05 视频 HW05 PDF 个人完整代码分享: GitHub | Gitee | GitCode 运行日志记录: wandb P.S. HW05/06 是在 Judgeboi 上提交的&#xff0c;完全遵循 hint 就可以达到预期效果。 因为无法在 Judgeboi 上…

Vue3 源码解读系列(八)——生命周期

生命周期 正常的生命周期 // 注册钩子函数 const onBeforeMount createHook(bm/* BEFORE_MOUNT */) const onMounted createHook(m/* MOUNTED */) const onBeforeUpdate createHook(bu/* BEFORE_UPDATE */) const onUpdated createHook(u/* UPDATED */) const onBeforeUnm…

商城免费搭建之java商城 java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

java桌面程序

目标之一是把打印导出的功能最终用java实现一套&#xff0c;首先选定javafx&#xff0c;因为idea默认创建工程就带的javafx&#xff0c;没找到swing。 创建工程&#xff0c;这里要选1.8&#xff0c;高版本jdk默认不带fx 实现主界面的代码 package sample;import javafx.app…

将word中的表格无变形的弄进excel中

在上篇文章中记录了将excel表拷贝到word中来&#xff1a; 记录将excel表无变形的弄进word里面来-CSDN博客 本篇记录&#xff1a;将word中的表格无变形的弄进excel中。 1.按F12&#xff0c;“另存为...”&#xff0c;保存类型&#xff1a;“单个文件页面”&#xff0c;保存。…