移动端网站排名,网上下载的网站模板怎么用,上海新闻最新消息今天,单页建站系统源IP地址和目的IP地址
唐僧例子1 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进… 源IP地址和目的IP地址
唐僧例子1 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析. 认识端口号 理解 端口号 和 进程ID
我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?
10086例子
另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;
理解源端口号和目的端口号
唐僧例子2
送快递例子
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 数据是谁发的, 要 发给谁; IPPort
ip地址4字节 端口号两个字节
跨主机 看到的公共部分 是网络 在公网上 IP地址能表示唯一的一台主机端口号port用来标识该主机上的唯一的一个进程 IPPort标识全网唯一的一个进程 认识TCP协议 认识UDP协议 网络字节序
ip地址4字节 端口号两个字节
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢? 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可; 为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。 网络字节序和主机字节序的转换
主机转网络
服务端 客户端 网络转主机后面ntoa就不建议使用了
在 C 或 C 中网络字节序和主机字节序的转换非常重要特别是在进行网络编程时因为不同的平台可能有不同的字节序Endianness。网络字节序通常是大端字节序Big Endian而主机字节序可能是大端或小端Little Endian。
网络字节序和主机字节序的转换函数
**htons()**将主机字节序Host转换为网络字节序Network适用于 16 位数据short。**htonl()**将主机字节序Host转换为网络字节序Network适用于 32 位数据long。**ntohs()**将网络字节序Network转换为主机字节序Host适用于 16 位数据short。**ntohl()**将网络字节序Network转换为主机字节序Host适用于 32 位数据long。
示例代码
#include stdio.h
#include arpa/inet.h // 包含字节序转换函数适用于 Linux 和 UNIXint main() {uint16_t host_short 0x1234; // 主机字节序的 16 位数据uint32_t host_long 0x12345678; // 主机字节序的 32 位数据// 主机字节序转网络字节序uint16_t network_short htons(host_short);uint32_t network_long htonl(host_long);// 网络字节序转主机字节序uint16_t converted_short ntohs(network_short);uint32_t converted_long ntohl(network_long);// 输出结果printf(Host short: 0x%04X, Network short: 0x%04X, Converted back: 0x%04X\n,host_short, network_short, converted_short);printf(Host long: 0x%08X, Network long: 0x%08X, Converted back: 0x%08X\n,host_long, network_long, converted_long);return 0;
}解释
**htons()**将主机字节序的 16 位数据host_short转换为网络字节序。**htonl()**将主机字节序的 32 位数据host_long转换为网络字节序。**ntohs()**将网络字节序的 16 位数据network_short转换为主机字节序。**ntohl()**将网络字节序的 32 位数据network_long转换为主机字节序。
输出示例
Host short: 0x1234, Network short: 0x3412, Converted back: 0x1234
Host long: 0x12345678, Network long: 0x78563412, Converted back: 0x12345678适用平台
这些字节序转换函数通常用于 Linux 和 UNIX 系统中通过包含 arpa/inet.h 头文件来访问。对于 Windows 系统提供了类似的转换函数但位于 winsock2.h 中使用方法相同。
总结
htons() 和 **htonl()**用于将主机字节序转换为网络字节序适用于 16 位和 32 位数据。ntohs() 和 **ntohl()**用于将网络字节序转换为主机字节序。
这些转换函数在进行网络编程时非常重要可以确保不同平台和字节序的主机之间进行正确的数据传输和处理。
网络转主机序列主机序列转网络序列 发送到网络里的要转网络序列从网络里拿的要转主机序列 地址转换函数字符串传4字节IP
本节只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址
但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;
字符串转in_addr的函数: in_addr转字符串的函数: 其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。 代码示例: 关于inet_ntoa
inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是 否需要调用者手动释放呢 man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.
那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:
运行结果如下: 因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果. 思考: 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?在APUE中, 明确提出inet_ntoa不是线程安全的函数;但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题; 套接字编程的种类
1.域间套接字编程2.原始套接字编程3.网络套接字编程 udp代码示例
udp服务器 socket(创建一个套接字)
socket创建套接字
第一个参数是一个域
socket 常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)
int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第一个参数创建一个套接字的域什么叫做域呢 可以理解为我们所要的那个套接字他是属于什么AF_INET将来是使用IPv4还是IPv6网络通信的还是有叫本地通信也叫作域间通信第二个参数是socket对应的类型udp是面向用户数据报SOCK_DGRAMtcp是面向字节流的第三个参数不用填协议类型创建一个套接字的本质就是打开一个文件必须要告诉服务器端口号然后再绑定套接字端口号 不同类型第一个参数的 第二个定义的套接字的类型 第三个写0就可以 #pragma once
#includeiostream
#include sys/types.h
#include sys/socket.h
using namespace std;
class UdpSever
{
public:UdpSever(){}void Init(){sockfd socket(AF_INET, SOCK_DGRAM, 0);}void Run(){}~UdpSever(){}
private:int sockfd;//网络文件描述符
};
把日志功能拷贝进来 创建一个Log对象 std::unique_ptr C11中开始提供更靠谱的unique_ptr
文档https://cplusplus.com/reference/memory/unique_ptr/
unique_ptr的实现原理简单粗暴的防拷贝下面简化模拟实现了一份UniquePtr来了解它的原 理
/ C11库才更新智能指针实现
// C11出来之前boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr // C11将boost库中智能指针精华部分吸收了过来
// C11-unique_ptr/shared_ptr/weak_ptr // unique_ptr/scoped_ptr // 原理简单粗暴 -- 防拷贝
测试
绑定bind
绑定端口号
sockaddr结构
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16 位端口号和32位IP地址. IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容. socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好 处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数; sockaddr 结构
sockaddr_in 结构 虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主 要有三部分信息: 地址类型, 端口号, IP地址.
in_addr结构 in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
bzero把上面的结构体清空
INET addr,ip地址(32位的)port端口号family所用的域或者家族AF_INET int,uint16_t,uint32_t的区别及其各自的用法
int、uint16_t 和 uint32_t 都是表示整数类型的数据类型它们在表示范围和用途上有所不同。下面是它们的区别和常见的用法
1. int 定义int 是C/C中的标准整数类型通常用于表示整数。它的大小和范围依赖于平台操作系统和硬件。 大小和范围 在大多数现代平台上int 通常是 32 位的4 字节但是也有一些平台会使用 16 位2 字节或 64 位8 字节来表示。 在 32 位系统上int 的范围通常是-2,147,483,648 到 2,147,483,647。 在 64 位系统上int 的范围一般相同但可能会有所不同具体取决于编译器和操作系统。 用途适用于一般整数的存储特别是当你不需要指定特定位数的整数时。通常是默认的整数类型。
2. uint16_t 定义uint16_t 是一个固定宽度的无符号整数类型表示一个 16 位2 字节的无符号整数。它是通过 C99 标准中的 stdint.h 头文件定义的。 大小和范围 它的大小是固定的 16 位2 字节。 范围是 0 到 65535即 2^16 - 1。 用途用于需要 16 位无符号整数的场景适用于存储比较小的正整数值节省内存空间常用于协议数据、传感器数据、颜色值等场景。
3. uint32_t 定义uint32_t 是一个固定宽度的无符号整数类型表示一个 32 位4 字节的无符号整数。它同样是通过 stdint.h 头文件定义的。 大小和范围 它的大小是固定的 32 位4 字节。 范围是 0 到 4,294,967,295即 2^32 - 1。 用途用于需要 32 位无符号整数的场景常用于表示较大范围的正整数如文件大小、内存地址、网络协议等。
总结对比 数据类型 大小 范围 用途 int 32 位常见 -2,147,483,648 到 2,147,483,647 通用整数类型平台相关 uint16_t 16 位 0 到 65535 用于小范围无符号整数如协议、传感器数据 uint32_t 32 位 0 到 4,294,967,295 用于大范围无符号整数如文件大小、网络地址
选择依据 如果你知道需要存储的数值范围并且希望节省内存可以选择 uint16_t 或 uint32_t。 如果不关心存储大小且需要处理负数int 是默认选择。 需要无符号整数时uint16_t 和 uint32_t 是更为合适的选择。
在网络中为什么用uint16_t不用int
在网络通信中通常使用 uint16_t 而不是 int 主要有以下几个原因
1. 一致性和标准化 uint16_t 是一个 无符号 16 位整数大小和范围是固定的明确且可移植。无论在哪个平台或操作系统上uint16_t 都始终表示一个 16 位的正整数范围为 0 到 65535没有任何平台差异。 相反int 的大小和范围依赖于编译器、操作系统以及硬件架构通常是 32 位或 64 位这使得其在不同平台间可能有不同的表现。例如32 位平台上的 int 范围是 -2,147,483,648 到 2,147,483,647而 64 位平台可能会有所不同。
在网络协议中数据交换需要确保不同系统间能正确地理解数据。如果每个平台的 int 范围不同可能会导致解析数据时出错。因此使用 uint16_t 等固定宽度的数据类型可以避免这种问题。
2. 明确的数值表示 网络通信中传输的很多数据例如端口号、长度、标识符等都是 非负整数并且其数值范围通常是有限的。uint16_t 正好满足这些需求它能够表示 0 到 65535 范围的正整数这对于大多数网络协议中的字段来说已经足够。 如果使用 int会引入不必要的负数范围。例如某些协议字段如长度或计数信息是不需要负值的因此使用 uint16_t 可以更好地表达数据的本意。
3. 节省带宽和存储空间 网络传输中的数据结构通常是非常紧凑的节省带宽和存储空间是设计网络协议时的重要考虑因素。uint16_t 是一个固定的 16 位类型比 int通常是 32 位节省了一半的空间。 如果某个字段本来只需要表示 0 到 65535 的值使用 int 会浪费空间特别是在带宽受限的网络环境中数据包大小需要尽量精简而 uint16_t 刚好满足需求。
4. 与协议格式一致 网络协议如 IP、TCP、UDP 等中通常定义了固定宽度的字段例如端口号、数据长度等。协议规范中往往会指定使用 16 位uint16_t或 32 位uint32_t等类型来表示数据。 例如TCP/IP 协议中端口号使用的是 16 位的无符号整数uint16_t而 数据包长度等字段也通常是无符号整数。 使用 uint16_t 可以确保与协议规范一致使得协议解析更加标准化避免不同平台或编译器之间的数据解释差异。
总结 uint16_t 在网络协议中比 int 更加适用因为它的大小和范围是固定的明确表示非负整数且可以确保不同平台之间的一致性。 int 的范围和大小是依赖于平台的可能导致不必要的负数值并且会占用更多的内存空间因此不适合在需要精确控制数据结构大小的网络通信中使用。
因此使用 uint16_t 可以提高协议的跨平台兼容性保证数据的准确传输并且更高效地使用带宽和存储。 ##的作用 主机序列转成网络序列
端口号只有两个字节 如果机器本来是大端机那么就什么都不做如果是小端机会转换成大端 main中这样传ip 如何快速的将整数IP-字符串IP
自己写的话如果实现 uint8_t一个字节 这样就把4字节的ip直接转换成了字符串风格的ip uint32_t是4个字节 把字符串风格的ip地址转化为四字节网络风格的四字节 local在用户栈上这样赋值只是把变量给赋值了并没有把local变量和内核的网络套接字相关联还没有进行绑定 再进行bind绑定绑定的本质把这个参数设置进内核指定的套接字内部 要强转 以上udp服务器核心的启动代码基本已经完成
思路精华 测试
#pragma once
#includeiostream
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include cstring
#include stdio.h
#include log.hpp
using namespace std;extern Log1 log1;//日志器声明enum{SOCKET_ERR 1,BIND_ERR
};uint16_t defaultport 8080;
string defaultip 0.0.0.0;
const int Size 1024;
class UdpSever
{
public:UdpSever(const uint16_t port defaultport, const string ip defaultip):sockfd_(0), port_(port), ip_(defaultip), isrunning_(false){}void Init(){//创建udp socketsockfd_ socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ 0){log1(Fatal, socket create error, sockfd:%d, sockfd_);exit(SOCKET_ERR);}log1(Info, socket create success, sockfd:%d, sockfd_);//2.bind socket绑定端口号struct sockaddr_in local;bzero(local, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port_);//需要保证我的端口号是网络字节序列因为该端口号是要给对方发送的local.sin_addr.s_addr inet_addr(ip_.c_str());//1.string - uint32_t uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)local, sizeof(local)) 0){log1(Fatal, bind error, error:%d, err string:%s, errno, strerror(errno)); exit(BIND_ERR);}log1(Info, bind success, error:%d, err string:%s, errno, strerror(errno)); }void Run(){isrunning_ true;//const int size 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len sizeof(client);ssize_t n recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)client, len);if(n 0){log1(Warning, recvfrom error, error:%d, err string:%s, errno, strerror(errno)); continue;}inbuffer[0] 0;//充当了一次数据的处理string info inbuffer;string echo_string sever echo# info;//发送回给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)client, len);}}~UdpSever(){if(sockfd_ 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_;bool isrunning_;
};#include udpServer.hpp
#includememory
using namespace std;
Log1 log1;//120.78.126.148点分十进制字符串风格的IP地址
int main()
{unique_ptrUdpSever svr(new UdpSever());//云服务器IPsvr-Init();svr-Run();return 0;
} 服务器应该周而复始的运行
isrunning是是否在运行 从udp中读取数据
readwrite是面向字节流的而udp面向数据报
recv 收消息
分别是客户端结构体和这个结构体的大小socklen_t
客户发的数据客户是谁
失败了继续重收 整理一下 发送回给客户端 查看端口号和IP地址netstat -naup 不带n 测试ip地址是云服务器的8080端口号
云服务器最好就直接不写ip地址就好了直接默认的0
绑定成功 关于port的问题端口号 sudo就可以了 0-1023不让绑定最好绑定1024的端口有的3306也不可以 写成命令行./udpServerport
IP默认0
#include udpServer.hpp
#includememory
using namespace std;
Log1 log1;//120.78.126.148点分十进制字符串风格的IP地址
void Usage(string proc)
{cout \n\rUsage proc port[1024]\n endl;
}//./udpServerport
int main(int argc, char* argv[])
{if(argc ! 2){Usage(argv[0]);exit(0);}uint16_t port stoi(argv[1]);unique_ptrUdpSever svr(new UdpSever(port));//云服务器IPsvr-Init();svr-Run();return 0;
} 写一个客户端
makefile里要同时执行两个程序 .PHONY:all
udpServer:main.ccg -o $ $^ -stdc11
udpClient:udpClient.ccg -o $ $^ -stdc11
.PHONY:clean
clean:rm -f udpServer udpClient创建套接字
#includeiostream
#include sys/types.h
#include sys/socket.h
using namespace std;int main()
{// IPv4 用户数据报int sockfd socket(AF_INET, SOCK_DGRAM, 0);if(sockfd 0){cout socker error endl;return 1;}return 0;
}
记得close网络文件 客户端要bind吗
一个端口号只能被一个进程绑定一个程序可以绑定多个端口号所以要OS自由随机选择防止定义重复造成冲突如果两个进程同一个端口号那么只能启动一个程序。。。客户端的端口号保持唯一性就可以了 服务器的端口号要确定是因为客户端是要访问服务端的需要知道确定的端口号和ip
客户端写成命令行./udpServeripport
void Usage(string proc)
{cout \n\rUsage: proc severip severport\n endl;
}int main(int argc, char* argv[])
{if(argc ! 3){Usage(argv[0]);exit(0);}string severip argv[1];uint16_t severport stoi(argv[2]);// IPv4 用户数据报int sockfd socket(AF_INET, SOCK_DGRAM, 0);if(sockfd 0){cout socker error endl;return 1;}close(sockfd);return 0;
}
1.数据 2.发给谁sendto sendto有可能向不同的服务器发消息
整理一下。主机转网络服务器信息
端口号主机转网络 接收一下recvfrom 收到的信息放进buffer
代码示例
#include iostream
#include sys/types.h
#include sys/socket.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include strings.h
using namespace std;
void Usage(string proc)
{cout \n\rUsage: proc severip severport\n endl;
}int main(int argc, char* argv[])
{if(argc ! 3){Usage(argv[0]);exit(0);}string severip argv[1];uint16_t severport stoi(argv[2]);struct sockaddr_in server;server.sin_family AF_INET;server.sin_port htons(severport);server.sin_addr.s_addr inet_addr(severip.c_str());bzero(server, sizeof(server));socklen_t len sizeof(server);//长度// IPv4 用户数据报int sockfd socket(AF_INET, SOCK_DGRAM, 0);if(sockfd 0){cout socker error endl;return 1;}string message;char buffer[1024];while(true){cout Please Enter ; getline(cin, message);//空格不会作为结束标识符//1.数据 2.发给谁sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)server, len);struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t s recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)temp, len);if(s 0){buffer[s] 0;cout buffer endl;}}close(sockfd);return 0;
}
测试客户端向服务器发信息 客户端怎么知道服务器ip地址和端口号呢日常生活中是不会知道的端口号是固定的约定好的服务器也要遵守这样的规则把服务器ip地址拿过来也就是云服务器的ip101.34.66.193(需要开放端口) 无法使用要用127.0.0.1 udpServer.hpp
#pragma once
#include iostream
#include string
#include strings.h
#include cstring
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include functional
#include log.hpp
using namespace std;using fun_t functionstring(const string);
//typedef functionstring(const string) fun_t;extern Log1 log1;//日志器声明enum{SOCKET_ERR 1,BIND_ERR
};uint16_t defaultport 8080;
string defaultip 0.0.0.0;
const int Size 1024;class UdpServer
{
public:UdpServer(const uint16_t port defaultport, const string ip defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init(){//创建udp socketsockfd_ socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ 0){log1(Fatal, socket create error, sockfd:%d, sockfd_);exit(SOCKET_ERR);}log1(Info, socket create success, sockfd:%d, sockfd_);//2.bind socket绑定端口号struct sockaddr_in local;bzero(local, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port_);//需要保证我的端口号是网络字节序列因为该端口号是要给对方发送的local.sin_addr.s_addr inet_addr(ip_.c_str());//1.string - uint32_t uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)local, sizeof(local)) 0){log1(Fatal, bind error, error:%d, err string:%s, errno, strerror(errno)); exit(BIND_ERR);}log1(Info, bind success, error:%d, err string:%s, errno, strerror(errno)); }void Run(fun_t func)//加了fun_t func本质是对代码进行分层{isrunning_ true;//const int size 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len sizeof(client);//收到数据ssize_t n recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)client, len);if(n 0){log1(Warning, recvfrom error, error:%d, err string:%s, errno, strerror(errno)); continue;}//收到了一个数据inbuffer[n] 0;//充当了一次数据的处理//处理数据放在外面去处理string info inbuffer;//string echo_string sever echo# info;string echo_string func(info);//在外面去处理 cout echo_string endl; // 这个你刚刚注释了//发送回给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)client, len);}}~UdpServer(){if(sockfd_ 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_;bool isrunning_;
};udpClient.cc
#include iostream
#include sys/types.h
#include sys/socket.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include strings.h
using namespace std;
void Usage(string proc)
{cout \n\rUsage: proc severip severport\n endl;
}int main(int argc, char* argv[])
{if(argc ! 3){Usage(argv[0]);exit(0);}string serverip argv[1];uint16_t serverport stoi(argv[2]);struct sockaddr_in server;bzero(server, sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverport);server.sin_addr.s_addr inet_addr(serverip.c_str());socklen_t len sizeof(server);//长度// IPv4 用户数据报int sockfd socket(AF_INET, SOCK_DGRAM, 0);if(sockfd 0){cout socker error endl;return 1;}string message;char buffer[1024];while(true){cout Please Enter ; getline(cin, message);//空格不会作为结束标识符//cout message endl;//1.数据 2.发给谁sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)server, len);struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t s recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)temp, len);if(s 0){buffer[s] 0;cout buffer endl;}}close(sockfd);return 0;
}
main.cc
#include udpServer.hpp
#includememory
using namespace std;
Log1 log1;//120.78.126.148点分十进制字符串风格的IP地址
void Usage(string proc)
{cout \n\rUsage proc port[1024]\n endl;
}//处理字符串----udpServer.hpp里的fun_t
string Handler(const string str)
{//这里收到客户发来的请求string res Server get a message: ;res str;return res;
}// bool SafeCheck(const std::string cmd)
// {
// int safe false;
// std::vectorstd::string key_word {
// rm,
// mv,
// cp,
// kill,
// sudo,
// unlink,
// uninstall,
// yum,
// top,
// while
// };
// for(auto word : key_word)
// {
// auto pos cmd.find(word);
// if(pos ! std::string::npos) return false;
// }// return true;
// }// std::string ExcuteCommand(const std::string cmd)
// {
// std::cout get a request cmd: cmd std::endl;
// if(!SafeCheck(cmd)) return Bad man;// FILE *fp popen(cmd.c_str(), r);
// if(nullptr fp)
// {
// perror(popen);
// return error;
// }
// std::string result;
// char buffer[4096];
// while(true)
// {
// char *ok fgets(buffer, sizeof(buffer), fp);
// if(ok nullptr) break;
// result buffer;
// }
// pclose(fp);// return result;
// }//./udpServerport
int main(int argc, char* argv[])
{if(argc ! 2){Usage(argv[0]);exit(0);}uint16_t port stoi(argv[1]);unique_ptrUdpServer svr(new UdpServer(port));//云服务器IPsvr-Init();svr-Run(Handler);return 0;
}makefile
.PHONY:all
all:udpServer udpClient
udpServer:main.ccg -o $ $^ -stdc11
udpClient:udpClient.ccg -o $ $^ -stdc11
.PHONY:clean
clean:rm -f udpServer udpClient多台机器网络信息传递测试
先编译一下程序再进行如图所示操作 然后把这个文件在另一台电脑打开 再在另一个电脑上打开云服务器创建一个目录rz一个把文件加入目录 默认是不可执行的 x就可以了 然后
./udpClient IP服务器IP port服务器启动时的端口号 此时在自己的机器上启动服务器在另一台机器启动客户端并且发送信息在服务器就可以看到了 网络处理数据和接收数据耦合度太高了
把网络通信功能和处理数据的功能进行解耦
两个同理返回类型是string参数类型是string 服务器外面给接收到的数据做处理 修改一下放在外部处理这样的意思就是希望服务器这边给服务端做什么处理 服务器收到了消息收到了消息然后回调是的去调用传进来的func方法把这个数据做加工处理完之后把结果返回
#include udpServer.hpp
#includememory
using namespace std;
Log1 log1;//120.78.126.148点分十进制字符串风格的IP地址
void Usage(string proc)
{cout \n\rUsage proc port[1024]\n endl;
}//处理字符串----udpServer.hpp里的fun_t
string Handler(const string str)
{//这里收到客户发来的请求string res Server get a message: ;res str;return res;
}//./udpServerport
int main(int argc, char* argv[])
{if(argc ! 2){Usage(argv[0]);exit(0);}uint16_t port stoi(argv[1]);unique_ptrUdpServer svr(new UdpServer(port));//云服务器IPsvr-Init();svr-Run(Handler);return 0;
}#pragma once
#include iostream
#include string
#include strings.h
#include cstring
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include functional
#include log.hpp
using namespace std;using fun_t functionstring(const string);
//typedef functionstring(const string) fun_t;extern Log1 log1;//日志器声明enum{SOCKET_ERR 1,BIND_ERR
};uint16_t defaultport 8080;
string defaultip 0.0.0.0;
const int Size 1024;class UdpServer
{
public:UdpServer(const uint16_t port defaultport, const string ip defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init(){//创建udp socketsockfd_ socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ 0){log1(Fatal, socket create error, sockfd:%d, sockfd_);exit(SOCKET_ERR);}log1(Info, socket create success, sockfd:%d, sockfd_);//2.bind socket绑定端口号struct sockaddr_in local;bzero(local, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port_);//需要保证我的端口号是网络字节序列因为该端口号是要给对方发送的local.sin_addr.s_addr inet_addr(ip_.c_str());//1.string - uint32_t uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)local, sizeof(local)) 0){log1(Fatal, bind error, error:%d, err string:%s, errno, strerror(errno)); exit(BIND_ERR);}log1(Info, bind success, error:%d, err string:%s, errno, strerror(errno)); }void Run(fun_t func)//加了fun_t func本质是对代码进行分层{isrunning_ true;//const int size 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len sizeof(client);//收到数据ssize_t n recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)client, len);if(n 0){log1(Warning, recvfrom error, error:%d, err string:%s, errno, strerror(errno)); continue;}//收到了一个数据inbuffer[n] 0;//充当了一次数据的处理//处理数据放在外面去处理string info inbuffer;//string echo_string sever echo# info;string echo_string func(info);//在外面去处理//cout echo_string endl;//发送回给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)client, len);}}~UdpServer(){if(sockfd_ 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_;bool isrunning_;
};如果发来的是命令呢
跟我们平时链接xshellf发命令类似
POPEN #include udpServer.hpp
#includememory
using namespace std;
Log1 log1;//120.78.126.148点分十进制字符串风格的IP地址
void Usage(string proc)
{cout \n\rUsage proc port[1024]\n endl;
}//处理字符串----udpServer.hpp里的fun_t
string Handler(const string str)
{//这里收到客户发来的请求string res Server get a message: ;res str;return res;
}// bool SafeCheck(const std::string cmd)
// {
// int safe false;
// std::vectorstd::string key_word {
// rm,
// mv,
// cp,
// kill,
// sudo,
// unlink,
// uninstall,
// yum,
// top,
// while
// };
// for(auto word : key_word)
// {
// auto pos cmd.find(word);
// if(pos ! std::string::npos) return false;
// }// return true;
// }// std::string ExcuteCommand(const std::string cmd)
// {
// std::cout get a request cmd: cmd std::endl;
// if(!SafeCheck(cmd)) return Bad man;// FILE *fp popen(cmd.c_str(), r);
// if(nullptr fp)
// {
// perror(popen);
// return error;
// }
// std::string result;
// char buffer[4096];
// while(true)
// {
// char *ok fgets(buffer, sizeof(buffer), fp);
// if(ok nullptr) break;
// result buffer;
// }
// pclose(fp);// return result;
// }//./udpServerport
int main(int argc, char* argv[])
{if(argc ! 2){Usage(argv[0]);exit(0);}uint16_t port stoi(argv[1]);unique_ptrUdpServer svr(new UdpServer(port));//云服务器IPsvr-Init();svr-Run(Handler);return 0;
}本地环回127.0.0.1
本地环回主要用于客户端和服务端的测试
测试并绑定127.0.0.1的ip地址
本地环回地址Loopback Address在网络技术中指的是一种特殊的IP地址用于网络软件测试以及本地机器上的服务访问而不会将数据包发送到本地机器之外的任何地方。最常用的本地环回地址是 127.0.0.1它对应于IPv4协议而在IPv6中环回地址通常表示为 ::1。
以下是本地环回地址在网络中的含义和用途 自我测试 环回地址允许网络应用程序在不涉及物理网络接口的情况下发送和接收数据包。这对于测试网络软件非常有用因为它不需要真实的网络连接。 服务本地访问 服务器应用程序可以在本地机器上运行并通过环回地址进行访问这允许开发者在服务部署到生产环境之前在本地进行开发和测试。 不占用网络带宽 通过环回地址发送的数据不会离开主机因此不会占用任何网络带宽。 系统内部通信 操作系统内部的不同服务和应用程序可以通过环回地址进行通信而无需通过网络接口。 配置和诊断工具 网络配置和诊断工具经常使用环回地址来检查网络堆栈是否正常工作。 安全 因为数据不会离开主机所以使用环回地址进行通信被认为是安全的不会暴露给外部网络。 标准化 环回地址是网络协议标准的一部分几乎所有的TCP/IP实现都支持环回地址。
当数据包被发送到环回地址时操作系统网络堆栈会立即将该数据包返回给发送者而不进行任何网络传输。这个过程不涉及物理网络接口因此不会产生任何网络流量。尽管环回地址看起来像是网络中的一个“虚拟”接口但在网络协议栈中它与任何其他网络接口一样被处理。 客户端怎么知道服务器ip地址和端口号呢日常生活中是不会知道的端口号是固定的约定好的服务器也要遵守这样的规则把服务器ip地址拿过来也就是云服务器的ip101.34.66.193(需要开放端口) 无法使用要用127.0.0.1 在服务器查看发信的客户端的端口号和IP
网络转主机拿到端口号和IP uint16_t port ntohs(client.sin_port);
网络转主机就可以打印出来了 改变一下处理数据方式打印出IP和port 代码示例
server
#pragma once
#include iostream
#include string
#include strings.h
#include cstring
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include functional
#include unordered_map
#include log.hpp
using namespace std;using fun_t functionstring(const string, string , uint16_t);
//typedef functionstring(const string) fun_t;extern Log1 log1;//日志器声明enum{SOCKET_ERR 1,BIND_ERR
};uint16_t defaultport 8080;
string defaultip 0.0.0.0;
const int Size 1024;class UdpServer
{
public:UdpServer(const uint16_t port defaultport, const string ip defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init(){//创建udp socketsockfd_ socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ 0){log1(Fatal, socket create error, sockfd:%d, sockfd_);exit(SOCKET_ERR);}log1(Info, socket create success, sockfd:%d, sockfd_);//2.bind socket绑定端口号struct sockaddr_in local;bzero(local, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port_);//需要保证我的端口号是网络字节序列因为该端口号是要给对方发送的local.sin_addr.s_addr inet_addr(ip_.c_str());//1.string - uint32_t uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)local, sizeof(local)) 0){log1(Fatal, bind error, error:%d, err string:%s, errno, strerror(errno)); exit(BIND_ERR);}log1(Info, bind success, error:%d, err string:%s, errno, strerror(errno)); }//检查用户是不是新用户void CheckUser(const struct sockaddr_in client){}void Run(fun_t func)//加了fun_t func本质是对代码进行分层{isrunning_ true;//const int size 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len sizeof(client);//收到数据ssize_t n recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)client, len);if(n 0){log1(Warning, recvfrom error, error:%d, err string:%s, errno, strerror(errno)); continue;}//接收端口号和ipuint16_t clientport ntohs(client.sin_port);string clientip inet_ntoa(client.sin_addr);CheckUser(client);//收到了一个数据inbuffer[n] 0;//充当了一次数据的处理//处理数据放在外面去处理string info inbuffer;//string echo_string sever echo# info;string echo_string func(info, clientip, clientport);//在外面去处理 cout echo_string endl; //发送回给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)client, len);}}~UdpServer(){if(sockfd_ 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_; //任意地址 0bool isrunning_;unordered_mapstring, struct sockaddr_t online_user;
};main.cc
#include udpServer.hpp
#includememory
using namespace std;
Log1 log1;//120.78.126.148点分十进制字符串风格的IP地址
void Usage(string proc)
{cout \n\rUsage proc port[1024]\n endl;
}//处理字符串----udpServer.hpp里的fun_t
string Handler(const string info, string clientip, uint16_t clientport)
{cout [clientip: clientip clientport ]# endl;//这里收到客户发来的请求string res Server get a message: ;res info;return res;
}//./udpServerport
int main(int argc, char* argv[])
{if(argc ! 2){Usage(argv[0]);exit(0);}uint16_t port stoi(argv[1]);unique_ptrUdpServer svr(new UdpServer(port));//云服务器IPsvr-Init();svr-Run(Handler);return 0;
}测试 只允许一个客户端连接/判断是不是一个新用户(kv)
检查用户是不是新用户 代码
#pragma once
#include iostream
#include string
#include strings.h
#include cstring
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include functional
#include unordered_map
#include log.hpp
using namespace std;using fun_t functionstring(const string, string , uint16_t);
//typedef functionstring(const string) fun_t;extern Log1 log1;//日志器声明enum{SOCKET_ERR 1,BIND_ERR
};uint16_t defaultport 8080;
string defaultip 0.0.0.0;
const int Size 1024;class UdpServer
{
public:UdpServer(const uint16_t port defaultport, const string ip defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init(){//创建udp socketsockfd_ socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ 0){log1(Fatal, socket create error, sockfd:%d, sockfd_);exit(SOCKET_ERR);}log1(Info, socket create success, sockfd:%d, sockfd_);//2.bind socket绑定端口号struct sockaddr_in local;bzero(local, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port_);//需要保证我的端口号是网络字节序列因为该端口号是要给对方发送的local.sin_addr.s_addr inet_addr(ip_.c_str());//1.string - uint32_t uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)local, sizeof(local)) 0){log1(Fatal, bind error, error:%d, err string:%s, errno, strerror(errno)); exit(BIND_ERR);}log1(Info, bind success, error:%d, err string:%s, errno, strerror(errno)); }//检查用户是不是新用户void CheckUser(const struct sockaddr_in client, string clientip, uint16_t clientport){auto iter online_user_.find(clientip);//kv结构检查在不在if(iter online_user_.end())//如果不在那就添加{online_user_.insert({clientip, client});}cout [clientip: clientip clientport ] add to online user endl;}//广播给所有人所有人都在unordered_map里void Broadcast(const string info, string clientip, uint16_t clientport){for(auto user : online_user_){string message [clientip:;message clientip;message ;message clientport;message ]#;message info;socklen_t len sizeof(user.second); sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)(user.second), len);}}void Run(/*fun_t func*/)//加了fun_t func本质是对代码进行分层{isrunning_ true;//const int size 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len sizeof(client);//收到数据ssize_t n recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)client, len);if(n 0){log1(Warning, recvfrom error, error:%d, err string:%s, errno, strerror(errno)); continue;}//接收端口号和ip// uint16_t clientport ntohs(client.sin_port);// string clientip inet_ntoa(client.sin_addr);uint16_t clientport ntohs(client.sin_port);string clientip inet_ntoa(client.sin_addr);CheckUser(client, clientip, clientport);//把消息广播给所有人string info inbuffer;Broadcast(info, clientip, clientport);// //收到了一个数据// inbuffer[n] 0;// //充当了一次数据的处理// //处理数据放在外面去处理// string info inbuffer;// //string echo_string sever echo# info;// string echo_string func(info, clientip, clientport);//在外面去处理 // cout echo_string endl; // //发送回给客户端// sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)client, len);}}~UdpServer(){if(sockfd_ 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_; //任意地址 0bool isrunning_;unordered_mapstring, struct sockaddr_in online_user_;
};客户端整改
#include iostream
#include sys/types.h
#include sys/socket.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include strings.h
using namespace std;
void Usage(string proc)
{cout \n\rUsage: proc severip severport\n endl;
}int main(int argc, char* argv[])
{if(argc ! 3){Usage(argv[0]);exit(0);}// IPv4 用户数据报int sockfd socket(AF_INET, SOCK_DGRAM, 0);string serverip argv[1];uint16_t serverport stoi(argv[2]);struct sockaddr_in server;bzero(server, sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverport);server.sin_addr.s_addr inet_addr(serverip.c_str());socklen_t len sizeof(server);//长度if(sockfd 0){cout socker error endl;return 1;}string message;char buffer[1024];while(true){cout Please Enter ; getline(cin, message);//空格不会作为结束标识符//cout message endl;//1.数据 2.发给谁sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)server, len);struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t s recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)temp, len);if(s 0){buffer[s] 0;cout buffer endl;}}close(sockfd);return 0;
}
登录qq时不发送信息也能收到信息但目前的客户端没法做到
会在getline阻塞住
发送完再回收到就又在getline阻塞了 客户端改成多线程一个线程输入一个线程显示
udp的sockfd是可以同时被读写的
是线程安全的 #include iostream
#include sys/types.h
#include sys/socket.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include strings.h
#include pthread.h
using namespace std;
void Usage(string proc)
{cout \n\rUsage: proc severip severport\n endl;
}//线程的数据
struct ThreadData
{struct sockaddr_in server;int sockfd;
};// 收
void *recv_message(void *args)
{ThreadData* td static_castThreadData*(args);//安全的类型转换char buffer[1024]; while (true){struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t s recvfrom(td-sockfd, buffer, 1023, 0, (struct sockaddr *)temp, len);if (s 0){buffer[s] 0;cout buffer endl;}}
}// 发 就要知道客户端的套接字信息
void *send_message(void *args)
{ThreadData* td static_castThreadData*(args);//安全的类型转换string message;socklen_t len sizeof(td-server); // 长度while (true){cout Please Enter ;getline(cin, message); // 空格不会作为结束标识符// cout message endl;// 1.数据 2.发给谁sendto(td-sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)(td-server), len);}
}int main(int argc, char *argv[])
{if (argc ! 3){Usage(argv[0]);exit(0);}//创建线程的数据 对象struct ThreadData td;// IPv4 用户数据报td.sockfd socket(AF_INET, SOCK_DGRAM, 0);string serverip argv[1];uint16_t serverport stoi(argv[2]);bzero(td.server, sizeof(td.server));td.server.sin_family AF_INET;td.server.sin_port htons(serverport);td.server.sin_addr.s_addr inet_addr(serverip.c_str());// 创建一个收线程一个发线程pthread_t recvr, sender;pthread_create(recvr, nullptr, recv_message, td);pthread_create(sender, nullptr, send_message, td);if (td.sockfd 0){cout socker error endl;return 1;}pthread_join(recvr, nullptr);pthread_join(sender,nullptr);close(td.sockfd);return 0;
}
思路和测试结果
制作简单的聊天室类似群聊
首先查看自己打开的几个终端 正好3个 上面的这个终端是0 这是1 这是2 将客户端的收消息用标准错误显示出来文件描述符是2 覆盖问题
这个是因为是在往/dev/pts/4这个里面写入你可以将其理解为一个文件本来文件中有一行内容为aaaaaaaa每次显示然后现在再从头输入bbbb这个bbbb覆盖bbbbaaaa还是会输出bbbbaaaa