东风草堂博客

公众号:开发者来风

使用设计模式的前提:具体需求既有稳定点,又有变化点。期望修改少量的变化点,就可以适应需求的变化。比如整齐的房间与好动的猫,设计模式就是要把猫关在笼子里。

策略模式

定义一系列算法,把它们一个一个封装起来,并且使它们可以互相替换,该模式使算法可独立于使用它们的客户程序而变化。
这里的稳定点是:客户程序与算法的调用关系。变化点是新增算法或者算法内容发生改变或者需要动态的切换算法。
例子:商场不同的促销活动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>

// 上下文类,包含了促销活动的信息
class Context {
public:
double totalPrice;
// 其他上下文信息...
};

// 抽象基类,具体的活动需要实现这个基类
class ProStrategy {
public:
virtual double CalcPro(const Context& ctx) = 0;
virtual ~ProStrategy() {}
};

// 具体促销活动1
class DiscountStrategy : public ProStrategy {
public:
double CalcPro(const Context& ctx) override {
// 实际的促销计算逻辑,这里简化为打八折
return ctx.totalPrice * 0.8;
}
};

// 具体促销活动2
class CashBackStrategy : public ProStrategy {
public:
double CalcPro(const Context& ctx) override {
// 实际的促销计算逻辑,这里简化为满100返20
return ctx.totalPrice >= 100 ? ctx.totalPrice - 20 : ctx.totalPrice;
}
};

// 促销活动的上下文类
class Promotion {
public:
Promotion(ProStrategy* strategy = nullptr) : strategy(strategy) {}

// 设置促销策略
void SetStrategy(ProStrategy* strategy) {
this->strategy = strategy;
}

// 计算促销价格
double CalculatePromotion(const Context& ctx) {
if (strategy) {
return strategy->CalcPro(ctx);
}
return ctx.totalPrice;
}

private:
ProStrategy* strategy;
};

int main() {
Context context;
context.totalPrice = 120;

// 创建促销活动上下文
Promotion promotion;

// 选择具体促销策略1(打八折)
promotion.SetStrategy(new DiscountStrategy());
double discountedPrice = promotion.CalculatePromotion(context);
std::cout << "Discounted Price: " << discountedPrice << std::endl;

// 选择具体促销策略2(满100返20)
promotion.SetStrategy(new CashBackStrategy());
double cashBackPrice = promotion.CalculatePromotion(context);
std::cout << "Cash Back Price: " << cashBackPrice << std::endl;

return 0;
}

应用案例:日志容错恢复机制(通常情况下,日志是记录在数据库中,数据库可能连接不上,此时可以将日志记录在文件中,在合适的时间转录到数据库中,把日志记录在数据库和记录在文件当成两种记录日志的策略)。

阅读全文 »

目前还有不少朋友对留言功能开通不是很了解,已经很久没有更新文章了,还是决定将一些大家都存在的疑惑问题再集中整理一下,写成一篇文章,希望能对大家有帮助。

网上已经有各种资料已经说的很清楚了,18年3月后就关闭了留言功能,政策原因,理解一下。

下面介绍两种开通留言功能的方法。

公众号迁移

如果你是个人类型订阅号,建议直接看第二种开通留言的小程序留言方法或本方法的最后一点,如果你是企业类型订阅号,有条件的还是建议进行迁移,一步到位,只是迁移的过程麻烦一点。下面分几个方向对迁移进行说明。

阅读全文 »

网上已经有各种资料已经说的很清楚了,18年3月后就关闭了留言功能,政策原因,理解一下。

企业类型公众号的可以考虑通过公众号迁移来开通留言功能,简单来说就是将别人订阅号的留言功能迁移到你的企业类型订阅号上,这样你的号就有留言功能了,迁移的目标账户必须是组织类型的账户如企业类型订阅号。迁移的具体流程为:
公众号迁移流程)

那么到底什么是公众号迁移?

因为公众号不支持直接变更主体,所以公众平台推出帐号迁移功能,通过此功能可将A帐号的粉丝、违规记录、文章素材(可选)、微信号(可选)迁移至B帐号。

温馨提示:例如帐号A迁移至B,那迁移完成,A帐号被回收,B帐号获得A帐号的粉丝等信息继续使用,但主体还是B。

阅读全文 »

在这个函数中,会调用getsockopt函数,获取socket套接字是否有连接的异常,但是这个getsockopt函数有个连续调用的问题,如果你第二次调用这个函数,之前fd上的错误就被清除了,导致本来这个socket套接字是有问题的,但是检测不出来了,目前这个evhttp_connection_cb的bug就在这里,如果后端服务器根本就没有开启的情况下,该函数中的getsockopt也检测不到异常了,导致其误以为连接上了,流程会一直往下走,会继续去读取后端服务器的body,却发现读不到数据,导致连接503错误,原本应该是getsockopt检测到连接异常后,就响应500的错误的。

阅读全文 »

可以参考下典型的http会话,服务器响应由一系列文本指令组成, 并使用 CRLF 分隔,它们被划分为三个不同的块:

  • 第一行是 状态行,包括使用的HTTP协议版本,状态码和一个状态描述(可读描述文本)。
  • 接下来每一行都表示一个HTTP首部,为客户端提供关于所发送数据的一些信息(如类型,数据大小,使用的压缩算法,缓存指示)。与客户端请求的头部块类似,这些HTTP首部组成一个块,并以一个空行结束。
  • 最后一块是数据块,包含了响应的数据(如果有的话)。

具体实现如下代码,收到postman发过来的消息后,将收到的消息显示出来,并回复状态ok的响应,这就完成了一个最简单的http服务器模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>

int main(void)
{
int fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd == -1) {
printf("create socket error %d\n", errno);
exit(1);
}
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
struct sockaddr_in addr;
addr.sin_family = PF_INET;
addr.sin_port = htons(8081);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd, reinterpret_cast<struct sockaddr *>(&addr),
sizeof(addr)) != 0) {
printf("bind socket error %d\n", errno);
switch (errno)
{
case EADDRINUSE:
std::cout << "Address already in use" << std::endl;
break;

default:
break;
}
close(fd);
exit(1);
}
if (listen(fd, 1024) != 0) {
printf("listen socket error %d\n", errno);
close(fd);
exit(1);
}
while(1) {
struct sockaddr_in cli;
int len = sizeof(cli);
int nfd = accept(fd, reinterpret_cast<struct sockaddr *>(&cli),
reinterpret_cast<socklen_t *>(&len));
if(nfd == -1) {
printf("accept socket error %d\n", errno);
close(fd);
exit(1);
}

char buf[1024];
char content[] = "hello http!";
bzero(buf, 1024);
recv(nfd, buf, 1024, 0);
printf("----------------recv msg----------------\n%s\n", buf);
bzero(buf, 1024);
sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n"
"Content-Type: text/html\r\n\r\n%s", static_cast<int>(strlen(content)), content);
printf("----------------ready to send buf----------------\n%s\n", buf);
if (send(nfd, buf, strlen(buf), 0) == -1) {
printf("write socket error %d\n", errno);
}
if (close(nfd) != 0)
printf("close nfd err: %d\n", errno);
}

if (close(fd) != 0)
printf("close fd err: %d\n", errno);
exit(0);
}

这里写法可以留意一下,将socket建立,绑定监听放在while循环之上,循环里面只要连续的accept就行了,完了之后把accept的fd给close掉,close就是代表主动关闭连接了。
发送http请求
服务器端接收情况
如果你没有下载postman的话,也可以使用curl命令发送请求。
curl发送请求
服务器端接收情况

阅读全文 »

参考链接:https://www.geeksforgeeks.org/tcp-server-client-implementation-in-c/

建立HTTP服务器的流程都是一样的,可以参考TCP Server的建立过程,因为Http是基于Tcp协议的,底层的原理都是相通的,参考了man手册大概介绍一下。

创建socket

创建socket,此时会返回一个描述符,头文件为#include <sys/socket.h>,函数原型为int socket(int domain, int type, int protocol);,domain参数指明通信将在哪个通信族发生,一般选PF_INET即Iternet版本4协议,socket参数指明通信的类型,可选择SOCK_STREAM/SOCK_DGRAM/SOCK_RAW,SOCK_STREAM类型提供基于序列的,可靠的,双向连接的字节流,可以支持带外数据传输机制,指的是TCP。 SOCK_DGRAM套接字支持数据报(无连接,不可靠的最大固定长度(通常很小)的消息),指的是UDP。 SOCK_RAW套接字提供对内部网络协议和接口的访问,仅超级用户可用。protocal协议指明与套接字使用的特定的协议,一般填0即可。
SOCK_STREAM类型的套接字是全双工字节流,类似于管道。 流套接字必须处于连接状态,然后才能在其上发送或接收任何数据。 通过connect或connectx调用创建与另一个套接字的连接。 连接后,可以使用read和write调用或send和recv调用的某些变型函数传输数据。 当会话已完成时,可以执行close。 带外数据也可以按照send中的说明进行发送,也可以按照recv中的说明进行接收。
如果发生错误,则返回-1,否则返回值是引用套接字的描述符。
所以可以这么写:

1
2
3
4
5
6
7
8
// socket create and verification 
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...\n");
exit(0);
}
else
printf("Socket successfully created..\n");
阅读全文 »

参考文章:
https://www.geeksforgeeks.org/tcp-3-way-handshake-process/
https://www.inetdaemon.com/tutorials/internet/tcp/3-way_handshake.shtml

传输控制协议中的TCP三向握手(也称为TCP握手;三消息握手和/或SYN-SYN-ACK)是TCP通过基于Internet协议的网络建立TCP / IP连接的方法。 TCP的三向握手技术通常称为“ SYN-SYN-ACK”(或更准确地说是SYN,SYN-ACK,ACK),因为TCP传输了三种消息来协商和启动两台计算机之间的TCP会话。 TCP握手机制经过设计,因此,尝试进行通信的两台计算机可以在传输SSH和HTTP Web浏览器请求之类的数据之前,协商网络TCP套接字连接的参数。

这也可以看作是建立TCP连接的一种方式。 在进入细节之前,让我们看一些基础知识。 TCP代表传输控制协议,该协议表示它做了一些事情以可靠的方式控制数据的传输。

互联网上设备之间的通信过程根据当前的TCP/IP套件模型(OSI参考模型的精简版)进行。 应用程序层是TCP/IP模型的顶层堆栈,在该层中,网络引用的应用程序(如客户端上的Web浏览器)与服务器建立连接。 信息从应用程序层传输到传输层,在这里我们的主题成为现实。 该层的两个重要协议是TCP,UDP(用户数据报协议),而TCP在其中很普遍(因为它为建立的连接提供了可靠性)。 但是,您可以在查询DNS服务器中找到UDP的应用程序,以获取与该网站使用的域名的二进制等效项。

TCP通过称为“带重传的肯定确认”(PAR)的方式提供可靠的通信。 传输层的协议数据单元(PDU)称为段。 现在,使用PAR的设备重新发送数据单元,直到它收到确认为止。 如果在接收器端接收的数据单元已损坏(它使用用于错误检测的传输层的校验和功能检查数据),则接收器将丢弃该段。 因此,发送方必须重新发送未收到肯定确认的数据单元。 通过上述机制,您可以实现在发送方(客户端)和接收方(服务器)之间交换三个段,以建立可靠的TCP连接。 让我们研究一下这种机制是如何工作的:
TCP-connection
3-way-handshake

阅读全文 »
0%