网络编程,端口号,网络字节序,udp

前面一篇我们讲了网络的基础,网络协议栈是什么样的,数据如何流动传输的;接下来这篇,我们将进行实践操作,真正的让数据跨网络进行传输;

1.网络编程储备知识

1.1 初步认识网络编程

首先我们需要知道我们的网络编程实例化到现实生活中就是使用app进行交互,这样的交互其实就是两台不同机器上的进程在进行通信,这其实也叫做进程间通信,不过是通过网络来进行的罢了;此时两个进程的共享内存叫做网络

1.网络协议栈的下三层主要是用来保证数据的安全传输的问题

2.我们编程主要编写的是应用层代码

3.用户通过使用我们编写好的程序来进行数据发送与接收

 1.2 端口号

认识端口号

我们编写的应用层代码形成程序载入内存时成为进程需要被标识,让网络可以通过IP找到机器通过标识找到当前进程,而这个标识就叫做——端口号;

1.端口号是一个2字节16比特位的整数

2.可以使用端口号找到机器上唯一进程

3.一个端口号只能对应一个进程(但一个进程可以有多个端口号)

端口号与pid 

在我们前面linux系统的学习中,我们知道进程都有自己唯一的pid,那为什么我们不直接使用pid来标识进程呢,为什么还要引出一个端口号? 

因为端口号是属于网络体系的,而pid是属于系统体系的,如果将pid直接作为端口号来标识唯一进程也是可以的,可是这样会存在一些问题:

1.pid是随机变化的,每次进程启动是pid都会发生改变,有些端口号是固定不变的

2.当系统中的pid需要调整时会导致网络中的端口号也随之改变,会增加维护的难度

3.高内聚,低耦合的思想

端口号绑定进程 

我们如何理解将端口号绑定到一个进程上? 

我们可以看作端口号与进程的pcb指针形成了hash键值对,从而可以通过端口号找到相应的进程;

 1.3 网络字节序

世界上的机器是无穷多的,所以设备是个性化的,机器的实现一定是具有差异的,但是为了让机器可以正常通信,要使用网络覆盖底层的实现,让上层的交互规则是一样的;网络字节序就是这样一种覆盖的方式,我们的机器是存在大小端之分的,数据的传输也会存在数据的发送顺序问题,为了解决不同机器数据传输顺序的问题,需要使用网络下的函数,让传输进入网络中的数据按照网络字节序的形式存在;

只要是要传输到网络中的数据都要使用下面的函数对数据进行转换:

记忆方法:h为host(主机),n为network,l为长整型32位,s位短整型16位

htons就是将16位短整型数据由主机字节序转换为网络字节序;

ntohs就是将16位短整型数据由网络字节序转换为主机字节序;

1.4 初步认识tcp与udp 

tcp

理解为打电话形式,数据是一定准确的传输到对方的

1.传输可靠(中性词,可靠但复杂)

2.有连接

3.面向字节流传输

udp 

理解为发电报模式,我们不清楚我们的数据是否成功送达

1.传输不可靠(中性词,不可靠但简单)

2.无连接

3.面向数据报传输

1.5 socket套接字接口

套接字我们可以理解为底层开放给我们的接口,我们可以通过这个接口将数据送入底层进行传输;下面是socket套接字的接口:

1.创建socket套接字(我们可以理解为打开底层的网卡文件)

2.绑定套接字与端口号(也就是将进程和网卡连接起来,让进程可以向网卡发送数据)

3.udp接收数据报接口 

4.udp发送数据报接口

2.实现upd客户端服务器通信

接下来我们通过实践来学习网络编程:

下面是我编写好的一份udp客户端与服务端通信的代码:

network_code/socket_2024_9_17 · future/Linux - 码云 - 开源中国 (gitee.com)

我们使用udp模拟的现象是两个进程可以通过网络进行通信;

下面是对代码关键地方的讲解:

2.1 socket与bind

首先,进程如果想通过网络进行交互,那么肯定需要先连接网络;那么如何连接呢,我们需要使用socket和bind函数;

2.1.1 socket

socket的头文件是<sys/types.h>与<sys/socket.h>

socket的第一个参数用来指定套接字是在哪个域中属于协议家族中的哪个协议,man手册中展示了有这些协议:

socket的第二个参数是指定套接字的数据类型 ,指定通信语义,其中sock_stream是面向字节流的数据类型,sock_dgram是面向数据报的数据类型

最后一个参数一般设置为0即可,可以自动绑定协议,我们也可以显示的设置

 socket函数的返回值是一个文件描述符,这个文件描述符指向的是我们的网卡文件(linux下一切皆文件),当返回值为-1时代表打开socket失败并会设置errno错误码;

2.1.2 bind

我们通过上一步创建了套接字后,我们接下来就要将进程绑定上套接字,使得可以让网络找到当前进程;

bind函数的返回值成功绑定返回0失败返回-1并设置errno错误码,第一个参数是通过socket函数打开的网卡文件的文件描述符,第二参数是一个结构体的指针,第三个参数是这个结构体的大小;这个结构体指针是一个结构类型,可以用来接收两种不同类型的结构体;

 2.1.3 sockaddr

这是bind函数的第二个参数的结构体类型,这个类型作为指针时可以接收两种不同结构体:

这样bind就可以通过一个参数接收不同类型的数据了;

我们在真正编程的时候是需要设置好sockaddr_in的各个成员变量的:

        struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = inet_addr(_ip.c_str());local.sin_port = htons(_port);

上面的代码中我们一个个的设置好了上面各个参数的值,这些值因为要输入网络中,所以都需要是网络字节序,我们要使用htons函数与inet_addr来操作修改为网络字节序;其中htons的头文件是<arpa/inet.h>,inet_addr的头文件是<netinet/in.h>和<arpa/inet.h>;

我们设置好变量后通过bind函数进行绑定即可;

2.2 recvfrom与sendto

我们打开socket并绑定bind好后,就可以向网络中发送数据了,由于我们现在使用的是udp协议的网络编程,我们使用recvfrom与sendto来进行数据接收与发送;

实现示例: 

                            
char buffer[1024];
struct sockaddr_in client;
socklen_t len = sizeof(client);int n = recvfrom(_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client, &len);
if (n == -1)
{log(WARNING, "recvfrom fail!");
}
buffer[n] = '\0';   

将从网络中接收到的数据放入buffer中;

 实现示例:

string info = "get message: ";
info += buffer;n = sendto(_fd, info.c_str(), info.size(), 0, (const struct sockaddr *)&client, len);
if (n == -1)
{log(WARNING, "sendto fail!");
}

 2.3 网络状态查看指令

netstat -naup(-nlup)

 2.4 IP地址绑定细节

进程在绑定自己的ip地址时一般是不需要自己进行绑定的,我们设置绑定ip地址为0.0.0.0即可

原因:

1.云服务器上的ip地址是虚拟地址,无法绑定虚拟ip

2.一台主机可以有多张网卡,当我们显示绑定其中一张的ip时其他ip的信息我们无法收到,所以我们直接设置为0可以接收所有发送到本机ip上的信息

所以我们可以这样绑定ip地址:

sin_addr.s_addr=htonl(INADDR_ANY);

INADDR_ANY这个宏在底层定义的也是0.0.0.0地址

2.5 端口号绑定细节 

对于用户端,绑定端口号时我们不需要显示绑定,当客户端进程,开始发送消息时,系统会帮进程自动绑定本地的某个端口号与本地IP地址;

原因:

1.用户使用的客户端是不同厂家写的,不同厂家在写程序时如果显式的绑定了端口号,无法预测会不会和用户机器上其他的进程冲突,所以会将绑定端口号的操作交给用户机器的操作系统;

2.端口号对于用户来说并不重要,只需要让端口号识别唯一进程即可;

2.6 用户如何知道服务端端口号与ip地址 

其实端口号和地址可以看作我们平时上网用的网站,网站的字符串可以被解析为IP地址与端口号,而浏览器就是通过这个ip地址与端口号找到相应服务器的;而服务器的厂商会通过宣传让人们知道它网站的域名;

2.8 云服务器防火墙

在我们使用云服务器时,我们是无法做到网络连接的,因为我们本主机上的端口号为了安全是被云服务器厂商设置了防火墙的,目的就是为了防止有人通过外部设备访问云服务器主机上的进程;而我们想要进行网络通信就必须得开放这个端口,我的腾讯云服务器是在控制台的防火墙处进行配置,要先配置好端口号才可以让其他进程通过网络连接云服务器上进程;如果你也遇到了明明编写好了程序可是就是无法进行网络交互,你试着把IP地址改为127.0.0.1这是本地环回地址,可以连接本地的网络,如果这样成功了就代表是你的防火墙拦住了你,你去设置一下即可;

2.9 实现的现象

3.udp服务器和客户端升级

3.1 服务器端接收数据处理的封装

上面我们实现了基础的udp客户端和服务器,接下来,我们可以将服务器的功能进行封装,使其成为回调函数,让服务器中的main函数,通过参数传递给服务器类,从而实现一个执行命令,和通信的功能:

network_code/socket_2024_9_17/2_udp_pro · future/Linux - 码云 - 开源中国 (gitee.com)

上面的代码相比与最前面的代码仅仅只是对处理客户端发送来的数据进行了封装,模拟处来了现实生活中,客户端对服务器发送请求,服务器接收请求后对请求进行处理,随后再返回给客户端的情况:

下面是实现的情况:

#include"server.hpp"string addStr(const string& buffer)
{string info = "get message: ";info += buffer;cout << info << endl;return info;
}bool checksafe(const string& comd)
{vector<string> v={"rm"};for(auto word:v){if(comd.find(word)!=string::npos){return true;}}return false;
}string command(const string& comd)
{cout<<comd<<endl;if(checksafe(comd)){return "command not safe";}FILE*f=popen(comd.c_str(),"r");if(f==nullptr){cout<<strerror(errno)<<endl;exit(-1);}string back_info;while(true){char buffer[4096]={0};char * ret=fgets(buffer,sizeof(buffer),f);if(ret==nullptr)break;back_info+=buffer;}return back_info;
}int main(int argc, char *argv[])
{if (argc != 2){log(ERROR,"argc should be 2");exit(-1);}uint16_t port=stoi(argv[1]);unique_ptr<udpserver> ptr(new udpserver(port));ptr->run(addStr);return 0;
}

通过将函数方法传递实现了对数据的不同方式的处理,上面的addStr和command两个函数就是我们封装好的方法可以通过将这个两个方法进行传递从而,从而改变,服务端对数据的处理,其实在未来的工作中,我们的代码一般都是合作交互的,所以我们设计一个这样的接口交给其他人时,就可以减少耦合度,其他人只需要编写他们想要的接口,不会和我们的代码产生修改的矛盾;

3.2 windows下udp客户端实现

其实这并没有难度,代码实现是与我们linux客户端一样的只不过,我们需要注意windows下的库是如何处理的,我们需要将windows下的网络库先初始化,之后方才可以进行访问,使用库的功能,下面是windows下udp客户端的实现:

#define _WINSOCK_DEPRECATED_NO_WARNINGS 1#include <WinSock2.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
#include<string>
#include <windows.h> // 用于字符编码转换
using namespace std;// 将 GBK 编码转换为 UTF-8
string GbkToUtf8(const string& gbkStr)
{int len = MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), (int)gbkStr.length(), NULL, 0);wchar_t* wstr = new wchar_t[len + 1];MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), (int)gbkStr.length(), wstr, len);wstr[len] = '\0';len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);char* str = new char[len + 1];WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);str[len] = '\0';string utf8Str(str);delete[] wstr;delete[] str;return utf8Str;
}// 将 UTF-8 编码转换为 GBK
string Utf8ToGbk(const string& utf8Str)
{int len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, NULL, 0);wchar_t* wstr = new wchar_t[len + 1];MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, wstr, len);wstr[len] = '\0';len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);char* str = new char[len + 1];WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);str[len] = '\0';string gbkStr(str);delete[] wstr;delete[] str;return gbkStr;
}int main()
{// 初始化套接字库WORD mVersion;WSADATA wsaData;int err;mVersion = MAKEWORD(1, 1);err = WSAStartup(mVersion, &wsaData);if (err != 0){return err;}// 创建 UDP 套接字SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = inet_addr("111.229.31.168"); // 服务器 IP 地址addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(10000); // 服务器端口SOCKADDR_IN addrCli;int len = sizeof(SOCKADDR);while (true){cout << "请输入:";string sendBuf;getline(cin, sendBuf);string sendMsg = GbkToUtf8(sendBuf);char recvBuf[100] = { 0 };// 发送数据到服务器sendto(sockCli, sendMsg.c_str(), sendMsg.size(), 0, (SOCKADDR*)&addrSrv, len);// 接收服务器发送的数据recvfrom(sockCli, recvBuf, sizeof(recvBuf)-1, 0, (SOCKADDR*)&addrCli, &len);string recvMsg = Utf8ToGbk(recvBuf);cout << recvMsg << endl;}// 关闭套接字并清理库closesocket(sockCli);WSACleanup();return 0;
}

未来防止windows上编译器编码格式和linux客户端的不同,我们对数据进行了处理,使得windows客户端上发送的信息发送到服务器上不会变成乱码,我们还加入了两个编码转换函数,当然这两个编码转换函数的实现不是我自己实现的,因为windows下的底层编码,我没有怎么学过;但我知道有编码转换的问题,我通过使用gpt找到编码转换的函数,载入我的代码中,成功的实现了windows和linux下进程的交互工作,下面是实现的现象:

从而,我们也可以理解,我们为什么在linux操作环境下编写代码部署到linux机器上,而大多数使用windows机器的人也都可以享受linux服务器的服务,这便是网络带来的便利;

3.3 服务器与客户端改造形成聊天室

接下来,我们将服务器再改造一下,使其可以将一个客户端发送的数据进行处理后发送给所有的客户端,使得可以形成一个群聊的模式;

network_code/socket_2024_9_17/3_udp_chatRoom · future/Linux - 码云 - 开源中国 (gitee.com)

上面是我代码的完整实现;

其中主要有这几个功能:

1.我们要识别每个不同的机器

2.我们要将接收的信息转发给每个连接了服务器的客户端

3.客户端接收信息和发送信息是并发的

解决方式:

1.将接收到的客户端套接字信息sockaddr中的port和ip获取出来,标识每个不同机器

2.通过存入不同机器的sockaddr和ip信息来分别发送给每个不同的机器

3.通过多线程的方式让客户端的接收和发送功能同时运行

 下面是实现的现象:

 本篇我们实现了udp客户端和服务器的功能下一篇我们将实现tcp服务器;

3.4 补充

补充俩个指令:

sz(文件名) 发送数据到本地主机

rz                     从本地主机上获取数据

 对网络字节序转换函数的提醒:

我们在转换ip地址时,有可能需要将网络字节序转回本机,我们可以使用:

inet_ntoa(可能存在线程问题)

inet_ntop(使用是安全的)

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

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

相关文章

用户态缓存:环形缓冲区(Ring Buffer)

目录 环形缓冲区&#xff08;Ring Buffer&#xff09;简介 为什么选择环形缓冲区&#xff1f; 代码解析 1. 头文件与类型定义 1.1 头文件保护符 1.2 包含必要的标准库 1.3 类型定义 2. 环形缓冲区结构体 2.1 结构体成员解释 3. 辅助宏与内联函数 3.1 min 宏 3.2 is…

OpenHarmony(鸿蒙南向)——平台驱动指南【MIPI CSI】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 CSI&#xff08;Camera Serial Interface&#xf…

vue项目npm run serve 报错,Error: read ECONNRESET at TCP.onStreamRead

背景&#xff1a;vue2的项目&#xff0c;之前npm run serve一直可以正常使用&#xff0c;突然每次启动都会报错了&#xff0c;报错信息如下&#xff1a; node:events:492 throw er; // Unhandled error event ^ Error: read ECONNRESET at TCP.onStreamRead (n…

【动态规划-多重背包】【hard】力扣2585. 获得分数的方法数

考试中有 n 种类型的题目。给你一个整数 target 和一个下标从 0 开始的二维整数数组 types &#xff0c;其中 types[i] [counti, marksi] 表示第 i 种类型的题目有 counti 道&#xff0c;每道题目对应 marksi 分。 返回你在考试中恰好得到 target 分的方法数。由于答案可能很…

计算机毕业设计Python+Flask微博情感分析 微博舆情预测 微博爬虫 微博大数据 舆情分析系统 大数据毕业设计 NLP文本分类 机器学习 深度学习 AI

首先安装需要的python库&#xff0c; 安装完之后利用navicat导入数据库文件bili100.sql到mysql中&#xff0c; 再在pycharm编译器中连接mysql数据库&#xff0c;并在设置文件中将密码修改成你的数据库密码。最后运行app.py&#xff0c;打开链接&#xff0c;即可运行。 B站爬虫数…

Java语言程序设计基础篇_编程练习题**18.31 (替换单词)

目录 题目&#xff1a;**18.31 (替换单词) 习题思路 代码示例 运行结果 替换前 替换后 题目&#xff1a;**18.31 (替换单词) 编写一个程序&#xff0c;递归地用一个新单词替换某个目录下的所有文件中出现的某个单词。从命令行如下传递参数&#xff1a; java Exercise18…

CSS中的字体样式、文本样式、列表样式以及背景和渐变

一、字体样式和文本样式 1.span标签 span标签的作用&#xff1a;能让某几个文字或者是词语凸显出来 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-…

【ComfyUI】生成图细节更清晰——Consistency_Decoder

原文&#xff1a;https://github.com/openai/consistencydecoder comfyui: https://github.com/gameltb/Comfyui_Consistency_Decoder_VAE 博文资料下载&#xff1a;https://pan.baidu.com/s/1SwfA4T6iMsA8IrRrGXm4sg?pwd0925 安装 【秋葉aaaki】comfyui一键运行包 夸克网盘…

Vue3 + TS 实现同一项目同一链接,pc端打开是web应用,手机打开是H5应用

前言&#xff1a; 我自己搭建的项目基本都是用 postcss-px-to-viewport 插件进行适配的&#xff1b; 最近在做一个项目&#xff0c;需求是同样的功能&#xff0c;用户可以在电脑上打开操作使用&#xff0c;也可以在手机上登录进去操作使用&#xff0c;但是跳转链接是同一个&am…

LVS-DR实战案例,实现四层负载均衡

环境准备&#xff1a;三台虚拟机&#xff08;NET模式或者桥接模式&#xff09; 192.168.88.200 &#xff08;web1&#xff09;(安装nginx服务器作为测试) 192.168.88.201 &#xff08;服务器&#xff09;&#xff08;用于部署lvs-dr&#xff09; 192.168.88.202 (web2)…

猫头虎分享:Python库 Jinja2 的简介、安装、用法详解入门教程

猫头虎分享&#xff1a;Python库 Jinja2 的简介、安装、用法详解入门教程 &#x1f42f; 摘要 今天有粉丝问猫哥&#xff1a;“如何使用Jinja2进行Python模板渲染&#xff1f;”这是一个非常常见的问题&#xff0c;特别是在开发Web应用时。Jinja2是一个强大的模板引擎&#x…

一篇带你搞定数据结构散列表

数据结构入门学习&#xff08;全是干货&#xff09;——散列表 1 散列表 1.1 引子&#xff1a;散列的基本思路 C语言变量名的管理&#xff1a; 定义/声明&#xff1a;先定义后使用。插入与查找&#xff1a; 插入&#xff1a;新变量定义。查找&#xff1a;检查变量是否已定义。…

Remotion:使用前端技术开发视频

前言 最近做文章突然想到很多文章其实也可以用视频的方式来展现&#xff0c;以现在短视频的火爆程度&#xff0c;肯定能让更多的人看到。 恰巧最近看了很多关于动画的前端 js 库&#xff0c;那如果将这些动画帧连续起来&#xff0c;岂不是就成了一个视频吗&#xff1f; 而且…

smartctl 命令:查看硬盘健康状态

一、命令简介 ​smartctl​ 命令用于获取硬盘的 SMART 信息。 介绍硬盘SMART 硬盘的 SMART (Self-Monitoring, Analysis, and Reporting Technology) 技术用于监控硬盘的健康状态&#xff0c;并能提供一些潜在故障的预警信息。通过查看 SMART 数据&#xff0c;用户可以了解硬…

Python第一篇:Python解释器

一&#xff1a;python解释器 python解释器是一款程序&#xff0c;用于解释、执行Python源代码。 一般python解释器都是c python使用c编写的&#xff0c;还有j python用java编写的。 二&#xff1a;python下载 三&#xff1a;使用示例 python进入控制台&#xff0c;python。 三…

Claude 的上下文检索功能提升了 RAG 准确率,这会是人工智能革命?

前言 在人工智能领域不断进步的过程中&#xff0c;人们对更准确且具备上下文理解能力的响应的追求&#xff0c;催生了诸多突破性创新。 而 Claude 的上下文检索技术就是其中一项进步&#xff0c;有望显著提升检索增强生成 (RAG) 系统的表现。 可能有同学就要问了&#xff1a;…

uniapp实现在表单中展示多个选项,并且用户可以选择其中的一个或多个选项

前言 uni-data-checkbox是uni-app的一个组件,用于在表单中展示多个选项,并且用户可以选择其中的一个或多个选项。该组件可以通过设置不同的参数来控制选项的样式、布局和行为。 提示:以下是本篇文章正文内容,下面案例可供参考 uni-data-checkbox组件具有以下特点:: 1、跨…

Html--笔记01:使用软件vscode,简介Html5--基础骨架以及标题、段落、图片标签的使用

一.使用VSC--全称&#xff1a;Visual Studio Code vscode用来写html文件&#xff0c;打开文件夹与创建文件夹&#xff1a;①选择文件夹 ②拖拽文件 生成浏览器的html文件的快捷方式&#xff1a; &#xff01;enter 运行代码到网页的方法&#xff1a; 普通方法&#xff1a…

Debian与Ubuntu:深入解读两大Linux发行版的历史与联系

Debian与Ubuntu&#xff1a;深入解读两大Linux发行版的历史与联系 引言 在开源操作系统的领域中&#xff0c;Debian和Ubuntu是两款备受瞩目的Linux发行版。它们不仅在技术上有着密切的联系&#xff0c;而且各自的发展历程和理念也对开源社区产生了深远的影响。本文将详细介绍…

从零开始学习Python

目录 从零开始学习Python 引言 环境搭建 安装Python解释器 选择IDE 基础语法 注释 变量和数据类型 变量命名规则 数据类型 运算符 算术运算符 比较运算符 逻辑运算符 输入和输出 控制流 条件语句 循环语句 for循环 while循环 循环控制语句 函数和模块 定…