最近尝试了一下服务器端的推送,之前的做法都是客户端轮询,定时向服务器发送请求。但这造成了我的一些困扰:
1:轮询是由客户端发起的,那么在服务端就不能判别我要推送的内容是否已经过期,因为我很难判断某个信息是否已经推送给全部的客户端,那么服务端就需要缓存大量的数据。如果数据保存在数据库,那么还要每次请求都需要查询数据库,这对数据库和系统设计都是一个很大的挑战。
2:请求的频率太高,每次的请求包中含有同样的数据,这对pc来说也许算不得什么,但是对于移动客户端来讲,这应该不是最佳的方案。尤其是遇到还要做权限判断的时候,那么服务端的逻辑和效率也会造成用户体验的降低。
好在Html5为我们提供了一种方式:Server-Sent Events包含新的HTML元素EventSource和新的MIME类型 text/event-stream来完成我的需要。
因为是第一次接触Html5,w3school中也有对EventSource的说明和使用。于是马上开始着手实践。
页面脚本就不用说了,按照w3school的方式即可。
source.onmessage=function(event)
{
document.getElementById("result").innerHTML+=event.data + "<br />";
};
服务端的代码也是如初一折,w3school提供了php和asp的代码:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$time = date('r');
echo "data: The server time is: {$time}\n\n";
flush();
?>
//asp方式
<%
Response.ContentType="text/event-stream"
Response.Expires=-1
Response.Write("data: " & now())
Response.Flush()
%>
//代码解释:
- 把报头 "Content-Type" 设置为 "text/event-stream"
- 规定不对页面进行缓存
- 输出发送日期(始终以 "data: " 开头)
- 向网页刷新输出数据
也许大家应该注意到,php和asp的案例有一点不一样,就是php推送的信息一个使用了"\n\n"作为结束标志,而asp却没有。而本人实践则是用asp.net+mvc,经过测试,如果不以"\n\n"作为结束标志,那么客户端将不能接收到推送的值。还有需要特别声明一下:推送的信息格式必须为”data:内容\n\n“,否则。。。
{
HttpContext.Response.ContentType = "text/event-stream";
HttpContext.Response.CacheControl = "no-cache";
HttpContext.Response.Write("data:" + DateTime.Now.ToString()+ "\n\n");
HttpContext.Response.Flush();
}
至此,客户端应该可以收到服务端推送的值。而如此简单的结构真的可以完成我们需要的功能设计吗?
此例我们只是推送了一个当前时间,而我们实际要推送的值是不断变化的,不然也就没有推送的必要了。
于是我想到了将订阅的请求保存起来,当需要推送的时候,在对每个请求进行循环推送,于是有了下面的代码:
{
private static IDictionary<string, HttpResponseBase> contexts = new Dictionary<string, HttpResponseBase>();
public static void AddHttpContext(HttpContextBase context)
{
var token = context.GetToken(”CookieName“);
if (!contexts.Keys.Contains(token))
contexts.Add(token, context.Response);
}
private static void Publish()
{
foreach (var context in contexts.Values)
{
context.ContentType = "text/event-stream";
context.CacheControl = "no-cache";
msg = GetData(context.GetToken("CookieName"));
context.Write("data:" + msg + "\n\n");
context.Flush();
}
}
public void Subscribe()
{
PublishService.AddHttpContext(HttpContext);
PublishService.Publish();
}
}
可是在进行测试的时候Chrome告诉我:EventSource's response has a MIME type ("text/plain") that is not "text/event-stream". Aborting the connection.
而FF告诉我:Firefox 无法建立到 http://localhost:8000/Location/Notification/Subscribe 服务器的连接。
经过调试发现,在每次flush的时候发生异常:Server cannot flush a completed response.这究竟是为啥呢?不论是google,还是baidu,我都没能找到合适的答案,所以此案至今未结,如哪位知道请细说一二。
于是乎,我放弃了这种方式,转而就推送一个时间看看是什么效果。结果发现Chrome每隔3秒向客户端推送一次,而FF是每5秒推送一次。有了这样一个发现,那么服务端的设计就应该是另一个样子:
原始链接:More Assertions
现在你应该已经读完了入门篇并且会使用GTest来写测试。是时候来学一些新把戏了。这篇文档将教会你更多知识:用断言构造复杂的失败信息,传递致命失败,重用和加速你的test fixtures,以及在你的测试中使用不同的标志位。
版本号:v_0.1
更多关于断言的知识
这一章节将覆盖一些较少被使用,但非常重要的关于断言的知识。
以下我们提到的几个断言实际上并不对值或表达式进行测试。取而代之的是它们直接产生一个通过或失败。像大多数做做测试的宏一样,我们可以定制失败信息并且流定向到它们。
SUCCESS()直接产生一个通过。但这并不表明整个测试通过。仅当所有断言都通过这个测试才被认为是通过。
注:这个宏目前只存在于文档中,是一个空操作,不会产生任何用户可见的输出。但是以后我们会考虑添加有关信息。
FAIL(); | ADD_FAILURE(); | ADD_FAILURE_AT("file_path", line_number); |
FAIL()产生一个致命失败,而ADD_FAILURE和ADD_FAILURE_AT生成非致命失败。在类似于switch-case的控制流程中决定测试是否通过,用这些宏比用bool表达式更直接方便。例如以下代码:
case 1: ... some checks ...
case 2: ... some other checks
...
default: FAIL() << "We shouldn't get here.";
}
在Linux,Windows和Mac上可用。
以下断言用来判断代码是否抛出异常。
致命断言 | 非致命断言 | 通过条件 |
ASSERT_THROW(statement, exception_type); | EXPECT_THROW(statement, exception_type); | 抛出指定类型的异常 |
ASSERT_ANY_THROW(statement); | EXPECT__ANY_THROW(statement); | 抛出任意类型的异常 |
ASSERT_NO_THROW(statement); | EXPECT_NO_THROW(statement); | 没有抛出异常 |
例子:
EXPECT_NO_THROW({
int n = 5;
Bar(&n);
});
在Linux,Windows和Mac上可用。
尽管GTest提供了丰富的断言,但还是不能覆盖我们在现实中所能遇到的场景,而且也不可能预见所有的情况(这不是一个好主意)。有时用户想使用EXPECT_TRUE来测试一个复杂的表达式,但是却没有合适的宏。这使得你无法显示表达式某一部分的值,当出错之后你很难找到问题到底出在哪里。某些用户选择自己构造失败信息,然后流定向到EXPECT_TRUE()。但这么做问题非常多,特别是当这个表达式有副作用或者开销巨大。
GTest提供了三个选项来解决这个问题。
如果你的函数或函数体返回bool类型(或者可以被隐式的转换为bool类型),在谓词断言中使用这些函数的话,参数将自动被打印。
致命断言 | 非致命断言 | 通过条件 |
ASSERT_PRED1(pred1, val1); | EXPECT_PRED1(pred1, val1); | pred1(val1)返回true |
ASSERT_PRED2(pred1, val1, val2); | EXPECT_PRED2(pred1, val1, val2); | pred2(val1, val2)返回true |
... | ... | ... |
在上表中,predn是一个有n个参数的谓词函数或函数体,val1, val2, ...直到valn代表它的参数。断言当谓词判断在给定参数返回true的情况下通过,不然失败。当断言失败后,它就会打印每一个参数。不管通过还是失败,这些参数的值只会被计算一次。
请看以下例子:
bool MutuallyPrime(int m, int n) { ... }
const int a = 3;
const int b = 4;
const int c = 10;
断言EXPECT_PRED2(MutuallyPrime, a, b)会通过,而断言EXPECT_PRED2(MutuallyPrime, b, c)会失败并打印以下信息:
!MutuallyPrime(b, c) is false, where
b is 4
c is 10
注:
- 当使用ASSERT_PRED*或EXPECT_PRED*遇到编译错误"no matching function to call"时,请参考这篇文章如何解决。
- 当前我们支持的参数数量小于等于5。如果你希望支持更多的参数,请让我们知道。
在Linux,Windows和Mac上可用。
虽然EXPECT_PRED*和它的小伙伴们用起来很方便,但是语法却不能让人满意。对于不同数量的参数你必须使用不同的宏,这看上去更向Lisp而不是C++。现在::testing::AssertionResult来帮助你解决这个问题。
一个AssertionResult对象代表一次断言的结果(通过或失败,以及相关的信息)。你可以用以下任意一个工厂函数来创建一个AssertionResult对象。
// Returns an AssertionResult object to indicate that an assertion has
// succeeded.
AssertionResult AssertionSuccess();
// Returns an AssertionResult object to indicate that an assertion has
// failed.
AssertionResult AssertionFailure();
}
你可以使用"<<"操作符把信息流定向到AssertionResult对象。
为了在bool断言(例如EXPECT_TRUE())中提供可读性更好的信息,你可以实现一个返回AssertionResult的函数而不是仅仅返回bool类型。例如,你可以这样定义IsEven()函数:
if ((n % 2) == 0
没有评论:
发表评论