当前位置: 首页 > news >正文

门窗网站模板福州百度推广排名优化

门窗网站模板,福州百度推广排名优化,网站建设 绵阳,做英文网站价格其实经过这几天写的几种不同的UDP的简易客户端与服务端,还是很有套路的,起手式都是非常像的。 更多的难点对我来说反而是解耦,各种各样的function一用,回调函数一调,呕吼,就会懵一下。 对于这篇文章&#x…

其实经过这几天写的几种不同的UDP的简易客户端与服务端,还是很有套路的,起手式都是非常像的。
更多的难点对我来说反而是解耦,各种各样的function一用,回调函数一调,呕吼,就会懵一下。
对于这篇文章,我主要是把那些起手式,还有我觉得有点难得解耦方式稍微进行一下说明,方便我自己回顾,当然如果可以帮助更多的小伙伴那自然是更好啦。

目录

  • Echo
    • 服务端起手式
    • 服务端LOOP
    • 客户端起手
    • 客户端LOOP
    • 验证
  • Dict
    • 设计思想
    • 验证
  • Chat
    • 服务端的修改
    • 客户段的修改
    • 效果展示

Echo

服务端起手式

这个echo的代码是为了熟悉起手式,因为几乎没有业务的附带,所以是很简单的。
而它的功能就是你向服务器中发送消息,你的服务端会重新发给你。

注意:日志真的很重要,可以让你知道你的程序在哪一步出错了,很快的定位。

首先大概的看一下起手式接口:
因为我们的网络要通信需要IP + 端口号才能定位到具体主机内具体进程,而IP + port就是我们说的套接字,关于套接字在UDP中需要知道2个接口,但是这两个接口中我们注意到有一个sockaddr 结构体,因此我们需要看一下结构体。

// 创建 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);

在这里插入图片描述
首先,这组接口不仅可以实现网络通信,也可以实现主机内通信。
其中sockaddr_in是网络,sockaddr_un是主机内,那么为什么bind接口内是sockaddr?

因为我们会填16位地址类型,所以当我们将对应的结构体强转为sockaddr *时,函数内部就会根据16位地址类型判断究竟是哪一种,这也就是C语言层面的多态!

首先,我们的server是面向对象的,代码中没有定义就出现变量都是私有成员。根据名字都可以知道大概意思,在最后会有完整代码。

 int fd = ::socket(AF_INET, SOCK_DGRAM, 0);if (fd < 0){exit(1);}_socketfd = fd;// 将Ip Port与套接字绑定struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;// 不要忘记转为网络序列!// addr.sin_addr.s_addr = inet_addr(_ip.c_str());addr.sin_addr.s_addr = INADDR_ANY;addr.sin_port = htons(_port);// bindint n = ::bind(_socketfd, (struct sockaddr *)&addr, sizeof(addr));

再来解释一下代码

第一个参数:

因为我们是网络通信,所以16位网络地址选择使用AF_INET

第二个参数:

我们选择的是UDP
在这里插入图片描述
也就是无连接,不可靠,数据报。

第三个参数:

表示希望使用的协议,我们通常设置为0,系统会根据情况自己处理。

返回值:

socket返回的是一个文件描述符在这里插入图片描述
为什么返回文件描述符?

我们在此感性的理解
因为我们网络的通信是建立在网卡上的,而linux中一切皆文件,所以就相当于我们返回的是网卡的文件描述符。

于是我们的socket就创建好了,但是还要与IP与port进行绑定起来。
那么就先要创建一个sockaddr_in的结构体填参

sin我们可以理解为socket Internet。
其中我们只关心图中的框起来部分,sin_zero是作为将结构体补齐用的,新的网络编程库中甚至都见不到这个字段了。
在这里插入图片描述

我们逐个分析一下这个要填的3个字段


在这里插入图片描述
第一个参数的形式是一个宏,在预处理阶段会进行处理,进行替换得到sa_family_t sin_family##在预处理阶段会将两边的字符串进行拼接)。
而这个填的就是AF_INET,与套接字对应。


第二个参数是一个无符号短整型,uint16_t的类型,我们填入自定义端口即可。
注意:由于我们要注意网络序列与主机序列的转换,自己进行判断的话过于麻烦,OS也提供了一组接口方便我们进行转换。在这里插入图片描述


第三个参数是IP
注意这个IP可是有很大讲究的,
首先他是结构体内嵌套结构体,填的时候要注意
其次我们刚开始肯定觉得绑定自己的公网IP,或者局域网IP,又或者是本地环回。
但是如果填一个具体的IP,那么就意味着你以后只能从这一个向指定的IP中获取信息,但是你的主机IP有多个,反而不能全部利用,因此这里我们选择填入INADDR_ANY(0)。
在这里插入图片描述

此时我们就可以接收多个IP+端口号发送来的信息了。

注意:我们一般在进行网络测试时,一般会使用本地环回IP测试,也就是127.0.0.1,当你的客服端向127.0.0.1这个IP发送时,那么就不会在网络中传输,而是在本机。
在这里插入图片描述
那么此时我们就完成起手式,socket的创建与绑定了。

服务端LOOP

while (true)
{char buffer[1024];sockaddr_in peer;socklen_t len = sizeof(peer);int n = recvfrom(_socketfd, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&peer, &len);if (n != -1){buffer[n] = 0;int m = sendto(_socketfd, buffer, n, 0, (struct sockaddr*)&peer, sizeof(peer));if (m == -1){perror("发送错误");break;}}
}

我们的服务端肯定是要进行接收消息的,然后在做一些加工返回给客服端。
这里就不得不说两个函数了。
在这里插入图片描述
在这里插入图片描述
他们的参数都非常的类似,在接收时我们传入一段缓冲区,填入大小,即可得到客户端发来的消息了,因为我们接收后还要发送给对方,所以后边的两个参数是输入型参数,会得到对方的sockaddr信息。

对于发送时,我们也是如此。
另外:它们的 flags 参数是用来控制函数行为的标志位,允许程序员指定一些特殊的选项或操作模式。flags 参数通常是多个标志的按位或(OR)组合,但大多数情况下,这些标志并不是必需的,因此绝大多数会传递0作为默认值。

客户端起手

与服务端有很大的不同。

int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1)
{exit(1);
}

先说结论:我们在服务端只需要创建socket即可,肯定需要bind,但无需显示bind,因为会在sendto中OS会自主绑定。

为什么不需要自己指定IP + 端口号?
OS肯定是知道你的IP,那么OS给你绑定也说得过去,那么端口号为什么不自主绑定?
我们举一个例子,一个主机上的端口号是有限的,如果客户端是自主定义,那么可能不同客户端会出现重复!比如抖音用端口号8888,那么快手绑定8888是势必不成功,因为IP + port标识一个唯一进程。所以这个由OS自主分配即可。

客户端LOOP

while (true)
{std::string buffer;std::cout << "Please write msg:";std::getline(std::cin, buffer);// 处理sockaddr结构体 + 发送数据到服务端struct sockaddr_in peer;peer.sin_addr.s_addr = inet_addr(ip.c_str());peer.sin_port = htons(port);peer.sin_family = AF_INET;socklen_t len = sizeof(peer);int n = sendto(fd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&peer, len);if (n != -1){char inbuffer[1024];struct sockaddr_in temp;socklen_t len;int m = recvfrom(fd, inbuffer, n, 0, (struct sockaddr *)&temp, &len);if (m != -1){inbuffer[m] = 0;std::cout << inbuffer << std::endl;}}
}

我们在服务端绑定时不需要填真正的IP,发送和接收时也直接使用现成的,但是在客户端我们需要手动填写,但是我们现在有的是一个字符串,我们压迫将他转为4字节,还要转为网络序列,这也是很繁琐的,因此也有一批函数用来转化。
在这里插入图片描述
可以看到我们放入一个字符串地址即可得到网络序列4字节IP,非常的方便,但是这里可以改进(我们最后说,一般使用inet_pton)。

所以我们现在也就没啥干货了,起手式已经完成!

验证

由于上图代码都是非常简略的,并没有将如何封装写出,但是还是要验证一下的
在这里插入图片描述
完整代码在Gitee链接中给出。
在这里插入图片描述

Dict

设计思想

我们在以上的基础上增加一些业务,这里也就开始涉及一些解耦的设计了。

我们的理想效果为输入一个单词返回他的意思。

其实服务器与客户端大的逻辑仍旧是不变的,但是这里进行设计解耦的思想是很好的,要学习!
首先我们要改变的就是服务端,我们在recvfrom到字符串单词后可以使用回调函数,将这个单词交给外部来做,返回汉语意思字符串sendto。这样就很好的完成了解耦,因为我们要用到回调函数,所以也就意味着在构造服务端对象时要传入可调用对象。

此时我们就可以利用function进行包装,包装出一个可调用对象类型。

using func_t = std::function<std::string(std::string)>; 

关于这里我其实还想补充几点
我们命名时可以看到func_t中的func代表这是函数,t代表typename表示类型,这样别人一看就知道这是一个函数类型。
而命名空间时也有这样的讲究,比如我们有一个日志类,使用log_ns域封装起来,ns就是namespace的缩写,也是一目了然。

另外就是关于function的一些点了,实际上我们的function绑定时与被绑定的函数类型并不需要完全相符,就像下图这样的代码甚至可以编过,我一点都不理解…
在这里插入图片描述
但是我认为还是最好保证一样。


回到主线:
由于我们希望解耦,因此function中我们也就没有必要传引用,就解耦解的结结实实(但实际上传也是可以的,还避免了拷贝)。
随后我们再编写一个字典类,最终要的是要有支持翻译的功能!

我们可以选择搞一个配置文件的形式,创建对象时进行加载即可~
在这里插入图片描述

const std::string sep = ": ";// 单词与翻译的分隔符class Dict
{
private:void Load(){std::ifstream in(_path.c_str());if (!in.is_open()){exit(1);}std::string line;while (std::getline(in, line)){std::string key;std::string value;auto pos = line.find(sep);if (pos == -1)continue;key = line.substr(0, pos);value = line.substr(pos + sep.size());_map.insert(std::make_pair(key, value));}}public:Dict(const std::string & path): _path(path){Load();}std::string GetChinese(std::string word){auto ite = _map.find(word);        if (ite != _map.end())return ite->second;elsereturn "None";}~Dict(){}
private:std::unordered_map<std::string, std::string> _map; std::string _path;
};

其中GetChinese函数就是我们未来在服务端回调的函数!

但是此时要注意,我们不能直接将这个函数传入服务器的构造函数中,因为这是一个静态成员函数!所以我们需要将这个函数bind一下,让this指针隐式写入即可!

我觉得这就是最精髓的地方了。
代码见链接。

验证

在这里插入图片描述

Chat

这是一个聊天室项目,我觉得是还算挑战性的,但实际上只是套的层数有点的,好多整合在一起。
听说Java那更喜欢各种封,各种套,什么结构啥的,害怕~

但是对于当前的chat聊天室来说最重要的搞清楚整体的大框架。
不仅仅是对于当前的聊天室,甚至可以说是任何比较嵌套的,只要把结构搞清楚了,那么就会轻松很多。
在这里插入图片描述
我们现在直接使用以上的服务端 + 客户端进行改进即可。

线程池的代码

服务端的修改

我们在字典中已经学到了在服务端使用回调进行业务处理,我们当然也可以使用回调完成转发!

我们进行转发需要3个元素。
sockfd描述符, message消息体,sockaddr结构体。
因此我们的回调设置为这样子即可。

这里的Inet就是我们带码云中封装过的转化类,

using service_t = std::function<void(int, const std::string &, const Inet &)>;

在服务器端上一个版本原本调用处理获取翻译的地方更改一下即可~

所以又到了设计转发类的时候了,我们一般喜欢在应用层中把这个工作叫做路由。
也就是设计一个路由类。

我们这样设计:当服务端回调到路由模块时,我们就得到了sockfd,message,addr,

  1. 首先检查当前ip + port是否在在线列表中,不在就add,在了就不管。
  2. 当消息为QUIT或者Q时,将在线列表中的user删除
  3. 转发我们只需要遍历一遍在线用户列表即可

也就是转发时使用线程池。

class Route
{
public:Route(){}void CheckOnlineUsers(const Inet &inet_addr){_online_users.insert(inet_addr)}void Offline(const Inet &inet_addr){LOG(DEBUG, "%s offline\n", inet_addr.AddrStr().c_str());_online_users.erase(inet_addr);}void ForwardHelper(int socket, const std::string &message){for (auto &user : _online_users){sockaddr_in peer = user.Sockaddr();socklen_t len = sizeof(peer);int n = ::sendto(socket, message.c_str(), message.size(), 0, (struct sockaddr *)&peer, len);}}void Forward(int socket, const std::string &message, const Inet &inet_addr){CheckOnlineUsers(inet_addr);if (message == "Q" || message == "QUIT"){Offline(inet_addr);}// 转发模块,线程池去执行std::function<void()> f = std::bind(&Route::ForwardHelper, this, socket, send_message);ThreadPool<std::function<void()>>::GetInstance()->Equeue(f);}~Route(){}private:std::set<Inet, Route_ns::comp> _online_users;pthread_mutex_t _mutex;
};

注意到我们的线程池中只需要push进去一个可调用对象即可,所以我们进行bind一下以进行适配线程池模板。

而我们在进行构造客户端时传入Route类中的Forward函数即可~
依旧和Dict服务器一样的方法套路。

这样服务端就设计好了

客户段的修改

我们当前的客户端首先是有问题的,因为我们当前只有一个线程同时进行收和发,当我们多起几个客户端时,如果客户端A进行发消息,其他的客户端其实都不会显示的,因为只有别的客户端进行sendto时才会收到消息,否则就一直阻塞在sendto中。

所以这里我们也是用多线程进行一下修改,一个线程一直读,一个一直进行发送。

main函数中我们创建2个线程,分别执行各自的读和写,这里就没什么细节了。

int ClientInit()
{int fd = socket(AF_INET, SOCK_DGRAM, 0);if (fd == -1){LOG(FATAL, "create socket err");exit(1);}return fd;
}void receiver(const std::string &name, int socketfd)
{while (true){char inbuffer[1024];struct sockaddr_in temp;socklen_t len;int n = recvfrom(socketfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&temp, &len);if (n >= 0){inbuffer[n] = 0;std::cerr << inbuffer << std::endl;}}
}void sender(const std::string &name, int socketfd, const std::string &ip, uint16_t port)
{while (true){std::string buffer;std::cout << "Please input msg:";std::getline(std::cin, buffer);// 处理sockaddr结构体 + 发送数据到服务端struct sockaddr_in peer;peer.sin_addr.s_addr = inet_addr(ip.c_str());peer.sin_port = htons(port);peer.sin_family = AF_INET;socklen_t len = sizeof(peer);int n = sendto(socketfd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&peer, len);}
}int main(int args, char *argv[])
{// 处理命令行行参数if (args != 3){std::cerr << "Usage:" << argv[0] << " Ip Port" << std::endl;exit(1);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);int socketfd = ClientInit();MyThread t1("thread-receiver", std::bind(receiver, std::placeholders::_1, socketfd));MyThread t2("thread-sender", std::bind(sender, std::placeholders::_1, socketfd, ip, port));t1.Start();t2.Start();t1.Join();t2.Join();return 0;
}

代码链接

效果展示

在这里插入图片描述

完~~

http://www.ds6.com.cn/news/78645.html

相关文章:

  • 甘肃省建设类证书查询网站友情链接的概念
  • 简单 手机 网站 源码网站seo优化方案设计
  • 大学网站开发与管理知识总结qq空间刷赞推广网站
  • 百度怎么搜索到自己的网站做网站建设优化的公司排名
  • 做网站大概需要多少费用广东vs北京首钢
  • 西安网站建设托管句容市网站seo优化排名
  • 有哪些做汽车变速箱的门户网站网络广告营销案例
  • 郑州网站建设的公司哪家好营销型网站建设目标
  • 杭州专业网站电商
  • 网站建设公司 北京网店seo名词解释
  • 微网站建设方案书百度广告屏蔽
  • 安徽省建设厅seo快排优化
  • 国家森林公园网站建设网上兼职外宣推广怎么做
  • 快速模仿一个网站石嘴山网站seo
  • 宝塔面板怎么做网站百度地图导航2021最新版
  • 帮人做网站赚钱网站排名优化客服
  • 网站开发框架 csdn百度指数功能模块有哪些
  • 产品展示型网站谷歌商店paypal三件套
  • 怎么让网站收录优化疫情防控
  • 常州最新通告今天公司百度官网优化
  • 网站的运行与维护网站制作定制
  • 备案网站可以做影视站自己如何制作网站
  • 太康做网站公司关键词你们都搜什么
  • tplink虚拟服务器做网站成全视频免费观看在线看
  • 网站建设基本常识qq推广链接生成
  • 太原0元网站建设好网站制作公司
  • 珠海建设公司网站网站推广的6个方法是什么
  • 网站域名去哪买百度seo有用吗
  • 网站申请备案seo顾问推推蛙
  • 辽宁省建设注册中心网站天津企业seo