MQTT协议探究3

src: https://www.cnblogs.com/linzhanfly/p/10108094.html

一 复习和目标

1 复习

  • Nodejs实现WebSocket服务器
  • Netty实现WebSocket服务器(附带了源码分析)
  • Js api实现WebSocket客户端

注:Nodejs使用的Socket.io模块实现,Netty本身对WebSocket有一定的支持,所以这两种实现都相对容易理解,大家自己可以使用自己喜欢的语言实现(参考Nodejs版本,即不需要考虑过多的情况)。

2 目标

  • 使用WebSocket协议进行发送Mqtt消息
  • 即Mqtt协议作为WebSocket协议的子协议进行通信

注1:WebSocket协议内容不多,但是远远不止这么少。但是学习不要完美主义(即学到一样东西就想着一开始就把这样东西学透),学习要带着目的性。比如:我就是想弄懂为什么前端需要使用WebSocket协议才能发送Mqtt消息?WebSocket为什么需要HTTP协议来进行协议升级协商?HTTP协议为什么基于TCP协议?TCP协议是全双工的为什么HTTP协议不能全双工?等等……

注2:协议目前学习了MQTT、TCP、HTTP和WebSocket,并不是现在学完了,以后就可以不再去学习这些,而是说对于现在的学习目的已经达到了,不需要再花费过多的精力罢了。

二 WireShark抓包

1 WebSocket连接

  • 请求升级协议:HTTP -> WebSocket(mqtt)
# 省略无关头部
Hypertext Transfer Protocol
GET /mqtt HTTP/1.1\r\n
Host: 103.61.37.192:8083\r\n
Sec-WebSocket-Version: 13\r\n
Sec-WebSocket-Protocol: mqtt\r\n # 子协议为mqtt
Sec-WebSocket-Extensions: permessage-deflate\r\n
Sec-WebSocket-Key: npQCPFw1Pc5G9vqjJyi90w==\r\n
Connection: keep-alive, Upgrade\r\n
Upgrade: websocket\r\n
\r\n

 

  • 响应成功
# 省略无关头部
Hypertext Transfer Protocol
HTTP/1.1 101 Switching Protocols\r\n
Connection: Upgrade\r\n
Content-Length: 0\r\n
Sec-Websocket-Accept: biw/vsujgvKx8B0bfoDnjuxHMgA=\r\n
Sec-Websocket-Protocol: mqtt\r\n # 确认子协议为mqtt
Upgrade: websocket\r\n
\r\n

2 MQTT建立连接

  • 客户端 -> 服务器:CONNECT
    • 本来MQTT TCP连接可以通过分段或一次传输完成的WebSocket则分成了13次发送
# 发送了13个帧
192.168.1.46    103.61.37.192   WebSocket   WebSocket Binary [FIN] [MASKED]
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0010 = Opcode: Binary (2)
1... .... = Mask: True
.000 0001 = Payload length: 1
Masking-Key: 0c3cb539
Masked payload
1C

# 帧序号 数据
# 1 Data: 10 -> Header Flags:Connect
# 2 Data: 58 -> Msg Len:88
# 3 Data: 0004 -> Protocol Name Length: 4
# 4 Data: 4d515454 -> Protocol Name: MQTT
# 5 Data: 04 -> Version:v3.1.1
# 6 Data: c2
# 1… …. = User Name Flag: Set # 用户名
# .1.. …. = Password Flag: Set # 密码
# ..0. …. = Will Retain: Not Set # 遗嘱保留
# …0 0… = Qos Level: 0
# …. .0.. = Will Flag: Not Set # 遗嘱
# …. ..1. = Clean Session Flag: Set # 清除会话
# …. …0 = (Reserverd): Not Set # 固定为0
# 7 Data: 003c -> Keep Alive: 60秒
# 8 Data: 0011 -> Client ID Length: 17
# 9 Data: 6d 71 74 74 6a 73 5f 39 38 33 37 66 65 30 62 61 38 -> ClientId
# 10 Data: 000b -> Username Length
# 11 Data: 64656c696768742f776562 -> Username
# 12 Data: 002c -> Password Length
# 13 Data: 45396a4d58416c35523939596b48756350506c3377545468… -> Password

  • 服务器 -> 客户端:CONNACK
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0010 = Opcode: Binary (2)
0... .... = Mask: False
.000 0100 = Payload length: 4
Data (4 bytes)
Data: 20 02 00 00
# 20 -> Header Flags:CONNACK
# 02 -> Msg Len:2
# 00 -> Acknowledge Flags: 0x00 # 连接确认标志
# 00 -> Return Code: Connection Accepted (0) # 连接返回码

3 MQTT的PINGREQ和PINGRESP

  • 客户端 -> 服务器:PINGREQ
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0010 = Opcode: Binary (2)
1... .... = Mask: True
.000 0010 = Payload length: 2
Masking-Key: 0657364e
Data (2 bytes)
Data: c000

# C0 -> Header Flags: 0xC0 (Ping Request)
# 00 -> Msg Len: 0

  • 服务器 -> 客户端 :PINGRESP
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0010 = Opcode: Binary (2)
0... .... = Mask: False
.000 0010 = Payload length: 2
Data (2 bytes)
Data: d000

# D0 -> Header Flags: 0xD0 (Ping Response)
# 00 -> Msg Len: 0

4 MQTT的SUBSCRIBE和SUBACK

  • 客户端 -> 服务器:SUBSCRIBE
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0010 = Opcode: Binary (2)
1... .... = Mask: True
.000 0001 = Payload length: 1
Masking-Key: d0f56152
Data (1 byte)
Data: 82

# 帧序号 数据
# 1 Data: 82 -> Header Flags: Subscribe Request
# 2 Data: 0b -> Msg Len:11
# 3 Data: a5b2 -> Message Identifier: a5b2
# 4 Data: 0006 -> Topic Length: 6
# 5 Data: 2f 57 6f 72 6c 64 -> Topic:/World
# 6 Data: 00 -> Requested QoS:0

  • 服务器 -> 客户端 :SUBACK
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0010 = Opcode: Binary (2)
0... .... = Mask: False
.000 0101 = Payload length: 5
Data (5 bytes)
Data: 9003a5b200

# 90 -> Header Flags: Subscribe Ack
# 03 -> Msg Len: 3
# a5b2 -> Message Identifier:a5b2
# 00 -> 成功

5 MQTT的PUBLISH(Qos0)

# 帧序号    数据
# 1        Data: 30 -> Header Flags: Publish Message
# 2        Data: 0d ->  Msg Len: 13
# 3        Data: 0006 -> Topic Length:6
# 4        Data: 2f 57 6f 72 6c 64 ->  Topic:/World
# 5        Data: 68 65 6c 6c 6f -> Message:hello

6 MQTT的DISCONNECT

WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0010 = Opcode: Binary (2)
1... .... = Mask: True
.000 0010 = Payload length: 2
Masking-Key: d70088a5
Data (5 bytes)
Data: e000

# e0 -> Header Flags: Disconnect
# 00 -> Msg Len: 0

注1:MQTT报文格式请参考MQTT协议探究(一) MQTT协议探究(二)

注2:MQTT其他报文就不做贴出来了,请自行测试。

注3:协议的学习基本完成,后面结合Netty来加深对协议的复习和补充。

Eclipse国内镜像源配置方法

src: https://www.cnblogs.com/sddai/p/9676096.html

1 下载Eclipse
首先,我们看一个链接地址:
http://mirror.bit.edu.cn/eclipse/technology/epp/downloads/release/juno/SR1/eclipse-jee-juno-SR1-win32.zip
其中, http://mirror.bit.edu.cn 代表一个国内的镜像源,后面的路径基本是固定的,所以,如果需要下载一个新的版本时,可以到:
http://mirror.bit.edu.cn/eclipse/technology/epp/downloads/release/
路径下去找时间戳最新的那个文件夹,然后,去下载最新的版本。常用的几个速度很快的源:

中国科学技术大学(5.6MB/s) http://mirrors.ustc.edu.cn/eclipse/
北京理工大学(600KB/s) http://mirror.bit.edu.cn/eclipse/
大连东软信息学院(400KB/s) http://mirrors.neusoft.edu.cn/eclipse/

照这么做,下载到了新版本的eclipse j2ee版本
http://111.0.93.35/mirrors.neusoft.edu.cn/eclipse/technology/epp/downloads/release/2019-09/M2/eclipse-jee-2019-09-M2-win32-x86_64.zip
这个支持Java12了,赞~

eclipse config

config.ini

#This configuration file was written by: org.eclipse.equinox.internal.frameworkadmin.equinox.EquinoxFwConfigFileParser
#Wed Dec 26 12:44:41 CST 2018
eclipse.p2.profile=epp.package.jee
osgi.instance.area.default=@user.home/workspace
osgi.framework=file\:plugins/org.eclipse.osgi_3.13.100.v20180827-1536.jar
equinox.use.ds=true
eclipse.buildId=4.9.0.I20180906-0745
ds.delayed.keepInstances=true
osgi.bundles=reference\:file\:org.eclipse.equinox.simpleconfigurator_1.3.100.v20180827-1122.jar@1\:start
org.eclipse.equinox.simpleconfigurator.configUrl=file\:org.eclipse.equinox.simpleconfigurator/bundles.info
eclipse.product=org.eclipse.platform.ide
osgi.splashPath=platform\:/base/plugins/org.eclipse.platform
osgi.framework.extensions=reference\:file\:org.eclipse.osgi.compatibility.state_1.1.200.v20180827-1536.jar
osgi.bundles.defaultStartLevel=4
eclipse.application=org.eclipse.ui.ide.workbench
eclipse.p2.data.area=@config.dir/../p2/

 

eclipse.ini

-startup
plugins/org.eclipse.equinox.launcher_1.5.100.v20180827-1352.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.800.v20180827-1352
-product
org.eclipse.epp.package.jee.product
-showsplash
org.eclipse.epp.package.common
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.8
-Dosgi.instance.area.default=@user.home/eclipse-workspace
-XX:+UseG1GC
-XX:+UseStringDeduplication
-Dosgi.requiredJavaVersion=1.8
-Dosgi.dataAreaRequiresExplicitInit=true
-Xms256m
-Xmx1024m
--add-modules=ALL-SYSTEM

 

自己写一个异步webapi,占了一个坑

照着前面文章 http://huqingyu.com/blog/2019/08/1511.htm,自己写了一个异步调用,改了路由,结果总是返回null

[Route("cc/async")]
[HttpGet]
public async Task<string> ATest()
{
return await Task.Factory.StartNew(()=> { Thread.Sleep(3000); return "a"; });
}

然后访问/api/cc/async,就是返回null,实际上它去了Get()方法…
访问/cc/async就正确了,嘿,被路由坑了

ASP.NET sync over async(异步中同步,什么鬼?)

src:https://www.cnblogs.com/xishuai/p/asp-net-sync-over-async.html

async/await 是我们在 ASP.NET 应用程序中,写异步代码最常用的两个关键字,使用它俩,我们不需要考虑太多背后的东西,比如异步的原理等等,如果你的 ASP.NET 应用程序是异步到底的,包含数据库访问异步、网络访问异步、服务调用异步等等,那么恭喜你,你的应用程序是没问题的,但有一种情况是,你的应用程序代码比较老,是同步的,但现在你需要调用异步代码,这该怎么办呢?有人可能会说,很简单啊,不是有个 .Result 吗?但事实真的就这么简单吗?我们来探究下。

首先,放出几篇经典文章:

上面文章的内容,我们后面会说。光看不练假把式,所以,如果真正要体会 sync over async,我们还需要自己动手进行测试:

  • 1. 异步调用使用 .Result,同步调用使用 .Result
  • 2. 异步调用使用 await,同步调用使用 Task.Run
  • 3. 异步调用使用 await,同步调用使用 .Result
  • 4. 异步调用使用 Task.Run,同步调用使用 .Result
  • 5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result
  • 6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result
  • 7. 异步调用使用 await,异步调用使用 await
  • 8. 测试总结

先说明一下,在测试代码中,异步调用使用的是 HttpClient.GetAsync 方法,并且测试请求执行两次,关于具体的分析,后面再进行说明。

1. 异步调用使用 .Result,同步调用使用 .Result

测试代码:

[Route("")]
[HttpGet]
public string Index()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
var result = Test();
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
return result;
}
public static string Test()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
using (var client = new HttpClient())
{
var response = client.GetAsync(url).Result;
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
return response.Content.ReadAsStringAsync().Result;
}
}

 

输出结果:

Thread.CurrentThread.ManagedThreadId1:13
Thread.CurrentThread.ManagedThreadId2:13
Thread.CurrentThread.ManagedThreadId3:13
Thread.CurrentThread.ManagedThreadId4:13
Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:6
Thread.CurrentThread.ManagedThreadId3:6
Thread.CurrentThread.ManagedThreadId4:6

 

简单总结:同步代码中调用异步,上面的测试代码应该是我们最常写的,为什么没有出现线程阻塞,页面卡死的情况呢?而且代码中调用了 GetAsync,为什么请求线程只有一个?后面再说,我们接着测试。

2. 异步调用使用 await,同步调用使用 Task.Run

测试代码:

[Route("")]
[HttpGet]
public string Index()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
var result = Task.Run(() => Test2()).Result;
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
return result;
}

public static async Task<string> Test2()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
using (var client = new HttpClient())
{
var response = await client.GetAsync(url);
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
return await response.Content.ReadAsStringAsync();
}
}

 

输出结果:

Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:7
Thread.CurrentThread.ManagedThreadId3:11
Thread.CurrentThread.ManagedThreadId4:6
Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:7
Thread.CurrentThread.ManagedThreadId3:12
Thread.CurrentThread.ManagedThreadId4:6

简单总结:根据上面的输出结果,我们发现,在一个请求过程中,总共会出现三个线程,一个是开始的请求线程,接着是 Task.Run 创建的一个线程,然后是异步方法中 await 等待的执行线程,需要注意的是,ManagedThreadId1 和 ManagedThreadId4 始终是一样的。

3. 异步调用使用 await,同步调用使用 .Result

测试代码:

[Route("")]
[HttpGet]
public string Index()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
var result = Test3().Result;
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
return result;
}

public static async Task<string> Test3()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
using (var client = new HttpClient())
{
var response = await client.GetAsync(url);
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
return await response.Content.ReadAsStringAsync();
}
}

 

输出结果:

Thread.CurrentThread.ManagedThreadId1:5
Thread.CurrentThread.ManagedThreadId2:5

 

简单总结:首先,页面是卡死状态,ManagedThreadId3 并没有输出,也就是执行到 await client.GetAsync 的时候,线程就阻塞了。

4. 异步调用使用 Task.Run,同步调用使用 .Result

测试代码:

[Route("")]
[HttpGet]
public string Index()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
var result = Test4().Result;
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
return result;
}

public static async Task<string> Test4()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
return await Task.Run(() =>
{
Thread.Sleep(1000);
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
return "xishuai";
});
}

 

输出结果:

Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:6
Thread.CurrentThread.ManagedThreadId3:7

 

简单总结:和第三种情况一样,页面也是卡死状态,但不同的是,ManagedThreadId3 是输出的,测试它的主要目的是和第三种情况形成对比,以便了解 HttpClient.GetAsync 中到底是什么鬼?

5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result

测试代码:

[Route("")]
[HttpGet]
public string Index()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
var result = Test5().Result;
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
return result;
}

public static async Task<string> Test5()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
using (var client = new HttpClient())
{
var task = client.GetAsync(url);
var response = await task.ConfigureAwait(true);
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
return await response.Content.ReadAsStringAsync();
}
}

 

输出结果:

Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:6

 

简单总结:和上面两种情况一样,页面也是卡死状态,它的效果和第三种完全一样,ManagedThreadId3 都没有输出的。

6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result

测试代码:

[Route("")]
[HttpGet]
public string Index()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
var result = Test6().Result;
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
return result;
}

public static async Task<string> Test6()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
using (var client = new HttpClient())
{
var task = client.GetAsync(url);
var response = await task.ConfigureAwait(false);
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
return await response.Content.ReadAsStringAsync();
}
}

输出结果:

Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:6
Thread.CurrentThread.ManagedThreadId3:10
Thread.CurrentThread.ManagedThreadId4:6
Thread.CurrentThread.ManagedThreadId1:8
Thread.CurrentThread.ManagedThreadId2:8
Thread.CurrentThread.ManagedThreadId3:11
Thread.CurrentThread.ManagedThreadId4:8

输出结果:

Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:6
Thread.CurrentThread.ManagedThreadId3:12
Thread.CurrentThread.ManagedThreadId4:12
Thread.CurrentThread.ManagedThreadId1:7
Thread.CurrentThread.ManagedThreadId2:7
Thread.CurrentThread.ManagedThreadId3:8
Thread.CurrentThread.ManagedThreadId4:8

 

简单总结:和第五种情况形成对比,仅仅只是把 ConfigureAwait 参数设置为 false,结果却完全不同。

7. 异步调用使用 await,异步调用使用 await

测试代码:

[Route("")]
[HttpGet]
public async Task<string> Index()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
var result = await Test7();
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
return result;
}

public static async Task<string> Test7()
{
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
using (var client = new HttpClient())
{
var response = await client.GetAsync(url);
System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
return await response.Content.ReadAsStringAsync();
}
}

 

简单总结:注意这是异步的写法,调用和被调用方法都是异步的,从输出的结果中,我们就会发现,这种情况和上面的六种情况,有一个最明显的区别就是,请求线程和结束线程不是同一个,说明什么呢?线程是异步等待的。

8. 测试总结

先梳理一下测试结果:

  1. 异步调用使用 .Result,同步调用使用 .Result:通过,始终一个线程。
  2. 异步调用使用 await,同步调用使用 Task.Run:通过,三个线程,请求开始和结束为相同线程。
  3. 异步调用使用 await,同步调用使用 .Result:卡死,线程阻塞。
  4. 异步调用使用 Task.Run,同步调用使用 .Result:卡死,线程阻塞。
  5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result:卡死,线程阻塞。
  6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result:通过,两个线程,await 执行为单独一个线程。
  7. 异步调用使用 await,异步调用使用 await:通过,两个线程,请求开始和结束为不同线程。

上面这么多的测试情况,看起来可能有些晕,我们先从最简单的第二种情况开始分析下,首先,页面是同步方法,请求线程可以看作是一个主线程 1,然后通过 Task.Run 创建线程 2,让它去执行 Test2 方法,需要注意的是,这时候主线程 1 并不会往下执行(从输出结果可以看出),它会等待线程 2 执行,主要是等待线程 2 执行返回结果,在 Test2 方法中,一切是异步方法,await client.GetAsync 会创建又一个线程 3 去执行,并且线程 2 等待它返回结果,然后最终回到线程 1 上,在整个过程中,虽然有三个线程,但这三个线程并不是同时工作的,而是一个执行之后等待另一个执行的结果,所以整个执行过程还是同步的。

第三种和第二种情况的不同就是,异步调用由 Task.Run 改成了 .Result,然后就造成了页面卡死,在 Don’t Block on Async Code 这篇文章中,就是详细说明的这种情况,为什么会卡死呢?其实你从同样卡死的第四种情况和第五种情况中,可以发现一些线索,ConfigureAwait 的说明是:试图继续回夺取的原始上下文,则为 true;否则为 false。什么意思呢?就是它可以变身为请求线程,最能体现出这一点的是,如果设置为 true,那么在这个线程中,就可以访问 HttpContext.Current,那为什么在同步调用中,设置为 true 就造成页面卡死呢?我们分析一下,页面是同步方法,请求线程可以看作是一个主线程 1,然后调用 Test3 异步方法,这时候主线程 1,会在这里等待异步的执行结果,在 Test3 方法中创建一个线程 2,因为把 ConfigureAwait 设置为了 true,那么线程 2 就想把自己变身成为请求线程(谋权篡位),也就是线程 1,但是人家线程 1 现在正在门口等它呢?线程 2 却想占有线程 1 的地位,很显然,这是不成功的,那什么情况下可以谋权篡位成功呢?就是线程 1 不在,也就是线程 1 回到线程池中了,这就是异步等待的效果,也是它的威力。

针对第三种情况,简单画了一个示意图:

在第五种情况中,因为把 ConfigureAwait 设置为 false,线程 2 不想谋权篡位了,它只想老老实实的做事,把执行结果返回给请求线程 1,那么整个请求执行过程就是顺利的。

同步调用异步测试中,还剩一个第一种情况,它和其他情况不同的是,没有异步方法,只是使用的是 .Result,那为什么它是通过的?并且线程始终是一个呢?首先,页面请求开始,创建一个请求线程 1,因为 Test 方法并不是异步方法,所以还是线程 1 去执行它,执行到了 client.GetAsync 这一步,因为没有使用 await,所以并不会创建一个线程去执行它,并且最终的是,虽然 GetAsync 是异步方法,但再其实现代码中,设置了 ConfigureAwait(false):

async Task<HttpResponseMessage> SendAsyncWorker(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
  using (var lcts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
  {
    lcts.CancelAfter(timeout);

    var task = base.SendAsync(request, lcts.Token);
    if (task == null)
      throw new InvalidOperationException("Handler failed to return a value");

    var response = await task.ConfigureAwait(false);//重点
    if (response == null)
      throw new InvalidOperationException("Handler failed to return a response");

    //
    // Read the content when default HttpCompletionOption.ResponseContentRead is set
    //
    if (response.Content != null && (completionOption & HttpCompletionOption.ResponseHeadersRead) == 0)
    {
      await response.Content.LoadIntoBufferAsync(MaxResponseContentBufferSize).ConfigureAwait(false);
    }

    return response;
  }
}

所以,整个过程应该是这样的,在测试代码中始终是一个请求线程在执行,并且在 client.GetAsync 的执行中,会创建另外一个线程 2 去执行,然后线程 1 等待线程 2 的执行结果,因为 GetAsync 的实现并不在测试代码中,所以表现出来就是一个线程在执行,虽然是异步方法,但它和同步方法一样,为什么?因为线程始终在等待另一个线程的执行结果,也就是说,在某一时刻,始终是一个线程在执行,其余线程都在等待。

sync over async(异步中同步)是否可行?通过上面的测试结果可以得出是可行的,但要注意一些写法问题:

  • 异步调用使用 .Result,而不能出现 await。
  • 不能出现 ConfigureAwait(true)。
  • 可以使用 Task.Run,但仅限于不返回结果的执行线程。

当然最好的方式是异步到底

freemarker tutorials

https://github.com/freemarker/freemarker-tutorials

00-running-a-server-locally

**Libraries Used**

* [FreeMarker 2.3.23](http://freemarker.org/docs/)
* [Spring MVC 4.2](https://spring.io/blog/2015/07/31/spring-framework-4-2-goes-ga)
* Javax Servlet API 3.1.0

## Step 1: Download or clone the FreeMarker Tutorials project

[Download and extract the FreeMarker Tutorials Github project](https://github.com/freemarker/freemarker-tutorials/archive/master.zip) or clone it using git (`git clone https://github.com/freemarker/freemarker-tutorials.git`)

## Step 2: Compile the Hello World project

Open up a console window (Command prompt for Windows users or Terminal for OS X users) and navigate to the `tutorials/01-hello-world` directory. Run `mvn compile war:inplace`.

“`bsh
mvn compile war:inplace
“`

(If you don’t have Maven, follow the installation instructions here: [Installing Java and Maven](../00-installing-java-and-maven/))

This will download the dependencies and compile the Java files.

## Step 3: Point Tomcat at your webapp directory

1. In the [previous tutorial](../00-running-a-server-locally) we set up a Tomcat server. Make sure your server is still running!

* Windows users: Open up Command Prompt, type `catalina start` and press enter
* OS X users: Open up Terminal, type `catalina start` and press enter

2. Navigate to your Tomcat installation folder, and find the `conf/Catalina/localhost` directory and create a context file called **hello-world.xml**.

* Windows users: If you followed the previous tutorial’s instructions, Windows users can navigate directly to the folder by copying and pasting `%CATALINA_HOME%\conf\Catalina\localhost` into a Windows explorer window.

![Windows 7 XML file](images/win7-catalina-home.png)

* OS X users: In Terminal you can do this (Replace **8.0.26** with your version of Tomcat):

“`bsh
cd /usr/local/Cellar/tomcat/8.0.26/libexec/conf/Catalina/localhost
vi hello-world.xml
“`
在${tomcat_home}/conf/Catalina/localhost新建一个文件hello-world.xml

3. Inside `hello-world.xml`, add the following (Replace **PATH_TO_FREEMARKER_TUTORIALS** with
wherever you have the FreeMarker tutorials project.):

“`xml
<?xml version=”1.0″ encoding=”utf-8″?>
<Context docBase=”PATH_TO_FREEMARKER_TUTORIALS/01-hello-world/src/main/webapp
path=”” reloadable=”true” />
“`

以上是新建文件hello-world.xml的内容,path自己替换一下

4. You should now be able to access the FreeMarker Hello World webapp at [http://localhost:8080/hello-world/](http://localhost:8080/hello-world/).

 

java maven checkstyle失败

官网: https://maven.apache.org/plugins/maven-checkstyle-plugin/index.html

checkstyle问题多多,需要一一解决,先来看看可能出现的问题:
Execution verify of goal org.apache.maven.plugins:maven-checkstyle-plugin:2.16 …
Could not find resource ‘etc/config/checkstyle.xml’
Could not find resource ‘etc/config/checkstyle-suppressions.xml’

它说没找到这两个文件,那么贴2个上去,在项目下建立这3个文件:

checkstyle.xml
etc/config/checkstyle-verify.xml

<?xml version="1.0"?>
<!DOCTYPE module PUBLIC 
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" 
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name = "Checker">
    <property name="charset" value="UTF-8"/>
    <property name="severity" value="warning"/>
    <property name="fileExtensions" value="c"/>
</module>

checkstyle-suppressions.xml

<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
    "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
    "https://checkstyle.org/dtds/suppressions_1_2.dtd">

<suppressions>
    <!-- can't split long messages between lines -->
    <suppress checks="RegexpSingleline" files="google_checks\.xml" lines="42,83"/>
    <!-- don't validate generated files -->
    <suppress checks="RegexpSingleline" files="releasenotes\.xml"/>

    <suppress checks="FileLength"
              files="TokenTypes.java|IndentationCheckTest.java"
              lines="1"/>

    <!-- illegal words are part of Javadoc -->
    <suppress checks="TodoComment" files=".*TodoCommentCheck\.java"/>

    <!-- 'Abstract' pattern is used to show it is checking for abstract class name -->
    <suppress checks="AbstractClassNameCheck"
              files="AbstractClassNameCheck.java"/>
    <!-- test should be named as their main class -->
    <suppress checks="AbstractClassNameCheck"
              files="AbstractCheckTest.java|AbstractClassNameCheckTest.java|
                     |AbstractTypeAwareCheckTest.java|AbstractJavadocCheckTest.java|
                     |AbstractViolationReporterTest.java|AbstractFileSetCheckTest.java"/>

    <!-- Tone down the checking for test code -->
    <suppress checks="ExecutableStatementCount|JavaNCSS|BooleanExpressionComplexity|
                      |NestedIfDepth|MethodLength"
              files="[\\/]XdocsPagesTest\.java"/>

    <!-- till https://github.com/checkstyle/checkstyle/issues/6336 -->
    <suppress checks="JavadocMethod" files=".*AbstractIndentationTestSupport\.java"/>
    <suppress checks="JavadocMethod" files=".*AbstractModuleTestSupport\.java"/>
    <suppress checks="JavadocMethod" files=".*AbstractXpathTestSupport\.java"/>
    <suppress checks="JavadocMethod" files=".*AbstractModuleTestSupport\.java"/>
    <suppress checks="JavadocMethod" files=".*AbstractPathTestSupport\.java"/>
    <suppress checks="JavadocMethod" files=".*AbstractXmlTestSupport\.java"/>

    <!--The Check generates too many violations, fixing them will make code unmanageable.-->
    <suppress checks="MagicNumber" files="(ParseTreeTablePresentation|MainFrame)\.java"/>

    <!-- Methods that build fake AST are very long-->
    <suppress checks="MethodLength" files=".*(Generated)?Java(doc)?TokenTypesTest\.java"/>
    <suppress checks="ExecutableStatementCount"
              files=".*(Generated)?Java(doc)?TokenTypesTest\.java"/>
    <suppress checks="JavaNCSS" files=".*(Generated)?Java(doc)?TokenTypesTest\.java"/>
    <suppress checks="ExecutableStatementCount" files=".*IllegalInstantiationCheckTest\.java"/>
    <suppress checks="ExecutableStatementCount" files=".*Main\.java"/>

    <!-- till https://github.com/checkstyle/checkstyle/issues/4983 -->
    <suppress checks="MissingDeprecated" files=".*JavadocTokenTypes\.java"/>

    <!-- Till https://github.com/checkstyle/checkstyle/issues/1854 -->
    <suppress checks="TrailingComment"
              files="(InnerAssignmentCheck\.java|OperatorWrapCheck\.java|
                     |AbbreviationAsWordInNameCheckTest\.java)"/>

    <!-- Fixing these cases will decrease code readability -->
    <suppress checks="MultipleStringLiterals" files="JavadocStyleCheck\.java|XMLLogger\.java"/>

    <!-- There are a lot of setters/getters in the Check.
         A small number of methods is left for Check's logic -->
    <suppress checks="MethodCount" files="[\\/]JavadocMethodCheck.java$"/>
    <!-- Apart from a complex logic there is a lot of small methods for a better readability.  -->
    <suppress checks="MethodCount" files="[\\/]CommentsIndentationCheck.java$"/>
    <!--VisibilityModifierCheck has 7 options which require 7 additional methods (setters)-->
    <suppress checks="MethodCount" files="[\\/]VisibilityModifierCheck.java$"/>
    <!--RequireThisCheck has a hierarchy of nested classes which contains a lot of methods. -->
    <suppress checks="MethodCount" files="[\\/]RequireThisCheck.java$"/>

    <!-- we need that set of converters -->
    <suppress checks="ClassDataAbstractionCoupling" files="AutomaticBean\.java"/>
    <!-- they are aggregators of logic, usage a several of classes are ok -->
    <suppress checks="ClassDataAbstractionCoupling"
              files="(Checker|Main|CheckstyleAntTask|JavadocDetailNodeParser)\.java"/>
    <suppress checks="ClassDataAbstractionCoupling"
              files="(CheckerTest|AbstractModuleTestSupport|AbstractItModuleTestSupport|
                     |CheckstyleAntTaskTest|
                     |TranslationCheckTest|LocalizedMessageTest|AbstractFileSetCheckTest|
                     |AbstractCheckTest|AutomaticBeanTest)\.java"/>
    <suppress checks="ClassDataAbstractionCoupling" files="PropertyCacheFileTest\.java"/>
    <suppress checks="ClassDataAbstractionCoupling"
              files="XpathFileGeneratorAuditListenerTest\.java"/>
    <suppress checks="ClassFanOutComplexity" files="[\\/]Main\.java"/>
    <suppress checks="ClassFanOutComplexity" files="CheckstyleAntTask\.java"/>
    <suppress checks="ClassFanOutComplexity" files="CheckerTest\.java"/>
    <suppress checks="ClassFanOutComplexity" files="Checker\.java"/>
    <!-- a lot of GUI elements is OK -->
    <suppress checks="ClassDataAbstractionCoupling" files="(TreeTable|MainFrame)\.java"/>

    <!-- Should be fixed after moving
    https://github.com/sevntu-checkstyle/sevntu.checkstyle/blob/master/sevntu-checks/src/main/java/com/github/sevntu/checkstyle/checks/coding/ReturnCountExtendedCheck.java
    into the main repo, to allow skip guard sentences(or by topLinesToIgnoreCount) -->
    <suppress checks="ReturnCount" files="(ConfigurationLoader|LambdaHandler)\.java"/>

    <!-- HandlerFactory crosses allowed limit for executable statements -->
    <suppress checks="ExecutableStatementCount" files="HandlerFactory\.java"/>

    <suppress id="ImportControlTest" files="[\\/]powermock[\\/]"
              message=".* - org\.(powermock|mockito).*" />

    <suppress id="noSourceforgeNetLinks" files="[\\/]releasenotes.xml"/>
    <suppress id="noSourceforgeIoLinks" files="[\\/]google_style.xml" lines="31"/>
    <suppress id="noSourceforgeIoLinks" files="[\\/]sun_style.xml" lines="31"/>
    <suppress id="noSourceforgeIoLinks" files="[\\/]index.xml.vm"/>

</suppressions>

 

特别说明:官网上这个是错的 https://maven.apache.org/plugins/maven-checkstyle-plugin/examples/suppressions-filter.html
里面的https://checkstyle.org/dtds/suppressions_1_0.dtd早就没了

然后在pom.xml的<build>里面加上:注意是build

    <build>
        <plugins>
	        <plugin>
	          <groupId>org.apache.maven.plugins</groupId>
	          <artifactId>maven-checkstyle-plugin</artifactId>
	          <version>2.16</version>
	          <configuration>
		          <configLocation>checkstyle.xml</configLocation>
		          <suppressionsLocation>checkstyle-suppressions.xml</suppressionsLocation>
		          <suppressionsFileExpression>checkstyle.suppressions.file</suppressionsFileExpression>
		        </configuration>
	        </plugin>
        </plugins>
    </build>

好,这样能解决大部分问题了。

iTextPDF7 Core for Java

download:
https://itextpdf.com/en/resources/api-documentation/itext-7-java

document (demo):
https://itextpdf.com/en/resources/examples/itext-7/itext-7-jump-start-tutorial-chapter-1#1725-c01e03_quickbrownfox.java

package com.xxx.pdf;

import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import java.io.File;
import java.io.IOException;
 
public class JPDFTest {
	
    public static final String DOG = "d://dog.bmp";
    public static final String FOX = "d://fox.bmp";
        
    public static final String DEST = "d://quick_brown_fox.pdf";
    

    public static void createPdf(String dest) throws IOException {
        //Initialize PDF writer
        PdfWriter writer = new PdfWriter(dest);
 
        //Initialize PDF document
        PdfDocument pdf = new PdfDocument(writer);
        
        // Initialize document
        Document document = new Document(pdf);
        
        // Compose Paragraph
        Image fox = new Image(ImageDataFactory.create(FOX));
        Image dog = new Image(ImageDataFactory.create(DOG));
        Paragraph p = new Paragraph("The quick brown ")
                .add(fox)
                .add(" jumps over the lazy ")
                .add(dog);
        // Add Paragraph to document
        document.add(p);
        
        //Close document
        document.close();
    }
		
	public static void main(String[] args) throws Throwable {

        File file = new File(DEST);
        file.getParentFile().mkdirs();
        createPdf(DEST);
        
	}
}

 

emcc编译选项

source: https://emscripten.org/docs/compiling/Building-Projects.html

To see a list of all available ports
emcc
 –show-ports

一、使用SDL_image

-s USE_SDL_IMAGE=2
-s SDL2_IMAGE_FORMATS='["bmp","png","xpm"]'

参考【1】用这个可以获得image宽、高,返回image data
Gets preloaded image data and the size of the image.

char *emscripten_get_preloaded_image_data(const char *path, int *w, int *h)

二、使用SDL_net
SDL_net has also been added to ports, use it with

-s USE_SDL_NET=2

三、编译优化
In order to properly optimize code, it is usually best to use the same optimization flags and other compiler options when compiling source to object code, and object code to JavaScript (or HTML).

# Usually the right thing: The SAME LLVM and JavaScript options are provided at both levels.
./emcc -O2 a.cpp -o a.bc
./emcc -O2 b.cpp -o b.bc
./emcc -O2 a.bc b.bc -o project.js

 

参考:
1. emscripten.h
https://emscripten.org/docs/api_reference/emscripten.h.html#c.emscripten_get_preloaded_image_data

[Emscripten+WebGL] jpg/jpeg image viewer

source code: https://github.com/svoisen/wasm-imageviewer.git

Makefile:

CFLAGS=-Werror -std=c++11 -O1 -g0
EMSCRIPTEN_FLAGS=-s FULL_ES3=1 \
-s USE_SDL=2 \
-s WASM=1 \
-s GL_DEBUG=1 \
-s TOTAL_MEMORY=134217728 \
-s EXTRA_EXPORTED_RUNTIME_METHODS=”[‘ccall’, ‘cwrap’]” \
-s EXPORTED_FUNCTIONS='[“_initializeOpenGL”, “_render”, “_loadJPEGImage”]’

由于这个demo问题比较多,所以打开了debug,然后优化也只用O1,尚不清楚FULL_ES3=1是不是必要的,有待验证

renderer.cpp改动:

1)出错信息打印:

    if (!shaderCompiled) {
      GLint infoLen = 0;
      glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );
      if ( infoLen > 1 )
      {
                // 原文件这里只是简单输出字符串,改成拿具体的出错信息,错误会比较清楚
		char *infoLog = (char *) malloc ( sizeof ( char ) * infoLen );
		glGetShaderInfoLog ( shader, infoLen, NULL, infoLog );
		std::cerr << "Error compiling shader:" << std::endl << infoLog;
		free ( infoLog );
      }
      glDeleteShader ( shader );
      return 0;
    }

2)编译之后的renderer.js
“webgl”改成”webgl2″!这个重要!

因为之前编译后运行起来提示:
ERROR: unsupported shader version
搞不懂为什么,寻思可能cpp中的shader头写的不对?

参考【1】说:#version 300 es必须顶格写!所以

<script type="x-shader/x-vertex" id="vertex-shader">
#version 300 es
...
</script>

是错误的!
正确是

<script type="x-shader/x-vertex" id="vertex-shader">#version 300 es
...
</script>

<scritpt>后面没有换行!

把cpp各种修改,都没啥效果,看了参考【2】,也是这么写的,没问题。

后来想,既然是初始化出问题,那把这个c代码的初始化改写成简单的html+js,看看怎么样。

html内容:

<!DOCTYPE html>
<html><body>
  <div id="container" class="container">
    <canvas id="canvas" class="canvas"></canvas>
  </div>
<script type="x-shader/x-vertex" id="vertex-shader">#version 300 es
uniform float zoom;                                                       
uniform float aspect;                                                     
in vec4 vert;                                                             
out vec2 texCoord;                                                        
void main() {
   gl_Position = vec4(vert.x * zoom, vert.y * aspect * zoom, 0.0f, 1.0f);
   texCoord = vec2((vert.x + 1.0f) / 2.0f, (1.0f - vert.y) / 2.0);
}</script>
	<script type="x-shader/x-fragment" id="fragment-shader">#version 300 es
precision mediump float;
uniform sampler2D tex;
vec2 texCoord;
vec4 finalColor;
void main() {                          
   finalColor = texture(tex, texCoord);
}</script>
  <script src="1.js"></script>
</body></html>

1.js内容:

;(function(){
"use strict"
window.addEventListener("load", setupWebGL, false);
var gl,
  program;
function setupWebGL (evt) {
  window.removeEventListener(evt.type, setupWebGL, false);
  if (!(gl = getRenderingContext()))
    return;

  var source = document.querySelector("#vertex-shader").innerHTML;
  var vertexShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vertexShader,source);
  gl.compileShader(vertexShader);
  source = document.querySelector("#fragment-shader").innerHTML
  var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fragmentShader,source);
  gl.compileShader(fragmentShader);
  program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  gl.detachShader(program, vertexShader);
  gl.detachShader(program, fragmentShader);
  gl.deleteShader(vertexShader);
  gl.deleteShader(fragmentShader);
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    var linkErrLog = gl.getProgramInfoLog(program);
    cleanup();
    document.querySelector("p").innerHTML =
      "Shader program did not link successfully. "
      + "Error log: " + linkErrLog;
    return;
  }

  initializeAttributes();

  gl.useProgram(program);
  gl.drawArrays(gl.POINTS, 0, 1);

  cleanup();
}

var buffer;
function initializeAttributes() {
  gl.enableVertexAttribArray(0);
  buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 0, 0);
}

function cleanup() {
gl.useProgram(null);
if (buffer)
  gl.deleteBuffer(buffer);
if (program)
  gl.deleteProgram(program);
}
function getRenderingContext() {
  var canvas = document.querySelector("canvas");
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
  var gl = canvas.getContext("webgl2");
  
  if(!gl)
    gl = canvas.getContext("experimental-webgl");
  if (!gl) {
    var paragraph = document.querySelector("p");
    paragraph.innerHTML = "Failed to get WebGL context."
      + "Your browser or device may not support WebGL.";
    return null;
  }
  gl.viewport(0, 0,
    gl.drawingBufferWidth, gl.drawingBufferHeight);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  return gl;
}
})();

关键就在于:
webgl1是 canvas.getContext(“webgl“);
webgl2是canvas.getContext(“webgl2“);

所以,把编译好的renderer.js里面”webgl”改成”webgl2″,就ok了!这个bug对初学者来说有点难度。这里记一个解决的过程。

 

 


第二天接着玩,换了台机器,发现照上面做了还是不行
自己做个最简单的canvas然后canvas.getContext(‘webgl2’)都不行,返回的是null
canvas.getContext(‘webgl’)是可以的,然后换Firefox是可以的,所以判断是chrome的flag哪里不对。于是一个个是过去,发现这个:

Choose ANGLE graphics backend
Choose the graphics backend for ANGLE. D3D11 is used on most Windows computers by default. Using the OpenGL driver as the graphics backend may result in higher performance in some graphics-heavy applications, particularly on NVIDIA GPUs. It can increase battery and memory usage of video playback. – Windows

它有四个选项:
Default(默认)
OpenGL
D3D11
D3D9

改成OpenGL就好了。

 

再一次试了下编译,发现这样的编译选项,编译后的js就可以有getContext(‘webgl2’),不用再改js。

emcc renderer.cpp -Werror -std=c++11 -O0 -g0 -s USE_SDL=2 -s USE_WEBGL2=1 -s FULL_ES3=1 -s GL_DEBUG=1 -s WASM=1 -s TOTAL_MEMORY=134217728 -s EXPORTED_FUNCTIONS=”[‘_initializeOpenGL’, ‘_render’, ‘_loadJPEGImage’]” -s EXTRA_EXPORTED_RUNTIME_METHODS=”[‘ccall’, ‘cwrap’]” –post-js renderer_post.js -o renderer.js

然而最后运行的结果却是还不行,调试的时候看到:
var ctx =
(webGLContextAttributes.majorVersion > 1) ? canvas.getContext(“webgl2”, webGLContextAttributes) :
(canvas.getContext(“webgl”, webGLContextAttributes) || canvas.getContext(“experimental-webgl”, webGLContextAttributes));

这个majorVersion总是1,看了下参考2,它的c代码用的是egl,不一样。感觉已经不是编译选项的问题了,要改代码。又去问度娘,问谷歌,最后在renderer.cpp上加一句:
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
再编译就行了!默认值是2,然后js里面会再减1,所以webGLContextAttributes.majorVersion就是1,然后就悲剧了。

 

 

 

参考:
【1】https://segmentfault.com/a/1190000012002453
【2】https://github.com/danginsburg/opengles3-book.git