深泽网站制作,做英文网站賺钱,最好看的直播免费的,建筑网页设计前面一篇我们讲了网络的基础#xff0c;网络协议栈是什么样的#xff0c;数据如何流动传输的#xff1b;接下来这篇#xff0c;我们将进行实践操作#xff0c;真正的让数据跨网络进行传输#xff1b;
1.网络编程储备知识
1.1 初步认识网络编程
首先我们需要知道我们的…前面一篇我们讲了网络的基础网络协议栈是什么样的数据如何流动传输的接下来这篇我们将进行实践操作真正的让数据跨网络进行传输
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为networkl为长整型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_addrhtonl(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)
上面的代码相比与最前面的代码仅仅只是对处理客户端发送来的数据进行了封装模拟处来了现实生活中客户端对服务器发送请求服务器接收请求后对请求进行处理随后再返回给客户端的情况
下面是实现的情况 #includeserver.hppstring addStr(const string buffer)
{string info get message: ;info buffer;cout info endl;return info;
}bool checksafe(const string comd)
{vectorstring v{rm};for(auto word:v){if(comd.find(word)!string::npos){return true;}}return false;
}string command(const string comd)
{coutcomdendl;if(checksafe(comd)){return command not safe;}FILE*fpopen(comd.c_str(),r);if(fnullptr){coutstrerror(errno)endl;exit(-1);}string back_info;while(true){char buffer[4096]{0};char * retfgets(buffer,sizeof(buffer),f);if(retnullptr)break;back_infobuffer;}return back_info;
}int main(int argc, char *argv[])
{if (argc ! 2){log(ERROR,argc should be 2);exit(-1);}uint16_t portstoi(argv[1]);unique_ptrudpserver 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)
#includestring
#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使用是安全的