手表怎么在网站做推广,莱芜大众网,做网站怎么选取关键词,亿级别网站开发注意文章目录 1. 网络通信的理解2.进程PID可以取代端口号吗#xff1f;3. 认识TCP协议4. 认识 UDP协议5. socket编程接口udp_server.hpp的代码解析socket——创建 socket 文件描述符Initserver——初始化1.创建套接字接口#xff0c;打开网络文件bind——绑定的使用 2.给服务器指… 文章目录 1. 网络通信的理解2.进程PID可以取代端口号吗3. 认识TCP协议4. 认识 UDP协议5. socket编程接口udp_server.hpp的代码解析socket——创建 socket 文件描述符Initserver——初始化1.创建套接字接口打开网络文件bind——绑定的使用 2.给服务器指明IP地址和端口号struct sockaddr_in的理解bzero 清空代码实现inet_addr ——字符串风格转化为4字节风格服务器自己指定IP地址 3. 云服务器或者一款服务器一般不要指明某一个确定的IP start ——启动1. 收到客户端发来的消息recvfrom——获取用户数据报inet_addr ——将4字节风格转为字符串风格 2.将消息发给别人sendto udp_client.cc的代码解析客户端如何绑定服务器为什么要自己绑定代码实现 完整代码err.hpp (枚举错误码)makefileudp_client.cc(客户端的实现无封装)udp_clinet.hppudp_server.cc (有封装)udp_server.hpp(服务器的实现) 1. 网络通信的理解 主机A将自己的数据交给主机B就需要给主机B发送消息主机B未来要给主机A回消息
但实际上 主机A将自己的数据交给主机B 并不是最终目的
如你在淘宝上买了一件衣服卖家发货后从广东省发货 到 你所在的地区 最终包裹成功到达你的手上你还需要决定这个快递该怎么用
数据的传送不是目的让两台主机通过数据进行通信来协同完成任务才是目的 如唐僧说要去西天去取经唐僧所对应的寺庙是A主机西天的大雷音寺是B主机唐僧并不是到大类饮食就完了这只是他的手段 他还需要面见如来如来会提供给他经书的服务 数据发起时从主机A的传输层开始交给主机B的传输层 而数据是从主机A的应用层中的某种客户端传来的 而将数据交给主机B的传输层不是直接目的要把数据再交给应用层 中的某种服务器
主机A对应的客户端一定要启动起来所以其本质是 进程
因为主机B的某种服务器在以进程的方式运行所以可以随时随地能够访问某种服务
网络通信的本质是 进程间的通信 通信的第一个阶段先将数据通过操作系统将数据发送到目标主机(手段) 通信的第二个阶段在本主机将收到的数据推送给自己上层的指定进程
第一个阶段 可以通过TCP/IP协议完成因为IP可以表示互联网上唯一的一台主机
当主机B的传输层把数据交给应用层应用层对应的进程非常多 所以为了标识自己主机上网络进程的唯一性提出了 端口号 的概念
端口号是传输层协议的字段是一个2个字节16位的整数用来标识系统层面上进程的唯一性
所以 IP地址 端口号 可以表示 互联网中唯一的一个进程
通信时是有两个进程进行通信所以就有源IP 和源 端口号 以及 目标IP 和目标 端口号 源IP 和源 端口号表示 互联网中唯一的一个进程 目标IP 和目标 端口号也表示 互联网中唯一的一个进程
所以 网络通信的本质 是通过IPPORT号 构建唯一性来进行网络进程间通信 简称 套接字通信
2.进程PID可以取代端口号吗
进程PID在系统层面上每个进程也是唯一的也能表示该系统上进程的唯一性所以用进程PID可以代替端口号的 但会存在一些问题 1.不是所有的进程都要进行网络通信只有部分进程可能会网络通信若用进程PID来作为网络标识该进程就很难区分清楚那些是进行网络通信的那些不是进行网络通信的
2. PID是操作系统进程管理的概念网络模块也要包含进程管理的部分要不然无法认识PID 就增加了系统当中进程管理和网络管理的耦合度
3. 认识TCP协议
TCP协议(Transmission Control Protocol) 传输控制协议 特点 传输层协议 面向连接 在通信过程中会自带可靠性 面向字节流 在进行发和收数据时在TCP层没有报文的概念收到一堆的数据把这一堆的东西一次将给上层的应用层也可一个字节一个字节交 字节数据如何解释TCP不关心只关心要都多少给你多少最终解释信息由应用层自己解释这种从称之为字节流
4. 认识 UDP协议
UDP协议(User Datagram Protocol)用户数据报协议 特点 传输层协议 无连接 不可靠传输 面向数据报 如收快递收一个就是一个完整的快递具体的快递不可能收半个或者一个半若对方发了三次你就必须收三次
5. socket编程接口
实验室做出来一套进程间通信的标准既可在本地通信又可以在网络跨主机通信的标准 即 socket标准 隶属于 posix标准
最常见的为 基于网络通信的套接字 sockaddr_in 预间套接字 (使用在两个进程间使用本地进程通信的) sockaddr_un
套接字的设计者为了能够让所有人以 一套接口的方式 既能本地通信 又能网络通信 所以设计出一个公共的数据结构 叫做 struct sockaddr 若想进行网络通信 (struct sockaddr_in) 或者 进行 本地通信 (struct sockaddr_un) 使用 sockaddr 进行强制转换即可 在结构最开始时都要有16位的地址类型 AF_INET 与AF_UNIX 实际上都是宏用整数来表示的 将地址进行比较判断 若等于 AF_INET就为网络通信把 sockaddr强转为 sockaddr_in 若等于 AF_UNIX就为本地通信把 sockaddr强转为 sockaddr_un udp_server.hpp的代码解析 通过网络协议栈的通信功能 来把数据交付给对方的应用层来完成双方进程的通信
将客户端的数据交给 服务端 就需要给服务端发送消息服务端再给客户端回消息 在 udp_server.hpp 中 使用namspace 将命名空间 命名为 ns_server 其中再定义一个类 udpserver
socket——创建 socket 文件描述符
输入 man socket创建套接字 第一个参数 domain 用于区分 进行网络通信还是 本地通信 若想为网络通信则使用 AF_INET 若想为本地通信则使用 AF_UNIX
第二个参数 type 套接字对应的服务类型 SOCK_STREAM 流式套接 SOCK_DGRAM 无连接不可靠的通信(用户数据报)
第三个参数 protocol 表示想用那种协议协议默认为0 若为 流式套接则系统会认为是TCP协议 若为用户数据报则系统会认为是UDP协议
套接字的返回值若成功则返回文件描述符若失败则返回 -1
Initserver——初始化
1.创建套接字接口打开网络文件 使用socket套接字创建出 网络通信、UDP协议 若套接字返回-1表示失败则初始化也就失败程序就没有必要在继续运行了所以使用exit终止程序
若套接字创建成功则返回文件描述符 文件描述符的前三个分别被 标准输入 标准输出 标准错误占用所以此时的文件描述符应该打印出3 bind——绑定的使用
输入 man 2 bind 查看绑定 给一个套接字绑定一个名字 第一个参数 sockfd 为 文件描述符 第二个参数 addr 为 通用结构体类型 第三个参数 addrlen 为 第二个参数的实际长度大小
bind返回值若成功则返回0若失败返回 -1
2.给服务器指明IP地址和端口号 想要使用struct sockaddr_in类型 需添加头文件
定义一个 struct sockaddr_in(网络通信) 类型的 变量 local
struct sockaddr_in的理解 将 struct sockaddr_in 转到定义 16位地址类型将 sa_prefix替换成 sin_ sin## family 实际上为 sin_family 此时的 sin_port 对应 当前绑定的端口号 sin_addr对应的是IP地址 再次将 in_addr转到定义IP地址就是一个32位的整数
bzero 清空
sin_zero 作为 该结构体的填充字段 结构体可能很大用不完则使用填充字段将其填充上即可
输入 man bzero 将有n个字节的缓冲区全部写为0
代码实现 将local对应的family(16位地址类型) 设置为 网络通信 设置一个私有的端口号port_ 在类外设置一个端口号用于构造时若没有端口号传入则8082充当缺省值 若我给你发消息未来也需要将消息发回来所以就必须知道我的IP地址和端口号 即端口号 以报文的形式发送到网络中
类内定义的port_被称为本地主机序列 需要把这个port_从主机序列 转成网络序列
输入 man htons 表示短整数的主机转网络序列 定义一个私有的变量 ip_ 由于我们设置的IP地址是字符串风格的而系统中的IP地址是4字节风格的 所以就需要将字符串风格的转化为 4字节风格的
inet_addr ——字符串风格转化为4字节风格
输入 man inet_addr 作用为将字符串风格的IP地址 转化为 4字节风格的IP地址并 默认会把主机序列 转换为 网络序列 由于local实际上定义在用户层的栈上并没有在内核 所以借助bind将填充好的套接字字段和文件字段进行绑定关联这样的文件才是网络文件 由于local 是 struct sock_addr_in 类型 需要强转为 struct sockaddr 公共类型 服务器自己指定IP地址 此时运行 udp_server可执行程序会发现套接字创建成功但绑定会失败 云服务器 不需要bind IP地址需要让服务器自己指定IP地址 所以在main函数中添加命令行参数 命令行参数 main函数的两个参数char* argv[] 为指针数组 argv为一张表包含一个个指针指针指向字符串 int argcargc为数组的元素个数 设计一个usage函数用以表示出 出现问题的可执行程序的名字 proc 再次创建一个err.hpp使用enum枚举将USAGE_ERR设置成1 默认将SOCKET_ERR(套接字报错)设置为2 将 BIND_ERR(绑定错误)设置为3 通过argv数组的第二个下标指明字符串风格的端口号再通过atoi将字符串转化为整数 最终只传入 端口号即可 3. 云服务器或者一款服务器一般不要指明某一个确定的IP 使用 INADDDR_ANY 让udpserver在启动的时候bind本主机上的任意IP 将 INADDDR_ANY 转到定义实际上为缺省的0值 start ——启动
服务器本质是一个死循环永远不退出 如半夜打开王者荣耀依旧可以玩 1. 收到客户端发来的消息
recvfrom——获取用户数据报
输入 man recvfrom 获取用户数据报 第一个参数 sockfd 为 套接字 第二个参数 buf 为 自己定义的缓冲区 第三个参数 len 为 缓冲区的长度 第四个参数 flags 为读取方式默认设为0以阻塞方式读取 剩余两个参数 src_addr 和 addrlen 为 输入 输出型 参数 使用recvfrom收到数据最终还要把数据还回去想要还回去就必须知道别人是谁 src_addr 为 作为一个结构体内部记录客户端的IP地址和端口号 addrlen 为 输出时结构体的大小 返回值若大于0则读取成功 定义一个 struct sockaddr_in(网络通信) 类型的 变量 peer 使用 len 来表示 未来的结构体大小 若n大于0则读取成功将最后一个位置的下一个位置设为\0 若读取失败则继续读取 peer下的IP地址为 4字节整数需要将其转为字符串风格
inet_addr ——将4字节风格转为字符串风格
输入 man inet_addr将4字节IP转为字符串风格的IP peer下的端口号为网络序列想要获取客户端的端口号 clientport需要使用 ntohs 将网络序列转为主机序列 2.将消息发给别人
sendto
输入 man sendto 第一个参数 sockfd 为 套接字 第二个参数 buf 为 自己定义的缓冲区 第三个参数 len 为 缓冲区的长度 第四个参数 flags 为读取方式默认设为0以阻塞方式读取 剩余两个参数 src_addr 和 addrlen 为 输入 输出型 参数 使用recvfrom收到数据最终还要把数据还回去想要还回去就必须知道别人是谁 src_addr 为 将以前收到的消息转会给客户端 addrlen 为 输出时结构体的大小 返回值若大于0则读取成功 udp_client.cc的代码解析 第一个参数 使用 AF_INET表示网络通信 第二个参数 使用SOCK_DRAM表示数据报 第三个参数 默认设为0由于上述为数据报所以为UDP协议 客户端如何绑定
客户端是需要绑定的 socket通信的本质 是 客户端的IP与端口号 与 服务器的IP与端口号 进行网络版本的进程间通信 但客户端是不需要自己绑定的由操作系统自动进行绑定 如电脑和手机充满大量客户端这些客户端来自于不同的企业每个客户端的端口号不可以是固定的 必须让操作系统随机去选择本质是为了防止确定的客户端被别人去占用减少客户端层面的冲突 所以客户端的端口号要让操作系统随机分配防止客户端出现启动冲突
服务器为什么要自己绑定
1.服务器的端口 是 众所周知并不能随意改变的 如110是报警电话不可能报警电话每天都变否则会导致当真正想打电话时都不知道打那个
2.服务器都是一家公司的所以端口号需要统一规范化 如淘宝不会把自己的服务部署到知乎上 代码实现
进行while循环向服务器发送消息 目前没有消息所以让用户输入充当消息源 使用 sendto将消息发送给服务端
作为客户端将消息发送给 服务器主机 想要运行 客户端 就需要服务器的IP 和端口号 借助命令行参数通过用户的输入的第二个参数 作为服务器的IP 用户输入的第三个作为 服务器的端口号
虽然此时服务器的IP和端口号知道了但是想要借助sendto后两个参数是需要套接字结构体 新建一个结构体server内部包含服务器的IP和端口号 使用 htons 将主机序列转为网络序列 使用inet_addr将字符串转化为 4字节 此时 sendto的后两个参数 添加 创建的结构体 sever 来完成发送服务器的任务 由于server 的类型 是 struct sockaddr_in 而参数的类型为 公共结构体类型 struct sockaddr 所以需要强转 使用 revfrom 获取用户数据报 收到来自服务器转回来的消息 所以 定义一个 temp结构体用于接收
在首次系统调用发送数据的时候操作系统在底层随机选择客户端的端口号 加上自己的IP 先构建bind再构建发送的数据报文
完整代码
err.hpp (枚举错误码)
#pragma onceenum
{USAGE_ERR1,SOCKET_ERR,BIND_ERR
};makefile
.PHONY:all
all: udp_client udp_serverudp_client:udp_client.ccg -o $ $^ -stdc11
udp_server:udp_server.ccg -o $ $^ -stdc11.PHONY:clean
clean:rm -f udp_clinet udp_server
udp_client.cc(客户端的实现无封装)
#includeudp_client.hpp
#includeerr.hpp
#includecstring
#includesys/types.h
#includesys/socket.h
#includenetinet/in.h
#includearpa/inet.hstatic void usage(std::string proc)
{std::coutusage:\n\tprocserverip serverport\n std::endl;
}
// ./udp_client serverip sevrerport
int main(int argc ,char* argv[])//命令行参数 传入的是 客户端的运行 服务器的IP和端口号
{if(argc!3){std::cout std::endl;exit( USAGE_ERR);//终止程序}std::string serverip argv[1];//服务器的IPuint16_t serverport atoi(argv[2]);//服务器的端口号int socksocket(AF_INET,SOCK_DGRAM,0);if(sock0)//创建套接字失败{std::coutcreate socket errorstd::endl;exit( SOCKET_ERR);}//明确server是谁struct sockaddr_in server;//设置网络通信的结构体memset(server,0,sizeof(server)); //将结构体清空server.sin_familyAF_INET;server.sin_porthtons(serverport);//端口号server.sin_addr.s_addrinet_addr(serverip.c_str());//IP地址while(true){//用户输入 std::string message;std::cout please enter# ;std::cin message;//发送消息sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)server,sizeof(server));//接收消息char buffer[1024];struct sockaddr_in temp;socklen_t lensizeof(temp);int nrecvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);if(n0){buffer[n]0;//收到回显消息std::coutserver echobufferstd::endl;}}return 0;
}udp_clinet.hpp
#pragma once
#includeiostream
using namespace std;udp_server.cc (有封装)
#includeudp_server.hpp
#includeerr.hpp
#includememory
#includestring
using namespace ns_server;
using namespace std;static void usage(string proc)
{std::coutusage:\n\tprocprot\n std::endl;
}//udp_server port
int main(int argc,char*argv[])//命令行参数
{if(argc!2)//若命令行参数个数不为2,则当前会报错{usage(argv[0]);exit(USAGE_ERR);//终止程序}//端口号uint16_t portatoi(argv[1]);//atoi可将字符串转化为整数//只需传入由用户指明的端口号unique_ptrUdpServer usvr(new UdpServer (port));usvr-Initserver();//服务器的初始化usvr-Start();//启动服务器return 0;
}
udp_server.hpp(服务器的实现)
#pragma once
#includeiostream
#includecerrno
#includecstring
#includecstdlib
#includestrings.h
#includefunctional
#includenetinet/in.h
#includearpa/inet.h
#includesys/types.h
#includesys/socket.h
#includeerr.hppnamespace ns_server
{const static uint16_t default_port8082;//设置端口号为8082 class UdpServer{public:UdpServer(uint16_t portdefault_port)//构造:port_(port){}void Initserver()//初始化{//1.创建套接字接口打开网络文件sock_socket(AF_INET,SOCK_DGRAM,0);if(sock_0)//创建失败{//打印错误信息std::cout create socket error: strerror(errno)std::endl;exit(SOCKET_ERR);//终止程序}std::coutcreate socket success:sock_std::endl;//3//2.给服务器指明IP地址和端口号struct sockaddr_in local;bzero(local,sizeof(local));//全部置为0local.sin_familyAF_INET;//将16位地址类型 置为 网络通信local.sin_port htons(port_); //主机转网络的端口号//1.需要将字符串风格转化为 4字节//2.需要 将主机序列转换为 网络序列local.sin_addr.s_addr INADDR_ANY ; //bind本机上的任意IP//bind 绑定int nbind(sock_,(struct sockaddr*)local,sizeof(local));if(n0)//绑定失败{std::cout bind socket error: strerror(errno)std::endl;exit(BIND_ERR);}std::coutbind socket success:sock_std::endl;//3}void Start()//启动{char buffer[1024];//用于保护用户数据//设置一个死循环while(true){//1.收到客户端发来的消息struct sockaddr_in peer;socklen_t lensizeof(peer);//传入的缓冲区大小int nrecvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)peer,len);if(n0){buffer[n]\0;}else {//读取失败则继续读取continue;}//提取客户端信息//4字节IP转为 字符串IPstd::string clientip inet_ntoa(peer.sin_addr);//客户端IP//将网络序列转换为主机序列uint16_t clientport ntohs(peer.sin_port);//客户端 端口号std::coutclientip-clientport-get message# bufferstd::endl;//2.将消息发给别人sendto(sock_,buffer,strlen(buffer),0,(struct sockaddr*)peer,sizeof(peer));}}~UdpServer()//析构{}private:int sock_; //文件描述符uint16_t port_;//端口号 };
}