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

news2024/11/15 15:38:28

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

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.coloradmin.cn/o/2163384.html

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

相关文章

Facebook对现代社交互动的影响

自2004年成立以来&#xff0c;Facebook已经成为全球最大的社交媒体平台之一&#xff0c;改变了人们的交流方式和社交互动模式。作为一个数字平台&#xff0c;Facebook不仅为用户提供了分享生活点滴的空间&#xff0c;也深刻影响了现代社交互动的各个方面。本文将探讨Facebook如…

Ollama在Windows安装,使用,简单调用API

一、安装ollama 1、安装 在Windows本地安装ollama&#xff0c;官方网页&#xff1a;https://ollama.com/download/windows 下载完安装包&#xff0c;安装就好&#xff0c;默认应该是C盘。应该是没办法改&#xff0c;如果有小伙伴能改&#xff0c;也请告知一下。 2、验证安装…

华为OD机试 - 数据单元的变量替换 - 分治、递归(Python/JS/C/C++ 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

基于yolov8+deepsort+gradio实现目标追踪演示

【效果展示】 【测试环境】 ultralytics8.2.95 gradio4.26.0 torch1.9.0cu111 理论上支持最新ultralytics版本 【实现部分代码】 with gr.Blocks() as demo:with gr.Tab("追踪"):# 使用Markdown显示文本信息&#xff0c;介绍界面的功能gr.Markdown(""…

linux固定串口别名

最近项目功能要求&#xff0c;需要将插入设备的串口设备占用的端口号固定住&#xff0c;这里记录一下设置过程方便以后查阅。 linux固定串口别名 配置过程相关补充 配置过程 列出当前插入USB端口的设备&#xff1a; lsusb查看当前设备的端口号&#xff1a; ls dev/查看当前设…

无人机飞手培训及巡检、吊运等飞行服务一体化技术详解

在无人机行业日益兴起的背景下&#xff0c;培养专业、合格的无人机飞手成为首要任务。飞手培训基础涵盖理论学习与安全意识建立两大方面。理论学习包括无人机基础知识&#xff08;如无人机类型、结构、原理&#xff09;、航空法规&#xff08;如民用无人驾驶航空器实名制登记管…

旧衣回收小程序搭建,开发功能优势

随着人们生活水平、消费水平的提高&#xff0c;在日常生活中产生了大量的限制物品&#xff0c;为了减少浪费&#xff0c;越来越多的人开始重视环保回收。旧衣物作为一种新型的回收方式&#xff0c;也逐渐得到了大众的关注&#xff0c;旧衣物回收市场发展规模也在持续上升&#…

Certbot自动申请并续期https证书

Certbot自动申请并续期https证书 一、 安装 Certbot&#xff1a;使用命令安装 Certbot&#xff1a; dnf install certbot python3-certbot-nginx获取 SSL 证书&#xff1a;运行 Certbot 命令来获取并安装 SSL 证书。 示例命令&#xff0c;替换其中的域名和路径信息&#xff1a…

PHP API 框架:构建高效API的利器【电商API接口】

在当今快速发展的互联网时代&#xff0c;API&#xff08;应用程序编程接口&#xff09;已成为连接不同应用程序和服务的关键。PHP&#xff0c;作为一种流行的服务器端脚本语言&#xff0c;提供了多种强大的框架来简化API的开发。本文将介绍PHP API框架的重要性&#xff0c;以及…

java并发编程笔记 之 线程和进程

文章目录 前言线程线程优先级和时间片创建多线程及运行线程的状态 进程查看进程的命令进程的通信方式 线程和进程的区别从关系上疑问集锦 前言 并发 1、并发是指在同一时间段内&#xff0c;计算机系统能够处理多个任务的能力。 2、在并发编程中&#xff0c;我们可以理解为多个…

chapter17-多线程基础——(自定义泛型)——day20

580-程序进程线程 581-并发并行 并发和并行也可以同时进行 582-继承Thread创建线程 583-多线程机制 主线程和子线程交替执行 单核&#xff1a;两个线程并发 多核&#xff1a;两个线程并行 主线程结束&#xff0c;不是说进程就结束&#xff0c;进程要等所有线程结束 584-为什…

模型Alignment之RLHF与DPO

1. RLHF (Reinforcement Learning from Human Feedback) RLHF 是一种通过人类反馈来强化学习的训练方法&#xff0c;它能够让语言模型更好地理解和执行人类指令。 RLHF 的三个阶段 RLHF 的训练过程一般分为三个阶段&#xff1a; 监督微调&#xff08;Supervised Fine-Tuning,…

认知杂谈82《跳出信息茧房,持续精进》

内容摘要&#xff1a; 互联网时代&#xff0c;信息丰富&#xff0c;但便捷性削弱了我们的好奇心。互联网是双刃剑&#xff0c;快速获取知识的同时&#xff0c;也让我们陷入“信息茧房”&#xff0c;限制视野。 好奇心减少&#xff0c;部分原因是互联网的“懒惰效应”&#xff0…

国家标准和团体标准有什么区别?

国家标准和团体标准的区别主要体现在以下几个方面&#xff1a; 1. 制定标准的主体不同&#xff1a;国家标准是由国家机构通过并公开发布的标准&#xff1b;团体标准是由学会、协会、商会、联合会、产业技术联盟等社会团体协调相关市场主体共同制…

【项目实战】如何在项目中基于 Spring Boot Starter 开发简单的 SDK

什么是SDK 通常在分布式项目中&#xff0c;类和方法是不能跨模块使用的。为了方便开发者的调用&#xff0c;我们需要开发一个简单易用的SDK&#xff0c;使开发者只需关注调用哪些接口、传递哪些参数&#xff0c;就像调用自己编写的代码一样简单。实际上&#xff0c;RPC(远程过…

element下拉框联动 或 多选 回显数据后页面操作不生效问题解决

第一种:多选回显不生效 解决方式: 代码: <el-form-item label"系统" prop"Key"> <el-select v-model"addForm.Key" multiple placeholder"请选择" change"$forceUpdate()"> <el-option v-for"item …

Typescript高级用法

TypeScript 是一种类型安全的 JavaScript 超集&#xff0c;除了基本类型和对象类型之外&#xff0c;TypeScript 还提供了一些高级类型系统&#xff0c;使得我们可以更好地处理复杂的数据结构和业务逻辑。本文将深入探讨 TypeScript 的高级类型系统&#xff0c;以更好地理解和使…

【AI大模型应用开发】【综合实战】AI+搜索,手把手带你实现属于你的AI搜索引擎(附完整代码)

现在市面上有很多的AI搜索的应用或插件&#xff0c;一直想学习其背后的实现原理。今天咱们就学习一下&#xff0c;并且亲自动手实践&#xff0c;从0开始&#xff0c;搭建一个自己的AI搜索引擎。最终实现效果如下&#xff1a; 话不多说&#xff0c;开干。 本文代码参考&#xff…

累加求和-C语言

1.问题&#xff1a; 计算123……100的和&#xff0c;要求分别用while、do while、for循环实现。 2.解答&#xff1a; 累加问题&#xff0c;先后将100个数相加。要重复进行100次加法运算&#xff0c;可以用循环结构来实现。重复执行循环体100次&#xff0c;每次加一个数。 3.代…

02DSP学习-了解syscfg

不是哥们儿&#xff0c;学习DSP为什么不是上来就写代码啊&#xff0c;说了一堆&#xff0c;写小说呢啊&#xff1f; 你别着急&#xff0c;学习DSP本身&#xff0c;真不需要写多少代码&#xff0c;我们需要的写的是自己的算法。开车知道方向盘、油门、刹车、后视镜之后也能开&a…