热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

微信协议交换、消息收发

为什么80%的码农都做不了架构师?微信技术总监解读微信架构的秘密视频:http:djt.qq.comvideo9PPT:http:djt.qq.c

为什么80%的码农都做不了架构师?>>>   hot3.png

微信技术总监解读微信架构的秘密

视频:http://djt.qq.com/video/9

PPT: http://djt.qq.com/bbs/cdnconnect.php?filename=05_WeiXinZhiDao_DJT.QQ.COM.zip



一。微信协议概览

微信传输协议,官方公布甚少,在微信技术总监所透漏PPT《微信之道—至简》文档中,有所体现。

纯个人理解:

因张小龙做邮箱Foxmail起家,继而又做了QQ Mail等,QQ Mail是国内第一个支持Exchange ActiveSync协议的免费邮箱,基于其从业背景,微信从一开始就采取基于ActiveSync的修改版状态同步协议Sync,也就再自然不过了。

一句话:增量式、按序、可靠的状态同步传输的微信协议。

大致交换简图如下:



如何获取新数据呢:

  1. 服务器端通知,客户端获取
  2. 客户端携带最新的SyncKey,发起数据请求
  3. 服务器端生成最新的SyncKey连同最新数据发送给客户端
  4. 基于版本号机制同步协议,可确保数据增量、有序传输
  5. SyncKey,由服务器端序列号生成器生成,一旦有新消息产生,将会产生最新的SyncKey。类似于版本号

服务器端通知有状态更新,客户端主动获取自从上次更新之后有变动的状态数据,增量式,顺序式。

二。微信Web端简单调试

在线版本微信:

https://webpush.weixin.qq.com/

通过Firefox + Firebug组合调试,也能证实了微信大致通过交换SyncKey方式获取新数据的论述。

1. 发起GET长连接检测是否存在新的需要同步的数据

会携带上最新SyncKey

https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery18306073923335455973_1393208247730&r=1393209241862&sid=s7c%2FsxpGRSihgZAA&uin=937355&deviceid=e542565508353877&synckey=1_620943725%7C2_620943769%7C3_620943770%7C11_620942796%7C201_1393208420%7C202_1393209127%7C1000_1393203219&_=1393209241865

返回内容:

window.synccheck={retcode:"0",selector:"2"}

selector值大于0,表示有新的消息需要同步。

据目测,心跳周期为27秒左右。

2. 一旦有新数据,客户端POST请求主动获取同步的数据

https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=s7c%2FsxpGRSihgZAA&r=1393208447375

携带消息体:

{"BaseRequest":{"Uin":937355,"Sid":"s7c/sxpGRSihgZAA"},"SyncKey":{"Count":6,"List":[{"Key":1,"Val":620943725},{"Key":2,"Val":620943767},{"Key":3,"Val":620943760},{"Key":11,"Val":620942796},{"Key":201,"Val":1393208365},{"Key":1000,"Val":1393203219}]},"rr":1393208447374}

会携带上最新的SyncKey,会返回复杂结构体JSON内容。

但浏览端收取到消息之后,如何通知服务器端已确认收到了?Web版本微信,没有去做。

在以往使用过程中,曾发现WEB端有丢失消息的现象,但属于偶尔现象。但Android微信客户端(只要登陆连接上来之后)貌似就没有丢失过。

3. 发送消息流程

  1. 发起一个POST提交,用于提交用户需要发送的消息

    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?sid=lQ95vHR52DiaLVqo&r=1393988414386

发送内容:

{"BaseRequest":{"Uin":937355,"Sid":"lQ95vHR52DiaLVqo","Skey":"A6A1ECC6A7DE59DEFF6A05F226AA334DECBA457887B25BC6","DeviceID":"e937227863752975"},"Msg":{"FromUserName":"yongboy","ToUserName":"hehe057854","Type":1,"Content":"hello","ClientMsgId":1393988414380,"LocalID":1393988414380}}

相应内容:

{ "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "MsgID": 1020944348, "LocalID": "1393988414380" }

  1. 再次发起一个POST请求,用于申请最新SyncKey

    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=lQ95vHR52DiaLVqo&r=1393988414756

发送内容:

{"BaseRequest":{"Uin":937355,"Sid":"lQ95vHR52DiaLVqo"},"SyncKey":{"Count":6,"List":[{"Key":1,"Val":620944310},{"Key":2,"Val":620944346},{"Key":3,"Val":620944344},{"Key":11,"Val":620942796},{"Key":201,"Val":1393988357},{"Key":1000,"Val":1393930108}]},"rr":1393988414756}

响应的(部分)内容:

"SKey": "8F8C6A03489E85E9FDF727ACB95C93C2CDCE9FB9532FC15B"

  1. 终止GET长连接,使用最新SyncKey再次发起一个新的GET长连接

    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery1830245810089652082181393988305564&r=1393988415015&sid=lQ95vHR52DiaLVqo&uin=937355&deviceid=e937227863752975&synckey=1620944310%7C2620944348%7C3620944344%7C11620942796%7C2011393988357%7C10001393930108&=1393988415016

三。微信Android简单分析

Windows桌面端Android虚拟机中运行最新版微信(5.2),通过tcpdump/Wireshark组合封包分析,以下为分析结果。

0. 初始连接记录

简单记录微信启动之后请求:

11:20:35 dns查询 dns.weixin.qq.com 返回一组IP地址 11:20:35 DNS查询 long.weixin.qq.com 返回一组IP地址,本次通信中,微信使用了最后一个IP作为TCP长连接的连接地址。 11:20:35 http://dns.weixin.qq.com/cgi-bin/micromsg-bin/newgetdns?uin=0&clientversion=620888113&scene=0&net=1 用于请求服务器获得最优IP路径。服务器通过结算返回一个xml定义了域名:IP对应列表。仔细阅读,可看到微信已经开始了国际化的步伐:香港、加拿大、韩国等。 具体文本,请参考:https://gist.github.com/yongboy/9341884 11:20:35 获取到long.weixin.qq.com最优IP,然后建立到101.227.131.105的TCP长连接 11:21:25 POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/getprofile HTTP/1.1 (application/octet-stream) 返回一个名为“micromsgresp.dat”的附件,估计是未阅读的离线消息 11:21:31 POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/whatsnews HTTP/1.1 (application/octet-stream) 大概是资讯、订阅更新等 中间进行一些资源请求等,类似于 GET http://wx.qlogo.cn/mmhead/Q3auHgzwzM7NR4TYFcoNjbxZpfO9aiaE7RU5lXGUw13SMicL6iacWIf2A/96 图片等一些静态资源都会被分配到wx.qlogo.cn域名下面 不明白做什么用途 POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/downloadpackage HTTP/1.1 (application/octet-stream) 输出为micromsgresp.dat文件 11:21:47 GET http://support.weixin.qq.com/cgi-bin/mmsupport-bin/reportdevice?channel=34&deviceid=A952001f7a840c2a&clientversion=620888113&platform=0&lang=zh_CN&installtype=0 HTTP/1.1 返回chunked分块数据 11:21:49 POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1 (application/octet-stream)

1. 心跳频率约为5分钟

上次使用Wireshark分析有误(得出18分钟结论),再次重新分析,心跳频率在5分钟左右。

2. 登陆之后,会建立一个长连接,端口号为8080

简单目测为HTTP,初始以为是双通道HTTP,难道是自定义的用于双通道通信的HTTP协议吗,网络上可见资料都是模棱两可、语焉不详。

具体查看长连接初始数据通信,没有发现任何包含"HTTP"字样的数据,以为是微信自定义的TCP/HTTP通信格式。据分析,用于可能用于获取数据、心跳交换消息等用途吧。这个后面会详谈微信是如何做到的。

2.0 初始消息传输

个人资料、离线未阅读消息部分等通过 POST HTTP短连接单独获取。

2.1 二进制简单分析

抽取微信某次HTTP协议方式通信数据,16进制表示,每两个靠近的数字为一个byte字节:

微信协议可能如下:

一个消息包 = 消息头 + 消息体

消息头固定16字节长度,消息包长度定义在消息头前4个字节中。

单纯摘取第0000行为例,共16个字节的头部:

00 00 00 10 00 10 00 01 00 00 00 06 00 00 00 0f

16进制表示,每两个紧挨着数字代表一个byte字节。

微信消息包格式: 1. 前4字节表示数据包长度,可变 值为16时,意味着一个仅仅包含头部的完整的数据包(可能表示着预先定义好的业务意义),后面可能还有会别的消息包 2. 2个字节表示头部长度,固定值,0x10 = 16 3. 2个字节表示谢意版本,固定值,0x01 = 1 4. 4个字节操作说明数字,可变 5. 序列号,可变 6. 头部后面紧跟着消息体,非明文,加密形式 7. 一个消息包,最小16 byte字节

通过上图(以及其它数据多次采样)分析:

  1. 0000 - 0040为单独的数据包
  2. 0050行为下一个数据包的头部,前四个字节值为0xca = 202,表示包含了从0050-0110共202个字节数据
  3. 一次数据发送,可能包含若干子数据包
  4. 换行符\n,16进制表示为0x0a,在00f0行,包含了两个换行符号
  5. 一个数据体换行符号用于更细粒度的业务数据分割 是否蒙对,需要问问做微信协议的同学
  6. 所有被标记为HTTP协议通信所发送数据都包含换行符号
2.2 动手试试猜想,模拟微信TCP长连接

开始很不解为什么会出现如此怪异的HTTP双通道长连接请求,难道基于TCP通信,然后做了一些手脚?很常规的TCP长连接,传输数据时(不是所有数据传输),被wireshark误认为HTTP长连接。这个需要做一个实验证实一下自己想法,设想如下:

写一个Ping-Pong客户端、服务器端程序,然后使用Wireshark看一下结果,是否符合判断。

Java版本的请求端,默认请求8080端口:


/*** Ping Client* @author nieyong*/
package com.learn;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;import java.util.concurrent.TimeUnit;class PingClientHandler extends ChannelInboundHandlerAdapter {
private final ByteBuf firstMessage;public PingClientHandler() {
firstMessage = PooledByteBufAllocator.DEFAULT.buffer(22);// weixin 16 byte's header
firstMessage.writeByte(0);
firstMessage.writeByte(0);
firstMessage.writeByte(0);
firstMessage.writeByte(16);firstMessage.writeByte(0);
firstMessage.writeByte(16);firstMessage.writeByte(0);
firstMessage.writeByte(1);firstMessage.writeByte(0);
firstMessage.writeByte(0);
firstMessage.writeByte(0);
firstMessage.writeByte(6);firstMessage.writeByte(0);
firstMessage.writeByte(0);
firstMessage.writeByte(0);
firstMessage.writeByte(1);// just for /n
firstMessage.writeByte('\n'); // 1 byte// footer 16 byte
String welcome = "hello"; // 5 byte
firstMessage.writeBytes(welcome.getBytes());
}@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(firstMessage);
}@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg)
throws Exception {
ctx.executor().schedule(new Runnable() {
@Override
public void run() {
ctx.channel().writeAndFlush(msg);
}
}, 1, TimeUnit.SECONDS);
}@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.err.println("Unexpected exception from downstream :"
+ cause.getMessage());
ctx.close();
}
}public class PingClient {private final String host;
private final int port;public PingClient(String host, int port) {
this.host = host;
this.port = port;
}public void run() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new PingClientHandler());
}
});ChannelFuture f = b.connect(host, port).sync();f.channel().closeFuture().sync();
} finally {
// Shut down the event loop to terminate all threads.
group.shutdownGracefully();
}
}public static void main(String[] args) throws Exception {
String host = "127.0.0.1";
int port = 8080;if (args.length == 3) {
host = args[0];
port = Integer.parseInt(args[1]);
}new PingClient(host, port).run();
}
}

C语言版本的服务器程序,收到什么发送什么,没有任何逻辑,默认绑定8080端口:

/*** nieyong@youku.com* how to compile it:* gcc pong_server.c -o pong_server /usr/local/lib/libev.a -lm */
#include
#include
#include
#include
#include
#include
#include
#include #include "../include/ev.h"static int server_port = 8080;struct ev_loop *loop;
typedef struct {
int fd;
ev_io ev_read;
} client_t;ev_io ev_accept;static void free_res(struct ev_loop *loop, ev_io *ws);int setnonblock(int fd) {
int flags = fcntl(fd, F_GETFL);
if (flags <0)
return flags;flags |&#61; O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) <0)
return -1;return 0;
}static void read_cb(struct ev_loop *loop, ev_io *w, int revents) {
client_t *client &#61; w->data;
int r &#61; 0;
char rbuff[1024];
if (revents & EV_READ) {
r &#61; read(client->fd, &rbuff, 1024);
}if (EV_ERROR & revents) {
fprintf(stderr, "error event in read\n");
free_res(loop, w);
return ;
}if (r <0) {
fprintf(stderr, "read error\n");
ev_io_stop(EV_A_ w);
free_res(loop, w);
return;
}if (r &#61;&#61; 0) {
fprintf(stderr, "client disconnected.\n");
ev_io_stop(EV_A_ w);
free_res(loop, w);
return;
}send(client->fd, rbuff, r, 0);
}static void accept_cb(struct ev_loop *loop, ev_io *w, int revents) {
struct sockaddr_in client_addr;
socklen_t client_len &#61; sizeof(client_addr);
int client_fd &#61; accept(w->fd, (struct sockaddr *) &client_addr, &client_len);
if (client_fd &#61;&#61; -1) {
fprintf(stderr, "the client_fd is NULL !\n");
return;
}client_t *client &#61; malloc(sizeof(client_t));
client->fd &#61; client_fd;
if (setnonblock(client->fd) <0)
err(1, "failed to set client socket to non-blocking");client->ev_read.data &#61; client;ev_io_init(&client->ev_read, read_cb, client->fd, EV_READ);
ev_io_start(loop, &client->ev_read);
}int main(int argc, char const *argv[]) {
int ch;
while ((ch &#61; getopt(argc, argv, "p:")) !&#61; -1) {
switch (ch) {
case &#39;p&#39;:
server_port &#61; atoi(optarg);
break;
}
}loop &#61; ev_default_loop(0);
struct sockaddr_in listen_addr;
int reuseaddr_on &#61; 1;
int listen_fd &#61; socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd <0)
err(1, "listen failed");
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) &#61;&#61; -1)
err(1, "setsockopt failed");memset(&listen_addr, 0, sizeof(listen_addr));
listen_addr.sin_family &#61; AF_INET;
listen_addr.sin_addr.s_addr &#61; INADDR_ANY;
listen_addr.sin_port &#61; htons(server_port);if (bind(listen_fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr)) <0)
err(1, "bind failed");
if (listen(listen_fd, 5) <0)
err(1, "listen failed");
if (setnonblock(listen_fd) <0)
err(1, "failed to set server socket to non-blocking");ev_io_init(&ev_accept, accept_cb, listen_fd, EV_READ);
ev_io_start(loop, &ev_accept);
ev_loop(loop, 0);return 0;
}static void free_res(struct ev_loop *loop, ev_io *w) {
client_t *client &#61; w->data;
if (client &#61;&#61; NULL) {
fprintf(stderr, "the client is NULL !!!!!!");
return;
}ev_io_stop(loop, &client->ev_read);
close(client->fd);
free(client);
}




这里有一个现场图&#xff1a;

可以尝试稍微改变输出内容&#xff0c;去除换行符“\n”&#xff0c;把端口换成9000&#xff0c;试试看&#xff0c;就会发现Wireshark输出不同的结果来。

2.3 结论是什么呢&#xff1f;

若使用原始TCP进行双向通信&#xff0c;则需要满足以下条件&#xff0c;可以被类似于Wireshark协议拦截器误认为是HTTP长连接&#xff1a;

  1. 使用80/8080端口&#xff08;81/3128/8000经测试无效&#xff09; 也许8080一般被作为WEB代理服务端口&#xff0c;微信才会享用这个红利吧。
  2. 输出的内容中&#xff0c;一定要包含换行字符"\n"

因此&#xff0c;可以定性为微信使用了基于8080端口TCP长连接&#xff0c;一旦数据包中含有换行"\n"符号&#xff0c;就会被Wireshark误认为HTTP协议。可能微信是无心为之吧。

3. 新消息获取方式

  1. TCP长连接接收到服务器通知有新消息需要获取
  2. APP发起一个HTTP POST请求获取新状态消息&#xff0c;会带上当前SyncKey 地址为&#xff1a;http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1&#xff0c;看不到明文
  3. APP获取到新的消息&#xff0c;会再次发起一次HTTP POST请求&#xff0c;告诉服务器已确认收到&#xff0c;同时获取最新SyncKey 地址为&#xff1a;http://short.weixin.qq.com/cgi-bin/micromsg-bin/kvreport&#xff0c;看不到明文
  4. 接受一个消息&#xff0c;TCP长连接至少交互两次&#xff0c;客户端发起两次HTTP POST请求 
    具体每次交互内容是什么&#xff0c;有些模糊
  5. 服务器需要支持&#xff1a;状态消息获取标记&#xff0c;状态消息确认收取标记。只有被确认收到&#xff0c;此状态消息才算是被正确消费掉
  6. 多个不同设备同一账号同时使用微信&#xff0c;同一个状态消息会会被同时分发到多个设备上

此时消息请求截图如下&#xff1a;

4. 发送消息方式

发送消息走已经建立的TCP长连接通道&#xff0c;发送消息到服务器&#xff0c;然后接受确认信息等&#xff0c;产生一次交互。

小伙伴接收到信息阅读也都会收到服务器端通知&#xff0c;产生一次交互等。

可以确定&#xff0c;微信发送消息走TCP长连接方式&#xff0c;因为不对自身状态数据产生影响&#xff0c;应该不交换SyncKey。

  • 在低速网络下&#xff0c;大概会看到消息发送中的提示&#xff0c;属于消息重发机制
  • 网络不好有时客户端会出现发送失败的红色感叹号
  • 已发送到服务器但未收到确认的消息&#xff0c;客户端显示红色感叹号&#xff0c;再次重发&#xff0c;服务器作为重复消息处理&#xff0c;反馈确认
  • 上传图片&#xff0c;会根据图片大小&#xff0c;分割成若干部分&#xff08;大概1.5K被划分为一部分&#xff09;&#xff0c;同一时间点&#xff0c;客户端会发起若干次POST请求&#xff0c;各自上传成功之后&#xff0c;服务器大概会合并成一个完整图片&#xff0c;返回一个缩略图&#xff0c;显示在APP聊天窗口内。APP作为常规的文字消息发送到服务器端
  • 上传音频&#xff0c;则单独走TCP通道&#xff0c;一个两秒的录制音频&#xff0c;客户端录制完毕&#xff0c;分为两块传输&#xff0c;一块最大1.5K左右&#xff0c;服务端响应一条数据通知确认收到。共三次数据传输。
    音频和纯文字信息一致&#xff0c;都是走TCP长连接&#xff0c;客户端发送&#xff0c;服务器端确认。

四。微信协议小结

  1. 发布的消息对应一个ID&#xff08;只要单个方向唯一即可&#xff0c;服务器端可能会根ID判断重复接收&#xff09;&#xff0c;消息重传机制确保有限次的重试&#xff0c;重试失败给予用户提示&#xff0c;发送成功会反馈确认&#xff0c;客户端只有收到确认信息才知道发送成功。发送消息可能不会产生新SyncKey。
  2. 基于版本号&#xff08;SynKey&#xff09;的状态消息同步机制&#xff0c;增量、有序传输需求水到渠成。长连接通知/短连接获取、确认等&#xff0c;交互方式简单&#xff0c;确保了消息可靠谱、准确无误到达。
  3. 客户端/服务器端都会存储消息ID处理记录&#xff0c;避免被重复消费客户端获取最新消息&#xff0c;但未确认&#xff0c;服务器端不会认为该消息被消费掉。下次客户端会重新获取&#xff0c;会查询当前消息是否被处理过。根据一些现象猜测。
  4. 总体上看&#xff0c;微信协议跨平台&#xff08;TCP或HTPP都可呈现&#xff0c;处理方式可统一&#xff09;&#xff0c;通过“握手”同步&#xff0c;很可靠&#xff0c;无论哪一个平台都可以支持的很好
  5. 微信协议最小成本为16字节&#xff0c;大部分时间若干个消息包和在一起&#xff0c;批量传输。微信协议说不上最简洁&#xff0c;也不是最节省流量&#xff0c;但是非常成功的。
  6. 若服务器检测到一些不确定因素&#xff0c;可能会导致微启用安全套接层SSL协议进行常规的TCP长连接传输。短连接都没有发生变化

以上&#xff0c;根据有限资料和数据拦截观察总结得出&#xff0c;啰啰嗦嗦&#xff0c;勉强凑成一篇&#xff0c;会存在一些不正确之处&#xff0c;欢迎给予纠正。在多次

五。附录

Microsoft Exchange Active Sync协议&#xff0c;简称EAS&#xff0c;分为folderrsync(同步文件夹目录&#xff0c;即邮箱内有哪几个文件夹)和sync&#xff08;每个文件夹内有哪些文档&#xff09;两部分。

某网友总结的协议一次回话大致示范&#xff1a;


Client: synckey&#61;0 //第一次key为0 Server: newsynckey&#61;1235434 //第一次返回新key
Client: synckey&#61;1235434 //使用新key查询 Server: newsynckey&#61;1647645,data&#61;*****//第一次查询&#xff0c;得到新key和数据 Client: synckey&#61;1647645
Server: newsynckey&#61;5637535,data&#61;null //第二次查询&#xff0c;无新消息 Client: synckey&#61;5637535
Server: newsynckey&#61;8654542, data&#61;****//第三次查询&#xff0c;增量同步



  • 上页中的相邻请求都是隔固定时间的&#xff0c;如两分钟
  • 客户端每次使用旧key标记自己的状态&#xff0c;服务端每次将新key和增量数据一起返回。
  • key是递增的&#xff0c;但不要求连续
  • 请求的某个参数决定服务器是否立即返回


网页版&#xff1a;请求sync

Remote Address:101.227.131.107:443
Request URL:https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?skey&#61;%40crypt_9d8701a_f204b64656015a7f443f93f205cf300f&callback&#61;jQuery18305112716106232256_1408758017484&r&#61;1408759718398&sid&#61;wX205zFq194BSWgk&uin&#61;1543895480&deviceid&#61;e332282535847305&synckey&#61;1_622457458%7C2_622458321%7C3_622458304%7C11_622457803%7C201_1408759529%7C1000_1408755736&_&#61;1408759718399
Request Method:GET
Status Code:200 OK
Request Headersview source
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q&#61;0.8,en;q&#61;0.6
Connection:keep-alive
COOKIE:ptui_loginuin&#61;24310247; lv_irt_id&#61;5834d0f4739e45237a0babe53bb25d48; pt2gguin&#61;o0024310247; uin&#61;o0024310247; skey&#61;&#64;nNtzDnsWb; ptisp&#61;ctc; RK&#61;pI8K3SSgaz; ptcz&#61;7e77bcbdd43d84b42d1aecace2c70ea7918b90f679fae6c4320dc3610f44494b; pgv_info&#61;ssid&#61;s7626563765; pgv_pvid&#61;1130740238; o_COOKIE&#61;24310247; pgv_pvi&#61;6056579072; pgv_si&#61;s1338950656; wxstaytime&#61;1408757408
Host:webpush.weixin.qq.com
Referer:https://wx.qq.com/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Query String Parametersview sourceview URL encoded
skey:&#64;crypt_9d8701a_f204b64656015a7f443f93f205cf300f
callback:jQuery18305112716106232256_1408758017484
r:1408759718398
sid:wX205zFq194BSWgk
uin:1543895480
deviceid:e332282535847305
synckey:1_622457458|2_622458321|3_622458304|11_622457803|201_1408759529|1000_1408755736
_:1408759718399
Response Headersview source
Access-Control-Allow-Origin:*
Content-Length:43
Content-Type:text/Javascript




转:https://my.oschina.net/greki/blog/285017



推荐阅读
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • jQuery实现简单的动画效果及用法详解
    本文详细介绍了使用jQuery实现简单动画效果的方法,包括显示/隐藏、向上收缩/向下展开、淡入/淡出、自定义动画等。同时提供了具体的用法示例,并解释了参数的含义和使用技巧。通过本文的学习,读者可以掌握如何使用jQuery实现各种动画效果,为网页增添生动和互动性。 ... [详细]
  • 本文介绍了Java后台Jsonp处理方法及其应用场景。首先解释了Jsonp是一个非官方的协议,它允许在服务器端通过Script tags返回至客户端,并通过javascript callback的形式实现跨域访问。然后介绍了JSON系统开发方法,它是一种面向数据结构的分析和设计方法,以活动为中心,将一连串的活动顺序组合成一个完整的工作进程。接着给出了一个客户端示例代码,使用了jQuery的ajax方法请求一个Jsonp数据。 ... [详细]
  • wpf+mvvm代码组织结构及实现方式
    本文介绍了wpf+mvvm代码组织结构的由来和实现方式。作者回顾了自己大学时期接触wpf开发和mvvm模式的经历,认为mvvm模式使得开发更加专注于业务且高效。与此同时,作者指出mvvm模式相较于mvc模式的优势。文章还提到了当没有mvvm时处理数据和UI交互的例子,以及前后端分离和组件化的概念。作者希望能够只关注原始数据结构,将数据交给UI自行改变,从而解放劳动力,避免加班。 ... [详细]
  • 本文介绍了如何在Jquery中通过元素的样式值获取元素,并将其赋值给一个变量。提供了5种解决方案供参考。 ... [详细]
  • Jquery 跨域问题
    为什么80%的码农都做不了架构师?JQuery1.2后getJSON方法支持跨域读取json数据,原理是利用一个叫做jsonp的概念。当然 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
  • 本文介绍了利用ARMA模型对平稳非白噪声序列进行建模的步骤及代码实现。首先对观察值序列进行样本自相关系数和样本偏自相关系数的计算,然后根据这些系数的性质选择适当的ARMA模型进行拟合,并估计模型中的位置参数。接着进行模型的有效性检验,如果不通过则重新选择模型再拟合,如果通过则进行模型优化。最后利用拟合模型预测序列的未来走势。文章还介绍了绘制时序图、平稳性检验、白噪声检验、确定ARMA阶数和预测未来走势的代码实现。 ... [详细]
  • 我正在尝试使用scrapycrallsingle运行完美运行的scrapy蜘蛛,但我无法在python脚本中运行它.主要问题是从不执行SingleBlogSpider.parse方 ... [详细]
  • document .ready中的函数怎么被按钮调用? ... [详细]
  • jquery定时器调用函数时传参varli$(.firstli:first-child);varindex0;vars$(.firstli);functiongundong(a ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • 有个事情移植想不明白
    后端开发|php教程不明白,移植,事情后端开发-php教程为什么我客户端通过http请求服务端服务端发张图片到客户端这个传输为什么那么慢一共也就300多kb但一共传了5秒多如果直接 ... [详细]
  • fiddler_Fiddler的原理和基本介绍
    一,Fiddler的工作原理   1,Fiddler是位于客户端和服务器端的HTTP ... [详细]
author-avatar
飞翔的小鸟52588
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有