<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>达是酒中趣，琴上偶然音</title><description>一个写了二十年的个人博客：生活、技术、诗歌与随笔。A personal blog since 2005 — life, tech, poetry and notes.</description><link>https://blog.wo.ai/</link><language>zh-cn</language><item><title>Finally, I am back</title><link>https://blog.wo.ai/archives/4000/</link><guid isPermaLink="true">https://blog.wo.ai/archives/4000/</guid><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;服务器挂了，数据全丢了。写了二十年的博客，一夜之间没了。&lt;/p&gt;
&lt;p&gt;还好有 archive.org。这个博客从 2005 年到 2025 年的 316 篇文章、147 张图片，都是从 Wayback Machine 的快照里一点点恢复回来的。旧的链接（&lt;code&gt;/archives/文章ID&lt;/code&gt;）依然有效。&lt;/p&gt;
&lt;p&gt;现在它是一个纯静态站点——没有数据库，没有后台，只有 Markdown 文件和 git。这次，它不会再丢了。&lt;/p&gt;
&lt;p&gt;Finally, I am back.&lt;/p&gt;
</content:encoded><category>Life</category></item><item><title>Preserving Original Client IP Addresses with OpenWrt Port Forwarding: A Fail2Ban Savior</title><link>https://blog.wo.ai/archives/3497/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3497/</guid><pubDate>Wed, 04 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you want to read the Chinese version, move down to the Chinese Part, 本文包含中文版本，请移到后面的中文版本。&lt;/p&gt;
&lt;p&gt;I run a self-hosted environment at home, including an Immich photo server and an SSH server, both happily residing on my local network (LAN). To access these services remotely, I configured port forwarding on my OpenWrt router. Initially, everything seemed to function flawlessly – I could access my services from anywhere.&lt;/p&gt;
&lt;p&gt;However, a peculiar issue soon surfaced: my SSH server began refusing connections. A dive into the auth.log and Nginx access logs revealed something unexpected. All incoming internet traffic, whether to SSH or Immich, appeared to originate from 192.168.x.x – the LAN IP address of my OpenWrt router itself! This became a critical problem because persistent SSH scanning attempts from the internet were, understandably, being flagged by Fail2Ban. The consequence? Fail2Ban banned the “offending” IP, which was my router, effectively severing access to  &lt;em&gt;all&lt;/em&gt; my forwarded services for everyone, including myself.&lt;/p&gt;
&lt;p&gt;My troubleshooting journey involved extensive Googling and even consulting AI assistants, but these efforts proved fruitless. One AI suggestion was to install Nginx on the OpenWrt router itself to act as a reverse proxy. While a reverse proxy  &lt;em&gt;can&lt;/em&gt; preserve client IPs (using headers like X-Forwarded-For), installing a full-fledged Nginx instance on a resource-constrained router solely for this felt like overkill and not the most direct solution.&lt;/p&gt;
&lt;p&gt;The breakthrough came from an unexpected source: an iStoreOS issue tracker. (For the uninitiated, iStoreOS is a popular OpenWrt fork). While the issue thread didn’t contain a step-by-step solution, the original poster mentioned resolving a similar problem by tweaking NAT rules to prevent source address translation. Given the shared lineage between OpenWrt and iStoreOS, I suspected this approach might be applicable.&lt;/p&gt;
&lt;p&gt;Armed with this clue, I navigated to my OpenWrt LuCI interface: &lt;strong&gt;Network - &amp;gt; Firewall -&amp;gt; NAT Rules&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here, I added a new rule with the following key parameters:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2025/06/Screenshot-2025-06-04-at-6.40.19%E2%80%AFPM.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2025/06/Screenshot-2025-06-04-at-6.40.19%E2%80%AFPM-1024x582.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Action&lt;/strong&gt; : ACCEPT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crucially&lt;/strong&gt; : I enabled an option typically labeled something like “&lt;strong&gt;Disable Source NAT&lt;/strong&gt; ” or “&lt;strong&gt;Disable Address Rewrite&lt;/strong&gt; ” (the exact wording might vary slightly depending on your OpenWrt version/theme, but the intent is to prevent the router from rewriting the source IP address of the incoming packets).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;(Self-correction: More accurately, the typical OpenWrt port forward is a DNAT rule. The issue is that an overly broad SNAT/Masquerade rule might be applying to this forwarded traffic. The key is to ensure that for these specific port-forwarded connections, SNAT is not performed, allowing the original source IP to pass through to the internal server. The “Disable Source NAT” option is often available directly within the port forward (DNAT) rule definition itself, or a specific “NO SNAT” rule needs to be crafted).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The result? &lt;strong&gt;Success!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Immediately after applying and saving this NAT rule, my server logs began showing the  &lt;em&gt;actual&lt;/em&gt; public IP addresses of external visitors. Most importantly, Fail2Ban could now accurately identify and ban malicious external IPs without inadvertently blacklisting my entire home network via the router’s IP.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why this works (The Geeky Bit):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Standard port forwarding involves Destination NAT (DNAT), where the router changes the destination IP of an incoming packet (from its public WAN IP to your internal server’s LAN IP). However, many default OpenWrt setups also apply Source NAT (SNAT) or Masquerading for traffic passing through it, especially from LAN to WAN. In some configurations, this SNAT can also incorrectly get applied to WAN-to-LAN forwarded traffic, replacing the original client’s IP with the router’s LAN IP before the packet reaches your internal server.&lt;/p&gt;
&lt;p&gt;The “Disable Source NAT” or “Disable Address Rewrite” option for the relevant traffic flow instructs OpenWrt’s netfilter firewall to  &lt;em&gt;not&lt;/em&gt; perform this source IP alteration for packets destined for your forwarded ports. This allows your internal services to see the true originating IP, essential for accurate logging, geolocation, and security tools like Fail2Ban.&lt;/p&gt;
&lt;p&gt;If you’re facing a similar issue where your internal services behind an OpenWrt router only see the router’s IP for external connections, diving into your NAT rules and looking for an option to prevent source address translation on your port forwards might just be the elegant solution you need.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Following is the Chinese part（中文版本）&lt;/p&gt;
&lt;p&gt;OpenWrt 端口转发：保留原始客户端 IP，解决 Fail2Ban 困境&lt;/p&gt;
&lt;p&gt;我在家中搭建了一套自托管服务环境，包括一个 Immich 照片服务器和一个 SSH 服务器，它们都安稳地运行在我的本地网络（LAN）中。为了能够远程访问这些服务，我在我的 OpenWrt 路由器上配置了端口转发。起初，一切似乎都完美无缺——我可以从任何地方访问我的服务。&lt;/p&gt;
&lt;p&gt;然而，一个奇怪的问题很快浮出水面：我的 SSH 服务器开始拒绝连接。深入查看 auth.log 和 Nginx 的访问日志后，我发现了一些出乎意料的情况。所有来自互联网的访问流量，无论是到 SSH还是 Immich，都显示其源 IP 地址为 192.168.x.x——这正是我 OpenWrt 路由器的局域网 IP 地址！这是一个严重的问题，因为来自互联网的持续性 SSH 扫描尝试，理所当然地被 Fail2Ban 标记了。结果呢？Fail2Ban 封禁了这个“违规”的 IP，也就是我的路由器，从而有效地切断了所有人（包括我自己）对我所有转发服务的访问。&lt;/p&gt;
&lt;p&gt;我的故障排除过程包括了大量的谷歌搜索，甚至咨询了 AI 助手，但这些努力都未能奏效。一个 AI 给出的建议是在 OpenWrt 路由器上安装 Nginx 作为反向代理。虽然反向代理 &lt;em&gt;确实&lt;/em&gt; 可以通过 X-Forwarded-For 之类的头部信息来保留客户端 IP，但在资源受限的路由器上仅仅为此目的安装一个完整的 Nginx 实例，感觉有些小题大做，也不是最直接的解决方案。&lt;/p&gt;
&lt;p&gt;转机来自一个意想不到的地方：一个 iStoreOS 的问题追踪器。（对于不熟悉的人来说，iStoreOS 是一个流行的 OpenWrt 分支）。虽然那个问题帖子里没有包含手把手的解决方案，但原帖主提到他通过调整 NAT 规则，阻止了源地址转换，从而解决了类似问题。考虑到 OpenWrt 和 iStoreOS 之间的同源关系，我猜测这种方法可能也适用。&lt;/p&gt;
&lt;p&gt;带着这条线索，我进入了我的 OpenWrt LuCI 管理界面：&lt;strong&gt;网络 (Network) - &amp;gt; 防火墙 (Firewall) -&amp;gt; NAT 规则 (NAT Rules)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在这里，我添加了一条新的规则，关键参数如下：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2025/06/Screenshot-2025-06-04-at-6.40.19%E2%80%AFPM.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2025/06/Screenshot-2025-06-04-at-6.40.19%E2%80%AFPM-1024x582.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动作 (Action)&lt;/strong&gt; : ACCEPT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;至关重要的一点&lt;/strong&gt; ：我启用了通常标记为“&lt;strong&gt;禁用源 NAT (Disable Source NAT)&lt;/strong&gt; ”或“&lt;strong&gt;禁用地址重写 (Disable Address Rewrite)&lt;/strong&gt; ”之类的选项（具体措辞可能因您的 OpenWrt 版本/主题而略有不同，但其目的是阻止路由器重写传入数据包的源 IP 地址）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;(自我修正：更准确地说，典型的 OpenWrt 端口转发是一条 DNAT 规则。问题在于，一个过于宽泛的 SNAT/Masquerade 规则可能也应用到了这些转发流量上。关键在于确保对于这些特定的端口转发连接，不执行 SNAT，从而允许原始源 IP 传递到内部服务器。“禁用源 NAT”选项通常可以直接在端口转发（DNAT）规则定义中找到，或者需要创建一个特定的“NO SNAT”规则。)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;结果呢？&lt;strong&gt;成功了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在应用并保存这条 NAT 规则后，我的服务器日志立即开始显示外部访问者 &lt;em&gt;真实&lt;/em&gt; 的公网 IP 地址。最重要的是，Fail2Ban 现在可以准确识别并封禁恶意的外部 IP，而不会通过路由器的 IP 无意中将我的整个家庭网络列入黑名单。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为何有效（技术解读）：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;标准的端口转发涉及目标网络地址转换 (DNAT)，路由器会将传入数据包的目标 IP（从其公网 WAN IP）更改为您内部服务器的 LAN IP。然而，许多默认的 OpenWrt 设置也会对通过它的流量（尤其是从 LAN 到 WAN 的流量）应用源网络地址转换 (SNAT) 或 IP 伪装 (Masquerading)。在某些配置下，这种 SNAT 也可能错误地应用于 WAN 到 LAN 的转发流量，在数据包到达您的内部服务器之前，将原始客户端的 IP 替换为路由器的 LAN IP。&lt;/p&gt;
&lt;p&gt;针对相关流量的“禁用源 NAT”或“禁用地址重写”选项，会指示 OpenWrt 的 netfilter 防火墙 &lt;em&gt;不要&lt;/em&gt; 对发往您转发端口的数据包执行这种源 IP 地址变更。这使得您的内部服务能够看到真实的原始 IP，这对于准确的日志记录、地理位置定位以及像 Fail2Ban 这样的安全工具至关重要。&lt;/p&gt;
&lt;p&gt;如果您也遇到类似的问题，即 OpenWrt 路由器后的内部服务对于外部连接只看得到路由器的 IP，那么深入研究您的 NAT 规则，寻找一个选项来阻止对端口转发流量进行源地址转换，或许正是您需要的那个优雅的解决方案。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;#OpenWrt Fail2Ban bans router IP&lt;/p&gt;
&lt;p&gt;#OpenWrt server sees router IP&lt;/p&gt;
&lt;p&gt;#OpenWrt logs show router IP&lt;/p&gt;
&lt;p&gt;#Fix OpenWrt port forward IP&lt;/p&gt;
&lt;p&gt;#SSH server behind OpenWrt shows router IP&lt;/p&gt;
&lt;p&gt;#Nginx behind OpenWrt shows router IP&lt;/p&gt;
&lt;p&gt;#OpenWrt hide client IP&lt;/p&gt;
</content:encoded></item><item><title>play mp3 on ubuntu cli – sound card not detecting</title><link>https://blog.wo.ai/archives/3494/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3494/</guid><pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I was trying play mp3 files on ubuntu with cli tools, like mp123, there was no sound.&lt;/p&gt;
&lt;p&gt;And I tried when I logged into gnome, there was sound.&lt;/p&gt;
&lt;p&gt;I googled, there were lots of ways, but none works for me.&lt;/p&gt;
&lt;p&gt;Then I got this from this page: &lt;a href=&quot;https://askubuntu.com/questions/471433/sound-card-not-detecting&quot;&gt;https://askubuntu.com/questions/471433/sound-card-not-detecting&lt;/a&gt;, the reason is current user has no right or not in the audio group.&lt;/p&gt;
&lt;p&gt;sudo apt-get update sudo apt-get –reinstall install pavucontrol linux-sound-base alsa-base alsa-utils libasound2 sudo usermod -aG $(grep -e ‘^pulse:’ -e ‘^audio:’ -e \ ‘^pulse-access:’ -e ‘^pulse-rt:’ -e ‘^video:’ /etc/group \ | awk -F: ‘{print $1}’ \ | tr ‘\n’ ‘,’ \ | sed ‘s:,$::g’) $(whoami)&lt;/p&gt;
&lt;p&gt;Da~da, it works. Hope it good for you.&lt;/p&gt;
</content:encoded><category>Technology</category></item><item><title>AI on Mac Studio – 1:  Running fuyu-8B on mac studio</title><link>https://blog.wo.ai/archives/3473/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3473/</guid><pubDate>Wed, 01 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the first article about running an AI model on Mac Studio, and I will continue to migrate the environment from CUDA / Nvidia GPU to Mac MPS.&lt;/p&gt;
&lt;p&gt;Why did I choose Mac Studio?&lt;/p&gt;
&lt;p&gt;I chose Mac Studio because it is less expensive. It has 192GB of memory that can be used as a GPU. This means that it is possible to migrate the program from Nvidia GPU and save some money for personal use.&lt;/p&gt;
&lt;p&gt;What is Fuyu-8B?&lt;/p&gt;
&lt;p&gt;We are releasing Fuyu-8B, a small version of the multimodal model that powers our product. The model is available on HuggingFace. We think Fuyu-8B is exciting because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It has a much simpler architecture and training procedure than other multimodal models, making it easier to understand, scale, and deploy.&lt;/li&gt;
&lt;li&gt;It is designed from the ground up for digital agents, so it can support arbitrary image resolutions, answer questions about graphs and diagrams, answer UI-based questions, and perform fine-grained localization on screen images.&lt;/li&gt;
&lt;li&gt;It is fast – we can get responses for large images in less than 100 milliseconds.&lt;/li&gt;
&lt;li&gt;Despite being optimized for our use case, it performs well at standard image understanding benchmarks such as visual question-answering and natural-image-captioning.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ok, let’s do it now.&lt;/p&gt;
&lt;p&gt;Prepare the environment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You need Python 3.6+ and virtualenv installed. Conda or venv also work.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;cssCopy code&lt;/p&gt;
&lt;p&gt;&lt;code&gt;virtualenv -p python3 py3&lt;/code&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Download the HuggingFace transformers and clone the transformer from GitHub.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;bashCopy code&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git clone https://github.com/huggingface/transformers.git cd transformers pip install .&lt;/code&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Download the model from &lt;a href=&quot;https://huggingface.co/adept/fuyu-8b&quot;&gt;https://huggingface.co/adept/fuyu-8b&lt;/a&gt;. You can use clone or download it manually, depending on your network speed.&lt;/li&gt;
&lt;li&gt;Install PyTorch from this page &lt;a href=&quot;https://pytorch.org/get-started/locally/&quot;&gt;https://pytorch.org/get-started/locally/&lt;/a&gt; depending on your environment. Since I am using Mac M2 Studio, I should install this:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Copy code&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pip3 install torch torchvision torchaudio&lt;/code&gt;&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;You are almost done here; now we can start the samples.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Sample 1: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from transformers import FuyuProcessor, FuyuForCausalLM
from PIL import Image
# load model and processor
model_id = &amp;quot;.&amp;quot;
processor = FuyuProcessor.from_pretrained(model_id)
model = FuyuForCausalLM.from_pretrained(model_id, device_map=&amp;quot;mps&amp;quot;, torch_dtype=torch.float16)
# prepare inputs for the model
text_prompt = &amp;quot;Generate a coco-style caption.\n&amp;quot;
image_path = &amp;quot;bus.png&amp;quot;  # https://huggingface.co/adept-hf-collab/fuyu-8b/blob/main/bus.png
image = Image.open(image_path)
inputs = processor(text=text_prompt, images=image, return_tensors=&amp;quot;pt&amp;quot;)
for k, v in inputs.items():
    inputs[k] = v.to(&amp;quot;mps&amp;quot;)
# autoregressively generate text
generation_output = model.generate(**inputs, max_new_tokens=7)
generation_text = processor.batch_decode(generation_output[:, -7:], skip_special_tokens=True)
print(generation_text)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sample 2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
from transformers import FuyuProcessor, FuyuForCausalLM
from PIL import Image
import torch
def list_files_in_directory(path, extensions=[&amp;quot;.png&amp;quot;, &amp;quot;.jpeg&amp;quot;, &amp;quot;.jpg&amp;quot;, &amp;quot;.JPG&amp;quot;, &amp;quot;.PNG&amp;quot;, &amp;quot;.JPEG&amp;quot;]):
    files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and any(f.endswith(ext) for ext in extensions)]
    return files
def main():
    # load model and processor
    model_id = &amp;quot;.&amp;quot; #adept/fuyu-8b&amp;quot;
    processor = FuyuProcessor.from_pretrained(model_id)
    model = FuyuForCausalLM.from_pretrained(model_id, device_map=&amp;quot;mps&amp;quot;, torch_dtype=torch.float16) # To solve OOM, float16 enables operation with only 24GB of VRAM. Alternatively float16 can be replaced with bfloat16 with differences in loading time and inference time.
    # Load last image path or ask user
    try:
        with open(&amp;quot;last_path.txt&amp;quot;, &amp;quot;r&amp;quot;) as f:
            last_path = f.read().strip()
        user_input = input(f&amp;quot;Do you want to use the last path &amp;#39;{last_path}&amp;#39;? (yes/no, default yes): &amp;quot;)
        if not user_input or user_input.lower() != &amp;#39;no&amp;#39;:
            last_path = last_path
        else:
            raise ValueError(&amp;quot;User chose to input a new path.&amp;quot;)
    except:
        last_path = input(&amp;quot;Please provide the image directory path: &amp;quot;)
        with open(&amp;quot;last_path.txt&amp;quot;, &amp;quot;w&amp;quot;) as f:
            f.write(last_path)
    while True:
        # List the first 10 images in the directory
        images = list_files_in_directory(last_path)[:10]
        for idx, image in enumerate(images, start=1):
            print(f&amp;quot;{idx}. {image}&amp;quot;)
        # Allow the user to select an image
        image_choice = input(f&amp;quot;Choose an image (1-{len(images)}) or enter its name: &amp;quot;)
        try:
            idx = int(image_choice)
            image_path = os.path.join(last_path, images[idx-1])
        except ValueError:
            image_path = os.path.join(last_path, image_choice)
        try:
            image = Image.open(image_path)
        except:
            print(&amp;quot;Cannot open the image. Please check the path and try again.&amp;quot;)
            continue
        questions = [
            &amp;quot;Generate a coco-style caption.&amp;quot;,
            &amp;quot;What color is the object?&amp;quot;,
            &amp;quot;Describe the scene.&amp;quot;,
            &amp;quot;Describe the facial expression of the character.&amp;quot;,
            &amp;quot;Tell me about the story from the image.&amp;quot;,
            &amp;quot;Enter your own question&amp;quot;
        ]
        # Asking the user to select a question from list, or select to input one
        for idx, q in enumerate(questions, start=1):
            print(f&amp;quot;{idx}. {q}&amp;quot;)
        q_choice = int(input(&amp;quot;Choose a question or enter your own: &amp;quot;))
        if q_choice &amp;lt;= 5:
            text_prompt = questions[q_choice-1] + &amp;#39;\n&amp;#39;
        else:
            text_prompt = input(&amp;quot;Please enter your question: &amp;quot;) + &amp;#39;\n&amp;#39;
        while True: # To enable the user to ask further question about an image
            inputs = processor(text=text_prompt, images=image, return_tensors=&amp;quot;pt&amp;quot;)
            for k, v in inputs.items():
                inputs[k] = v.to(&amp;quot;mps&amp;quot;)
            # To eliminate attention_mask warning
            inputs[&amp;quot;attention_mask&amp;quot;] = torch.ones(inputs[&amp;quot;input_ids&amp;quot;].shape, device=&amp;quot;mps&amp;quot;)
            generation_output = model.generate(**inputs, max_new_tokens=50, pad_token_id=model.config.eos_token_id)
            generation_text = processor.batch_decode(generation_output[:, -50:], skip_special_tokens=True)
            print(&amp;quot;Answer:&amp;quot;, generation_text[0])
            text_prompt = input(&amp;quot;Ask another question about the same image or type &amp;#39;/exit&amp;#39; to exit: &amp;quot;) + &amp;#39;\n&amp;#39;
            if text_prompt.strip() == &amp;#39;/exit&amp;#39;:
                break
#if name == &amp;quot;main&amp;quot;:
main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2023/11/download-27.jpeg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2023/11/download-27.jpeg&quot; alt=&quot;&quot;&gt;&lt;/a&gt; &lt;a href=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2023/11/Screenshot-2023-11-01-at-6.46.12%E2%80%AFPM.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2023/11/Screenshot-2023-11-01-at-6.46.12%E2%80%AFPM-1024x507.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;yes, It is Chinese. But not the Chinese fuyu-7b knows. It is not “食” (eating) , but “我不想洗碗”( i don’t want to wash the dishes). Fuyu-7b is lying. lol.&lt;/p&gt;
</content:encoded><category>AI</category><category>Technology</category></item><item><title>redsocks升级记录</title><link>https://blog.wo.ai/archives/3470/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3470/</guid><pubDate>Wed, 18 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一直在用redsocks做流量转发，用着还是挺不错的，但是偶尔还是会hang住，导致网络卡住。老早就有升级的想法，一直没有动手。&lt;/p&gt;
&lt;p&gt;什么是redsocks？&lt;/p&gt;
&lt;p&gt;This tool allows you to redirect any TCP connection to SOCKS or HTTPS proxy using your firewall, so redirection may be system-wide or network-wide.&lt;/p&gt;
&lt;p&gt;When is redsocks useful?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want to route part of TCP traffic via OpenSSH &lt;code&gt;DynamicForward&lt;/code&gt; Socks5 port using firewall policies. That was original redsocks development goal;&lt;/li&gt;
&lt;li&gt;you use DVB ISP and this ISP provides internet connectivity with some special daemon that may be also called “Internet accelerator” and the accelerator acts as a proxy and has no “transparent proxy” feature and you need it. Globax was an example of alike accelerator, but Globax 5 has transparent proxy feature. That was the second redsocks` development goal;&lt;/li&gt;
&lt;li&gt;you have to pass traffic through proxy due to corporate network limitation. That was never a goal for redsocks, but users have reported success with some proxy configurations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上是官方介绍。功能确实很强大，毕竟是俄国大佬的作品，继承了俄国产品强大而又粗糙的刻板印象。翻了他的repo，发现最后更新是2019年，而且还是改typo，其他的issue没有回复，大概大佬也不打算维护了吧。找了好久替代，发现了它的一个fork，似乎改了不少东西，就拖下来试试看吧。运行了一晚上，稳定性似乎还不错。&lt;/p&gt;
&lt;p&gt;github地址 &lt;a href=&quot;https://github.com/semigodking/redsocks&quot;&gt;https://github.com/semigodking/redsocks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;该repo依赖openssl和libevent2，libevent2在树莓派的repo中没有了，所以只能现编译。&lt;/p&gt;
&lt;p&gt;现在把安装过程记录一下（仅限Raspberry Pi）：&lt;/p&gt;
&lt;p&gt;#安装openssl开发包&lt;/p&gt;
&lt;p&gt;sudo apt install libssl-dev &lt;/p&gt;
&lt;p&gt;#获取代码&lt;/p&gt;
&lt;p&gt;wget &lt;a href=&quot;https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz&quot;&gt;https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;tar xvfz libevent-2.1.12-stable.tar.gz&lt;/p&gt;
&lt;p&gt;cd libevent-2.1.12-stable&lt;/p&gt;
&lt;p&gt;编译、安装&lt;/p&gt;
&lt;p&gt;make&lt;br&gt;sudo make install&lt;/p&gt;
&lt;p&gt;#克隆redsocks2的代码&lt;/p&gt;
&lt;p&gt;git clone &lt;a href=&quot;https://github.com/semigodking/redsocks&quot;&gt;https://github.com/semigodking/redsocks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;cd redsocks&lt;/p&gt;
&lt;p&gt;make DISABLE_SHADOWSOCKS=1 ENABLE_STATIC=1&lt;/p&gt;
&lt;p&gt;编译完，修改之前redsocks的配置文件、替换redsocks文件，重启服务即可。&lt;/p&gt;
&lt;p&gt;目前暂时稳定。&lt;/p&gt;
</content:encoded><category>Raspberry Pi</category><category>Technology</category></item><item><title>訓練diffusion模型</title><link>https://blog.wo.ai/archives/3467/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3467/</guid><pubDate>Fri, 25 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;訓練擴散模型&lt;/h1&gt;
&lt;p&gt;無條件圖像生成是擴散模型的一種流行應用，它生成的圖像與用於訓練的數據集中的圖像相似。通常，最好的結果是通過在特定數據集上微調預訓練模型來獲得的。您可以在 Hub 上找到許多這樣的檢查點，但如果您找不到您喜歡的檢查點，您可以隨時訓練自己的檢查點！&lt;/p&gt;
&lt;p&gt;本教程將教您如何在 Smithsonian Butterflies 數據集的子集上從頭開始訓練 UNet2DModel，以生成您自己的 🦋 蝴蝶 🦋。&lt;/p&gt;
&lt;p&gt;💡 本培訓教程基於 🧨 擴散器筆記本進行培訓。有關擴散模型的更多詳細信息和背景（例如它們的工作原理），請查看筆記本！&lt;/p&gt;
&lt;p&gt;在開始之前，請確保您安裝了🤗數據集來加載和預處理圖像數據集，並安裝了🤗加速，以簡化在任意數量的 GPU 上的訓練。以下命令還將安裝 TensorBoard 以可視化訓練指標（您還可以使用權重和偏差來跟踪您的訓練）。&lt;/p&gt;
&lt;p&gt;我們鼓勵您與社區分享您的模型，為此，您需要登錄您的 Hugging Face 帳戶（如果您還沒有帳戶，請在此處創建一個！）。您可以從筆記本登錄並在出現提示時輸入您的令牌：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from huggingface_hub import notebook_login &amp;gt;&amp;gt;&amp;gt; notebook_login()&lt;/p&gt;
&lt;p&gt;或者從終端登錄：&lt;/p&gt;
&lt;p&gt;Copiedhuggingface-cli login&lt;/p&gt;
&lt;p&gt;由於模型檢查點非常大，因此安裝 Git-LFS 來對這些大文件進行版本控制：&lt;/p&gt;
&lt;p&gt;Copied!sudo apt -qq install git-lfs !git config –global credential.helper store&lt;/p&gt;
&lt;h2&gt;訓練配置&lt;/h2&gt;
&lt;p&gt;為了方便起見，創建一個包含訓練超參數的 &lt;code&gt;TrainingConfig&lt;/code&gt; 類（隨意調整它們）：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from dataclasses import dataclass &amp;gt;&amp;gt;&amp;gt; @dataclass … class TrainingConfig: … image_size = 128 &lt;em&gt;# the generated image resolution&lt;/em&gt; … train_batch_size = 16 … eval_batch_size = 16 &lt;em&gt;# how many images to sample during evaluation&lt;/em&gt; … num_epochs = 50 … gradient_accumulation_steps = 1 … learning_rate = 1e-4 … lr_warmup_steps = 500 … save_image_epochs = 10 … save_model_epochs = 30 … mixed_precision = “fp16” &lt;em&gt;# &lt;code&gt;no&lt;/code&gt; for float32, &lt;code&gt;fp16&lt;/code&gt; for automatic mixed precision&lt;/em&gt; … output_dir = “ddpm-butterflies-128” &lt;em&gt;# the model name locally and on the HF Hub&lt;/em&gt; … push_to_hub = True &lt;em&gt;# whether to upload the saved model to the HF Hub&lt;/em&gt; … hub_private_repo = False … overwrite_output_dir = True &lt;em&gt;# overwrite the old model when re-running the notebook&lt;/em&gt; … seed = 0 &amp;gt;&amp;gt;&amp;gt; config = TrainingConfig()&lt;/p&gt;
&lt;h2&gt;加載數據集&lt;/h2&gt;
&lt;p&gt;您可以使用 🤗 數據集庫輕鬆加載史密森尼蝴蝶數據集：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from datasets import load_dataset &amp;gt;&amp;gt;&amp;gt; config.dataset_name = “huggan/smithsonian_butterflies_subset” &amp;gt;&amp;gt;&amp;gt; dataset = load_dataset(config.dataset_name, split=”train”)&lt;/p&gt;
&lt;p&gt;💡 您可以從 HugGan 社區活動中找到其他數據集，也可以通過創建本地 &lt;code&gt;ImageFolder&lt;/code&gt; 來使用自己的數據集。如果數據集來自 HugGan 社區活動，則將 &lt;code&gt;config.dataset_name&lt;/code&gt; 設置為數據集的存儲庫 ID；如果您使用自己的圖像，則將 &lt;code&gt;imagefolder&lt;/code&gt; 設置為。&lt;/p&gt;
&lt;p&gt;🤗 Datasets 使用 Image 功能自動解碼圖像數據並將其加載為我們可以可視化的 &lt;code&gt;PIL.Image&lt;/code&gt; ：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; import matplotlib.pyplot as plt &amp;gt;&amp;gt;&amp;gt; fig, axs = plt.subplots(1, 4, figsize=(16, 4)) &amp;gt;&amp;gt;&amp;gt; for i, image in enumerate(dataset[:4][“image”]): … axs[i].imshow(image) … axs[i].set_axis_off() &amp;gt;&amp;gt;&amp;gt; fig.show()&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/butterflies_ds.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;不過，這些圖像的尺寸各不相同，因此您需要先對它們進行預處理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Resize&lt;/code&gt; 將圖像大小更改為 &lt;code&gt;config.image_size&lt;/code&gt; 中定義的大小。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RandomHorizontalFlip&lt;/code&gt; 通過隨機鏡像圖像來擴充數據集。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Normalize&lt;/code&gt; 對於將像素值重新縮放到 [-1, 1] 範圍非常重要，這是模型所期望的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from torchvision import transforms &amp;gt;&amp;gt;&amp;gt; preprocess = transforms.Compose( … [ … transforms.Resize((config.image_size, config.image_size)), … transforms.RandomHorizontalFlip(), … transforms.ToTensor(), … transforms.Normalize([0.5], [0.5]), … ] … )&lt;/p&gt;
&lt;p&gt;使用 🤗 數據集的 set_transform 方法在訓練期間動態應用 &lt;code&gt;preprocess&lt;/code&gt; 函數：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; def transform(examples): … images = [preprocess(image.convert(“RGB”)) for image in examples[“image”]] … return {“images”: images} &amp;gt;&amp;gt;&amp;gt; dataset.set_transform(transform)&lt;/p&gt;
&lt;p&gt;請隨意再次可視化圖像以確認它們已調整大小。現在您已準備好將數據集包裝在 DataLoader 中進行訓練！&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; import torch &amp;gt;&amp;gt;&amp;gt; train_dataloader = torch.utils.data.DataLoader(dataset, batch_size=config.train_batch_size, shuffle=True)&lt;/p&gt;
&lt;h2&gt;創建 UNet2D 模型&lt;/h2&gt;
&lt;p&gt;🧨 Diffusers 中的預訓練模型可以使用您想要的參數輕鬆地從其模型類創建。例如，要創建 UNet2DModel：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from diffusers import UNet2DModel &amp;gt;&amp;gt;&amp;gt; model = UNet2DModel( … sample_size=config.image_size, &lt;em&gt;# the target image resolution&lt;/em&gt; … in_channels=3, &lt;em&gt;# the number of input channels, 3 for RGB images&lt;/em&gt; … out_channels=3, &lt;em&gt;# the number of output channels&lt;/em&gt; … layers_per_block=2, &lt;em&gt;# how many ResNet layers to use per UNet block&lt;/em&gt; … block_out_channels=(128, 128, 256, 256, 512, 512), &lt;em&gt;# the number of output channels for each UNet block&lt;/em&gt; … down_block_types=( … “DownBlock2D”, &lt;em&gt;# a regular ResNet downsampling block&lt;/em&gt; … “DownBlock2D”, … “DownBlock2D”, … “DownBlock2D”, … “AttnDownBlock2D”, &lt;em&gt;# a ResNet downsampling block with spatial self-attention&lt;/em&gt; … “DownBlock2D”, … ), … up_block_types=( … “UpBlock2D”, &lt;em&gt;# a regular ResNet upsampling block&lt;/em&gt; … “AttnUpBlock2D”, &lt;em&gt;# a ResNet upsampling block with spatial self-attention&lt;/em&gt; … “UpBlock2D”, … “UpBlock2D”, … “UpBlock2D”, … “UpBlock2D”, … ), … )&lt;/p&gt;
&lt;p&gt;快速檢查樣本圖像形狀與模型輸出形狀是否匹配通常是個好主意：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; sample_image = dataset[0][“images”].unsqueeze(0) &amp;gt;&amp;gt;&amp;gt; print(“Input shape:”, sample_image.shape) Input shape: torch.Size([1, 3, 128, 128]) &amp;gt;&amp;gt;&amp;gt; print(“Output shape:”, model(sample_image, timestep=0).sample.shape) Output shape: torch.Size([1, 3, 128, 128])&lt;/p&gt;
&lt;p&gt;偉大的！接下來，您需要一個調度程序來向圖像添加一些噪聲。&lt;/p&gt;
&lt;h2&gt;創建調度程序&lt;/h2&gt;
&lt;p&gt;根據您使用模型進行訓練還是推理，調度程序的行為會有所不同。在推理過程中，調度器根據噪聲生成圖像。在訓練期間，調度程序從擴散過程中的特定點獲取模型輸出（或樣本），並根據噪聲調度和更新規則將噪聲應用於圖像。&lt;/p&gt;
&lt;p&gt;讓我們看一下 DDPMScheduler，並使用 &lt;code&gt;add_noise&lt;/code&gt; 方法向之前的 &lt;code&gt;sample_image&lt;/code&gt; 添加一些隨機噪聲：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; import torch &amp;gt;&amp;gt;&amp;gt; from PIL import Image &amp;gt;&amp;gt;&amp;gt; from diffusers import DDPMScheduler &amp;gt;&amp;gt;&amp;gt; noise_scheduler = DDPMScheduler(num_train_timesteps=1000) &amp;gt;&amp;gt;&amp;gt; noise = torch.randn(sample_image.shape) &amp;gt;&amp;gt;&amp;gt; timesteps = torch.LongTensor([50]) &amp;gt;&amp;gt;&amp;gt; noisy_image = noise_scheduler.add_noise(sample_image, noise, timesteps) &amp;gt;&amp;gt;&amp;gt; Image.fromarray(((noisy_image.permute(0, 2, 3, 1) + 1.0) * 127.5).type(torch.uint8).numpy()[0])&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/noisy_butterfly.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;模型的訓練目標是預測添加到圖像中的噪聲。這一步的損失可以通過下式計算：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; import torch.nn.functional as F &amp;gt;&amp;gt;&amp;gt; noise_pred = model(noisy_image, timesteps).sample &amp;gt;&amp;gt;&amp;gt; loss = F.mse_loss(noise_pred, noise)&lt;/p&gt;
&lt;h2&gt;訓練模型&lt;/h2&gt;
&lt;p&gt;到目前為止，您已經掌握了開始訓練模型的大部分內容，剩下的就是將所有內容組合在一起。&lt;/p&gt;
&lt;p&gt;首先，您需要一個優化器和一個學習率調度器：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from diffusers.optimization import get_cosine_schedule_with_warmup &amp;gt;&amp;gt;&amp;gt; optimizer = torch.optim.AdamW(model.parameters(), lr=config.learning_rate) &amp;gt;&amp;gt;&amp;gt; lr_scheduler = get_cosine_schedule_with_warmup( … optimizer=optimizer, … num_warmup_steps=config.lr_warmup_steps, … num_training_steps=(len(train_dataloader) * config.num_epochs), … )&lt;/p&gt;
&lt;p&gt;然後，您需要一種評估模型的方法。為了進行評估，您可以使用 DDPMPipeline 生成一批樣本圖像並將其保存為網格：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from diffusers import DDPMPipeline &amp;gt;&amp;gt;&amp;gt; from diffusers.utils import make_image_grid &amp;gt;&amp;gt;&amp;gt; import math &amp;gt;&amp;gt;&amp;gt; import os &amp;gt;&amp;gt;&amp;gt; def evaluate(config, epoch, pipeline): … &lt;em&gt;# Sample some images from random noise (this is the backward diffusion process).&lt;/em&gt; … &lt;em&gt;# The default pipeline output type is &lt;code&gt;List[PIL.Image]&lt;/code&gt;&lt;/em&gt; … images = pipeline( … batch_size=config.eval_batch_size, … generator=torch.manual_seed(config.seed), … ).images … &lt;em&gt;# Make a grid out of the images&lt;/em&gt; … image_grid = make_image_grid(images, rows=4, cols=4) … &lt;em&gt;# Save the images&lt;/em&gt; … test_dir = os.path.join(config.output_dir, “samples”) … os.makedirs(test_dir, exist_ok=True) … image_grid.save(f”{test_dir}/{epoch:04d}.png”)&lt;/p&gt;
&lt;p&gt;現在，您可以使用 🤗 Accelerate 將所有這些組件包裝在一個訓練循環中，以輕鬆進行 TensorBoard 日誌記錄、梯度累積和混合精度訓練。要將模型上傳到 Hub，請編寫一個函數來獲取存儲庫名稱和信息，然後將其推送到 Hub。&lt;/p&gt;
&lt;p&gt;💡 下面的訓練循環可能看起來令人生畏且漫長，但是當您僅用一行代碼啟動訓練時，這一切都是值得的！如果您迫不及待地想要開始生成圖像，請隨意複製並運行下面的代碼。您可以隨時返回並更仔細地檢查訓練循環，例如當您等待模型完成訓練時。 🤗&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from accelerate import Accelerator &amp;gt;&amp;gt;&amp;gt; from huggingface_hub import HfFolder, Repository, whoami &amp;gt;&amp;gt;&amp;gt; from tqdm.auto import tqdm &amp;gt;&amp;gt;&amp;gt; from pathlib import Path &amp;gt;&amp;gt;&amp;gt; import os &amp;gt;&amp;gt;&amp;gt; def get_full_repo_name(model_id: str, organization: str = None, token: str = None): … if token is None: … token = HfFolder.get_token() … if organization is None: … username = whoami(token)[“name”] … return f”{username}/{model_id}” … else: … return f”{organization}/{model_id}” &amp;gt;&amp;gt;&amp;gt; def train_loop(config, model, noise_scheduler, optimizer, train_dataloader, lr_scheduler): … &lt;em&gt;# Initialize accelerator and tensorboard logging&lt;/em&gt; … accelerator = Accelerator( … mixed_precision=config.mixed_precision, … gradient_accumulation_steps=config.gradient_accumulation_steps, … log_with=”tensorboard”, … project_dir=os.path.join(config.output_dir, “logs”), … ) … if accelerator.is_main_process: … if config.push_to_hub: … repo_name = get_full_repo_name(Path(config.output_dir).name) … repo = Repository(config.output_dir, clone_from=repo_name) … elif config.output_dir is not None: … os.makedirs(config.output_dir, exist_ok=True) … accelerator.init_trackers(“train_example”) … &lt;em&gt;# Prepare everything&lt;/em&gt; … &lt;em&gt;# There is no specific order to remember, you just need to unpack the&lt;/em&gt; … &lt;em&gt;# objects in the same order you gave them to the prepare method.&lt;/em&gt; … model, optimizer, train_dataloader, lr_scheduler = accelerator.prepare( … model, optimizer, train_dataloader, lr_scheduler … ) … global_step = 0 … &lt;em&gt;# Now you train the model&lt;/em&gt; … for epoch in range(config.num_epochs): … progress_bar = tqdm(total=len(train_dataloader), disable=not accelerator.is_local_main_process) … progress_bar.set_description(f”Epoch {epoch}”) … for step, batch in enumerate(train_dataloader): … clean_images = batch[“images”] … &lt;em&gt;# Sample noise to add to the images&lt;/em&gt; … noise = torch.randn(clean_images.shape).to(clean_images.device) … bs = clean_images.shape[0] … &lt;em&gt;# Sample a random timestep for each image&lt;/em&gt; … timesteps = torch.randint( … 0, noise_scheduler.config.num_train_timesteps, (bs,), device=clean_images.device … ).long() … &lt;em&gt;# Add noise to the clean images according to the noise magnitude at each timestep&lt;/em&gt; … &lt;em&gt;# (this is the forward diffusion process)&lt;/em&gt; … noisy_images = noise_scheduler.add_noise(clean_images, noise, timesteps) … with accelerator.accumulate(model): … &lt;em&gt;# Predict the noise residual&lt;/em&gt; … noise_pred = model(noisy_images, timesteps, return_dict=False)[0] … loss = F.mse_loss(noise_pred, noise) … accelerator.backward(loss) … accelerator.clip_grad_norm_(model.parameters(), 1.0) … optimizer.step() … lr_scheduler.step() … optimizer.zero_grad() … progress_bar.update(1) … logs = {“loss”: loss.detach().item(), “lr”: lr_scheduler.get_last_lr()[0], “step”: global_step} … progress_bar.set_postfix(**logs) … accelerator.log(logs, step=global_step) … global_step += 1 … &lt;em&gt;# After each epoch you optionally sample some demo images with evaluate() and save the model&lt;/em&gt; … if accelerator.is_main_process: … pipeline = DDPMPipeline(unet=accelerator.unwrap_model(model), scheduler=noise_scheduler) … if (epoch + 1) % config.save_image_epochs == 0 or epoch == config.num_epochs – 1: … evaluate(config, epoch, pipeline) … if (epoch + 1) % config.save_model_epochs == 0 or epoch == config.num_epochs – 1: … if config.push_to_hub: … repo.push_to_hub(commit_message=f”Epoch {epoch}”, blocking=True) … else: … pipeline.save_pretrained(config.output_dir)&lt;/p&gt;
&lt;p&gt;唷，那是相當多的代碼！但您終於準備好使用 🤗 Accelerate 的 notebook_launcher 函數啟動訓練了。向函數傳遞訓練循環、所有訓練參數和進程數（您可以將此值更改為可用的 GPU 數量）以用於訓練：&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; from accelerate import notebook_launcher &amp;gt;&amp;gt;&amp;gt; args = (config, model, noise_scheduler, optimizer, train_dataloader, lr_scheduler) &amp;gt;&amp;gt;&amp;gt; notebook_launcher(train_loop, args, num_processes=1)&lt;/p&gt;
&lt;p&gt;訓練完成後，查看擴散模型生成的最終 🦋 圖像 🦋！&lt;/p&gt;
&lt;p&gt;Copied&amp;gt;&amp;gt;&amp;gt; import glob &amp;gt;&amp;gt;&amp;gt; sample_images = sorted(glob.glob(f”{config.output_dir}/samples/*.png”)) &amp;gt;&amp;gt;&amp;gt; Image.open(sample_images[-1])&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/butterflies_final.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;下一步&lt;/h2&gt;
&lt;p&gt;無條件圖像生成是可訓練任務的一個示例。您可以訪問🧨 擴散器培訓示例頁面來探索其他任務和培訓技術。以下是您可以學到的一些示例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文本反轉，一種向模型傳授特定視覺概念並將其集成到生成圖像中的算法。&lt;/li&gt;
&lt;li&gt;DreamBooth，一種在給定主題的多個輸入圖像的情況下生成主題的個性化圖像的技術。&lt;/li&gt;
&lt;li&gt;在您自己的數據集上微調穩定擴散模型的指南。&lt;/li&gt;
&lt;li&gt;LoRA 使用指南，這是一種內存高效技術，可以更快地微調大型模型。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>AI</category><category>Technology</category></item><item><title>如何訓練穩定的擴散模型：初學者指南</title><link>https://blog.wo.ai/archives/3465/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3465/</guid><pubDate>Sun, 20 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;掌握訓練穩定擴散模型的基礎知識。通過簡單的分步說明，我們將立即將初學者轉變為自信的模型訓練師。一起來學習吧！&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;近年來，人工智能 (AI) 和機器學習 (ML) 徹底改變了數據分析、預測建模和決策領域。其中一個發展就是穩定擴散模型，它是一種基於歷史數據生成圖像和預測結果的強大工具。&lt;/p&gt;
&lt;p&gt;在這份內容廣泛的指南中，我們將深入研究穩定擴散模型的訓練過程，為您提供掌握這一迷人技術必不可少的步驟、最佳實踐和策略。&lt;/p&gt;
&lt;p&gt;穩定擴散模型簡介&lt;/p&gt;
&lt;p&gt;穩定擴散模型包含一種機器學習算法類型，該算法使用歷史數據來預測特定結果或事件的概率。這些模型採用一種稱為擴散過程的技術，其中包括向輸入圖像添加噪聲，然後隨著時間的推移逐漸減少噪聲以生成最終圖像。此過程產生的圖像比傳統深度學習 (DL) 模型更詳細、更真實。&lt;/p&gt;
&lt;p&gt;穩定擴散模型因其處理複雜和抽象文本描述的能力而特別引人注目，這要歸功於一種稱為穩定訓練的新方法。該技術使模型能夠生成與文本輸入一致的高質量圖像，使其比以前的文本到圖像模型有了顯著改進。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;穩定擴散模型的數據準備&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在訓練穩定擴散模型之前，準備用於訓練模型的數據至關重要。該過程涉及以下步驟：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data collection 數據採集&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;收集與您期望的結果相關的準確且最新的數據。確保數據準確地代表您希望模型解決的問題。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data cleaning 數據清洗&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;消除數據集中的任何異常值、缺失數據或不一致之處，以最大限度地提高模型的準確性。這可能涉及填充缺失值、糾正錯誤或將數據轉換為更可用的格式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data preprocessing 數據預處理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;應用各種技術來提高模型的準確性和性能。這可能包括歸一化、標準化或降維。&lt;/p&gt;
&lt;p&gt;模型設計和算法選擇&lt;/p&gt;
&lt;p&gt;準備好數據後，下一步就是設計穩定擴散模型。這涉及為模型選擇適當的算法、架構和參數。穩定擴散模型中使用的一些流行算法包括：&lt;/p&gt;
&lt;p&gt;深度卷積神經網絡（DCNN）&lt;/p&gt;
&lt;p&gt;生成對抗網絡（GAN）&lt;/p&gt;
&lt;p&gt;變分自動編碼器（VAE）&lt;/p&gt;
&lt;p&gt;選擇算法和架構時，請考慮問題的複雜性、數據集的大小以及所需的準確度等因素。&lt;/p&gt;
&lt;p&gt;訓練穩定擴散模型&lt;/p&gt;
&lt;p&gt;要訓​​練您自己的穩定擴散模型，您可以使用各種工具和平台，例如 Google Colab、Jupyter Notebooks 或 TensorFlow。這些平台提供了用於運行實驗、管理模型和生成圖像的交互式環境。&lt;/p&gt;
&lt;p&gt;遵循本節中的步驟將使您能夠創建適合您的需求和偏好的擴散模型，從而產生富有洞察力的預測。以下是訓練穩定擴散模型的步驟：&lt;/p&gt;
&lt;p&gt;將準備好的數據集分為訓練集和驗證集。使用訓練集來訓練模型，使用驗證集來評估其性能。&lt;/p&gt;
&lt;p&gt;從各種可用選項中選擇合適的穩定擴散模型。&lt;/p&gt;
&lt;p&gt;使用 PyTorch 或 TensorFlow 等軟件訓練模型。請注意，根據數據集大小和模型複雜性，訓練持續時間可以從幾個小時到幾天不等。&lt;/p&gt;
&lt;p&gt;訓練後，使用驗證集評估模型的性能。&lt;/p&gt;
&lt;p&gt;一旦對模型的性能感到滿意，就可以通過向模型提供隨機噪聲向量來生成圖像。&lt;/p&gt;
&lt;p&gt;在我們詳細指南的幫助下輕鬆安裝穩定擴散。&lt;/p&gt;
&lt;p&gt;模型評估和驗證&lt;/p&gt;
&lt;p&gt;訓練穩定擴散模型後，必須評估其性能並驗證其準確性。這可以通過使用各種指標將模型的預測與實際結果進行比較來完成，例如：&lt;/p&gt;
&lt;p&gt;均方誤差 (MSE)。&lt;/p&gt;
&lt;p&gt;均方根誤差 (RMSE)。&lt;/p&gt;
&lt;p&gt;平均絕對誤差 (MAE)。&lt;/p&gt;
&lt;p&gt;R 平方（決定係數）。&lt;/p&gt;
&lt;p&gt;此外，通過確保模型隨著時間的推移產生一致的結果來評估模型的穩定性也至關重要。&lt;/p&gt;
&lt;h2&gt;穩定擴散模型最佳實踐&lt;/h2&gt;
&lt;p&gt;為了確保穩定擴散模型的成功訓練，請考慮以下最佳實踐：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用準確、最新且具有代表性的數據進行培訓。&lt;/li&gt;
&lt;li&gt;使用不同的數據集測試模型以評估其性能。&lt;/li&gt;
&lt;li&gt;在評估過程中評估模型的準確性和穩定性。&lt;/li&gt;
&lt;li&gt;為模型選擇適當的算法、架構和參數。&lt;/li&gt;
&lt;li&gt;通過應用數據預處理技術來提高模型的準確性。&lt;/li&gt;
&lt;li&gt;並通過持續監控變更或更新來確保其持續性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;擴散模型應用&lt;/h2&gt;
&lt;p&gt;擴散模型在各個行業都有廣泛的應用，包括金融、醫療保健、營銷和遊戲。穩定擴散模型的一些常見用途是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;電子商務：網站可以使用穩定的擴散模型根據文字描述生成產品圖像，從而無需真實照片即可展示產品。&lt;/li&gt;
&lt;li&gt;廣告：廣告公司可以利用穩定的擴散模型為其廣告活動生成獨特且具有視覺吸引力的視覺效果。&lt;/li&gt;
&lt;li&gt;遊戲：遊戲開發者可以採用穩定的擴散模型，根據文本描述生成遊戲資產，例如角色和環境。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;訓練穩定擴散需要多長時間？&lt;/h2&gt;
&lt;p&gt;訓練穩定擴散所需的時間取決於許多因素。因此包括數據集的大小、您使用的硬件以及輸出圖像的質量。&lt;/p&gt;
&lt;p&gt;如果您使用較小的數據集或功能較弱的硬件，則訓練穩定擴散將需要更長的時間。例如，一位用戶報告說，他們花了大約 2 週的時間在單個 V100 GPU 上訓練穩定擴散。&lt;/p&gt;
&lt;p&gt;如果您不確定訓練穩定擴散需要多長時間，您可以先嘗試使用較小的數據集或功能較弱的硬件。您還可以嘗試使用預先訓練的穩定擴散模型，這將為您節省一些時間。&lt;/p&gt;
&lt;p&gt;以下是一些加快穩定擴散訓練的技巧：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用更大的數據集：更大的數據集將為穩定擴散提供更多可供學習的信息，這將加快訓練過程。&lt;/li&gt;
&lt;li&gt;使用更強大的GPU：更強大的GPU將能夠更快地處理數據，這也將加快訓練過程。&lt;/li&gt;
&lt;li&gt;使用預先訓練的模型：預先訓練的模型已經具有一些如何生成圖像的知識，這將加快訓練過程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常見問題 (FAQ)&lt;/p&gt;
&lt;h3&gt;問：如何製作穩定擴散模型？&lt;/h3&gt;
&lt;p&gt;答：製作穩定擴散模型需要大量的技術知識和資源。這不是一件容易或快速就能完成的事情。以下是製作穩定擴散模型的基本步驟：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;收集大量圖像數據集：該數據集應盡可能多樣化，以確保模型可以生成各種圖像。&lt;/li&gt;
&lt;li&gt;預處理數據集：這涉及將圖像轉換為模型可以理解的格式。&lt;/li&gt;
&lt;li&gt;訓練模型：這是最耗時的步驟。該模型需要在數據集上進行長時間的訓練，才能學習如何生成圖像。&lt;/li&gt;
&lt;li&gt;測試模型：模型訓練完成後，需要在新的圖像數據集上進行測試。這將有助於確保模型生成高質量的圖像。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;問：訓練穩定擴散模型需要多長時間？&lt;/h3&gt;
&lt;p&gt;答：訓練穩定擴散模型所需的時間取決於多種因素，包括數據集的大小、模型的複雜性和可用的計算資源。&lt;/p&gt;
&lt;p&gt;一般來說，在大型數據集上訓練穩定擴散模型可能需要幾天甚至幾週的時間。例如，在 ImageNet 數據集上訓練穩定擴散模型在單個 GPU 上可能需要長達 10 天的時間。&lt;/p&gt;
&lt;p&gt;如果您使用較小的數據集或不太複雜的模型，訓練時間會更短。然而，值得注意的是，即使是小模型也可能需要幾個小時來訓練。&lt;/p&gt;
&lt;h3&gt;問：如何通過穩定擴散獲得更好的結果？&lt;/h3&gt;
&lt;p&gt;答：您可以採取一些措施來獲得更好的穩定擴散效果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用更大的數據集：模型需要學習的數據越多，結果就越好。&lt;/li&gt;
&lt;li&gt;使用更複雜的模型：更複雜的模型將能夠學習數據中更複雜的模式，從而獲得更好的結果。&lt;/li&gt;
&lt;li&gt;訓練模型的時間越長：模型訓練的時間越長，結果就越好。&lt;/li&gt;
&lt;li&gt;使用更好的優化器：優化器是一種幫助模型學習的數學算法。使用更好的優化器可以幫助模型更快收斂並獲得更好的結果。&lt;/li&gt;
&lt;li&gt;使用更複雜的損失函數：損失函數是一個數學方程，用於衡量模型的性能。使用更複雜的損失函數可以幫助模型更有效地學習。&lt;/li&gt;
&lt;li&gt;使用正則化技術：正則化是一種有助於防止模型過度擬合數據的技術。當模型學習數據中的噪聲而不是底層模式時，就會發生過度擬合。使用正則化技術有助於提高模型的泛化性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;問：如何訓練穩定擴散權重？&lt;/h3&gt;
&lt;p&gt;用戶可以使用多種方法訓練穩定擴散權重，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;監督學習：在監督學習中，模型在已標記有所需輸出的圖像數據集上進行訓練。例如，該模型可以使用貓和狗的圖像數據集，每個圖像標記為“貓”或“狗”。&lt;/li&gt;
&lt;li&gt;無監督學習：在無監督學習中，模型在沒有任何標籤的圖像數據集上進行訓練。該模型學習在數據中查找模式，而無需明確告知要查找什麼。&lt;/li&gt;
&lt;li&gt;半監督學習：在半監督學習中，模型在已部分標記的圖像數據集上進行訓練。當沒有足夠的標記數據可用時，這可能是提高模型性能的有用方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;訓練穩定擴散權重的最佳方法取決於具體應用。例如，如果你想訓練一個模型來生成貓和狗的圖像，監督學習將是一個不錯的選擇。如果你想訓練一個模型來生成任何物體的圖像，無監督學習將是更好的選擇。&lt;/p&gt;
&lt;h3&gt;問：穩定擴散可以訓練嗎？&lt;/h3&gt;
&lt;p&gt;答：是的，穩定擴散是可以訓練的。它是一種擴散模型，是一類可以訓練生成圖像的生成模型。穩定擴散是一種特殊類型的擴散模型，其設計比其他擴散模型更穩定且更易於訓練。&lt;/p&gt;
&lt;p&gt;穩定擴散模型使用稱為對抗訓練的技術進行訓練。在對抗性訓練中，兩個模型相互訓練。一種模型是生成器，它負責生成圖像。另一個模型是鑑別器，負責區分真實圖像和生成圖像。&lt;/p&gt;
&lt;p&gt;生成器可以生成盡可能真實的圖像。鑑別器可以區分真實圖像和生成圖像。作為生成器和鑑別器，它們在各自的任務上變得更好。最終，生成器變得非常擅長生成逼真的圖像，以至於鑑別器無法再區分它們。&lt;/p&gt;
&lt;h3&gt;問：訓練穩定擴散需要多少張照片？&lt;/h3&gt;
&lt;p&gt;答：訓練穩定擴散所需的照片數量取決於要訓練的模型的大小和復雜性。較大的模型比較小的模型需要更多的照片。&lt;/p&gt;
&lt;p&gt;一般來說，您至少需要幾千張照片來訓練穩定擴散模型。但是，如果使用稱為數據增強的技術，則可以使用更少的照片來訓練模型。數據增強是一種通過從現有圖像創建新圖像來人為地增加數據集大小的技術。&lt;/p&gt;
&lt;h2&gt;結論&lt;/h2&gt;
&lt;p&gt;訓練穩定的擴散模型似乎具有挑戰性。然而，理解這個過程並使用正確的方法可以將其變成結果預測的強大工具。&lt;/p&gt;
&lt;p&gt;本綜合指南的步驟和最佳實踐可以幫助您掌握這些模型的訓練並充分發揮其潛力。&lt;/p&gt;
&lt;p&gt;無論您是業餘愛好者還是專業人士，深入研究穩定擴散模型的令人興奮的世界都可以讓您創造出令人驚嘆的視覺效果並獲得寶貴的見解。&lt;/p&gt;
</content:encoded><category>AI</category><category>Technology</category></item><item><title>今夜は月が綺麗ですね</title><link>https://blog.wo.ai/archives/3442/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3442/</guid><pubDate>Thu, 19 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2023/02/v2-b883a9c9323c7ef198c00b6f25f65f7e_1440w.jpeg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/2/https://blog.wo.ai/wp-content/uploads/2023/02/v2-b883a9c9323c7ef198c00b6f25f65f7e_1440w.jpeg&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;月が綺麗な夜に、突然意中の人に「今夜は月が綺麗ですね」と言われたら、どう感じますか。ロマンチックな人や、なんかキザったらしい人だと感じる人も多いでしょう。中には「今夜は月が綺麗ですね」の意味を知っていて、洒落たことを言い返したいと思う人もいるでしょう。&lt;/p&gt;
&lt;p&gt;この「今夜は月が綺麗ですね」にはどんな意味を持っているのか、これを意味することを知れば、日本語の奥ゆかしさに歯がゆさを感じるでしょう。&lt;/p&gt;
&lt;p&gt;夏目漱石说在日语里要表达我爱你，应说成 今夜月色真好。&lt;/p&gt;
&lt;p&gt;那为什么月色又会成为我爱你的表达呢？ 在日语中月读作つき（tsuki），恰好和喜欢的日语好き（すき，suki）读音相近，日本人把谐音哏玩得很溜啊。东方人含蓄蕴藉，隐忍克制，大多不会像西方人那样热烈奔放，直抒胸臆。从他们口中断然不会轻易吐露那三个字，他们只会借赋比兴来表达爱意。他们会说，“思君如满月，夜夜减清辉。”他们会说，“此时相望不相闻，愿逐月华流照君。”他们会说，“愿我如星君如月，夜夜流光相皎洁。”他们会说，“月が绮丽ですね。”&lt;/p&gt;
</content:encoded><category>Life</category><category>Poem &amp; Reading</category></item><item><title>ED-CM4NANO：基于树莓派 CM4 的工业计算机</title><link>https://blog.wo.ai/archives/3411/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3411/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CM4 NANO 是 EDATEC 为工业应用设计的一款嵌入式计算机，提供与标准树莓派4 相同的功能。其设计兼顾了高性能和低成本的存储方案，能同时支持 eMMC 和 microSD 卡存储。更宽的工作温度、更好的无线性能也使其比标准的树莓派4 更适合用于工业领域。&lt;/p&gt;
&lt;p&gt;ED-CM4NANO 充分利用 CM4 的结构灵活性，解决 CPU、无线模块、PMU 的散热问题。 BT 天线提高了无线通信的可靠性，提供了灵活的扩展能力。&lt;/p&gt;
&lt;p&gt;系统可根据不同应用配置 1GB/2GB/4GB/8GB RAM 和 8GB/16GB/32GB eMMC，2.4/5.8G双频WiFi和蓝牙（可选），支持外接天线。&lt;/p&gt;
&lt;p&gt;对于需要大数据存储容量的应用场景，用户可以使用 eMMC 安装操作系统并存储应用数据，同时用户可以使用 SD 卡存储大用户数据，相比内置 eMMC 内存， SD 卡可以提供更大的容量，其成本更有效。&lt;/p&gt;
&lt;p&gt;ED-CM4NANO 提供报警蜂鸣器，电池供电的实时时钟，支持具有POE功能的千兆网口，兼容官方Raspberry Pi PoE HAT。&lt;/p&gt;
&lt;p&gt;ED-CM4NANO 的 CSI 和 DSI 接口完全兼容 Raspberry Pi4 Model B。CSI 支持 Raspberry Pi 官方 8M 和 12M Pixel HQ 摄像头，DSI 接口支持 Raspberry Pi 官方7寸触摸屏。标准 HDMI 接口可输出 4K 高清视频。&lt;/p&gt;
&lt;p&gt;ED-CM4NANO 提供 2 路 USB3.0 和 1 路 USB2.0 接口，USB2.0 接口可用于更新系统镜像。&lt;/p&gt;
&lt;p&gt;ED-CM4NANO 提供非常强大的扩展能力，支持标准的 Raspberry Pi 40 针扩展接口，用户可以通过板载 USB3.0 Type C 接口机械轻松扩展高速设备。&lt;/p&gt;
&lt;p&gt;系统支持 +12V~18VDC 电源输入，40PIN 插座提供 5V@2A 和 &lt;a href=&quot;mailto:3.3V@1.5A&quot;&gt;3.3V@1.5A&lt;/a&gt; 输出电源，支持大功率扩展模块。&lt;/p&gt;
&lt;p&gt;ED-CM4NANO PCB 的尺寸为 95*58mm，比树莓派4B 略大。出色的散热性能让整个系统能在高温环境下可靠工作。根据我们在实验室的实际测试，ED-CM4NANO 可以在 -25~60°C 的环境温度下可靠工作。&lt;/p&gt;
&lt;p&gt;ED-CM4NANO 机箱底部预留了四个M2.5螺丝孔，方便用户将 ED-CM4NANO 安装在其他设备上。 ED-CM4NANO 还支持 DIN 导轨安装。&lt;/p&gt;
&lt;p&gt;特性&lt;/p&gt;
&lt;p&gt;处理能力&lt;/p&gt;
&lt;p&gt;Broadcom BCM2711 4 核 Cortex A72 1.5GHz (ARM v8) 64 位 CPU&lt;/p&gt;
&lt;p&gt;支持 H.265（HEVC）（最高 4Kp60 解码），H.264（最高 1080p60 解码，1080p30 编码）&lt;/p&gt;
&lt;p&gt;OpenGL ES 3.0 图形支持&lt;/p&gt;
&lt;p&gt;内存 1GB/2GB/4GB/8GB LPDDR4-3200 SDRAM 可选&lt;/p&gt;
&lt;p&gt;8GB/16GB/32GB eMMC 可选&lt;/p&gt;
&lt;p&gt;摄像头和显示器&lt;/p&gt;
&lt;p&gt;1 个 CSI 接口，支持树莓派官方 5M &amp;amp; 8M 像素摄像头模组&lt;/p&gt;
&lt;p&gt;1 个 HDMI 接口&lt;/p&gt;
&lt;p&gt;1 个 DSI 接口，支持树莓派官方显示&lt;/p&gt;
&lt;p&gt;有线接口&lt;/p&gt;
&lt;p&gt;10/100M/1000 以太网，支持 PoE 供电（需要官方 PoE HAT）&lt;/p&gt;
&lt;p&gt;2 个 USB3.0（外置）&lt;/p&gt;
&lt;p&gt;1 个 USB3.0（内置）&lt;/p&gt;
&lt;p&gt;1 个 USB2.0&lt;/p&gt;
&lt;p&gt;无线通信&lt;/p&gt;
&lt;p&gt;2.4G/5.8G WiFi 兼容 IEEE 802.11 b/g/n/ac 标准&lt;/p&gt;
&lt;p&gt;蓝牙 5.0 BLE&lt;/p&gt;
&lt;p&gt;WiFi/BLE 外置天线和 PCB 天线可选&lt;/p&gt;
&lt;p&gt;存储&lt;/p&gt;
&lt;p&gt;microSD 卡座（用于扩展用户数据存储）&lt;/p&gt;
&lt;p&gt;可扩展接口&lt;/p&gt;
&lt;p&gt;Raspberry Pi 标准 40 针连接器&lt;/p&gt;
&lt;p&gt;Raspberry Pi 标准 4 针 POE 连接器&lt;/p&gt;
&lt;p&gt;其他&lt;/p&gt;
&lt;p&gt;带备用电池的 RTC&lt;/p&gt;
&lt;p&gt;红色 LED 电源指示灯&lt;/p&gt;
&lt;p&gt;绿色 LED 状态指示&lt;/p&gt;
&lt;p&gt;系统重置按钮&lt;/p&gt;
&lt;p&gt;蜂鸣器&lt;/p&gt;
&lt;p&gt;电源&lt;/p&gt;
&lt;p&gt;12~18V DC 电压范围输入&lt;/p&gt;
&lt;p&gt;5.5/2.5mm DC Jack 或 Phoenix 端子连接器可选&lt;/p&gt;
&lt;p&gt;产品型号的命名规则如图。&lt;/p&gt;
&lt;p&gt;FAQ&lt;/p&gt;
&lt;p&gt;Q: 我可以从 CM4 NANO 的 SD 卡启动系统吗？&lt;/p&gt;
&lt;p&gt;A: 不能。CM4 NANO 是为工业应用而设计的，为了保证系统的可靠性，CM4 NANO 必须使用带有 eMMC 内存的 CM4。系统只能从 eMMC 启动，SD 卡用于数据存储。&lt;/p&gt;
&lt;p&gt;Q：CM4 NANO 上 40PIN 引脚有没有被占用？&lt;/p&gt;
&lt;p&gt;A：CM4 NANO 的以下功能占用了 40PIN 引脚：&lt;/p&gt;
&lt;p&gt;蜂鸣器占用 GPIO6，如果不需要，可以去掉电阻 R39，将 GPIO6 与蜂鸣器电路断开。&lt;/p&gt;
&lt;p&gt;SD 卡模块占用 GPIO22、23、24、25、26、27。如果您不需要使用它，请不要插入 SD 卡。&lt;/p&gt;
&lt;p&gt;购买&lt;/p&gt;
&lt;p&gt;复制下方链接，在浏览器中打开即可下单：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://link.nxez.com/buy/ed-cm4nano&quot;&gt;http://link.nxez.com/buy/ed-cm4nano&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;介绍视频&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>NAS 私有云和 KODI 软硬集成的套件</title><link>https://blog.wo.ai/archives/3427/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3427/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本文作者：硬件老郭&lt;/p&gt;
&lt;p&gt;树莓派4B搭载了博通最新的BCM2711处理器，它采用了4枚性能更为强劲的ARM A72核心。相比上一代树莓派3B+，4B的性能有着2~4倍的提升，处理任务要更为流畅。而在内存上，树莓派4B拥有1GB/2GB/4GB三种规格可选，相比以往的1GB，大大拓展了多任务处理的可能。&lt;/p&gt;
&lt;p&gt;与上一代树莓派的性能对比，树莓派4B不只是频率的提升而已，重大的提升还有，采用了USB3.0接口，并且留够了充足的带宽，不像树莓派3是共享的总线带宽，性能受到制约，何况还是USB2.0。还有就是支持H.265 4K硬件解码。&lt;/p&gt;
&lt;p&gt;提升如此巨大，我们能怎么才能发挥出它的性能呢？&lt;/p&gt;
&lt;p&gt;作为一个“深度”树莓派爱好者，看到树莓派4B性能不再是挤牙膏，而是性能爆炸了，简直是出乎我的意料。要知道，树莓派官方树莓派基金会推出树莓派是为了 慈善目的 的产品，希望将树莓派推广给全世界的青少年电脑爱好者，用于培养计算机程序设计的兴趣和能力，所以价格是非常重要的因素。这也是为什么树莓派不是像手机或者平板那样采用性能强大CPU的原因，但也不排除以后的思路会转变。那么树莓派4B性能大幅提升，价格是不是更贵了？并没有，价格还是和树莓派3发售的价格一致，只不过根据内存大小分为了好几个版本。&lt;/p&gt;
&lt;p&gt;在以前实现不了或者说使用效果体验不好的玩法终于可以实现了。比如：AI图像处理、4K电影播放、NAS网络存储、私有云等等。AI图形处理这里就不是我们关心的重点了，我们接下来主要来谈后面几个。&lt;/p&gt;
&lt;p&gt;Kodi 媒体中心是一个屡获殊荣的自由和开源的跨平台媒体播放器和用于HTPC（Home theater PC，家庭影院PC）的数字媒体娱乐中心软件，可以运行在Windows、Android、iOS、Mac、Linux、Raspberry Pi系统。它是一个优秀的自由和开源的（GPL）媒体中心软件。最初为Xbox而开发，叫XBMC。因为全世界开发者的参与，这个软件已经拥有远远超过以往的功能，这已经不能用‘一个简单播放器’来包含所有的一切了。因为这个原因，XBMC.14后更名为Kodi。&lt;/p&gt;
&lt;p&gt;在树莓派上，我们用LibreELEC_RPi4。它是一个KODI的树莓派定制版本，做了很多优化，去掉了一些不需要的功能。&lt;/p&gt;
&lt;p&gt;这里，经过实际测试，CPU占有率20%左右（4核平均值，以大家习惯的win系统占有率计算方式表达，满载是100%）。&lt;/p&gt;
&lt;p&gt;内存占用在600M左右。可以打开KODI的资源检测查看。&lt;/p&gt;
&lt;p&gt;简单的说，我们可以用KODI 在电视上播放4K电影、电视剧、看电视直播、刷B站视频、听无损音乐，支持安卓苹果手机投屏，手机的照片和视频可以在电视机上播放，与家人分享。&lt;/p&gt;
&lt;p&gt;看到这里，你心动了吗？&lt;/p&gt;
&lt;p&gt;可能有人会说，我有电视机顶盒，也能实现你说的这些功能。弄这些有必要吗？&lt;/p&gt;
&lt;p&gt;是的，没错，但是其实是有很大差别的，虽然有部分是交叉的。听我慢慢道来。。。&lt;/p&gt;
&lt;p&gt;电视机顶盒目前的现状&lt;/p&gt;
&lt;p&gt;电视机顶盒现在配置都比较高了，或者有些是智能电视机，内部其实就是一个电视机顶盒。&lt;/p&gt;
&lt;p&gt;电视机顶盒用的是安卓系统，而我们这个树莓派KODI用的是linux系统。&lt;/p&gt;
&lt;p&gt;但是目前的机顶盒生态其实很不友好。只能安装自带应用市场的app，想通过U盘安装app人为设置了很多障碍。&lt;/p&gt;
&lt;p&gt;机顶盒开始买来的时候，运行都很流畅，但是只要用了2、3年，就发现越来越卡，不管配置有多高，好像都这样，用的还是那些厂家自带的软件。但就是会卡，这好像是安卓系统避免不了的一样，也有人说这是厂家的计划报废。。。&lt;/p&gt;
&lt;p&gt;机顶盒的视频都是会员制了，免费的电影很少，但是很多正在播的电视剧电影等又是独家的，意味着你买的其他视频app的会员可能没有你想看的电视剧、电影。而且就算你买了所有的会员，还有会员专属的广告等着你呢。就算你买了会员，能看的4K电影也是很有限的，少得可怜。在线看4K电影还很考验网速，看的过程中卡顿就很影响观看感受了。&lt;/p&gt;
&lt;p&gt;所以很多时候，机顶盒只是成为了一个手机投屏的工具了，都是通过手机来播放投屏观看的。&lt;/p&gt;
&lt;p&gt;关于看电视的问题，其实现在很少看电视了，但是家里面老一辈的父母是要看电视的，自己也会偶尔看下新闻、体育什么的。但是机顶盒看电视直播是有版权的，不是你想看就能看得，好像安装了电信宽带电信机顶盒的用户可以免费看。而且频道也就是各个地方台，中央台。&lt;/p&gt;
&lt;p&gt;还有，机顶盒它主要是在线播放，目前都是购买了影视的版权的，这也就是说，对于一些资深影迷来说，很多片子没有引进国内是看不到，或者是删减了的。像是很多美剧韩剧等等。&lt;/p&gt;
&lt;p&gt;总的来说，现在的机顶盒很畸形，本来它是可以作为一个大平板，和手机一样的，但是你们看到了，这其中有太多的人为限制。目前来说，机顶盒显得很鸡肋，只需要有一个手机投屏就可以了。&lt;/p&gt;
&lt;p&gt;KODI 系统的优势和特点&lt;/p&gt;
&lt;p&gt;KODI系统虽然使用的Linux内核，但是它是以插件形式安装软件的，并不是linux通用的软件安装方式，它属于高度定制的系统。而由于KODI属于国外开源的软件系统，它的开发都是依据国外的网络使用环境来的，比如：youtube、推特、奈飞等都是正常方式国内用户用不了的。国内用户能使用的我们已经验证并且集成到系统里面了。&lt;/p&gt;
&lt;p&gt;KODI 系统永远也不会卡顿。因为是采用的Linux系统，稳定性是很高的，绝大部分服务器都是使用的Linux系统。而且树莓派4B的性能经过我们实测，运行KODI绰绰有余，是跑不满CPU的。而KODI的插件是很纯净的，没有乱七八糟的后台，广告，获取用户权限搜集用户信息什么的，用再久也不会卡顿。&lt;/p&gt;
&lt;p&gt;KODI同样支持手机投屏，而且我感觉网速快得多，对应的清晰度也高一些，这点还有待验证。&lt;/p&gt;
&lt;p&gt;KODI看电视直播是使用PVR电视直播插件来实现的，支持m3u电视节目单，导入到插件中，重启后就可以看电视了。m3u我们知道是一种流媒体协议，是一种数据广播的形式，点对多传输的。这个电视节目网上搜一下很多的，电视台可达2000多个！不局限于什么地方台中央台，还有国内收费的电视节目，国外各种卫星电视节目，都是免费观看的。&lt;/p&gt;
&lt;p&gt;用KODI可以把存储的电影电视剧导入，然后刮削出海报、剧情介绍等信息，形成自己的影音库。这些影音都是存在本地硬盘的，平时通过我们的树莓派系统里面的Aria2 ,qBittorrent 下载软件下载到硬盘，而机顶盒是属于在线播放的。&lt;/p&gt;
&lt;p&gt;KODI还能结合NAS系统私有云，把手机备份的相片和视频，在电视上播放与家人一同观看。&lt;/p&gt;
&lt;p&gt;KODI还可以安装B站插件，可以观看B站的大量视频，喜欢二次元的不要错过。&lt;/p&gt;
&lt;p&gt;KODI可以使用红外线遥控器，或者鼠标键盘。&lt;/p&gt;
&lt;p&gt;NAS 网络存储私有云&lt;/p&gt;
&lt;p&gt;这里很多同学可能会说了，用树莓派来做NAS性能很拉跨很差劲吧？我买个二手的矿机来做NAS不香吗？价格还便宜不少。&lt;/p&gt;
&lt;p&gt;这里我多说两句，普及下矿机的事。在前些年，出了很多区块链的矿机，因为还可以赚点小钱，十分火爆。像是什么联想、迅雷也出过，蜗牛星际、X家云、X客云等等，后来因为政策，这些产品都踩了红线，一时之间全部退市。这样二手市场流出了大量的矿机。&lt;/p&gt;
&lt;p&gt;因为是二手商品，厂家都关门了，肯定是没有技术支持的没有售后的，只能买来自己折腾。肯定是需要你要有这方面技术能力和时间成本的。&lt;/p&gt;
&lt;p&gt;这些二手矿机年代久远了，由于是矿机，长时间高负荷工作，还能用多久全靠运气，就是花钱碰个运气。&lt;/p&gt;
&lt;p&gt;由于这些都是好多年前的硬件方案了。性能只能说是很勉强。&lt;/p&gt;
&lt;p&gt;由于带有USB3.0，数据传输相对于USB2.0提升了10倍！300M以上的速度我相信对于绝大多数人来说够用的很了。以前用USB2.0来做NAS系统的时代一去不复返了。USB2.0接硬盘做NAS系统传输速度只有10M，十分鸡肋，只能说能用。现在终于不再受USB2.0的速度限制了。盼了好久，终于等到树莓派USB3.0的到来。&lt;/p&gt;
&lt;p&gt;配合上真千兆网口，大大提升了使用体验。&lt;/p&gt;
&lt;p&gt;树莓派的千兆网络没问题, 几乎可以跑满:&lt;/p&gt;
&lt;p&gt;测试用一台工作站与树莓派直连，931M/s 接近1000M. 这意味着跑个NAS, 瓶颈不再是网络了。当然，你要说你家里是万兆宽带，千兆拖了后腿，也没办法。这个也毕竟是少数，远远还没有到普及的程度。树莓派4B的价格平易近人，还能要求什么呢？关键是看能不能满足我们想要的功能。&lt;/p&gt;
&lt;p&gt;整个存储都是放在硬盘上的，而硬盘是通过USB3.0连接到树莓派USB3.0口的。所以USB3.0口对于硬盘的读写速度是非常重要的。&lt;/p&gt;
&lt;p&gt;私有云存储&lt;/p&gt;
&lt;p&gt;采用在树莓派官方32位系统基础上来搭建。应用软件选择owncloud开源私有云系统。在这里，没有选择专门的NAS软件，如：openmediavault 。因为这类系统对于新手小白来说太复杂，而且使用过程中有一些问题。而owncloud使用起来，就像是使用普通的百度网盘一样，容易上手。像是samba共享服务协议，单独安装即可。&lt;/p&gt;
&lt;p&gt;下面我们开始讲解如何快速搭建系统&lt;/p&gt;
&lt;p&gt;分为三步：&lt;/p&gt;
&lt;p&gt;1、准备硬件。先看看自己手边有没有，没有的话我们有套件的。&lt;/p&gt;
&lt;p&gt;2、烧录免费镜像、按要求格式化分区硬盘同时进行。&lt;/p&gt;
&lt;p&gt;3、上电使用。&lt;/p&gt;
&lt;p&gt;这其实是组成一个完整的主机系统。&lt;/p&gt;
&lt;p&gt;硬件准备&lt;/p&gt;
&lt;p&gt;1、已经安装了普通散热片的树莓派4B 2G/4G/8G 主板一块。&lt;/p&gt;
&lt;p&gt;2、16G TF高速存储卡一张。16G够用了，太大的话，备份还原系统会花更多的时间。&lt;/p&gt;
&lt;p&gt;3、USB3.0移动硬盘一个。&lt;/p&gt;
&lt;p&gt;4、树莓派4B电源，至少5V/3A。&lt;/p&gt;
&lt;p&gt;5、hdmi小口转大口转接线。&lt;/p&gt;
&lt;p&gt;6、树莓派4B散热风扇。&lt;/p&gt;
&lt;p&gt;树莓派4B主机套件方案介绍&lt;/p&gt;
&lt;p&gt;这些东西比较零散，接了线之后，也是比较凌乱的。如果你身边有这些东西可以先用起来试试。&lt;/p&gt;
&lt;p&gt;也可以用我们提供的软硬件一体整合解决方案。免费提供已经配置和优化好的镜像文件和相关软件，烧录后即可使用。获取方式见文末。&lt;/p&gt;
&lt;p&gt;有详细的说明文档和视频。这些都是免费给大家使用的。有官方教程有指导，不怕你不会。&lt;/p&gt;
&lt;p&gt;这个方案整合了所有硬件在一个铝合金外壳内，十分的紧凑。内部可以安装2块2.5寸硬盘。软件上把已经优化配置好的系统和软件做成镜像文件，烧录到TF卡上就可以立即使用了。&lt;/p&gt;
&lt;p&gt;最终的一个安装完成效果：&lt;/p&gt;
&lt;p&gt;所有接口走线都在后面板上。&lt;/p&gt;
&lt;p&gt;烧录镜像文件&lt;/p&gt;
&lt;p&gt;TF卡内如果有重要资料的话，请先将TF卡资料备份。待会TF卡将会被格式化，所有数据将会丢失！&lt;/p&gt;
&lt;p&gt;运行格式化软件，插入TF卡，格式化提示不用理会。&lt;/p&gt;
&lt;p&gt;插入TF卡或者硬盘的时候，遇到任何提示需要格式化操作的，都不用理会，格式化会丢失掉所有数据，除非你需要格式化。这是因为TF卡中有linux分区，WIN系统电脑是无法识别的，所以弹出格式化提示。&lt;/p&gt;
&lt;p&gt;我们的系统在WINDOWS系统中会被识别成 RECOVE RY、boot、System 三个盘符。&lt;/p&gt;
&lt;p&gt;选择一个格式化就行。如果你还有其他的USB设备，一定要看清楚盘符，别格式化错了。&lt;/p&gt;
&lt;p&gt;选择Overwrite format，然后点击Format开始格式化。&lt;/p&gt;
&lt;p&gt;选择你要烧录的镜像文件和TF卡的盘符，点击Write开始写入，等待烧录完成。&lt;/p&gt;
&lt;p&gt;3、至少准备一块2.5寸SATA硬盘（机械和固态都可以）。硬盘需要按要求分区和命名，建立文件夹。&lt;/p&gt;
&lt;p&gt;文件路径说明：&lt;/p&gt;
&lt;p&gt;私有云admin账号数据存放位置：NAS1_yun :\owncloud\data\admin\files&lt;/p&gt;
&lt;p&gt;也就是手机里照片视频等备份的位置，其他账号依此类推。&lt;/p&gt;
&lt;p&gt;下载位置：NAS1_xiazai :\xiazai&lt;/p&gt;
&lt;p&gt;Aria2、QB下载软件的下载路径，下载完之后可以移动到其他地方。&lt;/p&gt;
&lt;p&gt;KODI电视剧存放位置：NAS1_xiazai :\电视剧&lt;/p&gt;
&lt;p&gt;KODI电影存放位置：NAS1_xiazai :\电影&lt;/p&gt;
&lt;p&gt;KODI视频存放位置：NAS1_xiazai :\视频&lt;/p&gt;
&lt;p&gt;KODI音乐存放位置：NAS1_xiazai :\音乐&lt;/p&gt;
&lt;p&gt;KODI电视直播m3u文件位置：NAS1_xiazai :\电视直播源&lt;/p&gt;
&lt;p&gt;微力同步同步文件夹：NAS1_xiazai :\verysync&lt;/p&gt;
&lt;p&gt;硬盘分区方法（以一块500G硬盘举例）：&lt;/p&gt;
&lt;p&gt;硬盘采用NTFS格式，逻辑分区。&lt;/p&gt;
&lt;p&gt;不管是多少块硬盘，要求有至少2个分区就可以了。分区大小根据自己需要和硬盘大小来分配。&lt;/p&gt;
&lt;p&gt;一个分区命名为：NAS1_xiazai，大小建议至少200G。这个是用来存放下载文件的，包括电影、电视剧、视频、音乐，是KODI的播放盘。&lt;/p&gt;
&lt;p&gt;另一个分区命名为：NAS1_yun，大小建议至少200G。 这个是owncloud 私有云工作盘符。手机的相片视频自动备份都是在这个盘。&lt;/p&gt;
&lt;p&gt;NAS1_yun分区格式化完成后，需要把官方发布的owncloud文件解压后，放在根目录！&lt;/p&gt;
&lt;p&gt;免费提供已经配置和优化好的镜像文件和相关软件，烧录后即可使用。见文末。&lt;/p&gt;
&lt;p&gt;有详细的说明文档和视频。&lt;/p&gt;
&lt;p&gt;这些都是免费给大家使用的。有官方教程有指导，不怕你不会。&lt;/p&gt;
&lt;p&gt;到这里，树莓派NAS和KODI影音播放系统搭建就已经介绍完成了。大家先利用现有的东西，烧录镜像文件就可以使用了。免去了自己折腾，中间有很多的问题要出处理，不是每个人都能顺利解决的。如果觉得好用的话，可以考虑用我们的软硬件集成方案。整体性会好很多。把树莓派可以当成一个真正的主机来使用，当然你也可以自己安装需要的软件。&lt;/p&gt;
&lt;p&gt;我们提供已经配置、优化好的 应用 、系统整合镜像给大家 免费 使用，仅限个人使用，严禁用于商业用途。&lt;/p&gt;
&lt;p&gt;所需的镜像和相关软件、说明文档打包下载地址：&lt;/p&gt;
&lt;p&gt;链接：&lt;a href=&quot;https://pan.baidu.com/s/1hi-_vfgGtK3psH-gaELSMg&quot;&gt;https://pan.baidu.com/s/1hi-_vfgGtK3psH-gaELSMg&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;提取码：6a9q&lt;/p&gt;
&lt;p&gt;如需文中所用到的组件，可以在这里选购：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://item.taobao.com/item.htm?id=658823159249&quot;&gt;https://item.taobao.com/item.htm?id=658823159249&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;希望大家都能愉快的使用树莓派！&lt;/p&gt;
&lt;p&gt;作者：硬件老郭&lt;/p&gt;
&lt;p&gt;链接：&lt;a href=&quot;https://www.jianshu.com/p/9a03c99644c5&quot;&gt;https://www.jianshu.com/p/9a03c99644c5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;链接：&lt;a href=&quot;https://www.jianshu.com/p/798ed10f812c&quot;&gt;https://www.jianshu.com/p/798ed10f812c&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;链接：&lt;a href=&quot;https://www.jianshu.com/p/95d71b072eae&quot;&gt;https://www.jianshu.com/p/95d71b072eae&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>PALPi 复古游戏主机，基于树莓派 Zero</title><link>https://blog.wo.ai/archives/3419/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3419/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;MAKER: Arnov Sharma／译：趣无尽 相逢已是初识（转载请注明出处）&lt;/p&gt;
&lt;p&gt;尽管基于树莓派开发的复古游戏机有不少作品，也很容易买到。我们之前也介绍过「树莓派CM3 掌上游戏机DIY全记录」。&lt;/p&gt;
&lt;p&gt;但是还是有必要推荐一下下面这款，基于树莓派 Zero 的复古游戏机。因为它为你展示了两个版本，其中包含了面包板搭建的版本，更加便于理解游戏机搭建的原理，并大幅降低了自己 DIY 的门槛。所用到的材料也很容易获得，这个项目主要的特性如下：&lt;/p&gt;
&lt;p&gt;1、使用 PAL 制式显示屏。&lt;/p&gt;
&lt;p&gt;2、由 Raspberry Pi Zero 驱动，采用 RecalBox OS 操作系统，该系统还附带一些预装游戏。&lt;/p&gt;
&lt;p&gt;3、由 IP5306 IC 供电，该 IC 是用于移动电源电路的 5V 2A 恒定升压 IC，非常适合为 Raspberry Pi Zero 供电。&lt;/p&gt;
&lt;p&gt;4、项目的前半部分为面包板版本，后半部分为 PCB 版本。&lt;/p&gt;
&lt;p&gt;材料清单&lt;/p&gt;
&lt;p&gt;Raspberry Pi Zero × 1&lt;/p&gt;
&lt;p&gt;16 GB 存储卡（8 GB 也可） × 1&lt;/p&gt;
&lt;p&gt;TV 屏幕 × 1&lt;/p&gt;
&lt;p&gt;HDMI 转 micro HDMI 适配器 × 1&lt;/p&gt;
&lt;p&gt;5V 2A 充电器/电源可稳定输出 2A × 1&lt;/p&gt;
&lt;p&gt;键盘 × 1&lt;/p&gt;
&lt;p&gt;USB 转 microUSB 适配器 × 1&lt;/p&gt;
&lt;p&gt;RecalBox OS 映像文件/ Raspberry Pi Image Flasher × 1&lt;/p&gt;
&lt;p&gt;普通按键 × 1&lt;/p&gt;
&lt;p&gt;定制 PCB 板 × 1&lt;/p&gt;
&lt;p&gt;IP5306 IC × 1&lt;/p&gt;
&lt;p&gt;10uf 0805 电容 × 1&lt;/p&gt;
&lt;p&gt;USB 端口 × 1&lt;/p&gt;
&lt;p&gt;microUSB 端口 × 1&lt;/p&gt;
&lt;p&gt;带有 CON2 连接器线的锂离子电池 × 1&lt;/p&gt;
&lt;p&gt;CON2 连接器 × 1&lt;/p&gt;
&lt;p&gt;10k 0603 电阻 × 1&lt;/p&gt;
&lt;p&gt;2R 0805 电阻 × 1&lt;/p&gt;
&lt;p&gt;垂直按钮 × 1&lt;/p&gt;
&lt;p&gt;一、初步构想&lt;/p&gt;
&lt;p&gt;设备包括 PCB 板和 3D 打印外壳，两者由螺钉连接起来。正面是显示器和按钮，使用 4.3 英寸的显示屏，尺寸大约是 135mm x 140mm，比普通的 Gameboy 屏幕大。反面是 Raspberry Pi Zero 同升压转换电路和锂离子电池。&lt;/p&gt;
&lt;p&gt;二、配置 RecalBox 系统&lt;/p&gt;
&lt;p&gt;下载 Raspberry Pi 映像文件。&lt;/p&gt;
&lt;p&gt;Select the right OS for your device 这里选择 RecalBox&lt;/p&gt;
&lt;p&gt;Select your system 这里选择 Rpi0&lt;/p&gt;
&lt;p&gt;Raspberry Pi imager will do your work of downloading and installing the RecalBox on the memory card.（完成下载与安装）&lt;/p&gt;
&lt;p&gt;安装完成后，将树莓派接上电视屏幕和键盘。&lt;/p&gt;
&lt;p&gt;启动设备，RecalBox 就可以正常工作了。&lt;/p&gt;
&lt;p&gt;三、GPIO 接线原理图&lt;/p&gt;
&lt;p&gt;注：原理图中将按键一端连到 GPIO 口，一端接地。&lt;/p&gt;
&lt;p&gt;原理图在本项目文件库中可以下载：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://make.quwj.com/project/385&quot;&gt;https://make.quwj.com/project/385&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;四、显示器与 GPIO 控制&lt;/p&gt;
&lt;p&gt;显示器种类有很多，既有 HDMI 端口，也有带状电缆端口，在这里从成本考虑选用 PAL 制式的车载显示器(带 PAL 端口)。因为车载显示器工作电压为 12V，而这里需要 5V 或 3.3V 的显示器，所以需要对显示器内部降压电路进行改装，这里移除了 12V 转 3.3V 的降压芯片，并在 Vout 端的电容上增加 VCC 和 GND ，为显示器提供 5V 电压。&lt;/p&gt;
&lt;p&gt;为使 PAL 端口运行显示器，需要在配置文件中编辑一些内容。用键盘在网络设置中输入 SSID 和密码。（将其与 winSCP 连接）&lt;/p&gt;
&lt;p&gt;需要编辑以下内容：&lt;/p&gt;
&lt;p&gt;SDTV 输出的设置&lt;/p&gt;
&lt;p&gt;HDMI 显示器连接到的 Raspberry Pi 上就会输出其视频信号，但在 PAL 端口下，需要设置 SDTV 的输出。&lt;/p&gt;
&lt;p&gt;进入 boot &amp;gt; config.txt 更改默认设置，从 sdtv_mode 中删除 # 并添加 sdtv_aspect = 1，此外，在 HDMI lines 前加 #。（如图，检查 Boot/config.txt 并复制其中的内容）&lt;/p&gt;
&lt;p&gt;GPIO Control&lt;/p&gt;
&lt;p&gt;GPIO 口与按键的设置。&lt;/p&gt;
&lt;p&gt;进入 recalbox &amp;gt; share &amp;gt; system &amp;gt; recalbox.conf&lt;/p&gt;
&lt;p&gt;通过以下两步启用 GPIO 外接按键&lt;/p&gt;
&lt;p&gt;设置 controller.gpio.enabled=1（之前是 0）并将 controller.gpio.args.map=1,2 更改为 controller.gpio.args.map=1（2 是第二个玩家）&lt;/p&gt;
&lt;p&gt;更改完默认设置（如图，检查 D2 GPIO Controlers），然后重启，RecalPi 便可适用于 PAL 端口和自定义按键。&lt;/p&gt;
&lt;p&gt;五、完善面包板版本&lt;/p&gt;
&lt;p&gt;这里用了定制的 GPIO 适配器，接线有所变化，参考图片将按键一端连到 GPIO 口，一端接地，完成后可以通过接 TV 测试按键是否正常工作，没问题后将 TV 更换成车载显示器，车载显示器的 VCC 接到 5V，地线连到一起，然后用 5V 2A 的电源供电，车载显示器显示画面则成功（注意之前的 SDTV 设置）。&lt;/p&gt;
&lt;p&gt;六、设计电路板&lt;/p&gt;
&lt;p&gt;根据面包板搭建的电路绘制 PCB 文件，包括 13 个按键与 Rpi Zero 相连，加 IP5306 IC 驱动的电源电路。其中 IP5306 是一款适用于 3.7 V 锂离子电池的电源管理芯片，可以调节输出电压的大小，这里将其提升到 5V 2A 适用于 Raspberry Pi 和车载显示器。&lt;/p&gt;
&lt;p&gt;设计流程如下：&lt;/p&gt;
&lt;p&gt;首先在 Fusion360 中设计整体结构，然后利用 Fusion360 的 PCB Layout 在 My PCB Cad 软件中对 PCB Outline 进行建模。（这里在经典的 Gameboy 布局中放置了额外的热键按钮）&lt;/p&gt;
&lt;p&gt;七、电路板打样&lt;/p&gt;
&lt;p&gt;将生产文件 Gerber 给到 PCB 制板厂商打样。&lt;/p&gt;
&lt;p&gt;Gerber 文件在本项目文件库中可以下载：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://make.quwj.com/project/385&quot;&gt;https://make.quwj.com/project/385&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;八、焊接 PCB 板&lt;/p&gt;
&lt;p&gt;1.SMD 元件焊接&lt;/p&gt;
&lt;p&gt;将焊膏涂到待焊焊盘上，摆放好元件后，将 PCB 板放到 SMD 加热板上进行回流焊，加热板从底部向上加热 PCB 板至焊膏熔化，焊膏熔化时从加热板上取下 PCB 板冷却 3-4 分钟。之后按照正确的元件焊接顺序，依次重复此操作。&lt;/p&gt;
&lt;p&gt;2.THT 元件焊接&lt;/p&gt;
&lt;p&gt;将 USB 端口、按键和 CON2 连接器添加到 PCB 板，然后焊接。&lt;/p&gt;
&lt;p&gt;最后焊接排母。PCB 板的焊接就大功告成了，接上锂离子电池，在 USB 端口处接 USB 电源监视器，检查 USB 的输出是否为 5V，没问题后接入 Raspberry Pi Zero W（已安装 RecalBox，并完成配置文件的更改）。&lt;/p&gt;
&lt;p&gt;九、整体组装&lt;/p&gt;
&lt;p&gt;利用 3D 打印的支撑部件将显示器固定到 3D 打印的外壳上，用四个 M2 螺丝将电路板安装到外壳上，并将显示器的 VCC、GND 和 PAL 端口与 Raspberry Pi 和电路板连接起来。完成后就可以通过 ON – OFF 垂直按钮启动 Recalbox 了。&lt;/p&gt;
&lt;p&gt;3D 打印文件在本项目文件库中可以下载：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://make.quwj.com/project/385&quot;&gt;https://make.quwj.com/project/385&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;完成！开启你的复古游戏之旅吧！&lt;/p&gt;
&lt;p&gt;十、添加更多游戏&lt;/p&gt;
&lt;p&gt;添加游戏只需下载相应 ROM 即可，具体操作如下：&lt;/p&gt;
&lt;p&gt;进入 Settings &amp;gt; Network Settings，添加路由器的 SSID 和密码，将树莓派连网，可以在网络设置菜单的顶部看到树莓派的 IP 地址。&lt;/p&gt;
&lt;p&gt;然后在电脑上打开 Win SCP，输入树莓派的 IP 地址，用户名是 root，pi 的密码是 recalboxroot。&lt;/p&gt;
&lt;p&gt;进入 recalbox &amp;gt; share &amp;gt; rom，这里包含所有的 ROM 文件，如果想玩其中一款游戏，只需将其 ROM 文件复制粘贴到 GBA 文件夹中即可。&lt;/p&gt;
&lt;p&gt;重启树莓派后打开 GBA 模拟器，就可以看到新添加的游戏了。&lt;/p&gt;
&lt;p&gt;希望你可以从这个项目中得到一些制作复古游戏机的启发。&lt;/p&gt;
&lt;p&gt;项目所用的代码在本项目文件库中可以下载：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://make.quwj.com/project/385&quot;&gt;https://make.quwj.com/project/385&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;via&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>使用静态 IP 地址</title><link>https://blog.wo.ai/archives/3439/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3439/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;MCC 172是一个两通道DAQ HAT，用于通过IEPE传感器（如加速度计和麦克风）进行声音和振动测量。它的每个通道具有24位A/D，最大采样率为51.2 kS/s/Ch。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>启用树莓派 CM4 CM4IO 的双路摄像头</title><link>https://blog.wo.ai/archives/3429/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3429/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;树莓派 CM4IO 底板上提供双 MIPI CSI-2 摄像头接口，但系统默认是没有开启的。本文将介绍如何配置和使用双摄像头。&lt;/p&gt;
&lt;p&gt;硬件准备&lt;/p&gt;
&lt;p&gt;CM4 + CM4IO 开发板 X 1&lt;/p&gt;
&lt;p&gt;树莓派摄像头模块 X 2&lt;/p&gt;
&lt;p&gt;摄像头模块软排线 X 2&lt;/p&gt;
&lt;p&gt;这里使用官方 800 万像素摄像头模块，默认配的软排线不兼容 CM4IO，所以需要自己配一条软排线如图所示。&lt;/p&gt;
&lt;p&gt;使用软排线将摄像头模块与 CM4IO 板上的 CAM0、CAM1 接口连接，注意排线银色亮面朝下。&lt;/p&gt;
&lt;p&gt;如果只使用其中一个摄像头，则不需要特别的硬件设置。如果要启用多个摄像头，需要使用跳线帽将 CM4IO 板上的 J6 短接。如下图所示。&lt;/p&gt;
&lt;p&gt;系统设置&lt;/p&gt;
&lt;p&gt;运行下面的命令更新 dt-blob.bin 文件。&lt;/p&gt;
&lt;p&gt;sudo wget &lt;a href=&quot;https://datasheets.raspberrypi.org/cmio/dt-blob-dualcam.bin&quot;&gt;https://datasheets.raspberrypi.org/cmio/dt-blob-dualcam.bin&lt;/a&gt; -O /boot/dt-blob.bin&lt;/p&gt;
&lt;p&gt;运行命令开启摄像头接口。&lt;/p&gt;
&lt;p&gt;sudo raspi-config&lt;/p&gt;
&lt;p&gt;选择 Interface Options -&amp;gt; Camera -&amp;gt; Enable，设置完后按提示重启系统（有图形界面的系统可以在设置里直接设置开启 Camera）。&lt;/p&gt;
&lt;p&gt;使用方法&lt;/p&gt;
&lt;p&gt;重启后进入终端输入命令。&lt;/p&gt;
&lt;p&gt;ls /dev&lt;/p&gt;
&lt;p&gt;确认一下是否出现了 video0、video1 两个设备。&lt;/p&gt;
&lt;p&gt;然后就可以开始使用了。至于如何指定某个摄像头，可以使用 -cs 参数。&lt;/p&gt;
&lt;h1&gt;打开第一个摄像头拍照和录制视频 sudo raspistill -o test.jpg -cs 0 sudo raspivid -o test.flv -cs 0 # 打开第二个摄像头拍照和录制视频 sudo raspistill -o test.jpg -cs 1 sudo raspivid -o test.flv -cs 1&lt;/h1&gt;
&lt;p&gt;如有问题欢迎在小组讨论~&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://talk.quwj.com/&quot;&gt;https://talk.quwj.com/&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>在树莓派上安装 PyCharm</title><link>https://blog.wo.ai/archives/3435/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3435/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;PyCharm 是一种 Python IDE（Integrated Development Environment，集成开发环境），带有一整套可以帮助用户在使用 Python 语言开发时提高其效率的工具，比如调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单元测试、版本控制。本教程为你介绍如何在树莓派上安装 PyCharm。本教程作者 @Tony 源自趣小组。&lt;/p&gt;
&lt;p&gt;运行 PyCharm 需要 Java 环境，如果树莓派上还没有安装过 JRE，可以使用以下命令安装即可：&lt;/p&gt;
&lt;p&gt;sudo apt install default-jre -y&lt;/p&gt;
&lt;p&gt;下载安装&lt;/p&gt;
&lt;p&gt;前往 JetBrains 官网下载 PyCharm 社区版（Community）：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jetbrains.com/pycharm/download/#section=linux&quot;&gt;https://www.jetbrains.com/pycharm/download/#section=linux&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;#解压，命令行中的文件名以实际下载的为准。 tar -zxvf pycharm-community-2020.2.1.tar.gz #运行，命令行中的目录名以实际下载的为准。 cd pycharm-community-2020.2.1/bin ./pycharm.sh&lt;/p&gt;
&lt;p&gt;创建快捷方式&lt;/p&gt;
&lt;p&gt;为了方便起见，可以添加快捷方式到启动器和桌面。&lt;/p&gt;
&lt;p&gt;在启动器中添加图标，编辑文件。&lt;/p&gt;
&lt;p&gt;sudo nano /usr/share/applications/pycharm.desktop&lt;/p&gt;
&lt;p&gt;输入以下内容&lt;/p&gt;
&lt;p&gt;[Desktop Entry] Name=PyCharm Type=Application Exec=/home/pi/pycharm-community-2020.2/bin/pycharm.sh Icon=/home/pi/pycharm-community-2020.2/bin/pycharm.png Categories=Development&lt;/p&gt;
&lt;p&gt;然后你就会看到树莓派菜单中的 PyCharm 图标。&lt;/p&gt;
&lt;p&gt;在桌面上添加图标，编辑文件。&lt;/p&gt;
&lt;p&gt;nano ~/Desktop/pycharm.desktop&lt;/p&gt;
&lt;p&gt;输入以下内容&lt;/p&gt;
&lt;p&gt;[Desktop Entry] Type=Link Name=PyCharm Icon=/home/pi/pycharm-community-2020.2/bin/pycharm.png URL=/usr/share/applications/pycharm.desktop&lt;/p&gt;
&lt;p&gt;然后你就会在桌面上看到 PyCharm 图标。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>在树莓派登录信息中加入温度 CPU、内存硬盘占用率信息</title><link>https://blog.wo.ai/archives/3433/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3433/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;MCC 172是一个两通道DAQ HAT，用于通过IEPE传感器（如加速度计和麦克风）进行声音和振动测量。它的每个通道具有24位A/D，最大采样率为51.2 kS/s/Ch。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>基于树莓派和 ESP32 制造一台掌上电脑</title><link>https://blog.wo.ai/archives/3423/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3423/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;MAKER:rahmanshaber／译：趣无尽（转载请注明出处）&lt;/p&gt;
&lt;p&gt;因为树莓派本身就是一台微型的电脑，大家对于如何使用树莓派来实现一台多功能的电脑一直在做不同的尝试。&lt;/p&gt;
&lt;p&gt;在前面的教程中我们曾发表过《Pi-Micro：用树莓派ZeroW制作的掌上电脑》，如手机般大小的掌上电脑；还有《Arduino制作：支持Basic和VGA显示器的复古计算机》基于 Arduino 的掌上电脑。&lt;/p&gt;
&lt;p&gt;今天我们给大家带来一个基于 ESP32-S2 ，附带 56 个键盘，4 英寸显示屏以及扩展接口的树莓派掌上 PC。整个项目所需材料不多，易于构建，且外壳使用 3D 打印，整体造价不高。&lt;/p&gt;
&lt;p&gt;项目特点：&lt;/p&gt;
&lt;p&gt;– ESP32-S2，易于焊接且损耗小。&lt;/p&gt;
&lt;p&gt;– 4 英寸 IPS 显示屏，4GB RAM，2 个 HDMI 输出端口。&lt;/p&gt;
&lt;p&gt;– 56 个键可定制键盘，2 个用于导航的侧边按钮。&lt;/p&gt;
&lt;p&gt;– 带有 RTC 时钟、蜂鸣器、振动、光敏传感器和红外发射模块。&lt;/p&gt;
&lt;p&gt;– 支持红外遥控；支持自动息屏；支持显示新旧加载项；支持鼠标左右键。&lt;/p&gt;
&lt;p&gt;– 支持 MPU6050 陀螺仪模块、BME280 压力模块和温湿度模块。&lt;/p&gt;
&lt;p&gt;– 支持键盘按钮关闭模块、显示器以及附加组件；支持使用操作系统和键盘键关闭电源。&lt;/p&gt;
&lt;p&gt;– 带有 USB type C 型号的 Nurolink / Dock 端口，支持 UART、I2C 和供电。&lt;/p&gt;
&lt;p&gt;– 支持读取电池电量并在电池电量不足时关闭设备电源。&lt;/p&gt;
&lt;p&gt;– 支持 ESP32-S2 与树莓派通过 Neopixel 库双向连接。&lt;/p&gt;
&lt;p&gt;– 18650 锂电池，带充放电保护。&lt;/p&gt;
&lt;p&gt;– 改进版的外壳使其更好地贴合 PCB。&lt;/p&gt;
&lt;p&gt;组件清单&lt;/p&gt;
&lt;p&gt;Esp32-S2 × 1&lt;/p&gt;
&lt;p&gt;树莓派 × 1&lt;/p&gt;
&lt;p&gt;3D 打印部件 × 若干&lt;/p&gt;
&lt;p&gt;USB C 到 USB C 转换器 × 1&lt;/p&gt;
&lt;p&gt;4 x 2 公头排针 × 1&lt;/p&gt;
&lt;p&gt;2 x 20 母头排针 × 1&lt;/p&gt;
&lt;p&gt;4 英寸 IPS 显示屏 × 1&lt;/p&gt;
&lt;p&gt;FPC 连接器 × 1&lt;/p&gt;
&lt;p&gt;锂电池 × 1&lt;/p&gt;
&lt;p&gt;PCB 板 × 1&lt;/p&gt;
&lt;p&gt;GPS 模块 × 1&lt;/p&gt;
&lt;p&gt;DS3231 × 1&lt;/p&gt;
&lt;p&gt;蜂鸣器 × 1&lt;/p&gt;
&lt;p&gt;TP4056 × 1&lt;/p&gt;
&lt;p&gt;RTC 时钟 × 1&lt;/p&gt;
&lt;p&gt;按键 × 56&lt;/p&gt;
&lt;p&gt;USB C 接口 × 2&lt;/p&gt;
&lt;p&gt;螺丝螺母 × 若干&lt;/p&gt;
&lt;p&gt;电阻 × 若干&lt;/p&gt;
&lt;p&gt;电容 × 若干&lt;/p&gt;
&lt;p&gt;LED 灯 × 1&lt;/p&gt;
&lt;p&gt;接线 × 若干&lt;/p&gt;
&lt;p&gt;贴片按键 × 2&lt;/p&gt;
&lt;p&gt;吸锡带 × 1&lt;/p&gt;
&lt;p&gt;电烙铁 × 1&lt;/p&gt;
&lt;p&gt;焊锡丝 × 1&lt;/p&gt;
&lt;p&gt;剪线钳 × 1&lt;/p&gt;
&lt;p&gt;助焊剂 × 1&lt;/p&gt;
&lt;p&gt;修边刀 × 1&lt;/p&gt;
&lt;p&gt;PCB 功能介绍&lt;/p&gt;
&lt;p&gt;我将提供完整的清单，它包含所有的功能，但你可以根据自己的需求来组装。&lt;/p&gt;
&lt;p&gt;组件功能介绍如下（参考）：&lt;/p&gt;
&lt;p&gt;S – 1 开关电源模块，用于控制设备开关（必备）。&lt;/p&gt;
&lt;p&gt;S – 2 连续充电模块 ，断开充电器，防止设备重启&lt;/p&gt;
&lt;p&gt;S – 3 升压模， 将电池出来的电压升至 5 V&lt;/p&gt;
&lt;p&gt;S – 4 ESP32-S2 + 复位模块，控制器 （必备）。&lt;/p&gt;
&lt;p&gt;S – 5 振动模块 提供振动反馈（可选）。&lt;/p&gt;
&lt;p&gt;S – 6 时钟模块 断电后保持计时（可选）。&lt;/p&gt;
&lt;p&gt;S – 7 MP6050 / BME280 陀螺仪 / 温湿度（可选）。&lt;/p&gt;
&lt;p&gt;S – 8 扩展电源 设备关闭时可保持显示模块 S – 7 的功能（可选）。&lt;/p&gt;
&lt;p&gt;S – 9 红外模块 红外遥控器 （可选）。&lt;/p&gt;
&lt;p&gt;我列出两个版本供大家参考，一个精简版和一个完整版。你可以根据自己的需求对组件进行了删减。&lt;/p&gt;
&lt;p&gt;PCB 与 3D 外壳打印&lt;/p&gt;
&lt;p&gt;PCB 选定好功能后就可以开始搭建以及 3D 打印外壳。外壳总有五个部分。&lt;/p&gt;
&lt;p&gt;关于外壳 3D 打印文件可以在本项目文件库中下载：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://make.quwj.com/project/401&quot;&gt;https://make.quwj.com/project/401&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PCB 总有三个部分。&lt;/p&gt;
&lt;p&gt;关于 PCB 3D 打印文件可以在本项目文件库中下载：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://make.quwj.com/project/401&quot;&gt;https://make.quwj.com/project/401&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;焊接部分&lt;/p&gt;
&lt;p&gt;在 PCB 中一共有八个模块，我将分模块进行焊接，先焊接三个模块的零件，这样有助于分步查错。&lt;/p&gt;
&lt;p&gt;焊接顺序如下：&lt;/p&gt;
&lt;p&gt;– USB C 端口 &amp;gt; S – 2 &amp;gt; 电池连接器 &amp;gt;&amp;gt; 查看电池是否充电。&lt;/p&gt;
&lt;p&gt;– S-4 （暂不焊接 FPC 连接器） &amp;gt; 4 x 2 公头排针 &amp;gt;&amp;gt; 测试能否能够上传固件。&lt;/p&gt;
&lt;p&gt;– S-1 &amp;gt; S – 3 &amp;gt;&amp;gt; 连接电池并按 S-3 模块中的按钮，查看指示灯是否亮起。&lt;/p&gt;
&lt;p&gt;– 焊接 FPC 连接器（外接显示器用）和 2 x 20 母头排针 &amp;gt; S-8 &amp;gt;&amp;gt; 键盘 &amp;gt;&amp;gt; 连接树莓派和电池，按住电源按钮查看树莓派是否启动。&lt;/p&gt;
&lt;p&gt;– S-6 &amp;gt; S-9 &amp;gt; S-7 &amp;gt;&amp;gt; 查看在 OS 的指导下，整个 PCB 板的是否工作正常。&lt;/p&gt;
&lt;p&gt;组装部分&lt;/p&gt;
&lt;p&gt;螺丝的型号如图所示。&lt;/p&gt;
&lt;p&gt;3D 打印外壳所需的螺丝的型号为：&lt;/p&gt;
&lt;p&gt;– 22mm x 2&lt;/p&gt;
&lt;p&gt;– 9mm x 2&lt;/p&gt;
&lt;p&gt;– 6mm x 4&lt;/p&gt;
&lt;p&gt;– 8mm x 1&lt;/p&gt;
&lt;p&gt;– 10mm x 1&lt;/p&gt;
&lt;p&gt;– 16mm x 1&lt;/p&gt;
&lt;p&gt;尝试将 PCB 放入，毛边的地方需要用砂纸打磨。&lt;/p&gt;
&lt;p&gt;线路连接及其他功能的扩展（可选）&lt;/p&gt;
&lt;p&gt;在 PCB 有一个扩展端口（2 x 10 pin 的母头排针），接入后可扩展一下六个功能：&lt;/p&gt;
&lt;p&gt;– 无线电广播, LoRa 通信&lt;/p&gt;
&lt;p&gt;– 自定义 Wifi&lt;/p&gt;
&lt;p&gt;– GPS&lt;/p&gt;
&lt;p&gt;– Micro SD&lt;/p&gt;
&lt;p&gt;– 蓝牙&lt;/p&gt;
&lt;p&gt;– 压力+湿度+温度模块&lt;/p&gt;
&lt;p&gt;– 自定义专属模块&lt;/p&gt;
&lt;p&gt;Nurolink / Dock 端口的扩展&lt;/p&gt;
&lt;p&gt;该设备的 Nurolink / Dock 对接端口，可以用来供电、连接外部设备或外部电路。&lt;/p&gt;
&lt;p&gt;如图所示，可连接到 Nurolink / Dock 端口的 GPS 模块。&lt;/p&gt;
&lt;p&gt;同时也可通过 Nurolink / Dock 端口连接两个接口，需要将 USB C 和 USB C 的电缆进行改动，需要交换 D+ 和 D- 线，因为 TX 要连接至 RX，没有改动的线是 Tx 是连接到 TX。&lt;/p&gt;
&lt;p&gt;Nurolink / Dock 端口的 6 个引脚如下：&lt;/p&gt;
&lt;p&gt;-2 个引脚分别为 UART / TTL 引脚：Tx 和 Rx&lt;/p&gt;
&lt;p&gt;-2 个引脚分别为电源引脚：3v 和接地&lt;/p&gt;
&lt;p&gt;-2 个引脚分别为 I2c 引脚：SDL 和 SCL&lt;/p&gt;
&lt;p&gt;可升级的地方&lt;/p&gt;
&lt;p&gt;DIY PC 的过程不会一帆风顺，仍有许多改进的地方，可以不断的优化项目。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;p&gt;Arduino 编码，可在固件中添加功能。&lt;/p&gt;
&lt;p&gt;Fusion 360，可改进支持且更容易构建。&lt;/p&gt;
&lt;p&gt;Python，可使附件充分发挥作用，例如基于 Lora 的通信应用程序能够与其他 PC 用户进行通信。&lt;/p&gt;
&lt;p&gt;FreeCAD，将其移植到 freeCAD，能够更加开源。&lt;/p&gt;
&lt;p&gt;KiCAD，能够更加开源。&lt;/p&gt;
&lt;p&gt;via&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>基于树莓派的 WebDAQ 504 应用案例</title><link>https://blog.wo.ai/archives/3421/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3421/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;WebDAQ 504 是 MCC 基于树莓派设计的数据采集产品，与我们之前推荐的 MCC DAQ HATs 系列一样，被用于解决工业数据采集的需求。下面这一则案例带你进一步了解 WebDAQ 504 以及树莓派在工业中的应用。&lt;/p&gt;
&lt;p&gt;风力涡轮机变速箱故障测试系统&lt;/p&gt;
&lt;p&gt;上海朋禾智能科技有限公司是一家专注于工业设备健康评估和故障诊断的定制解决方案提供商。他们专注于工业物联网（IIoT）平台设计，边缘计算系统架构和工业级硬件平台设计作。&lt;/p&gt;
&lt;p&gt;风力发电行业的客户需要风力涡轮机的测试解决方案。通过收集和分析轴承和其它组件的振动数据，对涡轮传动系统齿轮箱进行诊断。&lt;/p&gt;
&lt;p&gt;预测风力涡轮机中的齿轮和轴承故障可以降低维护成本，缩短停机时间并防止绿色风电能源生产中断。&lt;/p&gt;
&lt;p&gt;挑战&lt;/p&gt;
&lt;p&gt;动力传动系统是风力涡轮机的心脏，包含发电机和齿轮箱，可以将叶片的旋转转换为电能。风力涡轮机变速箱可能会经受极端应变和扭矩使用场景，这使组件的耐用性成为一个挑战。&lt;/p&gt;
&lt;p&gt;当典型的涡轮机仅以10~18RPM转速旋转时，感应发电机的转速可以达到1000RPM以上。这样的速度会产生了巨大的应力，给每个部件带来了沉重的负担。由于风力涡轮机的远距离监控的特点，传动系统的可靠性是重中之重。&lt;/p&gt;
&lt;p&gt;所需的变速箱故障测试系统可模拟常见的轴承故障情况。通过模拟不同的运行和负载条件，它可以帮助检测可能发生轴承和齿轮故障的情况。&lt;/p&gt;
&lt;p&gt;客户需要一种振动测量和分析解决方案，该解决方案不仅要功能强大，而且还要足够灵活以满足他们定制化算法的要求。该解决方案将需要在两种模式下使用。在独立模式下，该解决方案将收集数据作为测试平台的一部分，就地完成实时数据查看和分析。在IIoT模式下，数据采集解决方案将与涡轮机一起部署，远程实现实时记录和分析数据。&lt;/p&gt;
&lt;p&gt;解决方案&lt;/p&gt;
&lt;p&gt;MCC的WebDAQ 504被选为系统的核心数据采集部分。两个基于IEPE的加速度计直接连接到WebDAQ，且无需增加信号调理电路。这两个通道是同步采样的，还使用了内置的抗混叠滤波功能。水平和垂直加速度传感器已预先安装在测试台的齿轮箱轴承上，并且每个通道的采样速率高达25kS/s作为核心。&lt;/p&gt;
&lt;p&gt;WebDAQ 504包含一个易于使用的基于Web浏览器的应用程序，该应用程序允许用户设置采集，记录, 分析和显示数据，启用警报以及将数据导出到第三方应用程序。&lt;/p&gt;
&lt;p&gt;WebDAQ 504的计算引擎是树莓派计算模块（Raspberry Pi computer module）。通过使用WebDAQ开源代码，程序员可以直接访问树莓派计算模块，这使该系统成为真正的边缘计算解决方案。随附的Python示例程序允许程序员快速构建其应用程序，并连接程序的自定义显示和分析部分。&lt;/p&gt;
&lt;p&gt;执行&lt;/p&gt;
&lt;p&gt;在独立模式下，通过WebDAQ内置的应用程序进行配置及数据采集，实时分析不同工况（负载和速度）下的振动数据。&lt;/p&gt;
&lt;p&gt;在IIoT模式下，WebDAQ 504与涡轮一起部署，并通过以太网进行通信。该系统支持将WebDAQ 504连接到公有云或私有云。通过自定义界面，可以实现诸如设备管理，远程监视和健康评估之类的应用程序，这些应用程序适合于分布式在线监视和健康管理应用程序场景。数据还可以通过MQTT协议上载，与辅助数据应用程序一起使用。&lt;/p&gt;
&lt;p&gt;测试台可模拟多种操作条件，为操作员提供有价值的反馈。负载分析数据使工程师能够检测轴承和齿轮故障情况。&lt;/p&gt;
&lt;p&gt;成果&lt;/p&gt;
&lt;p&gt;完整的系统是一个灵活的平台，可以收集和分析振动数据。它满足了客户的专用软件需求，为客户提高了设备可靠性并减少了设备维护时间。&lt;/p&gt;
&lt;p&gt;崔鹏&lt;/p&gt;
&lt;p&gt;总经理&lt;/p&gt;
&lt;p&gt;上海朋禾智能科技有限公司&lt;/p&gt;
&lt;p&gt;上海市浦东新区纳贤路800号科海大楼&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.phengine.com&quot;&gt;http://www.phengine.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;获取更多关于Measurement Computing的信息，请访问官网：china.mccdaq.com&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>工业树莓派结合USB摄像头实现远程网络监控</title><link>https://blog.wo.ai/archives/3425/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3425/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;背景&lt;/p&gt;
&lt;p&gt;工业环境下为了更好地查看设备附近的运行状况，一般都会安装摄像头达到监控的效果，工业树莓派适用于工业环境，本次测试就使用工业树莓派和USB摄像头结合达到远程网络监控的效果。本次测试采用的远程网络监控工具是MJPG-streame，它是一款免费基于IP地址的视频流服务器，它的输入插件从摄像头读取视频数据，这个输入插件产生视频数据并将视频数据复制到内存中，它有多个输出插件将这些视频数据经过处理，其中最重要的输出插件是网站服务器插件，它将视频数据传送到用户浏览器中，MJPG-streamer的工作就是将其中的一个输入插件和多个输出插件绑定在一起，所有的工作都是通过它的各个插件完成的。&lt;/p&gt;
&lt;p&gt;准备工作&lt;/p&gt;
&lt;p&gt;1台工业树莓派&lt;/p&gt;
&lt;p&gt;1个USB摄像头：普通摄像头即可，本次测试采用的是无需驱动的USB摄像头。&lt;/p&gt;
&lt;p&gt;安装步骤&lt;/p&gt;
&lt;p&gt;1、检查是否存在USB摄像头设备。&lt;/p&gt;
&lt;p&gt;1）方法一：lsusb&lt;/p&gt;
&lt;p&gt;2）方法二：ls /dev&lt;/p&gt;
&lt;p&gt;可以看到video0就表示有USB摄像头设备在运行&lt;/p&gt;
&lt;p&gt;2、安装MJPG-Streamer。&lt;/p&gt;
&lt;p&gt;依次按照以下命令安装：&lt;/p&gt;
&lt;p&gt;1）sudo apt-get install cmake libjpeg8-dev&lt;/p&gt;
&lt;p&gt;2）wget &lt;a href=&quot;https://github.com/Five-great/mjpg-streamer/archive/master.zip&quot;&gt;https://github.com/Five-great/mjpg-streamer/archive/master.zip&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;（注意：如果无法连接到github，可以用PC端下载压缩包，然后通过FinalShell或者FTP服务器上传到树莓派。）&lt;/p&gt;
&lt;p&gt;unzip mjpg-streamer-master.zip&lt;/p&gt;
&lt;p&gt;3）&lt;/p&gt;
&lt;p&gt;cd mjpg-streamer-master cd mjpg-streamer-experimental&lt;/p&gt;
&lt;p&gt;4）make&lt;/p&gt;
&lt;p&gt;5）sudo make install&lt;/p&gt;
&lt;p&gt;6）cd /home&lt;/p&gt;
&lt;p&gt;3、启动MJPG-Streamer&lt;/p&gt;
&lt;p&gt;/usr/local/bin/mjpg_streamer -i “/usr/local/lib/mjpg-streamer/input_uvc.so -n -f 30 -r 1280×720” -o “/usr/local/lib/mjpg-streamer/output_http.so -p 8080 -w /usr/local/share/mjpg-streamer/www”&lt;/p&gt;
&lt;p&gt;4、实时视频接收&lt;/p&gt;
&lt;p&gt;用浏览器打开网址：http://&amp;lt;树莓派IP地址&amp;gt;:8080/javascript.html&lt;/p&gt;
&lt;p&gt;5、总结&lt;/p&gt;
&lt;p&gt;如此便可以实现远程网络监控，适用的场景可以是监控树莓派控制的电机的运行状态或者传感器的运行状态。在一定程度上也降低了成本，相较于以太网接口摄像头，USB摄像头更加实惠。&lt;/p&gt;
&lt;p&gt;关于虹科&lt;/p&gt;
&lt;p&gt;虹科是一家在工业物联网IIoT行业经验超过3年的高科技公司，虹科与世界领域顶级公司包括EXOR、Eurotech、Unitronics、Matrikon、KUNBUS等合作，提供一系列先进的高端的工业4.0 HMI、高端边缘计算机、IoT开发方案、PLC与HMI一体机、OPC UA、工业级树莓派等解决方案。物联网事业部所有成员都受过国内外专业培训，并获得专业资格认证，平均3年+的技术经验和水平一致赢得客户极好口碑。&lt;/p&gt;
&lt;p&gt;我们积极参与行业协会的工作，为推广先进技术的普及做出了重要贡献。至今，虹科已经为行业内诸多用户提供从硬件到软件的不同方案，并参与和协助了众多OEM的设备研发和移植项目，以及终端用户的智能工厂和工业4.0升级改造项目。我们致力于为中国乃至全球的万物互联、智能制造贡献自己的力量。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>工业树莓派零代码实现工业协议与MQTT互转</title><link>https://blog.wo.ai/archives/3415/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3415/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在以往的文章中，我们已经介绍了虹科的工业树莓派与Node-RED之间配合的种种可能性。今天，我们将通过实操案例，来看看如何使用Node-RED，以拖拽式低代码，甚至是无代码编程的方式，轻松实现工业协议与MQTT互转以及简单的HMI数据面板，帮助您有效降低在OT协议方面的学习成本，在低经验，甚至是无OT编程经验的情况下，快速在OT协议中的数据与云端数据之间建立联系。&lt;/p&gt;
&lt;p&gt;准备工作&lt;/p&gt;
&lt;p&gt;1.硬件准备&lt;/p&gt;
&lt;p&gt;PC一台&lt;/p&gt;
&lt;p&gt;工业树莓派一台&lt;/p&gt;
&lt;p&gt;24V直流电源&lt;/p&gt;
&lt;p&gt;网线&lt;/p&gt;
&lt;p&gt;相关协议网关模块（可选，根据实际需求添加）&lt;/p&gt;
&lt;p&gt;2.软件准备&lt;/p&gt;
&lt;p&gt;PC上：&lt;/p&gt;
&lt;p&gt;Chrome内核的浏览器&lt;/p&gt;
&lt;p&gt;树莓派上：&lt;/p&gt;
&lt;p&gt;Node-RED、Node-RED RevPi Nodes Server（默认已预装，若发现预装缺失，或需要版本更新可联系广州虹科获取相关文档）&lt;/p&gt;
&lt;p&gt;node-red-dashboard（可选，用于制作简单的HMI数据面板）&lt;/p&gt;
&lt;p&gt;操作步骤&lt;/p&gt;
&lt;p&gt;1.树莓派上电，连好网线，使用Advanced IP Scanner或类似软件扫描工业树莓派的IP地址，或在路由器后台中直接查看DHCP客户端列表，从而确定树莓派所在IP地址。&lt;/p&gt;
&lt;p&gt;2.浏览器打开树莓派所在IP地址，出现登录画面，账号为admin，默认密码会印在机身侧面的贴纸上，每台机子默认密码各不相同。&lt;/p&gt;
&lt;p&gt;3.登录后打开SERVICES选项卡，分别将以下三项设为Enable，最后点SAVE ALL保存设置。&lt;/p&gt;
&lt;p&gt;Modbus Master（本文以Modbus为例，因此需要将Modbus主站功能使能，若不使用Mobus协议则无需Enable此项）&lt;/p&gt;
&lt;p&gt;Node-RED&lt;/p&gt;
&lt;p&gt;Node-RED RevPi Nodes Server&lt;/p&gt;
&lt;p&gt;4.回到APPS选项卡，点击PiCtory右边的START进入树莓派的模块配置界面——PiCtory。&lt;/p&gt;
&lt;p&gt;5.首先把主模块按实际情况从左边设备列表中拖到右边（作为例子，本文使用Core作为主模块），把其它IO模块和协议网关模块按实际情况中的左右物理位置，按顺序把模块拖到主模块两边（本文没有使用额外模块）。最后，若要使用主模块的Modbus RTU/TCP主站功能，则在Virtual Devices中找到相应的虚拟模块，拖动到右边，如下图所示。&lt;/p&gt;
&lt;p&gt;6.设置相应的协议模块，本文以设置Modbus TCP主站为例：选定要设置的Modbus TCP主站虚拟模块，在右下角变量表中拖到最下方，在slave_IP_address中填入Modbus TCP从站所在IP地址，slave_TCP_port中填入Modbus TCP通讯设置的端口号，默认是502。&lt;/p&gt;
&lt;p&gt;7.对着Modbus TCP主站虚拟模块右键点击，打开Extended Data设置界面，并按实际情况设置Modbus变量地址等相关参数，输入完成后点OK确定，附上本文Modbus TCP从站相关参数表：&lt;/p&gt;
&lt;p&gt;8.配置完成后点上方菜单的File -&amp;gt; Save as Start-Config. 保存到启动方案，然后点Tools -&amp;gt; Reset Driver立即使配置生效。&lt;/p&gt;
&lt;p&gt;9.开一个新标签页，再次输入树莓派IP打开工业树莓派设置主页，点击Node-RED右边的START，或直接访问 [树莓派IP]:1880 进入Node-RED编程界面。&lt;/p&gt;
&lt;p&gt;10.在左边的节点列表中找到Revolution Pi分类，此分类底下的节点即为专为工业树莓派设计的节点。拖动一个revpi-multiple-input节点到右边网格区域，然后双击打开对其进行配置。先点击Server右边的铅笔案例，保持默认的localhost地址和8000端口，其它参数保持留空，然后点击右上角的添加，最后在Input Pins列表中找到以下3个变量（该列表只能找到输入变量，输入是对树莓派而言），分别勾上然后点右上角的完成。&lt;/p&gt;
&lt;p&gt;Input_Word_1&lt;/p&gt;
&lt;p&gt;Input_Word_2&lt;/p&gt;
&lt;p&gt;Input_Word_3&lt;/p&gt;
&lt;p&gt;11.拖一个Debug节点到revpi-multiple-input节点的右边，简单的把两个模块连上，点整个网页右上角的“部署”。&lt;/p&gt;
&lt;p&gt;12.把Modbus TCP从站中的变量值稍作更改，这里我把从站中地址1变量的值改成了321（revpi-multiple-input节点需要它监视的变量中至少一个值有变化，才会向后面的节点输出信息），此时在Debug信息区中找到如下信息表示Modbus TCP数据获取成功。（变量属性是数字形式或者字符串形式都没关系，不影响后续使用）。&lt;/p&gt;
&lt;p&gt;13.按下图方式配置节点，其中MQTT输入输出节点仅需配置MQTT地址、端口、Topic等基本信息即可，本文把树莓派的发布Topic设为了RevPiPub，订阅Topic设为了RevPiSub。&lt;/p&gt;
&lt;p&gt;分割消息和消息分流节点配置分别如下。&lt;/p&gt;
&lt;p&gt;分流节点右边的3个节点为revpi – output，默认会选定前面设置过的localhost:8000作为Server，这部分不需要更改，在下方Pin中分别选择3个输出变量名Output_Word_1、Output_Word_2、Output_Word_3即可。&lt;/p&gt;
&lt;p&gt;14.修改完成后，别忘了点击整个网页右上角的“部署”。然后我们尝试把Modbus TCP从站中地址2变量的值改成44555，此时即能通过MQTT获取到一条记录Modbus变量的信息。&lt;/p&gt;
&lt;p&gt;15.随后我们通过MQTT发送指令，分别通过JSON中out1、out2、out3三个字段，修改3个Modbus写入变量的值，效果如下图。&lt;/p&gt;
&lt;p&gt;16.至此，MQTT和工业通讯协议中的变量交互已经完成，我们既实现了MQTT数据上传，也实现了数据下发。接下来，我们可以尝试创建一个简单的HMI数据面板。首先，在Node-RED右上角的“三”菜单中找到“节点管理”，然后进入“安装”选项卡，搜索node-red-dashboard并安装（安装速度取决于网络环境和树莓派工作负载）。&lt;/p&gt;
&lt;p&gt;17.我们在前面的基础上，额外加上按下图形式编排的一些节点。其中左边一列节点均为inject节点，被设为每秒为周期重复注入字符串（变量名），中间一列节点全是工业树莓派专用节点中的revpi – getpin，用以通过变量名获取值，右边全是dashboard分类下的gauge。&lt;/p&gt;
&lt;p&gt;18.点击部署后，浏览器访问 访问 [树莓派IP]:1880/ui 即可看到由Node-RED自动生成的简易HMI数据面板。&lt;/p&gt;
&lt;p&gt;总结&lt;/p&gt;
&lt;p&gt;通过以上的简单步骤，我们可以轻松实现Modbus变量数据与MQTT的交互，中途没有输过任何一行代码，甚至没有进入过命令行界面，全部都是浏览器上的设置操作，其它工业树莓派所支持的通讯协议的配置也是大同小异。总的思路就是给树莓派设置好相应的参数，树莓派自动从相应协议中获取值并存到变量中，我们只需要在Node-RED中拖拽编程，把变量送到MQTT发送组件即可。这种编程方式大幅降低了非OT从业人员开发相关应用的学习成本，用户也不用担心之前从未使用过树莓派，甚至完全没有Linux系统使用经验也没关系。此外，利用Node-RED上的其它开源节点，我们也能够以MQTT以外的多种方式传输数据、以丰富多样方式的展示数据。有编程经验的用户，也可以通过function节点在发送前对数据进行运算、过滤等多种处理，甚至利用exec节点接入其它应用，进行更为复杂的边缘运算。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>树莓派4 UART 多串口配置通信</title><link>https://blog.wo.ai/archives/3409/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3409/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;相比树莓派零、1、2 以及 3 的双串口 UART0（PL011）和 UART1（mini UART），树莓派4 中新增了 4 个 PL011 串口共计有 6 个 UART，整理此笔记用作记录和配置参考。&lt;/p&gt;
&lt;p&gt;注意，目前搜到的大多数描述树莓派 4 串口的文章，大多数开头都是禁用下蓝牙，这个做法针对树莓派0-3 是必须的，因为本身串口不够用，但对树莓派 4 来说并不需要，因为有额外 4 个串口可以利用，默认配置好的两串口一个用于蓝牙（UART0）另一个是 miniUART 可以保留设置。此方面的文章大多都是一个流程，原因是参考的最初版本是树莓派 3 的设置；树莓派 4 的额外串口设置在树莓派论坛中可以看到相关的介绍，外面的文章不太多。&lt;/p&gt;
&lt;p&gt;UART 配置&lt;/p&gt;
&lt;p&gt;1. 展示所有串口命令&lt;/p&gt;
&lt;p&gt;$ dtoverlay -a | grep uart&lt;/p&gt;
&lt;p&gt;展示 pi4 中所有串口&lt;/p&gt;
&lt;p&gt;pi@raspberrypi:~ $ dtoverlay -a | grep uart midi-uart0 midi-uart1 miniuart-bt uart0 uart1 uart2 uart3 uart4 uart5&lt;/p&gt;
&lt;p&gt;2. 查看特定串口信息&lt;/p&gt;
&lt;p&gt;$ dtoverlay -h uart2&lt;/p&gt;
&lt;p&gt;查看 UART2 的配置信息等：&lt;/p&gt;
&lt;p&gt;pi@raspberrypi:~ $ dtoverlay -h uart2 Name: uart2 Info: Enable uart 2 on GPIOs 0-3 Usage: dtoverlay=uart2, Params: ctsrts Enable CTS/RTS on GPIOs 2-3 (default off)&lt;/p&gt;
&lt;p&gt;相关信息会展现 GPIOs 与新的 UART 串口的分配：0-3 对应 UART2， 4-7 对应 UART3，8-11 对应 UART 4，以及 12-15 对应 GUIO 5&lt;/p&gt;
&lt;p&gt;关于 4 个针脚中后两位对应的 CTS/RTS，可参考：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/zeroboundary/article/details/8966586&quot;&gt;https://blog.csdn.net/zeroboundary/article/details/8966586&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;3. 配置开启串口 UART2-5&lt;/p&gt;
&lt;p&gt;执行编辑 config.txt 命令：&lt;/p&gt;
&lt;p&gt;sudo nano /boot/config.txt&lt;/p&gt;
&lt;p&gt;在文件结尾添加如下：&lt;/p&gt;
&lt;p&gt;dtoverlay=uart2 dtoverlay=uart3 dtoverlay=uart4 dtoverlay=uart5&lt;/p&gt;
&lt;p&gt;保存（Write Out）并退出（Exit）&lt;/p&gt;
&lt;p&gt;重启后查看是否生效：&lt;/p&gt;
&lt;p&gt;$ ls /dev/ttyAMA*&lt;/p&gt;
&lt;p&gt;结果显示如下：&lt;/p&gt;
&lt;p&gt;pi@raspberrypi:~ $ ls /dev/ttyAMA* /dev/ttyAMA0 /dev/ttyAMA1 /dev/ttyAMA2 /dev/ttyAMA3 /dev/ttyAMA4&lt;/p&gt;
&lt;p&gt;各 UART 串口与 GPIO 对应关系：&lt;/p&gt;
&lt;p&gt;GPIO14 = TXD0 -&amp;gt; ttyAMA0 GPIO0 = TXD2 -&amp;gt; ttyAMA1 GPIO4 = TXD3 -&amp;gt; ttyAMA2 GPIO8 = TXD4 -&amp;gt; ttyAMA3 GPIO12 = TXD5 -&amp;gt; ttyAMA4 GPIO15 = RXD0 -&amp;gt; ttyAMA0 GPIO1 = RXD2 -&amp;gt; ttyAMA1 GPIO5 = RXD3 -&amp;gt; ttyAMA2 GPIO9 = RXD4 -&amp;gt; ttyAMA3 GPIO13 = RXD5 -&amp;gt; ttyAMA4&lt;/p&gt;
&lt;p&gt;4. 测试&lt;/p&gt;
&lt;p&gt;4.1 串口自发自收测试&lt;/p&gt;
&lt;p&gt;现在我们先测试 UART2 是否启用成功，比较简单的测试方式是将其 TXD 和 RXD 相连，自发自收。&lt;/p&gt;
&lt;p&gt;根据上方对应关系，UART2 对应 TXD2 和 RXD2，对应 GPIO0 和 GPIO1，对应 ttyAMA1&lt;/p&gt;
&lt;p&gt;注：UART0 对应的 ttyAMA0，UART1 对应的 ttyS0，UART2 到 UART5 对应的 ttyAMA1 到 ttyAMA4。&lt;/p&gt;
&lt;p&gt;找到对应的 GPIO0 和 GPIO1 针脚连起来：&lt;/p&gt;
&lt;p&gt;在树莓派端命令行进入 Python3 环境，通过如下模块和命令自发自收：&lt;/p&gt;
&lt;p&gt;pi@raspberrypi:~ $ python3 Python 3.7.3 (default, Jul 25 2020, 13:03:44) [GCC 8.3.0] on linux Type “help”, “copyright”, “credits” or “license” for more information. &amp;gt;&amp;gt;&amp;gt; import serial &amp;gt;&amp;gt;&amp;gt; ted = serial.Serial(port=”/dev/ttyAMA1″, baudrate=9600) &amp;gt;&amp;gt;&amp;gt; ted.write(“Hello World”.encode(“gbk”)) 11 &amp;gt;&amp;gt;&amp;gt; ted.read(11) b’Hello World’ &amp;gt;&amp;gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;同理，我们可以继续用跳线帽将 GPIO4 和 5 相连测试 UART3；GPIO8 和 9 相连测试 UART4； GPIO12 和 13 相连测试 UART5。&lt;/p&gt;
&lt;p&gt;pi@raspberrypi:~ $ python3 Python 3.7.3 (default, Jul 25 2020, 13:03:44) [GCC 8.3.0] on linux Type “help”, “copyright”, “credits” or “license” for more information. &amp;gt;&amp;gt;&amp;gt; import serial &amp;gt;&amp;gt;&amp;gt; ted = serial.Serial(port=”/dev/ttyAMA1″, baudrate=9600) &amp;gt;&amp;gt;&amp;gt; ted.write(“Hello World”.encode(“gbk”)) 11 &amp;gt;&amp;gt;&amp;gt; ted.read(11) b’Hello World’ &amp;gt;&amp;gt;&amp;gt; ted3 = serial.Serial(port=”/dev/ttyAMA2″, baudrate=9600) &amp;gt;&amp;gt;&amp;gt; ted3.write(“Hello No.3”.encode(“gbk”)) 10 &amp;gt;&amp;gt;&amp;gt; ted3.read(10) b’Hello No.3′ &amp;gt;&amp;gt;&amp;gt; ted4 = serial.Serial(port=”/dev/ttyAMA3″, baudrate=9600) &amp;gt;&amp;gt;&amp;gt; ted4.write(“Hello No.4”.encode(“gbk”)) 10 &amp;gt;&amp;gt;&amp;gt; ted4.read(10) b’Hello No.4′ &amp;gt;&amp;gt;&amp;gt; ted5 = serial.Serial(port=”/dev/ttyAMA4″, baudrate=9600) &amp;gt;&amp;gt;&amp;gt; ted5.write(“Hello No.5”.encode(“gbk”)) 10 &amp;gt;&amp;gt;&amp;gt; ted5.read(10) b’Hello No.5′ &amp;gt;&amp;gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;4.2 串口间通信测试&lt;/p&gt;
&lt;p&gt;接下来测试 UART2 和 UART3 间的通信，将 TXD2 连接 RXD3 即 GPIO0 与 GPIO5 相连；将 TXD3 与 RXD2 连接即 GPIO4 与 GPIO1 相连。&lt;/p&gt;
&lt;p&gt;GPIO0 = TXD2 -&amp;gt; ttyAMA1 GPIO4 = TXD3 -&amp;gt; ttyAMA2 GPIO1 = RXD2 -&amp;gt; ttyAMA1 GPIO5 = RXD3 -&amp;gt; ttyAMA2&lt;/p&gt;
&lt;p&gt;pi@raspberrypi:~ $ python3 Python 3.7.3 (default, Jul 25 2020, 13:03:44) [GCC 8.3.0] on linux Type “help”, “copyright”, “credits” or “license” for more information. &amp;gt;&amp;gt;&amp;gt; import serial &amp;gt;&amp;gt;&amp;gt; ted = serial.Serial(port=”/dev/ttyAMA1″, baudrate=9600) &amp;gt;&amp;gt;&amp;gt; ted3 = serial.Serial(port=”/dev/ttyAMA2″, baudrate=9600) &amp;gt;&amp;gt;&amp;gt; ted.write(“Msg from UART2…”.encode(“gbk”)) 17 &amp;gt;&amp;gt;&amp;gt; ted3.read(17) b’Msg from UART2…’ &amp;gt;&amp;gt;&amp;gt; ted3.write(“Msg from UART3…”.encode(“gbk”)) 17 &amp;gt;&amp;gt;&amp;gt; ted.read(17) b’Msg from UART3…’ &amp;gt;&amp;gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;OK 挺顺利，UART2 和 UART3 间通信正常。&lt;/p&gt;
&lt;p&gt;参考&lt;/p&gt;
&lt;p&gt;官方 UART 配置文档：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.raspberrypi.org/documentation/configuration/uart.md&quot;&gt;https://www.raspberrypi.org/documentation/configuration/uart.md&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;论坛关于多串口的命令与指引：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.raspberrypi.org/forums/viewtopic.php?t=244827#p1493698&quot;&gt;https://www.raspberrypi.org/forums/viewtopic.php?t=244827#p1493698&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文遵循CC 4.0 BY-SA版权协议，原文出处：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/weixin_40796925/article/details/107907991&quot;&gt;https://blog.csdn.net/weixin_40796925/article/details/107907991&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>用树莓派改造猫厕所：智能通风，告别异味</title><link>https://blog.wo.ai/archives/3437/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3437/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;之前我们有介绍过「激光逗猫神器」来增加猫主子的运动量。今天要介绍的项目则可以解决铲屎官们痛点之一：猫主子粑粑的气味问题。每次铲屎的时候，猫砂的气味就会飘散出来，那是一种巨酸爽的味道。&lt;/p&gt;
&lt;p&gt;这个猫咪个智能厕所使用一块树莓派就能将猫砂盆改造成自动通风的猫厕，每当猫主子「出恭」完毕，它就能自行启动风扇，进行散味。&lt;/p&gt;
&lt;p&gt;硬件组成&lt;/p&gt;
&lt;p&gt;组件清单如下：&lt;/p&gt;
&lt;p&gt;树莓派 Zero W&lt;/p&gt;
&lt;p&gt;80mm×80mm 风扇（CPU散热器）&lt;/p&gt;
&lt;p&gt;12V 电源&lt;/p&gt;
&lt;p&gt;1 截空调通风管&lt;/p&gt;
&lt;p&gt;PIR 运动传感器（热释电传感器）若干&lt;/p&gt;
&lt;p&gt;1k 欧姆电阻若干&lt;/p&gt;
&lt;p&gt;三极管&lt;/p&gt;
&lt;p&gt;1 个降压-升压变换器&lt;/p&gt;
&lt;p&gt;这些硬件的总成本在 400 元左右。其中，10 美元的树莓派 Zero W 是整个装置的核心，用于控制电路什么时候运行。&lt;/p&gt;
&lt;p&gt;作者表示，树莓派也可以用更便宜的树莓派 Pico 来代替（5美元），但他想了解自家猫咪的具体拉粑粑时间……因此选择了贵一点的树莓派 Zero W，可以远程登录查看数据。&lt;/p&gt;
&lt;p&gt;风扇用于散味，空调通风管则用于排气。&lt;/p&gt;
&lt;p&gt;PIR（红外）运动传感器，则用来检测猫咪什么时候进猫砂盆、什么时候出去，以决定风扇通风的时间。&lt;/p&gt;
&lt;p&gt;此外，还需要一个降压-升压变换器（此处用了汽车 USB 充电器代替），因为风扇的供电电压需要达到 12V，但树莓派的额定运行电压却在 5V。&lt;/p&gt;
&lt;p&gt;同时，树莓派的输出电压只有 3.3V，因此在树莓派输出 3.3V 信号的时候，还需要一个三极管来控制 12V 电路的开启和关闭。&lt;/p&gt;
&lt;p&gt;整体搭建电路图如下（其中333Ω的电阻，由3个1kΩ的电阻并联获得）：&lt;/p&gt;
&lt;p&gt;图中，PIR 指运动传感器，Pz 指树莓派（输出通过 333 欧姆电阻与三极管相连），fan 指风扇，而风扇和树莓派之间的就是三极管。&lt;/p&gt;
&lt;p&gt;至于图中的 5V 电压，可以直接用降压变换器，也可以对汽车 USB 充电器进行一个改装：先将它拆开，再把充电器的金属尖端用电线焊接替代，最后连接到主电路中。&lt;/p&gt;
&lt;p&gt;到这里，硬件部分的组装就已经完成，搭建到猫砂盆上方后，看起来是这个样子的：&lt;/p&gt;
&lt;p&gt;再用胶带或者热熔胶枪组装起来就行。&lt;/p&gt;
&lt;p&gt;当然，这也只是完成硬件部分的制作，软件上还需要对树莓派进行设置。&lt;/p&gt;
&lt;p&gt;软件和程序的安装&lt;/p&gt;
&lt;p&gt;首先，需要给树莓派的SD卡安装系统，然后连接电源启动等。&lt;/p&gt;
&lt;p&gt;再就可以开始部署程序了。由于作者想记录猫咪的拉粑粑时间，因此还安装了 Python 和数据库 MariaDB：&lt;/p&gt;
&lt;p&gt;sudo apt install mariadb-server-10.0 python3-pip pip3 install mariadb&lt;/p&gt;
&lt;p&gt;除了数据库以外，还需要一个可以控制树莓派 GPIO引脚的 Python 库 GPIO ZERO（GPIO 引脚指图中这部分）：&lt;/p&gt;
&lt;p&gt;sudo apt install python3-gpiozero&lt;/p&gt;
&lt;p&gt;安装后，就可以运行主要的程序了，这部分的原理，是在传感器触发后，将风扇启动10分钟：&lt;/p&gt;
&lt;p&gt;FAN_ON_DURATION = 10 * 60 # 10 min outpin = LED(22) pir = MotionSensor(27, sample_rate=1) # I used the GPIO pin 27 while True: pir.wait_for_motion() outpin.on() time.sleep(FAN_ON_DURATION) outpin.off()&lt;/p&gt;
&lt;p&gt;同时，作者还希望能在整个系统启动时，自动运行程序，因此还写了另一个文件，将它命名为.service，确保它在 mysqld 启动后就能运行：&lt;/p&gt;
&lt;p&gt;[Unit] Description=Motion detection service for cat litter After=mysqld.service [Service] ExecStart=/usr/bin/python3 -u motion_detection.py WorkingDirectory=/home/pi/ StandardOutput=append:/home/pi/execution.log StandardError=append:/home/pi/execution.log Restart=alwaysUser=pi [Install] WantedBy=multi-user.target&lt;/p&gt;
&lt;p&gt;放进/etc/systemd/system文件夹，并允许跟随系统启动：&lt;/p&gt;
&lt;p&gt;sudo systemctl enable .service&lt;/p&gt;
&lt;p&gt;完成！现在软件部分也搞定了，可以愉快地给自家猫用上了，据作者表示，打开风扇时，即使站在旁边，也基本闻不到味道。&lt;/p&gt;
&lt;p&gt;解决更多铲屎官难题&lt;/p&gt;
&lt;p&gt;这个智能“无臭”猫砂盆的作者，目前在硅谷工作，自我介绍是一名“攻城狮”。&lt;/p&gt;
&lt;p&gt;事实上，作者自己家里就有猫，还是两只：&lt;/p&gt;
&lt;p&gt;除了这个猫砂盆，作者此前还自己做过家用版大数据处理平台、以及树莓派运行的GPS跟踪器。作者表示，下一步计划研发一个新的装置，阻止猫咪到桌上和自己抢饭。&lt;/p&gt;
&lt;p&gt;GitHub：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xinixgit/rpi/tree/main/pi-cat-litter&quot;&gt;https://github.com/xinixgit/rpi/tree/main/pi-cat-litter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;参考链接：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://xinxindai.medium.com/build-a-ventilated-cat-litter-box-with-pi-zero-ce943d55b446&quot;&gt;https://xinxindai.medium.com/build-a-ventilated-cat-litter-box-with-pi-zero-ce943d55b446&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>虹科工业树莓派 VNC Viewer 远程桌面的配置</title><link>https://blog.wo.ai/archives/3431/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3431/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一、什么是 VNC Viewer&lt;/p&gt;
&lt;p&gt;VNC(Virtual Network Console)是虚拟网络控制台的缩写，VNC 是在基于 UNIX 和 Linux 操作系统的免费的开源软件，远程控制能力强大，高效实用，其性能可以和 Windows 和 MAC 中的任何远程控制软件媲美。 在 Linux 中，VNC 包括以下四个命令：VNCserver，VNCviewer，VNCpasswd，和 VNCconnect。大多数情况下用户只需要其中的两个命令：VNCserver 和 VNCviewer。&lt;/p&gt;
&lt;p&gt;VNC viewer是一款远程控制的软件，一般用于远程解决电脑故障或软件调试。&lt;/p&gt;
&lt;p&gt;二、准备工作&lt;/p&gt;
&lt;p&gt;工业树莓派1台&lt;/p&gt;
&lt;p&gt;VNC Viewer（根据计算机环境选择性下载，下载链接：&lt;a href=&quot;https://www.realvnc.com/en/connect/download/viewer/%EF%BC%89&quot;&gt;https://www.realvnc.com/en/connect/download/viewer/）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;三、配置步骤&lt;/p&gt;
&lt;p&gt;1、SSH连接登录&lt;/p&gt;
&lt;p&gt;2、在终端输入以下命令进入配置界面：&lt;/p&gt;
&lt;p&gt;sudo raspi-config&lt;/p&gt;
&lt;p&gt;3、启动VNC服务（根据以下路径依次进入：Interfacing Options -&amp;gt; VNC -&amp;gt; Yes）&lt;/p&gt;
&lt;p&gt;4、返回配置页面首页，启动Desktop Autologin服务，该服务在不同核心模块的路径不同，如果是消费级树莓派请按照根据处理器型号对应到工业树莓派核心模块的路径。&lt;/p&gt;
&lt;p&gt;核心模块：RevPi Core3、RevPi Connect&lt;/p&gt;
&lt;p&gt;路径：Select Boot Options-&amp;gt;Desktop/CII-&amp;gt;Desktop Autologin&lt;/p&gt;
&lt;p&gt;核心模块：RevPi Core3+、RevPi Connect+&lt;/p&gt;
&lt;p&gt;路径：System Options-&amp;gt;Boot/Auto Login-&amp;gt;Desktop Autologin&lt;/p&gt;
&lt;p&gt;5、退出配置界面，选择重启，拔掉电源。&lt;/p&gt;
&lt;p&gt;6、给工业树莓派重新上电，重启工业树莓派，再进行SSH登录&lt;/p&gt;
&lt;p&gt;7、VNC Viewer远程访问，输入相应的IP地址、SHH登录账号和密码。&lt;/p&gt;
&lt;p&gt;8、完成VNC远程访问，可以根据需求进行配置更新。&lt;/p&gt;
&lt;p&gt;关于虹科&lt;/p&gt;
&lt;p&gt;虹科是一家在工业物联网IIoT行业经验超过3年的高科技公司，虹科与世界领域顶级公司包括EXOR、Eurotech、Unitronics、Matrikon、KUNBUS等合作，提供一系列先进的高端的工业4.0 HMI、高端边缘计算机、IoT开发方案、PLC与HMI一体机、OPC UA、工业级树莓派等解决方案。物联网事业部所有成员都受过国内外专业培训，并获得专业资格认证，平均3年+的技术经验和水平一致赢得客户极好口碑。&lt;/p&gt;
&lt;p&gt;我们积极参与行业协会的工作，为推广先进技术的普及做出了重要贡献。至今，虹科已经为行业内诸多用户提供从硬件到软件的不同方案，并参与和协助了众多OEM的设备研发和移植项目，以及终端用户的智能工厂和工业4.0升级改造项目。我们致力于为中国乃至全球的万物互联、智能制造贡献自己的力量。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>虹科工业树莓派：根据天气控制温室窗户开合</title><link>https://blog.wo.ai/archives/3417/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3417/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;1.简介&lt;/p&gt;
&lt;p&gt;在本案例中，虹科工业树莓派RevPi Core3通过Node-RED编程获取并处理荷兰在线天气门户网站Buienradar的实时天气数据，并根据获取的天气信息对温室的窗户进行控制，从而实现温室的IoT改造。&lt;/p&gt;
&lt;p&gt;2.所需器件&lt;/p&gt;
&lt;p&gt;虹科RevPi Core3&lt;/p&gt;
&lt;p&gt;虹科RevPi DIO模块&lt;/p&gt;
&lt;p&gt;继电器&lt;/p&gt;
&lt;p&gt;3.实现方式&lt;/p&gt;
&lt;p&gt;在本案例中，温室的IoT改造主要集中在两个方面：温室窗户的自动开闭以及自动获取天气数据并分析。&lt;/p&gt;
&lt;p&gt;3.1温室窗户开闭控制&lt;/p&gt;
&lt;p&gt;温室的窗户通过行程长度为500 mm的线性驱动器执行打开和关闭动作。此线性驱动器配备有24V电机，可以通过虹科RevPi Core3和DIO扩展模块进行控制。&lt;/p&gt;
&lt;p&gt;所使用的执行器在冲程结束时自动停止电动机，无需其他传感器或开关。由于执行器需要3A的驱动电流，而DIO模块最多只能输出500 mA的电流，因此这里需要一个额外的继电器来控制24V电机的开闭。&lt;/p&gt;
&lt;p&gt;3.2天气数据的获取及分析&lt;/p&gt;
&lt;p&gt;为了实现根据天气情况自动打开和关闭窗户的功能，本案例采用可从网络上免费获取的天气数据。此类数据可以采用Node-RED获取。&lt;/p&gt;
&lt;p&gt;Node-RED具有用户友好的图形开发界面，可以通过添加HTTP请求节点以及功能节点从Buienradar的网站上获取预计降水量数据。&lt;/p&gt;
&lt;p&gt;通过链接 &lt;a href=&quot;https://gpsgadget.buienradar.nl/data/raintext?lat=52.08&amp;lon=5.47&quot;&gt;https://gpsgadget.buienradar.nl/data/raintext?lat=52.08&amp;amp;lon=5.47&lt;/a&gt; 可以文本形式请求未来两个小时的预期降水量。“lat”和“lon”之后的数字对应于需要降水数据的地方的GPS坐标。这些数据由Node-RED每五分钟检索一次。如果预计在15分钟内下雨，窗户将自动关闭。但是，如果在接下来的15分钟内仍保持干燥，则系统将再次打开窗户。&lt;/p&gt;
&lt;p&gt;4.更进一步&lt;/p&gt;
&lt;p&gt;当前，RevPi只区分“窗口打开”以及“窗口关闭”两个状态，要获取更多设置，可以进一步开发。另外，也可以连接温度和湿度传感器，准确获取当前天气状况，以进一步提高窗户自动开闭的准确性。还可以通过仪表盘节点进一步升级，通过web浏览器显示当前降雨预报以及窗口状态等。&lt;/p&gt;
&lt;p&gt;总而言之，虹科工业树莓派RevPi具有极大的灵活性和无限的潜力，并且软件硬件完全开源，您可以以最大的自由度在RevPi上进行开发。&lt;/p&gt;
&lt;p&gt;5.本文使用的模块&lt;/p&gt;
&lt;p&gt;RevPi Core 3具有定制的Raspbian系统，预先安装了RT补丁，配备Raspberry Pi计算模块3，内置Broadcom的多核处理器具有足够的能力来执行诸如图像处理之类的复杂任务，可以完成高性能要求的任务。&lt;/p&gt;
&lt;p&gt;处理器博通BCM2837&lt;/p&gt;
&lt;p&gt;核心数4&lt;/p&gt;
&lt;p&gt;时钟频率1.2 GHz&lt;/p&gt;
&lt;p&gt;内存1 GB&lt;/p&gt;
&lt;p&gt;eMMC闪存4GB&lt;/p&gt;
&lt;p&gt;RevPi Core 3使用了最先进的高效DC-DC转换器（总体效率大于80％），从而可以有效减少发热。模块不仅可以在20.4 V至28.8 V标准化的电源电压范围运行，也可以在10.2 V输入电压下运行，这意味着您甚至可以使用汽车电池或太阳能电池板作为电源。先进的保护电路可确保即使在输入电源线上受到大量电磁干扰的情况下，模块也能连续运行（前提是正确连接了功能性接地）。&lt;/p&gt;
&lt;p&gt;像Raspberry Pi一样，RevPi Core 3也配备了通用接口：&lt;/p&gt;
&lt;p&gt;1个RJ45以太网插座&lt;/p&gt;
&lt;p&gt;2个USB 2.0插座&lt;/p&gt;
&lt;p&gt;1个Micro HDMI插座&lt;/p&gt;
&lt;p&gt;1个Micro USB 2.0插槽&lt;/p&gt;
&lt;p&gt;2 个PiBridge（用于RevPi模块扩展）&lt;/p&gt;
&lt;p&gt;RevPi Core 3模块可以适应恶劣的工业环境：&lt;/p&gt;
&lt;p&gt;电源：12-24 VDC -15％/ +20％，max.10W&lt;/p&gt;
&lt;p&gt;工作温度：-40°C至55°C&lt;/p&gt;
&lt;p&gt;ESD保护：符合EN 61131-2和IEC 61000-6-2的4 kV / 8 kV&lt;/p&gt;
&lt;p&gt;浪涌/冲击测试：根据EN 61131-2和IEC 61000-6-2进行&lt;/p&gt;
&lt;p&gt;EMI测试：根据EN 61131-2和IEC 61000-6-2&lt;/p&gt;
&lt;p&gt;UL认证（UL文件编号E494534）&lt;/p&gt;
&lt;p&gt;I/O模块有三种版本，均可以通过PiBridge与Core模块直接相连。它们的前端都具有相同的28针I/O连接器。除了具有14个数字输入和14个数字输出的标准版本外，还有两个特殊版本，它们仅具有16个数字输入或16个数字输出。根据EN 61131-2的要求，所有版本都有抗干扰保护，并且可以在-40°C到55°C的温度范围，相对湿度高达93%的环境内运行。此外，所有数字I/O模块都具有UL认证（UL文件号E494534）。&lt;/p&gt;
&lt;p&gt;输入和输出的开关状态通过PiBridge与RevPi Core的中央处理映像进行周期性数据交换。除了切换状态之外，还可以周期性地传输可能的错误状态（诊断数据）和配置数据。RevPi DIO模块还配备了PWM（脉冲宽度调制）和计数器输入，可以分别为14个输出中的每个激活PWM功能，为14个输入通道中的每个通道激活计数器功能。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>透过树莓派为 Pico 添加 WiFi 功能</title><link>https://blog.wo.ai/archives/3413/</link><guid isPermaLink="true">https://blog.wo.ai/archives/3413/</guid><pubDate>Mon, 25 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;树莓派 Pico 是一款很棒的设备。它具有出彩的特性，拥有强大的 SDK 和文档，而且成本很低。美中不足的是，它没有网络功能。&lt;/p&gt;
&lt;p&gt;几款基于 RP2040 的 MCU 目前已上市。但是与 Pico 的价格相比，这几款开发板比较贵，而且它们也不具有网络功能。&lt;/p&gt;
&lt;p&gt;本文将向你介绍如何通过几个简单的步骤，使用树莓派为树莓派 Pico 添加网络功能。我们将使用 Telemetrix 树莓派 Pico 包和两个开源网络实用程序，无需额外的硬件。&lt;/p&gt;
&lt;p&gt;你只需按照下面列出的步骤操作即可。另外，你也可以使用 Python 脚本来控制和监视 Pico 的 GPIO 引脚。&lt;/p&gt;
&lt;p&gt;硬件部分&lt;/p&gt;
&lt;p&gt;树莓派 Pico&lt;/p&gt;
&lt;p&gt;支持 WiFi 或以太网的树莓派&lt;/p&gt;
&lt;p&gt;USB-A 转 Micro USB 数据线&lt;/p&gt;
&lt;p&gt;一台 Linux、macOS 或 Windows 10 PC&lt;/p&gt;
&lt;p&gt;用到的软件和在线服务&lt;/p&gt;
&lt;p&gt;Raspberry Pi OS&lt;/p&gt;
&lt;p&gt;Telmetrix4RpiPico（适用于 树莓派 Pico 的开源服务器固件）&lt;/p&gt;
&lt;p&gt;Telemetrix-rpi-pico（适用于 PC 的开源 Python 客户端 API）&lt;/p&gt;
&lt;p&gt;ser2net（适用于树莓派点免费开源串行端口网络代理）&lt;/p&gt;
&lt;p&gt;sosat（适用于 PC 的开源免费多功能中继工具）&lt;/p&gt;
&lt;p&gt;原理&lt;/p&gt;
&lt;p&gt;Telemetrix 项目允许你通过 USB/串行 将消息从 PC 中上传到 Pico 的固件中，从而监控和控制树莓派 Pico 的 GPIO。&lt;/p&gt;
&lt;p&gt;如果我们可以将串行数据流转换为 TCP/IP 流，通过 TCP 中继串行数据会怎样？&lt;/p&gt;
&lt;p&gt;没错，这正是我们将要做的，所以让我们现在就开始吧。&lt;/p&gt;
&lt;p&gt;第 1 步：给 Pico 上传 Telemetrix&lt;/p&gt;
&lt;p&gt;安装步骤很简单，只需按照 Telemetrix 用户指南上描述的步骤操作即可：&lt;/p&gt;
&lt;p&gt;1、下载 Telemetrix4RpiPico.uf2 文件。&lt;/p&gt;
&lt;p&gt;2、按住 Pico 开发板上的 BOOTSEL 按钮，然后将 Pico 插入树莓派或 PC 的 USB 接口，然后松开 BOOTSEL 按钮。&lt;/p&gt;
&lt;p&gt;3、Pico 会被识别为大容量存储设备。&lt;/p&gt;
&lt;p&gt;4、将下载的 MicroPython UF2 文件放入 RPI-RP2 卷上。你的 Pico 将自动重启，然后程序就开始运行了。&lt;/p&gt;
&lt;p&gt;第 2 步：更新 Raspberry Pi OS&lt;/p&gt;
&lt;p&gt;在树莓派上打开一个终端窗口，并执行以下命令：&lt;/p&gt;
&lt;p&gt;sudo apt-get update&lt;/p&gt;
&lt;p&gt;然后运行命令（如果系统已经是最新的话，可选，运行这个命令会很耗时）：&lt;/p&gt;
&lt;p&gt;sudo apt-get upgrade&lt;/p&gt;
&lt;p&gt;完成后，可以继续下面的步骤。&lt;/p&gt;
&lt;p&gt;第 3 步：查找树莓派的 IP 地址&lt;/p&gt;
&lt;p&gt;打开终端窗口并输入以下命令：&lt;/p&gt;
&lt;p&gt;ifconfig&lt;/p&gt;
&lt;p&gt;eth0是为以太网分配的 IP 地址，wlan0是为 WiFi 分配的 IP 地址。由于我用网线把树莓派连接到路由器，并将树莓派连接到 WiFi，因此这两个条目都分配了 IP 地址。当然，我可以选择使用这两个地址中的任何一个，但是由于我想在本演示中使用 WiFi，所以我使用了wlan0地址192.168.2.126。&lt;/p&gt;
&lt;p&gt;请记下树莓派列出的 IP 地址，我们在后面的步骤中会用到。&lt;/p&gt;
&lt;p&gt;第 4 步：在树莓派上安装配置 ser2net&lt;/p&gt;
&lt;p&gt;ser2net 是一个串行转网络的代理。它将 IP 数据流转换为串行数据，并将数据重定向到特定的串行端口。它还把来自串行端口的数据转换为 TCP/IP 流。ser2net 作为守护进程运行，安装之后，它会在每次启动树莓派时自动运行。&lt;/p&gt;
&lt;p&gt;要安装 ser2net，请在终端中输入以下命令：&lt;/p&gt;
&lt;p&gt;sudo apt-get install ser2net&lt;/p&gt;
&lt;p&gt;接下来，我们需要通过修改配置文件来配置 ser2net。首先，在终端中输入以下命令来停止守护进程：&lt;/p&gt;
&lt;p&gt;sudo systemctl stop ser2net&lt;/p&gt;
&lt;p&gt;然后，在终端中输入以下命令来修改 /etc/ser2net.config 文件：&lt;/p&gt;
&lt;p&gt;sudo nano /etc/ser2net.config&lt;/p&gt;
&lt;p&gt;请将以下内容添加到文件的最后：&lt;/p&gt;
&lt;p&gt;3333:raw:0:/dev/ttyACM0:115200,remctl&lt;/p&gt;
&lt;p&gt;保存文件并退出编辑器。&lt;/p&gt;
&lt;p&gt;添加的这行内容告诉 ser2net 将在 IP 端口 3333 上接收到的 IP 数据重定向到 COM 端口 /dev/ttyACM0 上的串行设备。要了解 ser2net 的更多信息，请输入以下内容来参考其手册页：&lt;/p&gt;
&lt;p&gt;man ser2net&lt;/p&gt;
&lt;p&gt;现在，让我们重启 ser2net 守护进程。在终端窗口中，输入：&lt;/p&gt;
&lt;p&gt;sudo systemctl start ser2net&lt;/p&gt;
&lt;p&gt;第 5 步：用 USB 数据线连接 Pico 和树莓派&lt;/p&gt;
&lt;p&gt;连接两个设备之间的数据线后，然后输入：&lt;/p&gt;
&lt;p&gt;ls -l /dev/ttyA*&lt;/p&gt;
&lt;p&gt;你应该会看到列出的 ttyACM0。&lt;/p&gt;
&lt;p&gt;ls -l /dev/ttyA* crw-rw—- 1 root dialout 166, 0 Jun 12 19:17 /dev/ttyACM0 crw-rw—- 1 root dialout 204, 64 Jun 12 19:40 /dev/ttyAMA0&lt;/p&gt;
&lt;p&gt;第 6 步：你的客户端计算机是否基于 Windows？&lt;/p&gt;
&lt;p&gt;如果你使用 Linux 或 macOS，请跳转到第 7 步。&lt;/p&gt;
&lt;p&gt;如果你使用 Windows，你需要在计算机上安装适用于 Linux 的 Windows 子系统 (WSL)。&lt;/p&gt;
&lt;p&gt;WSL 可以让开发人员直接在 Windows 上运行 GNU/Linux 环境——包括大多数命令行工具、实用工具和应用程序，免去了使用传统虚拟机或配置双引导的麻烦。&lt;/p&gt;
&lt;p&gt;要安装 WSL，请按照此教程进行操作。在选择 Linux 版本时，请选择 Ubuntu 20.04。&lt;/p&gt;
&lt;p&gt;安装后，请转到第 7 步。使用 WSL 终端窗口执行所有后续步骤。&lt;/p&gt;
&lt;p&gt;第 7 步：安装 Socat&lt;/p&gt;
&lt;p&gt;Socat 是基于命令行的实用程序，它建立两个双向字节流并在它们之间传输数据。Socat 是树莓派服务器计算机上使用的 ser2net 的对等点。我们将把它安装在客户端 PC 上。&lt;/p&gt;
&lt;p&gt;在客户端计算机的终端窗口中，键入：&lt;/p&gt;
&lt;p&gt;sudo apt-get install socat&lt;/p&gt;
&lt;p&gt;第 8 步：安装 Telemetrix 客户端&lt;/p&gt;
&lt;p&gt;请检查你的计算机上是否安装了 Python3。&lt;/p&gt;
&lt;p&gt;python3 -V&lt;/p&gt;
&lt;p&gt;如果没有，请先安装。&lt;/p&gt;
&lt;p&gt;接下来，通过输入以下命令来检查是否已安装 pip3：&lt;/p&gt;
&lt;p&gt;pip3 -V&lt;/p&gt;
&lt;p&gt;pip3 的版本会显示在终端中。如果显示空行或有关安装 pip3 的错误消息，请输入以下命令来安装 pip3：&lt;/p&gt;
&lt;p&gt;sudo apt-get install python3-pip&lt;/p&gt;
&lt;p&gt;我们现在准备从 PyPi 安装 Telemetrix。请在终端窗口中，输入：&lt;/p&gt;
&lt;p&gt;sudo pip3 install telemetrix-rpi-pico&lt;/p&gt;
&lt;p&gt;第 9 步：下载并修改 Telemetrix 示例脚本&lt;/p&gt;
&lt;p&gt;访问 GitHub 项目：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/MrYsLab/telemetrix-rpi-pico&quot;&gt;https://github.com/MrYsLab/telemetrix-rpi-pico&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下载源码。&lt;/p&gt;
&lt;p&gt;保存并解压缩 .zip 文件。解压后进入examples目录，编辑 blink.py。我们之所以使用这个例子，是因为它不需要任何额外的硬件。&lt;/p&gt;
&lt;p&gt;请将文件中的以下几行&lt;/p&gt;
&lt;h1&gt;Create a Telemetrix instance. board = telemetrix_rpi_pico.TelemetrixRpiPico()&lt;/h1&gt;
&lt;p&gt;更改为：&lt;/p&gt;
&lt;h1&gt;Create a Telemetrix instance. board = telemetrix_rpi_pico.TelemetrixRpiPico(com_port=’/home/afy/MyProxySerialPort’)&lt;/h1&gt;
&lt;p&gt;修改后的行包含在一行代码中。然后保存文件。&lt;/p&gt;
&lt;p&gt;正如我们将在第 10 步中看到的那样，Socat 将创建一个名为 /home/afy/MyProxySerialPort 的虚拟 com 端口。&lt;/p&gt;
&lt;p&gt;在这里，我们告诉 Python 脚本使用该 COM 端口。&lt;/p&gt;
&lt;p&gt;第 10 步：启动 Socat 并运行 Blink 示例&lt;/p&gt;
&lt;p&gt;在终端窗口中，复制并粘贴以下命令。在执行命令之前，请把其中的 IP 地址改为你的树莓派的 IP 地址。&lt;/p&gt;
&lt;p&gt;socat pty,link=$HOME/MyProxySerialPort,waitslave tcp:192.168.2.126:3333,forever,reuseaddr,keepalive &amp;amp;&lt;/p&gt;
&lt;p&gt;该命令包含在一行中。在该行的末尾，&amp;amp; 符号使命令在后台运行，以便你可以在同一终端窗口中运行 blink.py 脚本。&lt;/p&gt;
&lt;p&gt;现在运行修改后的 blink.py，你会看到 Pico 上的 LED 开始闪烁。&lt;/p&gt;
&lt;p&gt;python3 blink.py&lt;/p&gt;
&lt;p&gt;总结&lt;/p&gt;
&lt;p&gt;恭喜你，完成了！现在，你可以编写自己的程序了。如果你愿意，也可以运行一些其他的 demo。请记住一定要像第 9 步中那样指定 com_port 参数。&lt;/p&gt;
&lt;p&gt;另外，如果你想编写自己的脚本，请参阅 Telemetrix API 和用户指南。&lt;/p&gt;
&lt;p&gt;via&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>優やさしい詩</title><link>https://blog.wo.ai/archives/1338/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1338/</guid><pubDate>Fri, 16 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;優やさしい詩うた – RSP&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;広ひろい世界せかいでたったひとつの&lt;/li&gt;
&lt;li&gt;在這廣闊世界僅有的一首&lt;/li&gt;
&lt;li&gt;小ちいさなあたしの願ねがい詩うた&lt;/li&gt;
&lt;li&gt;屬於我小小的祈願詩歌&lt;/li&gt;
&lt;li&gt;笑わらって欲ほしいどこか遠とおくの&lt;/li&gt;
&lt;li&gt;想要微笑好像又漸行漸遠&lt;/li&gt;
&lt;li&gt;名前なまえも知しらないあなたにも&lt;/li&gt;
&lt;li&gt;連你的名字都不曾知曉&lt;/li&gt;
&lt;li&gt;うまく言いえたら辛つらくならない&lt;/li&gt;
&lt;li&gt;能好好表達的話就不會心酸不已&lt;/li&gt;
&lt;li&gt;うまく泣なけたなら苦くるしくもない&lt;/li&gt;
&lt;li&gt;能好好哭出來的話就不會飽受痛苦&lt;/li&gt;
&lt;li&gt;うつむいたままそんなあなたに 安やすらぎのひとつ与あたえられたら…&lt;/li&gt;
&lt;li&gt;低下頭 若是能授予這樣的你一份安逸的話…&lt;/li&gt;
&lt;li&gt;空そらはいつでも誰だれの上うえでも&lt;/li&gt;
&lt;li&gt;就算天空總是一直在我們上方&lt;/li&gt;
&lt;li&gt;青色あおいろしてた見上みあげてごらん&lt;/li&gt;
&lt;li&gt;抬頭觀賞那抹蔚藍色彩&lt;/li&gt;
&lt;li&gt;優やさしい詩うたを唄うたっていたい&lt;/li&gt;
&lt;li&gt;想要歌唱那首温柔的歌謠&lt;/li&gt;
&lt;li&gt;弱よわい人ひとにも強つよい人ひとにも&lt;/li&gt;
&lt;li&gt;不管是對柔弱的人 還是對堅强的人&lt;/li&gt;
&lt;li&gt;嘘うそやいいわけ心こころ隠かくして&lt;/li&gt;
&lt;li&gt;謊言或藉口隐藏心中&lt;/li&gt;
&lt;li&gt;傷付きずつくことをずっと避さけていた&lt;/li&gt;
&lt;li&gt;一直都避免受傷&lt;/li&gt;
&lt;li&gt;隣となりに座すわるほんとの自分じぶん&lt;/li&gt;
&lt;li&gt;一旁坐著真正的自己&lt;/li&gt;
&lt;li&gt;あなたはそれに気付きづかなくて&lt;/li&gt;
&lt;li&gt;而你也没有發現到這一點&lt;/li&gt;
&lt;li&gt;人ひとの痛いたみを感かんじた時ときも&lt;/li&gt;
&lt;li&gt;當感覺到為人之痛的時候&lt;/li&gt;
&lt;li&gt;幸しあわせな人ひとを見みつけた時ときも&lt;/li&gt;
&lt;li&gt;當發現幸福的人的時候&lt;/li&gt;
&lt;li&gt;同おなじ気持きもちで涙なみだしたのも&lt;/li&gt;
&lt;li&gt;也都帶著同樣地心情落下淚水&lt;/li&gt;
&lt;li&gt;あなたの心こころ忘わすれないよう&lt;/li&gt;
&lt;li&gt;也不能忘却你的真心&lt;/li&gt;
&lt;li&gt;空そらはいつでも誰だれの上うえでも&lt;/li&gt;
&lt;li&gt;就算天空總是一直在我們上方&lt;/li&gt;
&lt;li&gt;青色あおいろしてた見上みあげてごらん&lt;/li&gt;
&lt;li&gt;抬頭觀賞那抹蔚藍色彩&lt;/li&gt;
&lt;li&gt;優やさしい詩うたを唄うたっていたい&lt;/li&gt;
&lt;li&gt;想要歌唱那首温柔的歌謠&lt;/li&gt;
&lt;li&gt;弱よわい人ひとにも強つよい人ひとにも&lt;/li&gt;
&lt;li&gt;不管是對柔弱的人 還是對堅强的人&lt;/li&gt;
&lt;li&gt;空そらはいつでも誰だれの上うえでも&lt;/li&gt;
&lt;li&gt;就算天空總是一直在我們上方&lt;/li&gt;
&lt;li&gt;青色あおいろしてたそれが嬉うれしい&lt;/li&gt;
&lt;li&gt;那抹蔚藍讓人歡心喜悦&lt;/li&gt;
&lt;li&gt;優やさしい詩うたを唄うたっていたい&lt;/li&gt;
&lt;li&gt;想要歌唱那首温柔的歌謠&lt;/li&gt;
&lt;li&gt;弱よわい人ひとにも強つよい人ひとにも&lt;/li&gt;
&lt;li&gt;不管是對柔弱的人 還是對堅强的人&lt;/li&gt;
&lt;li&gt;どこで生うまれて どんな色いろ持もって&lt;/li&gt;
&lt;li&gt;在某處被降生下來 不管帶著什麼色彩&lt;/li&gt;
&lt;li&gt;たったひとりのあなたいるから&lt;/li&gt;
&lt;li&gt;你是只此唯一的存在&lt;/li&gt;
&lt;li&gt;今日きょうも生いきてる 人ひとがいること&lt;/li&gt;
&lt;li&gt;今天也安然活著 生命的存在&lt;/li&gt;
&lt;li&gt;忘わすれないでよ 強つよいあなたへ&lt;/li&gt;
&lt;li&gt;别忘記了哦 獻給堅强的你&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;歌词来源：&lt;a href=&quot;http://blog.xuite.net/l42934293/blog/213750290&quot;&gt;http://blog.xuite.net/l42934293/blog/213750290&lt;/a&gt; 及 &lt;a href=&quot;https://www.jpmarumaru.com/tw/JPSongPlay-2781.html&quot;&gt;https://www.jpmarumaru.com/tw/JPSongPlay-2781.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;距离上一篇博文已经3年多了，一直没有静下心来写点东西的机会。&lt;/p&gt;
&lt;p&gt;上一篇博文还在而立，这篇已经到了不惑了。终于还是活成了自己讨厌的人。在年轻的时候觉得以自己方式活着就好，到了临近中年才发现，终究还是要碰壁的，这确实够讽刺的。&lt;/p&gt;
&lt;p&gt;但是生活还是要继续，面对现实，一生都要抱着学习的态度提高自己，保持自己的竞争力。&lt;/p&gt;
</content:encoded><category>Life</category></item><item><title>Ubuntu 16.04 升级OPENSSL</title><link>https://blog.wo.ai/archives/1327/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1327/</guid><pubDate>Wed, 07 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ubuntu 16.04 系统默认带的是1.0.2的openssl，而openssl暴露的漏洞越来越多，而ubuntu似乎也不打算在升级包中包含它，只能手动升级它了。&lt;/p&gt;
&lt;p&gt;首先下载最新的openssl源码包&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;wget &lt;a href=&quot;https://www.openssl.org/source/openssl-1.1.1k.tar.gz&quot;&gt;https://www.openssl.org/source/openssl-1.1.1k.tar.gz&lt;/a&gt;&lt;br&gt;tar xvfz openssl-1.1.1k.tar.gz&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;cd openssl-1.1.1k/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;./config&lt;br&gt;make&lt;br&gt;make test&lt;br&gt;make install&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;创建openssl软链接&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;mv /usr/bin/openssl /root/&lt;br&gt;ln -s /usr/local/ssl/bin/openssl /usr/bin/openssl&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;链接加密缺失的so文件&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ln -s /usr/local/lib/libssl.so.1.1 /usr/lib/&lt;br&gt;ln -s /usr/local/lib/libcrypto.so.1.1 /usr/lib/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;来试试看效果
openssl version
OpenSSL 1.1.1k  25 Mar 2021
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Technology</category></item><item><title>以太坊入门（三）用web3j进行以太转账及代币转账</title><link>https://blog.wo.ai/archives/1316/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1316/</guid><pubDate>Mon, 01 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上章讲到账户的查询，本章讲述账户转账。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;以太坊转账 &lt;code&gt;We3j web3j = Web3j.build(new HttpService(ConstantLibs.WEB3_ADDRESS)); Credentials credentials = WalletTool.loadCredentials(fromAddress); EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount( fromAddress, DefaultBlockParameterName.LATEST).sendAsync().get(); BigInteger nonce = ethGetTransactionCount.getTransactionCount(); RawTransaction rawTransaction = RawTransaction.createEtherTransaction( nonce, Convert.toWei(&amp;quot;18&amp;quot;, Convert.Unit.GWEI).toBigInteger(), Convert.toWei(&amp;quot;45000&amp;quot;, Convert.Unit.WEI).toBigInteger(), toAddress, new BigInteger(amount)); byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); String hexValue = Numeric.toHexString(signedMessage); EthSendTransaction ethSendTransaction = web3.ethSendRawTransaction(hexValue).sendAsync().get(); if (ethSendTransaction.hasError()) { log.info(&amp;quot;transfer error:&amp;quot;, ethSendTransaction.getError().getMessage()); } else { String transactionHash = ethSendTransaction.getTransactionHash(); log.info(&amp;quot;Transfer transactionHash:&amp;quot; + transactionHash); }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;以太坊代币转账 &lt;code&gt;Web3j web3j = Web3j.build(new HttpService(ConstantLibs.WEB3_ADDRESS)); Credentials credentials = WalletTool.loadCredentials(fromAddress); EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount( fromAddress, DefaultBlockParameterName.LATEST).sendAsync().get(); BigInteger nonce = ethGetTransactionCount.getTransactionCount(); Function function = new Function( &amp;quot;transfer&amp;quot;, Arrays.asList(new Address(toAddress), new Uint256(new BigInteger(amount))), Arrays.asList(new TypeReference&amp;lt;Type&amp;gt;() { })); String encodedFunction = FunctionEncoder.encode(function); RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, Convert.toWei(&amp;quot;18&amp;quot;, Convert.Unit.GWEI).toBigInteger(), Convert.toWei(&amp;quot;100000&amp;quot;, Convert.Unit.WEI).toBigInteger(), contractAddress, encodedFunction); byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); String hexValue = Numeric.toHexString(signedMessage); log.debug(&amp;quot;transfer hexValue:&amp;quot; + hexValue); EthSendTransaction ethSendTransaction = web3.ethSendRawTransaction(hexValue).sendAsync().get(); if (ethSendTransaction.hasError()) { log.info(&amp;quot;transfer error:&amp;quot;, ethSendTransaction.getError().getMessage()); } else { String transactionHash = ethSendTransaction.getTransactionHash(); log.info(&amp;quot;Transfer transactionHash:&amp;quot; + transactionHash); }&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代币转账和以太转账的区别在于，to地址是合约地址，而input是有三部分数据构成：transfer方法的哈希+收款人的地址+转账金额。此处比较难理解的正是Function部分，设置好参数以后，调用rawTransaction就可以了。&lt;/p&gt;
&lt;p&gt;本站始发，转载于 &lt;a href=&quot;https://www.jianshu.com/p/8ae984e6bafc&quot;&gt;https://www.jianshu.com/p/8ae984e6bafc&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Technology</category></item><item><title>以太坊入门（二）用web3j进行以太查询及通证查询</title><link>https://blog.wo.ai/archives/1314/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1314/</guid><pubDate>Fri, 01 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以太坊的开发，基本都是go语言和nodejs的天下，web3j出现给java开发人员提供了很大的便利。本文会对一些以太坊的基本操作用java语言来实现。&lt;/p&gt;
&lt;p&gt;本章会讲述通过web3j进行账户余额的查询。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;以太余额查询&lt;br&gt;以太的余额查询比较简单，直接调用web3j的ethGetBalance就可以。 &lt;code&gt;Web3j web3j = Web3j.build(new HttpService(ConstantLibs.WEB3_ADDRESS)); EthGetBalance ethGetBalance = web3j.ethGetBalance( address, DefaultBlockParameterName.LATEST).sendAsync().get(); BigInteger balance = ethGetBalance.getBalance();&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通证的余额查询&lt;br&gt;代币的查询就比较复杂一些，研究了好长时间，最后发现每个代币合约都会实现balanceOf方法，可以通过这个方法来查询通证的余额。&lt;code&gt; Web3j web3j = Web3j.build(new HttpService(ConstantLibs.WEB3_ADDRESS)); Function function = new Function( &amp;quot;balanceOf&amp;quot;, Arrays.asList(new Address(address)), // Solidity Types in smart contract functions Arrays.asList(new TypeReference&amp;lt;Type&amp;gt;() { })); String encodedFunction = FunctionEncoder.encode(function); org.web3j.protocol.core.methods.response.EthCall response = web3j.ethCall( org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction(address, contract, encodedFunction), DefaultBlockParameterName.LATEST) .sendAsync().get(); String returnValue = response.getValue(); //返回16进制余额 returnValue = returnValue.substring(2); BigInteger balance = new BigInteger(returnValue, 16);&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本站始发，转载于&lt;a href=&quot;https://www.jianshu.com/p/0f3e65622d65&quot;&gt;https://www.jianshu.com/p/0f3e65622d65&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Technology</category></item><item><title>以太坊入门（一）账户和nonce的关系</title><link>https://blog.wo.ai/archives/1308/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1308/</guid><pubDate>Mon, 11 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;什么是nonce？&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;在以太坊的交易数据中，大家都可以看到一个数字，nonce，从0开始，一直向上递增，这个代表什么意思呢？&lt;/p&gt;
&lt;p&gt;以太坊所有的交易都是基于account的，不同于基于utxo的比特币，因此需要对每次交易都按顺序记录，nonce值就是这个顺序，主要用来防止重放攻击。&lt;/p&gt;
&lt;p&gt;每&lt;strong&gt;发起&lt;/strong&gt; 一笔交易，nonce就会加一。对于&lt;strong&gt;发起&lt;/strong&gt; 的解释：&lt;/p&gt;
&lt;p&gt;1.外部账户每发送一笔交易；&lt;/p&gt;
&lt;p&gt;2.合约账户每创建一个合约&lt;/p&gt;
&lt;p&gt;而转入交易、合约调用其他合约等属于内部调用，因此nonce值不变。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;如何使用nonce&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;发起转账或者创建合约的时候，通过web3从以太坊网络查询当前的nonce（ethGetTransactionCount）值，使用此值作为当前交易的nonce值，发送到以太坊网络即可。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;nonce使用的几条规则&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;1. 当nonce太小（小于当前的nonce值），交易会被直接拒绝，Transactions with too low a nonce get immediately rejected；&lt;/p&gt;
&lt;p&gt;2. 当nonce太大，大于当前nonce，交易会一直处于队列之中，Transactions with too high a nonce get placed in the transaction pool queue；&lt;/p&gt;
&lt;p&gt;3.当发送一个比较大的nonce值，然后补齐开始nonce到那个值之间的nonce，那么交易依旧可以被执行，If transactions with nonces that fill the gap between the last valid nonce and the too high nonce are sent and the nonce sequence is complete, all the transactions in the sequence will get processed and mined.&lt;/p&gt;
&lt;p&gt;4. 交易队列只保存最多64个从同一个账户发出的交易，The transaction pool queue will only hold a maximum of 64 transactions with the same From:address with nonces out of sequence. 也就是说，如果要批量转账，同一节点不要发出超过64笔交易。&lt;/p&gt;
&lt;p&gt;5.当某节点queue中还有交易，但此时停止geth客户端，queue中的交易会被清除掉，When the geth instances are shut down and restarted, transactions in the transaction pool queue disappear.&lt;/p&gt;
&lt;p&gt;6.当前nonce合适，但是账户余额不足时，会被以太坊拒绝；&lt;/p&gt;
&lt;p&gt;7.如果发起一笔交易，但是因为gwei比较低或者网络比较忙的时候，该交易还没矿工挖出，可以通过使用相同的nonce和较高的gas费用，从而“覆盖”前一笔交易；&lt;/p&gt;
</content:encoded><category>Technology</category></item><item><title>树莓派充当airplay接收器</title><link>https://blog.wo.ai/archives/1305/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1305/</guid><pubDate>Wed, 30 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;iOS升级以后，新版本经常不兼容xbian之类自带的airplay功能，目前来Shairport的支持还是不错的。我在新的树梅派3b+上编译了一下，支持ios 11.0.4版本。&lt;/p&gt;
&lt;p&gt;首先升级树莓派&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get update
sudo apt-get upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后安装依赖包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install autoconf automake avahi-daemon build-essential git libasound2-dev libavahi-client-dev libconfig-dev libdaemon-dev libpopt-dev libssl-dev libtool xmltoman
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载shairport源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/mikebrady/shairport-sync.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd shairport-sync
autoreconf -i -f
./configure --with-alsa --with-avahi --with-ssl=openssl --with-systemd --with-metadata
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;make
sudo make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置为自动启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable shairport-sync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo service shairport-sync start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以在手机里面找到airplay设备，raspberrypi了。&lt;/p&gt;
</content:encoded><category>Raspberry Pi</category><category>Technology</category></item><item><title>Nginx反向代理跨域option问题解决</title><link>https://blog.wo.ai/archives/1299/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1299/</guid><pubDate>Mon, 26 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;CORS on Nginx&lt;/h1&gt;
&lt;p&gt;The following Nginx configuration enables CORS, with support for preflight requests.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# Wide-open CORS config for nginx
#
location / {
     if ($request_method = &amp;#39;OPTIONS&amp;#39;) {
        add_header &amp;#39;Access-Control-Allow-Origin&amp;#39; &amp;#39;*&amp;#39;;
        add_header &amp;#39;Access-Control-Allow-Methods&amp;#39; &amp;#39;GET, POST, OPTIONS&amp;#39;;
        #
        # Custom headers and headers various browsers *should* be OK with but aren&amp;#39;t
        #
        add_header &amp;#39;Access-Control-Allow-Headers&amp;#39; &amp;#39;DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range&amp;#39;;
        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header &amp;#39;Access-Control-Max-Age&amp;#39; 1728000;
        add_header &amp;#39;Content-Type&amp;#39; &amp;#39;text/plain; charset=utf-8&amp;#39;;
        add_header &amp;#39;Content-Length&amp;#39; 0;
        return 204;
     }
     if ($request_method = &amp;#39;POST&amp;#39;) {
        add_header &amp;#39;Access-Control-Allow-Origin&amp;#39; &amp;#39;*&amp;#39;;
        add_header &amp;#39;Access-Control-Allow-Methods&amp;#39; &amp;#39;GET, POST, OPTIONS&amp;#39;;
        add_header &amp;#39;Access-Control-Allow-Headers&amp;#39; &amp;#39;DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range&amp;#39;;
        add_header &amp;#39;Access-Control-Expose-Headers&amp;#39; &amp;#39;Content-Length,Content-Range&amp;#39;;
     }
     if ($request_method = &amp;#39;GET&amp;#39;) {
        add_header &amp;#39;Access-Control-Allow-Origin&amp;#39; &amp;#39;*&amp;#39;;
        add_header &amp;#39;Access-Control-Allow-Methods&amp;#39; &amp;#39;GET, POST, OPTIONS&amp;#39;;
        add_header &amp;#39;Access-Control-Allow-Headers&amp;#39; &amp;#39;DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range&amp;#39;;
        add_header &amp;#39;Access-Control-Expose-Headers&amp;#39; &amp;#39;Content-Length,Content-Range&amp;#39;;
     }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First is tricking Nginx that a 405 status is actually a 200 OK and then proxy_pass it to your HAProxy like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error_page 405 =200 @405;
location @405 {
    root /;
    proxy_pass http://yourproxy:8080;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second solution is just to catch the OPTIONS request and build a response for those requests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location / {
    if ($request_method = OPTIONS ) {
        add_header Content-Length 0;
        add_header Content-Type text/plain;
        return 200;
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Technology</category></item><item><title>春风十里</title><link>https://blog.wo.ai/archives/1287/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1287/</guid><pubDate>Wed, 03 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;春风十里&lt;br&gt; 作曲作词 倍倍&lt;/p&gt;
&lt;p&gt;我在二环的里边 想着你&lt;br&gt; 你在远方的山上 春风十里&lt;br&gt; 今天的风吹向你 下了雨&lt;br&gt; 我说所有的酒 都不如你&lt;/p&gt;
&lt;p&gt;我在鼓楼的夜色中 为你唱花香自来&lt;br&gt; 在别处 沉默相遇和期待&lt;br&gt; 飞机飞过 车水马龙的城市&lt;br&gt; 千里之外 不离开&lt;/p&gt;
&lt;p&gt;把所有的春天 都揉进了一个清晨&lt;br&gt; 把所有停不下来的言语变成秘密 关上了门&lt;br&gt; 莫名的情愫啊 请问 谁来将它带走呢&lt;br&gt; 只好把岁月化成歌 留在山河&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://s3.wo.ai/upload/mp3/2661437940.mp3&quot;&gt;http://s3.wo.ai/upload/mp3/2661437940.mp3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;时间过得很快，2017年就这么过去了。&lt;/p&gt;
&lt;p&gt;这两天一直在循环播放《春风十里》这首歌。最喜欢的一句就是：我说所有的酒，都不如你，千里之外 不离开。&lt;/p&gt;
&lt;p&gt;酒，对于每个人来说，都是不同的东西，有喜、有恶。正如小站的名字，酒趣琴音，达是酒中趣，琴上偶然音。前两天朋友圈有人晒出在米国时候的照片，一想已经毕业了七年。三五好友凑一起，小酌，谈谈古，论论今，甚是怀念。当年春田还是留下了太多的身影，十一叔，River，Candy，十三姨，老姑…回国以后，各奔东西，即使是同一城市，也难得碰一次。虽然联系很少，偶尔在异地出差，聚在一起，总还是有说不完的话题。从城市各个角落聚拢，陪吃，陪喝，陪聊，还有陪压马路，当今的快节奏，每个人都很忙，花时间陪伴，也只有真切的同学之间才可能做到了。鸿鹄也好，鹡鸰也好，缘分让大家聚在春田，也让大家留下了这份纯真。&lt;/p&gt;
&lt;p&gt;对我来说2017，算是比较动荡的一年，在某个字母公司待了8年，终于下定决心离开了，跑到另外一座城市，迎接未知的挑战。虽然和来之前的初衷有很大的偏差，人性好的、坏的都见识和领教过，至少不算好也不算坏，这一年就这么过去了。生活不只有眼前苟且，还有诗远方。要用眼睛看到诗和远方，还要尝试把诗与远方变成生活，2018谈谈理想吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2018/01/WechatIMG548-225x300.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;所有的酒，都不如你；千里之外，不曾离开。&lt;/p&gt;
</content:encoded><category>Life</category></item><item><title>区块链翻译文章系列（一）初学者指南 Blockchain For Dummies: A Beginner’s Guide</title><link>https://blog.wo.ai/archives/1278/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1278/</guid><pubDate>Mon, 20 Nov 2017 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Blockchain For Dummies: A Beginner’s Guide&lt;/h2&gt;
&lt;p&gt;Blockchain: the single most confusing term since Bitcoin. Everyone has a vague idea of what it does. It’s either the ultimate evolution of financial technologies, or a silly fad that can be summed up in the disconcerting phrase: “dogechain.” In reality, major companies around the world have already shown favor to the burgeoning money exchange system and it may become harder and harder to stay away from the financial dark art.&lt;br&gt;In reality, it is all relatively easy to understand. The Blockchain is a public ledger where transactions are recorded and confirmed anonymously. It’s a record of events that is shared between many parties. More importantly, once information is entered, it cannot be altered. So, if the blockchain is the public record, what is being recorded? What are all of these “transactions”?&lt;/p&gt;
&lt;p&gt;Cryptocurrencies, like bitcoin, are currencies that exist solely in digital. There are no physical golden coins with a big “B” on them. Moreover, owning these non-real coins entails a new idea of “ownership.” You don’t literally have it in your hands, or even in your bank account, but you have the ability to transfer “ownership” to someone else simply by creating a record in the blockchain. Rather than using bills, your transfer is pure data.&lt;/p&gt;
&lt;p&gt;Where exactly is this chain located? Due to the open nature of cryptocurrencies, and the importance of the public having access to other blocks, the blockchain isn’t located on just one guy’s large computer. For example, the bitcoin blockchain is actually managed by distributed nodes. These nodes all have a copy of the entire blockchain. Nodes will forever come and go, synchronizing their own copies of the chain with those of other users. By distributing copies and access, the chain can’t simply “go down,” or disappear. It’s a decentralized system that is both sturdy and secure.&lt;/p&gt;
&lt;p&gt;All of your dogecoins are in a row, but what do you do with them? Whether you’re using them IRL or online, the blockchain makes it happen. There are many reasons people are falling in love with cryptocurrencies: it’s anonymous, decentralized, and there are no fees or third parties trying to grab a percentage. However, if there were absolutely no regulations in place, the new currency would quickly become a greedfest of users trying to screw each other over. The public nature of the blockchain means that anyone can check it. It is effectively anonymous, yet public, simultaneously, and it is in the best interest of users if it remains so.&lt;br&gt;You can accept and trade coins, or you can mine for them. Miners can spend thousands of dollars on the right equipment just to mine coin. But what do they really do? What miners do is quite similar to real-world miners in that they are actively looking for something. Their computer repeatedly works through complex calculations to find a very specific answer.&lt;/p&gt;
&lt;p&gt;Miners solve problems, but how in the world is that helpful? Short story, miners are actually verifying that transactions posted by other users are legitimate, and the numbers all add up. Long story…&lt;/p&gt;
&lt;p&gt;Miners collect transactions and put them into a single block. A block generally contains four pieces of information: a reference to the previous block, a summary of included transaction, a time stamp, and Proof of Work that went into creating the secure block. The blocks are strung together into a chain—a fluid chain that does not allow for any inconsistencies; this means there are no “bad cheques” in the system, and transactions entered are necessarily valid and can be processed. By checking the blockchain and confirming transactions, the entire system is effectively self-regulated and fully secure. No, that doesn’t mean some kid cooped up in a basement can just click “okay” and confirm a billion dollar transfer. Blocks generally need numerous independent confirmations, and the equations are intended to be hard to crack. Not to mention, the hardware required is far more specialized than the average laptop. Finally, what’s to stop someone from simply going back and editing existing blocks? Each block is securely hashed—meaning it is rendered into seeming gibberish and nearly impossible to invert or undo. Once it’s in the blockchain, it’s there forever.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://byteacademy.co/wp-content/uploads/2016/09/roughblockchain-300x223.png&quot; alt=&quot;rough idea of blockchain&quot;&gt;A rough idea of what a block chain may look like, courtesy of Yevgeniy Brikman&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://byteacademy.co/wp-content/uploads/2016/09/roughblockchain-300x223.png&quot; alt=&quot;python for dummies pdf&quot;&gt;&lt;br&gt;A rough idea of what a block chain may look like, courtesy of Yevgeniy Brikman&lt;/p&gt;
&lt;p&gt;So, why waste time and resources helping other people, or the blockchain? Why not let someone else do all that “confirmation stuff,” while you just mine? Because, you don’t necessarily have a choice. Confirmation of the blockchain is central to mining. It’s part of the actual mining process; however, miners are generally given incentives. For example, after solving a problem (and creating a new hash) they are rewarded with coins.&lt;/p&gt;
&lt;p&gt;Will you be seeing a blockchain-styled ledger in your future? Short answer: oh yes. Blockchain and cryptocurrencies have caused quite a stir over the past years. However, it seems their real importance has yet to be fully realized. The future isn’t just in businesses around the globe sporting happy “Now Accepting Bitcoins” signs, but rather emerging companies (and revolutionary existing ones, too) finding new uses for the cutting edge technology. VC firms and investors are placing their bets on the blockchain because there is untapped potential. Identity management, international contracts, and all sorts of complicated bank transactions can be greatly altered with the public ledger system. The process could (in an ideal world) work seamlessly, crossing boundaries where banks, logistics or a plethora of other obstacles once existed. They could be combined with the Internet of Things to create a more connected and automated world. Future companies may be able to absorb mountains of new data, or even digitize real-world things that are hard to quantify. Unfortunately, many big companies are remaining mum on the studies in the blockchain field for obvious reasons.&lt;/p&gt;
&lt;p&gt;However, it is public knowledge that nine major banks (including JP Morgan and Goldman Sachs) recently joined a partnership to develop blockchain technologies. That’s not to say major companies are getting in on the cryptocurrency game; rather, they realize that the blockchain system, itself, could be a powerful tool for efficiency. With a system as versatile and secure as the blockchain, there may many unexpected innovations in the coming months and years.&lt;/p&gt;
&lt;p&gt;Origin post:&lt;/p&gt;
&lt;p&gt;Blockchain For Dummies: A Beginner’s Guide&lt;/p&gt;
&lt;p&gt;BY: HANNAH AUGUR&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://byteacademy.co/blockchain-for-dummies-a-beginners-guide/&quot;&gt;http://byteacademy.co/blockchain-for-dummies-a-beginners-guide/&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Technology</category></item><item><title>[转]古诗中的”斜“究竟怎么读</title><link>https://blog.wo.ai/archives/1275/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1275/</guid><pubDate>Mon, 16 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;《小学语文教师》2000年第5期上刊有胡孟铭老师的文章《“斜”在〈山行〉中还是读xia好》，主张将“斜”字还其旧读、读古音xia，理由是：“既然课题为古诗，就应读古韵，取古义，才能字义准确，和谐动听”。本刊2000年第10期上又刊有史南先生的文章《“斜”不可读xia》，予以反对，理由是：1985年国家公布的《普通话异读词审音表》“不管那些当时不存在异读的词；如‘斜’字在普通话字（词）典上早已取消了古读音，因此在《审音表》中没有收列它，表明它除了读xie之外，普通话中不存在别的读音”“小学语文教学大纲要求小学生能用普通话朗读课文……再坚持读古音，就违背了国家有关语言文字的政策法规”。  &lt;/p&gt;
&lt;p&gt;问题至此似乎已有了答案。但笔者的看法却与以上两位老师皆有所不同。语言文字的使用，的确如史老师所说，应当以国家的政策法规为依据。而最高的依据则是2000年10月31日九届人大常委会通过并颁布的《中华人民共和国国家通用语言文字法》（以下简称《语言文字法》）。两位老师讨论时，本法尚未公布。但本法自2001年1月1日实施以后，“斜”的读音问题理当有新的答案。&lt;/p&gt;
&lt;p&gt;“斜”字确实存在着旧读音xia，后来演变出xie音，但意义没变（胡老师所谓“‘斜’的读音发生了变化，词义的范围也大大缩小了。倘若将‘斜’还其旧韵xia，（则有）古义‘倾侧或曲折向前延伸’”是无根据的，《康熙字典》《辞源》等古今字书辞书足以为训）。但在古诗教学中究竟应读古音xia，还是今音xie？则应视情形而论。&lt;br&gt;《语言文字法》规定：“本法所称的国家通用语言文字是普通话和规范汉字”，我们在教学中就应当以国家通用语言文字为准，这是毫无疑义的。所以，教材在古诗《山行》中将“斜”注音为xie是正确的，不但如此，“斜”在本篇课文中还是个生字，教师在教学“斜”这个单字时，也应当训为xie。胡老师对这一点提出异议，笔者不能苟同。但在读本诗的时候也读为xie，又确实拗口。本诗的“斜”字出现在首句，似乎还可将就一二，很多唐诗中“斜”字都出现在偶句末尾。我们知道，古诗的平仄、对仗、押韵，以押韵为第一，当时这些诗都是严格押韵的。若诵读这些诗时也读该字为xie，古诗固有的韵律美被破坏，更让人难于接受。如：&lt;br&gt;刘禹锡《乌衣巷》&lt;br&gt;朱雀桥边野草花，乌衣巷口夕阳斜。旧时王谢堂前燕，飞入寻常百姓家。&lt;br&gt;刘方平《夜月》&lt;br&gt;更深月色半人家，北斗阑干南斗斜。今夜偏知春气暖，虫声新透绿窗纱。&lt;br&gt;张泌《寄人》&lt;br&gt;别梦依依到谢家，小廊回合曲阑斜。多情只有春庭月，犹为离人照落花。&lt;br&gt;韩君平《寒食》&lt;br&gt;春城无处不飞花，寒食东风御柳斜。日暮汉宫传蜡烛，轻烟散入五侯家。&lt;br&gt;元稹《菊花》&lt;br&gt;秋丛绕舍似陶家，遍绕篱边日渐斜。不是花中偏爱菊，此花开尽更无花。&lt;br&gt;皎然《寻陆鸿渐不遇》&lt;br&gt;移家虽带郭，野径入桑麻。近种篱边菊，秋来未着花。扣门无犬吠，，欲去问西家。报道山中去，归来每日斜。&lt;br&gt;张若虚《春江花月夜》（节录）&lt;br&gt;昨夜闲潭梦落花，可怜春半不还家。江水流春去欲尽，江潭落月复西斜。&lt;br&gt;张祜《吴兴新堤》（节录）&lt;br&gt;春堤一望思无涯，树势还同水势斜。深映菰蒲三十里，晴分功利几千家。&lt;/p&gt;
&lt;p&gt;史老师在文章最后说：“但为了让学生知道原来是押韵的，在教学时不妨告诉学生‘斜’在古代的韵母就读ia，直到今天，吴方言中还保留它的古读音，读zia”。笔者认为这样还不够，在诵读这些古诗时就应当读‘斜’为xia。因为教学语言文字与欣赏文学艺术是两回事。我们教学古诗时，这两种活动是同时存在的，但也是完全能够而且应该把二者区分开来的。有的老师就是这样教的：领着学生读“远上寒山石径斜xia……”，然后跟学生讲：“‘斜’字课本上注音为xie，就是‘歪斜’的意思，平时就应当读xie，为什么在诗里读xia呢？这是因为这个字在以前的韵母就是ia，这首诗当时是很押韵的。我们读诗时，为了体会诗的韵律美，也为了更上口，就暂时读xia”。可以确信，只要老师教得明白，学生是不会在其他场合、在说话和写作应用中也读为xia的。这样，不但不会影响到语言文字规范化纯洁化的问题，相反学生还会多学到一点知识，知道“斜”字在古音韵母读ia，知道古人作诗是很重押韵的，更能体会到古诗的韵律美，更容易体会到前人的诗歌艺术才华和成就。岂不更好？重要的是，这样做也有法律的依据。《语言文字法》明确规定：“有下列情形的，可以使用方言：……三戏曲影视等艺术形式中需要使用的；四出版、教学、研究中确需使用的”。这里所谓的“方言”，当然也包括像“斜xia”这样的以方言形式存在的古音韵母。&lt;/p&gt;
&lt;p&gt;当然，这种“暂时地读为古音”也是有条件的：必须是“斜”字作韵脚，在韵律的“胁迫”下自然而然地读为xia；如果“斜”虽在古诗中，但不作韵脚，则须还其正读xie音。&lt;/p&gt;
&lt;p&gt;对于这种处理，我们还可从对古字形的处理当中找到类似的旁证。比如胡、史二位老师讨论的“鹿柴”。“鹿柴”中“柴”之所以读zhai，是因为“柴”是“寨”的通假字。但是在现代汉语用字标准中，“寨”是不能再写作“柴”了。课本之所以仍保留原来的字形“柴”，无非也是让学生多一点知识，知道“寨”在古代可通写作“柴”，为以后直接阅读古书提供方便而已。《语言文字法》也有相应的规定：“有下列情形的，可以保留或使用繁体字、异体字：……三书法、篆刻等艺术作品；……五出版、教学、研究中需要使用的”。&lt;br&gt;至于汉匈奴单于“伊稚斜”仍读cha，不读xie，则是因为《审音表》“对大量的人名、地名中的异读字未加审订，这些人名、地名中的异读字照样异读”。&lt;/p&gt;
&lt;p&gt;在欣赏性的唐诗吟诵中，为求合于韵律、为求顺口而需要暂时恢复原来读音的字，还有很多。除“斜”字外，另一个比较常见的是“回”字。古诗中“回”作韵脚时，应读古音huai，而不读hui。如：&lt;br&gt;李白《望天门山》&lt;br&gt;天门中断楚江开，碧水东流至此回。两岸青山相对出，孤帆一片日边来。&lt;br&gt;杜甫《登高》（节录）&lt;br&gt;风急天高猿啸哀，渚清沙白鸟飞回。无边落木潇潇下，不尽长江滚滚来。&lt;br&gt;刘禹锡《戏赠看花诸君子》&lt;br&gt;紫陌红尘拂面来，无人不道看花回。玄都观里桃千树，尽是刘郎去后栽。&lt;br&gt;刘禹锡《石头城》&lt;br&gt;山围故国周遭在，潮打空城寂寞回。淮水东边旧时月，夜深还过女墙来。&lt;br&gt;杜牧《早雁》&lt;br&gt;金河秋半虏弦开，云外惊飞四散哀。仙掌月明孤影过，长门灯暗数声来。须知胡骑纷纷在，岂逐春风一一回。莫厌潇湘少人处，水多菰米岸莓苔。&lt;/p&gt;
&lt;p&gt;“回”在唐诗韵脚中读huai，当无疑义。不过这又牵涉到另一个“衰”的读音。《长歌行》有“阳春布德泽，万物生光辉。长恐秋节至，焜黄华叶衰。百川东到海，何时复西归。”；贺知章《回乡偶书》有“少小离家老大回，乡音无改鬓毛衰。儿童相见不相识，笑问客从何处来。”－－“衰”当读何音？历来说法不一：或有前首训为shuai的，或有后首训为cui的。笔者的意见则是：“衰”在前诗中训为cui，或有后诗中训为shuai。因为：“衰”字，作“形容萎缩、体量递减”时读cui，如“等衰”；作“气色衰老”时读shuai，如“衰草”。因此，“焜黄华叶衰”中读cui，是指鲜亮的花和叶子萎缩脱落、是形体之变；而“鬓毛衰”中读shuai，是两鬓斑白之意，是气色之变，不是形体之变，不是鬓毛萎缩脱落之意（因为人的鬓毛是头发中最不易脱落的）。诗中的韵脚“辉、归”“回、来”等字可作旁证。&lt;/p&gt;
</content:encoded><category>Poem &amp; Reading</category><category>Uncategorized</category></item><item><title>CentOS 7 下 firewalld的基本操作</title><link>https://blog.wo.ai/archives/1257/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1257/</guid><pubDate>Thu, 07 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这两天服务器升级，升级后发现有些服务不能用了。后来研究了一下，是因为防火墙的原因。之前调试，为了偷懒，把防火墙关了，就没启动过，现在想想裸奔了几个月，心真是大。&lt;/p&gt;
&lt;p&gt;对外增加服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;firewall-cmd --zone=public --add-port=80/tcp --permanent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对内设置安全区&lt;/p&gt;
&lt;p&gt;firewall-cmd –permanent –zone=internal –change-interface=enp03s&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;firewall-cmd --zone=public --add-port=3306/tcp --permanent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，再重新加载firwalld策略&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;firewall-cmd --reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下转载:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/linux-centos-7-fang-huo-qiang-zhi-ju-you-ming-ling-xing-de-firewalldde-ji-ben-cao-zuo/&quot;&gt;http://qianxunclub.com/linux-centos-7-fang-huo-qiang-zhi-ju-you-ming-ling-xing-de-firewalldde-ji-ben-cao-zuo/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;启动FirewallD服务：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1 2&lt;/th&gt;
&lt;th&gt;&lt;code&gt;systemctl ``enable&lt;/code&gt; &lt;code&gt;firewalld.service ``#设置开机启动&lt;/code&gt; &lt;code&gt;systemctl start firewalld.service ``#开启服务&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;查看防火墙状态：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;systemctl status firewalld&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;1. 区域管理&lt;/p&gt;
&lt;p&gt;1.1. 网络区域简介&lt;/p&gt;
&lt;p&gt;通过将网络划分成不同的区域，制定出不同区域之间的访问控制策略来控制不同程序区域间传送的数据流。例如，互联网是不可信任的区域，而内部网络是高度信任的区域。网络安全模型可以在安装，初次启动和首次建立网络连接时选择初始化。该模型描述了主机所连接的整个网络环境的可信级别，并定义了新连接的处理方式。有如下几种不同的初始化区域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阻塞区域（block）：任何传入的网络数据包都将被阻止。&lt;/li&gt;
&lt;li&gt;工作区域（work）：相信网络上的其他计算机，不会损害你的计算机。&lt;/li&gt;
&lt;li&gt;家庭区域（home）：相信网络上的其他计算机，不会损害你的计算机。&lt;/li&gt;
&lt;li&gt;公共区域（public）：不相信网络上的任何计算机，只有选择接受传入的网络连接。&lt;/li&gt;
&lt;li&gt;隔离区域（DMZ）：隔离区域也称为非军事区域，内外网络之间增加的一层网络，起到缓冲作用。对于隔离区域，只有选择接受传入的网络连接。&lt;/li&gt;
&lt;li&gt;信任区域（trusted）：所有的网络连接都可以接受。&lt;/li&gt;
&lt;li&gt;丢弃区域（drop）：任何传入的网络连接都被拒绝。&lt;/li&gt;
&lt;li&gt;内部区域（internal）：信任网络上的其他计算机，不会损害你的计算机。只有选择接受传入的网络连接。&lt;/li&gt;
&lt;li&gt;外部区域（external）：不相信网络上的其他计算机，不会损害你的计算机。只有选择接受传入的网络连接。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注：FirewallD的默认区域是public。&lt;/p&gt;
&lt;p&gt;1.2. 显示支持的区域列表&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --get-zones&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;1.3. 设置为家庭区域&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --``set``-default-zone=home&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;1.4. 查看当前区域&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --get-active-zones&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;1.5. 设置当前区域的接口&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --get-zone-of-interface=enp03s&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;1.6. 显示所有公共区域（public）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --zone=public --list-all&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;1.7. 临时修改网络接口（enp0s3）为内部区域（internal）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --zone=internal --change-interface=enp03s&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;1.8. 永久修改网络接口enp03s为内部区域（internal）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --permanent --zone=internal --change-interface=enp03s&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;2. 服务管理&lt;/p&gt;
&lt;p&gt;2.1. 显示服务列表&lt;/p&gt;
&lt;p&gt;Amanda, FTP, Samba和TFTP等最重要的服务已经被FirewallD提供相应的服务，可以使用如下命令查看：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --get-services&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;2.2. 允许SSH服务通过&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --``enable&lt;/code&gt; &lt;code&gt;service=``ssh&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;2.3. 禁止SSH服务通过&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --disable service=``ssh&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;2.4. 打开TCP的8080端口&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --``enable&lt;/code&gt; &lt;code&gt;ports=8080``/tcp&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;2.5. 临时允许Samba服务通过600秒&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --``enable&lt;/code&gt; &lt;code&gt;service=samba --timeout=600&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;2.6. 显示当前服务&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --list-services&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;2.7. 添加HTTP服务到内部区域（internal）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1 2&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --permanent --zone=internal --add-service=http&lt;/code&gt; &lt;code&gt;firewall-cmd --reload ``#在不改变状态的条件下重新加载防火墙&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;3. 端口管理&lt;/p&gt;
&lt;p&gt;3.1. 打开端口&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1 2&lt;/th&gt;
&lt;th&gt;&lt;code&gt;#打开443/TCP端口&lt;/code&gt; &lt;code&gt;firewall-cmd --add-port=443``/tcp&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1 2 3 4 5&lt;/th&gt;
&lt;th&gt;&lt;code&gt;#永久打开3690/TCP端口&lt;/code&gt; &lt;code&gt;firewall-cmd --permanent --add-port=3690``/tcp&lt;/code&gt; &lt;code&gt;#永久打开端口好像需要reload一下，临时打开好像不用，如果用了reload临时打开的端口就失效了&lt;/code&gt; &lt;code&gt;#其它服务也可能是这样的，这个没有测试&lt;/code&gt; &lt;code&gt;firewall-cmd --reload&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1 2&lt;/th&gt;
&lt;th&gt;&lt;code&gt;#查看防火墙，添加的端口也可以看到&lt;/code&gt; &lt;code&gt;firewall-cmd --list-all&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;4. 直接模式&lt;/p&gt;
&lt;p&gt;FirewallD包括一种直接模式，使用它可以完成一些工作，例如打开TCP协议的9999端口&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1 2&lt;/th&gt;
&lt;th&gt;&lt;code&gt;firewall-cmd --direct -add-rule ipv4 filter INPUT 0 -p tcp --dport 9000 -j ACCEPT&lt;/code&gt; &lt;code&gt;firewall-cmd --reload&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;5. 关闭服务的方法&lt;/p&gt;
&lt;p&gt;你也可以关闭目前还不熟悉的FirewallD防火墙，而使用iptables，命令如下：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://qianxunclub.com/blog?id=67e0d709-ba9f-4ce2-a2f7-128c2cf5cf8b&quot;&gt;?&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1 2 3 4 5&lt;/th&gt;
&lt;th&gt;&lt;code&gt;systemctl stop firewalld&lt;/code&gt; &lt;code&gt;systemctl disable firewalld&lt;/code&gt; &lt;code&gt;yum ``install&lt;/code&gt; &lt;code&gt;iptables-services&lt;/code&gt; &lt;code&gt;systemctl start iptables&lt;/code&gt; &lt;code&gt;systemctl ``enable&lt;/code&gt; &lt;code&gt;iptables&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
</content:encoded><category>Technology</category></item><item><title>擦干眼泪，继续工作</title><link>https://blog.wo.ai/archives/1241/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1241/</guid><pubDate>Tue, 05 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;昨天注定是个悲伤的日子。&lt;/p&gt;
&lt;p&gt;网上流传不会炒币的码农不是一个好码农，我也加入了，昨天中央发文严管ICO和虚拟货币，所有虚拟货币在半小时内价格一落千丈，跌掉20%以上的比比皆是，我相信是哀鸿遍野了。韭菜党估计已经早就开始割了。今早还有同学发来贺电，说老外昨晚好不容易把价格救上来一些，昨晚哭了一晚的韭菜党早上又开始割了，价格继续下跌，慢慢接着哭吧。&lt;/p&gt;
&lt;p&gt;还有一件事就是昨天发现博客不能访问了，到邮箱里面看到催缴费的邮件，续费完发现还是找不到我的服务器，提了ticket后，工作人员说服务器已经删除，无法再恢复。虽然一个人絮絮叨叨的自说自话，也没多少人看，这可是俺写了10多年的博客啊。翻箱倒柜，总算找到一个3月份的数据备份，最后几篇也找不到了。前不久博客被鬼子黑过，插入了木马，google的所有的链接都被变成了他们的广告，正好备份了网站文件。把这两个加一起基本恢复到了3月份的状态，也算是不幸中的万幸。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2017/09/yukinoka.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://s3.wo.ai/upload/mp3/977922946.mp3&quot;&gt;http://s3.wo.ai/upload/mp3/977922946.mp3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;放上中岛美嘉的雪の華、经历了多少不甘之后还能站出来在舞台上用自己沙哑的声音重新演绎自己的前半生。不幸又能怎么样，擦干眼泪，大不了重新再来。&lt;/p&gt;
&lt;p&gt;雪の華-中島美嘉&lt;/p&gt;
&lt;p&gt;のびた人陰(かげ)を舗道にならべ&lt;br&gt;不断延伸的影子 在红砖道上并列&lt;br&gt;夕闇のなかを君と歩いてる&lt;br&gt;在深夜里与你并肩走着&lt;br&gt;手をつないでいつまでもずっと&lt;br&gt;永远紧紧牵着手&lt;br&gt;そばにいれたなら&lt;br&gt;只要能在你身旁&lt;br&gt;泣けちゃうくらい&lt;br&gt;我就感动得快要哭了&lt;br&gt;風が冷たくなって&lt;br&gt;风儿变得寒冷&lt;br&gt;冬の匂いがした&lt;br&gt;有了冬天的味道&lt;br&gt;そろそろこの街に&lt;br&gt;这条街也即将到了&lt;br&gt;君と近付ける季節がくる&lt;br&gt;能和你接近的季节来临&lt;br&gt;今年、最初の雪の華を&lt;br&gt;今年 最初的雪花&lt;br&gt;ふたり寄り添って&lt;br&gt;向我俩靠近&lt;br&gt;眺めているこの瞬間(とき)に&lt;br&gt;在眺望着的这个瞬间里（时间）&lt;br&gt;幸せがあふれだす&lt;br&gt;充满了幸福的喜悦&lt;br&gt;甘えとか弱さじゃない&lt;br&gt;没有撒娇和脆弱&lt;br&gt;ただ、君を愛してる&lt;br&gt;只是 爱你&lt;br&gt;心からそう思った&lt;br&gt;打从心底爱你&lt;br&gt;君がいるとどんなことでも&lt;br&gt;只要有你在 无论发生什么&lt;br&gt;乗りきれるような気持ちになってる&lt;br&gt;都会有可以克服的心情&lt;br&gt;こんな日々がいつまでもきっと&lt;br&gt;我祈祷着 这样的日子&lt;br&gt;続いてくことを祈っているよ&lt;br&gt;一定会直到永远&lt;br&gt;風が窓を揺らした&lt;br&gt;风儿吹的窗摇&lt;br&gt;夜は揺り起こして&lt;br&gt;把夜晚摇醒&lt;br&gt;どんな悲しいことも&lt;br&gt;无论多么悲伤的事情&lt;br&gt;僕が笑顔へと変えてあげる&lt;br&gt;我用笑容为你改变&lt;br&gt;舞い落ちてきた雪の華が&lt;br&gt;雪花飞舞飘落&lt;br&gt;窓の外ずっと&lt;br&gt;在窗外一定&lt;br&gt;降りやむことを知らずに&lt;br&gt;不知何时雪已停&lt;br&gt;僕らの街を染める&lt;br&gt;把我们的街道染色&lt;br&gt;誰かのために何かを&lt;br&gt;为某人想做&lt;br&gt;したいと思えるのが&lt;br&gt;些什么事&lt;br&gt;愛ということを知った&lt;br&gt;原来 这就是爱&lt;br&gt;もし、君を失ったとしたなら&lt;br&gt;如果 失去了你&lt;br&gt;星になって君を照らすだろう&lt;br&gt;我会变成星星照亮你&lt;br&gt;笑顔も涙に濡れてる夜も&lt;br&gt;微笑或被泪水沾湿的夜晚&lt;br&gt;いつもいつでもそばにいるよ&lt;br&gt;我会永远在你身旁&lt;br&gt;今年、最初の雪の華を&lt;br&gt;今年 最初的雪花&lt;br&gt;ふたり寄り添って&lt;br&gt;向我俩靠近&lt;br&gt;眺めているこの瞬間(とき)に&lt;br&gt;在眺望着的这个瞬间里&lt;br&gt;幸せがあふれだす&lt;br&gt;充满了幸福的喜悦&lt;br&gt;甘えとか弱さじゃない&lt;br&gt;没有撒娇和脆弱&lt;br&gt;ただ、君とずっと&lt;br&gt;只是 想永远地&lt;br&gt;このまま一緒にいたい&lt;br&gt;就这么一直在一起&lt;br&gt;素直にそう思える&lt;br&gt;我真心地这么想&lt;br&gt;この街に降り積もってく&lt;br&gt;在这条街上堆积的&lt;br&gt;真っ白な雪の華&lt;br&gt;纯白雪花&lt;br&gt;ふたりの胸にそっと想い出を描くよ&lt;br&gt;悄悄地在我俩胸口画上回忆&lt;br&gt;これからも君とずっと…&lt;br&gt;从今而后也要永远和你在一起&lt;/p&gt;
</content:encoded><category>Life</category><category>Music &amp; Movie</category></item><item><title>候鸟模式</title><link>https://blog.wo.ai/archives/1247/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1247/</guid><pubDate>Mon, 08 May 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在一个字母公司一待就是8年，8年没有感情是不可能的，但是上升无望，如果继续无忧无虑的下去，不知道前途在何方。在去年年底终于下定决心离开，来到另外一家字母公司。&lt;/p&gt;
&lt;p&gt;也许本身对这家字母公司也是短期跳板的期望，没有过多融入，在三观还没有完全毁尽之前快速离开，来到现在这家研究院。&lt;/p&gt;
&lt;p&gt;这项技术目前处于风口，是否能够让我成功转型尚属未知。未知的自己，未知的明天。也正因为如此，抛妻弃子，开启了周末候鸟模式，铁老大该发笑了。&lt;/p&gt;
&lt;p&gt;在公寓里还能听取外边还能蛙声一片，但愿今年能够稻花香里说丰年吧。&lt;/p&gt;
</content:encoded><category>Life</category></item><item><title>Auto start a java Swing GUI program when raspberry boots</title><link>https://blog.wo.ai/archives/1237/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1237/</guid><pubDate>Tue, 07 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I planed make an self-desgin photo or movie player base on Raspberry. Also I can use it as photo frame. If I need improve the performance of the PI, I need write it with Python, I think.&lt;/p&gt;
&lt;p&gt;Part 1 – Build the Foundation&lt;/p&gt;
&lt;p&gt;In this part, we will focus on preparing Raspbian Lite.&lt;/p&gt;
&lt;p&gt;1. Download the latest Raspbian Lite image.&lt;br&gt;2. Format the SD / microSD card with Raspbian Lite (Plenty of guides out there on how to do this. For macOS, Linux, and Windows users, Etcher is an easy to use application that can help you do this.)&lt;br&gt;3. Insert the SD / microSD card into the Pi.&lt;br&gt;4. Connect the Pi to the Internet using an Ethernet cable. If you want to use Wi-Fi instead, you will have to read on how to configure your wireless receiver using the command line after your Pi has finished booting.&lt;br&gt;5. Connect your TV / Monitor and keyboard. (Mouse is optional at this time.) Turn on the Pi. The Pi should boot up successfully and a prompt to log in will appear.&lt;br&gt;6. Log into Raspbian. The username is pi and the password is raspberry.&lt;/p&gt;
&lt;p&gt;7. We will install Xorg. To do this type in:&lt;/p&gt;
&lt;p&gt;sudo apt-get install –no-install-recommends xserver-xorg&lt;/p&gt;
&lt;p&gt;sudo apt-get install –no-install-recommends xinit&lt;/p&gt;
&lt;p&gt;now, you can write you java program now. For example, I wrote a test program with a button in the center of screen. once I click the button, the window will change to the full size of the screen.&lt;/p&gt;
&lt;p&gt;import java.awt.FlowLayout;&lt;br&gt;import java.awt.event.ActionEvent;&lt;br&gt;import java.awt.event.ActionListener;&lt;/p&gt;
&lt;p&gt;import javax.swing.JButton;&lt;br&gt;import javax.swing.JFrame;&lt;/p&gt;
&lt;p&gt;public class FullScreenTest {&lt;br&gt;public static void main(String[] args) {&lt;br&gt;final JFrame f = new JFrame(“FullScreenTest”);&lt;br&gt;final JButton btn = new JButton(“FullScreen”);&lt;br&gt;btn.addActionListener(new ActionListener() {&lt;br&gt;public void actionPerformed(ActionEvent e) {&lt;br&gt;if (btn.getText().equals(“FullScreen”)) {&lt;br&gt;f.dispose();&lt;br&gt;f.setUndecorated(true);&lt;br&gt;f.getGraphicsConfiguration().getDevice().setFullScreenWindow(f);&lt;br&gt;f.setVisible(true);&lt;br&gt;btn.setText(“NormalMode”);&lt;br&gt;} else {&lt;br&gt;f.dispose();&lt;br&gt;f.setUndecorated(false);&lt;br&gt;f.getGraphicsConfiguration().getDevice().setFullScreenWindow(null);&lt;br&gt;f.setVisible(true);&lt;br&gt;btn.setText(“FullScreen”);&lt;br&gt;}&lt;br&gt;}&lt;br&gt;});&lt;/p&gt;
&lt;p&gt;f.getContentPane().setLayout(new FlowLayout());&lt;br&gt;f.getContentPane().add(btn);&lt;br&gt;f.pack();&lt;br&gt;f.setLocationRelativeTo(null);&lt;br&gt;f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);&lt;br&gt;f.setVisible(true);&lt;br&gt;}&lt;br&gt;}&lt;/p&gt;
&lt;p&gt;Pack the file into a jar file like GUI.jar.&lt;/p&gt;
&lt;p&gt;In order to have a command or program run when the Pi boots, you can add commands to the &lt;code&gt;rc.local&lt;/code&gt; file. This is especially useful if you want to be able to plug your Pi in to power headless, and have it run a program without configuration or a manual start.&lt;/p&gt;
&lt;h2&gt;EDITING RC.LOCAL&lt;/h2&gt;
&lt;p&gt;On your Pi, edit the file &lt;code&gt;/etc/rc.local&lt;/code&gt; using the editor of your choice. You must edit with root, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nano /etc/rc.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add commands below the comment, but leave the line &lt;code&gt;exit 0&lt;/code&gt; at the end, then save the file and exit.&lt;/p&gt;
&lt;p&gt;sudo xinit /usr/local/jdk1.8.0_77/bin/java -jar /usr/local/mypi/GUI.jar — :1 &amp;amp;&lt;/p&gt;
&lt;p&gt;Reboot your PI, then done!&lt;/p&gt;
&lt;h3&gt;&lt;/h3&gt;
</content:encoded><category>Raspberry Pi</category><category>Technology</category></item><item><title>防范 CSRF 跨站请求伪造</title><link>https://blog.wo.ai/archives/1235/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1235/</guid><pubDate>Wed, 01 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CSRF（Cross-site request forgery，中文为&lt;strong&gt;跨站请求伪造&lt;/strong&gt; ）是一种利用网站可信用户的权限去执行未授权的命令的一种恶意攻击。通过&lt;strong&gt;伪装可信用户的请求来利用信任该用户的网站&lt;/strong&gt; ，这种攻击方式虽然不是很流行，但是却难以防范，其危害也不比其他安全漏洞小。&lt;/p&gt;
&lt;p&gt;本文将简要介绍CSRF产生的原因以及利用方式，然后对如何避免这种攻击方式提供一些可供参考的方案，希望广大程序猿们都能够对这种攻击方式有所了解，避免自己开发的应用被别人利用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CSRF也称作&lt;strong&gt;one-click attack&lt;/strong&gt; 或者&lt;strong&gt;session riding&lt;/strong&gt; ，其简写有时候也会使用&lt;strong&gt;XSRF&lt;/strong&gt; 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;什么是CSRF？&lt;/h2&gt;
&lt;p&gt;简单点说，CSRF攻击就是 &lt;strong&gt;攻击者利用受害者的身份，以受害者的名义发送恶意请求&lt;/strong&gt; 。与XSS（Cross-site scripting，跨站脚本攻击）不同的是，XSS的目的是获取用户的身份信息，攻击者窃取到的是用户的身份（session/cookie），而CSRF则是利用用户当前的身份去做一些未经过授权的操作。&lt;/p&gt;
&lt;p&gt;CSRF攻击最早在2001年被发现，由于它的请求是从用户的IP地址发起的，因此在服务器上的web日志中可能无法检测到是否受到了CSRF攻击，正是由于它的这种隐蔽性，很长时间以来都没有被公开的报告出来，直到2007年才真正的被人们所重视。&lt;/p&gt;
&lt;h2&gt;CSRF有哪些危害&lt;/h2&gt;
&lt;p&gt;CSRF可以盗用受害者的身份，完成受害者在web浏览器有权限进行的任何操作，想想吧，能做的事情太多了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;以你的名义发送诈骗邮件，消息&lt;/li&gt;
&lt;li&gt;用你的账号购买商品&lt;/li&gt;
&lt;li&gt;用你的名义完成虚拟货币转账&lt;/li&gt;
&lt;li&gt;泄露个人隐私&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;产生原理以及利用方式&lt;/h2&gt;
&lt;p&gt;要完成一个CSRF攻击，必须具备以下几个条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;受害者已经登录到了目标网站（你的网站）并且没有退出&lt;/li&gt;
&lt;li&gt;受害者有意或者无意的访问了攻击者发布的页面或者链接地址&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://segmentfault.com/img/remote/1460000008505620?w=1190&amp;h=832&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;（图片来自网络，出处不明，百度来的😂）&lt;/p&gt;
&lt;p&gt;整个步骤大致是这个样子的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户小明在你的网站A上面登录了，A返回了一个session ID（使用cookie存储）&lt;/li&gt;
&lt;li&gt;小明的浏览器保持着在A网站的登录状态，事实上几乎所有的网站都是这样做的，一般至少是用户关闭浏览器之前用户的会话是不会结束的&lt;/li&gt;
&lt;li&gt;攻击者小强给小明发送了一个链接地址，小明打开了这个地址，查看了网页的内容&lt;/li&gt;
&lt;li&gt;小明在打开这个地址的时候，这个页面已经自动的对网站A发送了一个请求，这时候因为A网站没有退出，因此只要请求的地址是A的就会携带A的cookie信息，也就是使用A与小明之间的会话&lt;/li&gt;
&lt;li&gt;这时候A网站肯定是不知道这个请求其实是小强伪造的网页上发送的，而是误以为小明就是要这样操作，这样小强就可以随意的更改小明在A上的信息，以小明的身份在A网站上进行操作&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;利用方式&lt;/h3&gt;
&lt;p&gt;利用CSRF攻击，主要包含两种方式，一种是基于GET请求方式的利用，另一种是基于POST请求方式的利用。&lt;/p&gt;
&lt;h4&gt;GET请求利用&lt;/h4&gt;
&lt;p&gt;使用GET请求方式的利用是最简单的一种利用方式，其隐患的来源主要是由于在开发系统的时候没有按照HTTP动词的正确使用方式来使用造成的。&lt;strong&gt;对于GET请求来说，它所发起的请求应该是只读的，不允许对网站的任何内容进行修改&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;但是事实上并不是如此，很多网站在开发的时候，研发人员错误的认为GET/POST的使用区别仅仅是在于发送请求的数据是在Body中还是在请求地址中，以及请求内容的大小不同。对于一些危险的操作比如删除文章，用户授权等允许使用GET方式发送请求，在请求参数中加上文章或者用户的ID，这样就造成了只要请求地址被调用，数据就会产生修改。&lt;/p&gt;
&lt;p&gt;现在假设攻击者（用户ID=121）想将自己的身份添加为网站的管理员，他在网站A上面发了一个帖子，里面包含一张图片，其地址为&lt;code&gt;http://a.com/user/grant_super_user/121&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img src=&amp;quot;http://a.com/user/grant_super_user/121&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设想管理员看到这个帖子的时候，这个图片肯定会自动加载显示的。于是在管理员不知情的情况下，一个赋予用户管理员权限的操作已经悄悄的以他的身份执行了。这时候攻击者121就获取到了网站的管理员权限。&lt;/p&gt;
&lt;h4&gt;POST请求利用&lt;/h4&gt;
&lt;p&gt;相对于GET方式的利用，POST方式的利用更加复杂一些，难度也大了一些。攻击者需要伪造一个能够自动提交的表单来发送POST请求。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要想办法实现用户访问的时候自动提交表单就可以了。&lt;/p&gt;
&lt;h2&gt;如何防范&lt;/h2&gt;
&lt;h3&gt;防范原理&lt;/h3&gt;
&lt;p&gt;防范CSRF攻击，其实本质就是要求网站&lt;strong&gt;能够识别出哪些请求是非正常用户主动发起的&lt;/strong&gt; 。这就要求我们&lt;strong&gt;在请求中嵌入一些额外的授权数据，让网站服务器能够区分出这些未授权的请求&lt;/strong&gt; ，比如说在请求参数中添加一个字段，这个字段的值从登录用户的Cookie或者页面中获取的（这个字段的值必须对每个用户来说是随机的，不能有规律可循）。攻击者伪造请求的时候是无法获取页面中与登录用户有关的一个随机值或者用户当前cookie中的内容的，因此就可以避免这种攻击。&lt;/p&gt;
&lt;h3&gt;防范技术&lt;/h3&gt;
&lt;h4&gt;Synchronizer token pattern&lt;/h4&gt;
&lt;p&gt;令牌同步模式（Synchronizer token pattern，简称STP）是在用户请求的页面中的所有表单中嵌入一个token，在服务端验证这个token的技术。token可以是任意的内容，但是一定要保证无法被攻击者猜测到或者查询到。攻击者在请求中无法使用正确的token，因此可以判断出未授权的请求。&lt;/p&gt;
&lt;h4&gt;Cookie-to-Header Token&lt;/h4&gt;
&lt;p&gt;对于使用Js作为主要交互技术的网站，将CSRF的token写入到cookie中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-Cookie: CSRF-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后使用javascript读取token的值，在发送http请求的时候将其作为请求的header&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;X-CSRF-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后服务器验证请求头中的token是否合法。&lt;/p&gt;
&lt;h4&gt;验证码&lt;/h4&gt;
&lt;p&gt;使用验证码可以杜绝CSRF攻击，但是这种方式要求每个请求都输入一个验证码，显然没有哪个网站愿意使用这种粗暴的方式，用户体验太差，用户会疯掉的。&lt;/p&gt;
&lt;h3&gt;简单实现STP&lt;/h3&gt;
&lt;p&gt;首先在index.php中，创建一个表单，在表单中，我们将session中存储的token放入到隐藏域，这样，表单提交的时候token会随表单一起提交&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
$token = sha1(uniqid(rand(), true));
$_SESSION[&amp;#39;token&amp;#39;] = $token;
?&amp;gt;
&amp;lt;form action=&amp;quot;buy.php&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
    &amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;token&amp;quot; value=&amp;quot;&amp;lt;?=$token; ?&amp;gt;&amp;quot; /&amp;gt;
    ... 表单内容
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在服务端校验请求参数的&lt;code&gt;buy.php&lt;/code&gt;中，对表单提交过来的token与session中存储的token进行比对，如果一致说明token是有效的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;code&amp;gt;
  &amp;lt;?php if ($_POST[&amp;#39;token&amp;#39;] != $_SESSION[&amp;#39;token&amp;#39;]) {
    // TOKEN无效
    throw new \Exception(&amp;#39;Token无效，请求为伪造请求&amp;#39;);
}
// TOKEN有效，表单内容处理
&amp;lt;/code?&amp;gt;
&amp;lt;/code&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于攻击者来说，在伪造请求的时候是无法获取到用户页面中的这个&lt;code&gt;token&lt;/code&gt;值的，因此就可以识别出其创建的伪造请求。&lt;/p&gt;
&lt;h3&gt;解析Laravel框架中的VerifyCSRFToken中间件&lt;/h3&gt;
&lt;p&gt;在Laravel框架中，使用了&lt;code&gt;VerifyCSRFToken&lt;/code&gt;这个中间件来防范CSRF攻击。&lt;/p&gt;
&lt;p&gt;在页面的表单中使用&lt;code&gt;{{ CSRF_field() }}&lt;/code&gt;来生成token，该函数会在表单中添加一个名为&lt;code&gt;_token&lt;/code&gt;的隐藏域，该隐藏域的值为Laravel生成的token，Laravel使用随机生成的40个字符作为防范CSRF攻击的token。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$this-&amp;gt;put(&amp;#39;_token&amp;#39;, Str::random(40));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果请求是ajax异步请求，可以在&lt;code&gt;meta&lt;/code&gt;标签中添加token&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;meta name=&amp;quot;CSRF-token&amp;quot; content=&amp;quot;{{ CSRF_token() }}&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;code&gt;jquery&lt;/code&gt;作为前端的框架时候，可以通过以下配置将该值添加到所有的异步请求头中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$.ajaxSetup({
    headers: {
        &amp;#39;X-CSRF-TOKEN&amp;#39;: $(&amp;#39;meta[name=&amp;quot;CSRF-token&amp;quot;]&amp;#39;).attr(&amp;#39;content&amp;#39;)
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在启用session的时候，Laravel会生成一个名为&lt;code&gt;_token&lt;/code&gt;的值存储到session中。而使用前面两种方式在页面中加入的token就是使用的这一个值。在用户请求到来时，&lt;code&gt;VerifyCSRFToken&lt;/code&gt;中间件会对符合条件的请求进行CSRF检查&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (
  $this-&amp;gt;isReading($request) ||
  $this-&amp;gt;runningUnitTests() ||
  $this-&amp;gt;shouldPassThrough($request) ||
  $this-&amp;gt;tokensMatch($request)
) {
  return $this-&amp;gt;addCookieToResponse($request, $next($request));
}

throw new TokenMismatchException;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;if&lt;/code&gt;语句中有四个条件，只要任何一个条件结果为&lt;code&gt;true&lt;/code&gt;则任何该请求是合法的，否则就会抛出&lt;code&gt;TokenMismatchException&lt;/code&gt;异常，告诉用户请求不合法，存在CSRF攻击。&lt;/p&gt;
&lt;p&gt;第一个条件&lt;code&gt;$this-&amp;gt;isReading($request)&lt;/code&gt;用来检查请求是否会对数据产生修改&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected function isReading($request)
{
    return in_array($request-&amp;gt;method(), [&amp;#39;HEAD&amp;#39;, &amp;#39;GET&amp;#39;, &amp;#39;OPTIONS&amp;#39;]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里判断了请求方式，如果是&lt;code&gt;HEAD&lt;/code&gt;，&lt;code&gt;GET&lt;/code&gt;，&lt;code&gt;OPTIONS&lt;/code&gt;这三种请求方式则直接放行。你可能会感到疑惑，为什么GET请求也要放行呢？这是因为Laravel认为这三个请求都是请求查询数据的，&lt;strong&gt;如果一个请求是使用GET方式，那无论请求多少次，无论请求参数如何，都不应该最数据做任何修改&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;第二个条件顾名思义是对单元测试进行放行，第三个是为开发者提供了一个可以对某些请求添加例外的功能，最后一个&lt;code&gt;$this-&amp;gt;tokensMatch($request)&lt;/code&gt;则是真正起作用的一个，它是Laravel防范CSRF攻击的关键&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$sessionToken = $request-&amp;gt;session()-&amp;gt;token();
$token = $request-&amp;gt;input(&amp;#39;_token&amp;#39;) ?: $request-&amp;gt;header(&amp;#39;X-CSRF-TOKEN&amp;#39;);

if (! $token &amp;amp;&amp;amp; $header = $request-&amp;gt;header(&amp;#39;X-XSRF-TOKEN&amp;#39;)) {
  $token = $this-&amp;gt;encrypter-&amp;gt;decrypt($header);
}

if (! is_string($sessionToken) || ! is_string($token)) {
  return false;
}

return hash_equals($sessionToken, $token);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Laravel会从请求中读取&lt;code&gt;_token&lt;/code&gt;参数的的值，这个值就是在前面表单中添加的&lt;code&gt;CSRF_field()&lt;/code&gt;函数生成的。如果请求是异步的，那么会读取&lt;code&gt;X-CSRF-TOKEN&lt;/code&gt;请求头，从请求头中读取token的值。&lt;/p&gt;
&lt;p&gt;最后使用&lt;code&gt;hash_equals&lt;/code&gt;函数验证请求参数中提供的token值和session中存储的token值是否一致，如果一致则说明请求是合法的。&lt;/p&gt;
&lt;p&gt;你可能注意到，这个检查过程中也会读取一个名为&lt;code&gt;X-XSRF-TOKEN&lt;/code&gt;的请求头，这个值是为了提供对一些javascript框架的支持（比如Angular），它们会自动的对异步请求中添加该请求头，而该值是从Cookie中的&lt;code&gt;XSRF-TOKEN&lt;/code&gt;中读取的，因此在每个请求结束的时候，Laravel会发送给客户端一个名为&lt;code&gt;XSRF-TOKEN&lt;/code&gt;的Cookie值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$response-&amp;gt;headers-&amp;gt;setCookie(
    new Cookie(
        &amp;#39;XSRF-TOKEN&amp;#39;, $request-&amp;gt;session()-&amp;gt;token(), time() + 60 * $config[&amp;#39;lifetime&amp;#39;],
        $config[&amp;#39;path&amp;#39;], $config[&amp;#39;domain&amp;#39;], $config[&amp;#39;secure&amp;#39;], false
    )
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;写在最后&lt;/h2&gt;
&lt;p&gt;本文只是对CSRF做了一个简单的介绍，主要是侧重于CSRF是什么以及如何应对CSRF攻击。有一个事实是我们无法回避的：&lt;strong&gt;没有绝对安全的系统&lt;/strong&gt; ，你有一千种防御对策，攻击者就有一千零一种攻击方式，但不管如何，我们都要尽最大的努力去将攻击者拦截在门外。如果希望深入了解如何发起一个CSRF攻击，可以参考一下这篇文章 从零开始学CSRF。&lt;/p&gt;
&lt;p&gt;作为一名web方向的研发人员，无论你是从事业务逻辑开发还是做单纯的技术研究，了解一些安全方面的知识都是很有必要的，多关注一些安全方向的动态，了解常见的攻击方式以及应对策略，必将在你成长为一名大牛的路上为你“推波助澜”。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cross-site_request_forgery&quot;&gt;wikipedia: Cross-site request forgery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://shiflett.org/articles/cross-site-request-forgeries&quot;&gt;Cross-Site Request Forgeries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html&quot;&gt;浅谈CSRF攻击方式&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.jobbole.com/110398/&quot;&gt;防范 CSRF 跨站请求伪造&lt;/a&gt;，首发于&lt;a href=&quot;http://blog.jobbole.com&quot;&gt;文章 – 伯乐在线&lt;/a&gt;。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>想写无Bug的安全代码？看防御性编程的艺术</title><link>https://blog.wo.ai/archives/1234/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1234/</guid><pubDate>Tue, 21 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;为什么开发者不编写安全的代码？我们在这并不是要再一次讨论「整洁代码」。我们要从纯粹的实用观点出发，讨论软件的安全性和保密性。是的，因为不安全的软件不仅无用，而且还可怕。我们来看看什么是不安全的软件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1996年6月4日，欧洲航天局的 Ariane 5 Flight 501 在起飞后 40 秒被引爆。因为导航软件里的一个 bug，这个价值 10 亿美金的运载火箭不得不自毁。&lt;br&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/d418b3a7c8_7cc829d3gw1f6r1wqy5zdj20b408cwem.jpg&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;li&gt;1991年2月25日，MIM-104 Patriot（爱国者）里的一个软件错误使它的系统每一百小时有三分之一秒的时钟偏移，导致定位拦截入侵导弹失败。结果伊拉克的飞毛腿导弹击中宰赫兰（沙特阿拉伯东北部城市）的一个美军军营，28 人死亡，100 多人受伤。&lt;/li&gt;
&lt;li&gt;其他案例，请参见《&lt;a href=&quot;http://blog.jobbole.com/104583/&quot; title=&quot;Bug 引发的 18 次重大事故&quot;&gt;Bug 引发的 18 次重大事故&lt;/a&gt;》。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这应该能够说明编写安全软件的重要性了，尤其在特定的环境中。当然也包括其他用例中，我们也应该意识到我们的软件 bug 会导致什么后果。&lt;/p&gt;
&lt;h2&gt;防御性编程初窥&lt;/h2&gt;
&lt;p&gt;为什么我认为在特定种类的工程中，防御性编程是解决这些问题好办法？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;抵御那些不可能的事，因为看似不可能的事也会发生。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;防御性编程中有很多防御方式，这也取决于你的软件项目所需的「安全」级别和资源级别。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;防御性编程是防御式设计的一种形式，用来确保软件在未知的环境中能继续运行。防御性编程的实践往往用于需要高可用性、安全性、保密性的地方。—— 维基百科&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我个人相信这种方法适合很多人参与的大型、长期的项目。例如，一个需要大量维护的开源项目。&lt;/p&gt;
&lt;p&gt;我们来探索一下我提出的关键点，来完成一个防御性编程的实现。&lt;/p&gt;
&lt;h2&gt;永远不要相信用户输入&lt;/h2&gt;
&lt;p&gt;设想你总是获取到你不想要的东西。因为像我们说过的，我们预期的是异常情况的出现，（所以）要时刻防备用户输入以及通常会传入你系统的东西，这是你成为一个防御性程序员的方法。试着做到尽可能的严格，确保输入的值就是你所期望的值。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/0ae4be85f5_1-2AwJBEFQ8XcNR7RzlMnTF-fw.png&quot; alt=&quot;&quot;&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;进攻是最好的防守&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;设置白名单而不是黑名单。举个例子，当你验证图像扩展名时，不要检查非法的类型，而是检查合法的类型并排除其他类型。在 PHP 有无数的开源校验库可以让你的工作变简单。&lt;/p&gt;
&lt;p&gt;进攻是最好的防守。共勉&lt;/p&gt;
&lt;h2&gt;数据库抽象化&lt;/h2&gt;
&lt;p&gt;在 &lt;a href=&quot;https://www.veracode.com/directory/owasp-top-10&quot;&gt;OWASP Top 10 Security Vulnerabilities&lt;/a&gt; 排首位的是注入攻击。这意味着有些人（很多人）还没有使用安全的工具来查询数据库。请使用数据库抽象包或库。在 PHP 里你可以使用 &lt;a href=&quot;http://php.net/manual/en/book.pdo.php&quot;&gt;PDO&lt;/a&gt; 来&lt;a href=&quot;http://stackoverflow.com/questions/134099/are-pdo-prepared-statements-sufficient-to-prevent-sql-injection&quot;&gt;确保基本的注入攻击防范&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;不要重复发明轮子&lt;/h2&gt;
&lt;p&gt;你不用框架（或微框架）吗？好吧恭喜你，你喜欢毫无理由地做额外的工作。这并不仅跟框架有关，也意味着你可以方便地&lt;a href=&quot;https://packagist.org/&quot;&gt;使用已经存在的、经过测试的、受万千开发者信任的、稳定的&lt;/a&gt;新特性，而不是你只为了自己从中受益而制作的东西。你自己创建方法的唯一原因是你需要的东西不存在，或存在但不符合你的需求（性能差、缺失特性等等）。&lt;/p&gt;
&lt;p&gt;这就是所谓的智能代码重用。拥抱它吧。&lt;/p&gt;
&lt;h2&gt;不要相信开发者&lt;/h2&gt;
&lt;p&gt;防御性编程与防御性驾驶相关联。在防御性驾驶中，我们假设周围的每个人都可能犯错。所以我们要留意别人的行为。相同概念也适用于防御性编程，我们作为开发者不要相信其他开发者的代码。我们同样也不要相信我们的代码。&lt;/p&gt;
&lt;p&gt;在很多人参与的大型项目中，我们有许多方式编写并组织代码。这也导致混乱甚至更多的 bug。这也是我们需要加强规范代码风格和代码检查的原因，让生活更轻松。&lt;/p&gt;
&lt;h2&gt;编写符合 SOLID 原则的代码&lt;/h2&gt;
&lt;p&gt;这是（防御性）编程最困难的部分——&lt;a href=&quot;https://medium.com/web-engineering-vox/how-to-write-solid-code-that-doesnt-suck-2a3416623d48&quot;&gt;编写不糟糕的代码&lt;/a&gt;。这也是很多人知道并一直在讨论的，但没有人真正关心或将注意力和精力放在实现符合 SOLID 原则的代码上。&lt;/p&gt;
&lt;p&gt;让我们看一些糟糕的例子&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;避免：未初始化的属性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

class BankAccount
{
    protected $currency = null;
    public function setCurrency($currency) { ... }
    public function payTo(Account $to, $amount)
    {
        // sorry for this silly example
        $this-&amp;gt;transaction-&amp;gt;process($to, $amount, $this-&amp;gt;currency);
    }
}

// I forgot to call $bankAccount-&amp;gt;setCurrency(&amp;#39;GBP&amp;#39;);
$bankAccount-&amp;gt;payTo($joe, 100);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，我们需要牢记签发付款前要先调用 setCurrency。这是很糟糕的事情，一个像这样的改变状态的操作（签发付款）不应该分两步来完成，且使用两个公开的方法。我们还可以用很多方法付款，但我们必须只有一个公开的方法来改变状态（对象不应该存在不一致的状态）。&lt;/p&gt;
&lt;p&gt;在这个例子中，我们把它改进，将未初始化的属性封装进 Money 对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

class BankAccount
{
    public function payTo(Account $to, Money $money) { ... }
}

$bankAccount-&amp;gt;payTo($joe, new Money(100, new Currency(&amp;#39;GBP&amp;#39;)));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使它万无一失。不要使用未初始化的对象属性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;避免：在类的作用域外泄露状态&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

class Message
{
    protected $content;
    public function setContent($content)
    {
        $this-&amp;gt;content = $content;
    }
}

class Mailer
{
    protected $message;
    public function __construct(Message $message)
    {
        $this-&amp;gt;message = $message;
    }
    public function sendMessage(
    {
        var_dump($this-&amp;gt;message);
    }
}

$message = new Message();
$message-&amp;gt;setContent(&amp;quot;bob message&amp;quot;);
$joeMailer = new Mailer($message);

$message-&amp;gt;setContent(&amp;quot;joe message&amp;quot;);
$bobMailer = new Mailer($message);

$joeMailer-&amp;gt;sendMessage();
$bobMailer-&amp;gt;sendMessage();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，Message 是通过引用传递的，两个实例的输出都是 “joe message”。一个解决方案是复制 Mailer 构造函数中的 message 对象。但是我们应该做的是试着使用（不可变的）&lt;a href=&quot;https://en.wikipedia.org/wiki/Value_object&quot;&gt;值对象&lt;/a&gt;，而不是简单可变的 Message 对象。尽可能使用不可变的对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

class Message
{
    protected $content;
    public function __construct($content)
    {
        $this-&amp;gt;content = $content;
    }
}

class Mailer 
{
    protected $message;
    public function __construct(Message $message)
    {
        $this-&amp;gt;message = $message;
    }
    public function sendMessage()
    {
        var_dump($this-&amp;gt;message);
    }
}

$joeMailer = new Mailer(new Message(&amp;quot;bob message&amp;quot;));
$bobMailer = new Mailer(new Message(&amp;quot;joe message&amp;quot;));

$joeMailer-&amp;gt;sendMessage();
$bobMailer-&amp;gt;sendMessage();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;编写测试&lt;/h2&gt;
&lt;p&gt;这点我们很还需要再说吗？编写单元测试可以帮助你秉承一般的原则，比如高内聚、单一职责、低耦合和正确的对象组合。它不仅帮助你测试小的单元用例，也能测试你组织对象的方式。确实，当测试你的小功能时，你会清晰的看到你需要测试多少情况和需要模拟多少对象，来达到 100% 的覆盖率。&lt;/p&gt;
&lt;h2&gt;结论&lt;/h2&gt;
&lt;p&gt;希望你喜欢这篇文章。记住这些仅仅是建议而已，由你决定何时、何处以及是否应用。&lt;/p&gt;
&lt;p&gt;感谢阅读！&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.jobbole.com/110123/&quot;&gt;想写无Bug的安全代码？看防御性编程的艺术&lt;/a&gt;，首发于&lt;a href=&quot;http://blog.jobbole.com&quot;&gt;文章 – 伯乐在线&lt;/a&gt;。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>百亿互金平台救火故事</title><link>https://blog.wo.ai/archives/1233/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1233/</guid><pubDate>Mon, 20 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;多年前，又是周六客服打电话过来，平台官网不能访问，app完全无法打开，客户在QQ群和微信群中各种反馈，说平台是不是跑路了？客服的多条400热线完全被打爆，电话已经接不过来…&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;一直以来总是想以什么方式去记录下自己在互金行业的这段经历，趁着自己还记得清楚，还能找到一些资料原型，一方面可以分享出来供大家参考，但是更重要就是多年以后我可以根据这些文章回忆起来自己的那段激情岁月。&lt;/p&gt;
&lt;p&gt;想了很久但一直没有实施，后来觉得应该从架构的角度来梳理一篇文章，就写《&lt;a href=&quot;http://blog.jobbole.com/110234/&quot;&gt;从零到百亿互联网金融架构发展史&lt;/a&gt;》这篇文章；最后认为只有实战出来的东西以及解决问题的过程，才是工作中最宝贵的经验，应该把它分享出来，在梳理的过程中觉得有三起事故比较有代表性就整理出了下面这三篇文章，本篇文章从整体来回忆一下一路走过来所经历过的救火故事。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/110152/&quot;&gt;一次生产事故的优化经历&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/110165/&quot;&gt;一次 DNS 缓存引发的惨案&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/110225/&quot;&gt;一个脚本引发的血案&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;作为一个互联网金融平台，涉及到用户资金，任何的服务（资金）差错用户都是不可容忍的，用户不懂什么是数据库，不知道什么网络不通，就是一会看不到钱在app里面展示都会觉得不安。在已经有很多P2P公司跑路的前提下，用户个个已经被锻炼成为福尔摩斯侦探，每天打开app查看收益，监控着平台一切，甚至半夜升级断网十分钟，也会被用户察觉，直接就发到群里面，更有甚者直接在QQ群或者微信群中你们的技术行不行!&lt;/p&gt;
&lt;p&gt;我们常说的互联网工作经验，一方面是开发经验，但其实更重要的是处理问题的能力。那么处理问题的能力怎么来呢，就是不断的去解决问题，不断的去总结经验，其中处理生产环境中问题的经验更甚,因为在处理生产环境中对个人的压力和临危应变的能力要求最高，你不但需要面临千万个用户反馈，客服不时得催促而且旁边可能就站了N个领导在看着你，一副你行不行的样子要求立刻马上解决问题！这个时候你的操作就非常重要，稍有不慎便会引发二次生产事故。&lt;/p&gt;
&lt;p&gt;说了这么多，只是想说明，生产事故对技术综合能力要求颇高，更是锻炼处理问题能力最佳时机！下面给大家介绍我们从零开发到现在百亿交易量所遇到的几次关键事故，有大有小挑出一些比较有代表性的事件来分享。&lt;/p&gt;
&lt;h2&gt;并发满标&lt;/h2&gt;
&lt;p&gt;公司系统刚上线的时候，其实没有经历过什么大量用户并发的考验，结果公司做了一个大的推广，涌入了一批用户来抢标，共1000万的标的几乎都在10秒之内搞定，大概会有上万左右的用户会同时去抢标，平均每秒大概有千人左右的并发，满标控制这块没有经过大的并发测试，上来之后就被打垮了，导致得结果是什么呢，1000万的标的，有可能到一千零几万满标，也有可能会九百多万就满标，也就说要不就是多了一些，要不就是少了一些，就满标了。&lt;/p&gt;
&lt;p&gt;这就会很尴尬，因为借款用户就借款一千万整，那么多出来的钱既不能给用户退回了，因为用户好不容易才抢上了，无端退了用户也闹；少了也是问题，用户借款一千万，少了几十万也不行，如果短得少了可以想办法找一些有钱的客户直接给买了，多了就必须重新放出来让用户投资，非常影响士气，这个问题困扰了我们有一段时间。&lt;/p&gt;
&lt;p&gt;购买标的流程图，不知道大家是否能根据此图发现问题呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/5ecf0cf72b_buy.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;超募&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为何会产生超募？在最早前的版本中没有使用乐观锁来控制，如果在最后购买的用户一单出现并发，就会出现超募，比如最后剩余30000份的购买份额，因为并发量特别大，可能同时会有十几个用户拿到了剩余30000份余额的可购买额度，有的买1000份、有的买上3000份、有的买上20000份都会驱动满标，所以最后导致了超募。&lt;/p&gt;
&lt;p&gt;针对这个问题，主要是引入了memcached乐观锁的概念（底层主要是&lt;code&gt;cas&lt;/code&gt;、&lt;code&gt;gets&lt;/code&gt;两个命令），在发标的时候存入标的总份额，当用户购买的时候首先去锁定用户购买的份额，因为乐观锁的原因，如果同时有两个用户拿到份额的时候保证只有一个最后可以更新成功（锁定份额），（锁定份额）失败直接返回，这样就保证了在入口的时候就直接屏蔽了部分并发的请求。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;少募&lt;/strong&gt;&lt;br&gt;为何产生少募？少募是可能1000万的标的突然到980万就给满标了，这是因为在超募情况下我们完善了代码，用户一进来首先就是锁定购买份额，只有锁定购买份额才能进行下面的流程，如果锁定购买份额失败直接返回，这样虽然保证了在1000万份额在购买初期必须每一个用户只能锁定一份，但是在高并发的情况下，因为购买流程中有十几个分支，每一个分支失败就会退回锁定的份额，这样就会导致这样的现象，就是可能是并发一上来，马上就满标了，过了一会进度就回退回来了。&lt;/p&gt;
&lt;p&gt;少募主要是因为分支失败回退导致的，一方面我们分析了容易导致回退热点，因为在用户抢标的时候会给用户实时的展示标的进度，在很早的版本中直接就是存入到一个标的进度表里面，并且采用了乐观锁，如果并发一高就频繁的更新失败导致回退，因此优化了标的进度这块，直接去掉了标的进度表，实时根据查询来展示标的进度（可以有延迟，有缓存）；另一方面在回退份额的时候在次判断试下memcached的份额和标的的状态，如果份额不为零并且标的状态是满标，马上自动更新状态保证后续用户可以立即购买再次驱动满标。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;做了以上的两种优化后，我们还遇到了其它的一些小问题，在不断的优化过程中，终于稳定下来；在后期版本中将考虑使用MQ队列或者redis队列来处理抢标更合理对用户也更公平一些。&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;黑客攻击&lt;/h2&gt;
&lt;p&gt;2015年应该是互联网行业受黑客攻击最多的一年吧，各互金公司都深受其害，其中我就记得网贷之家有一段时间被黑客攻击的太厉害，连续几天网站都无法打开。当然了我们也未能幸免，什么DDOS攻击、SQL注入、寻找系统漏洞等都几乎都经历过了，有的黑客还比较好，应该是出于善意或者展示自己，将漏洞放到乌云上面或者漏洞盒子里面让厂商来修复。但更多的是一些黑产完全就是威胁、敲诈想捞一笔钱，先看看下面这位吧：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/f9811b8c8c_hacker.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;这个家伙潜伏到我们公司的客户群里面，冒充我们的客户代表将头像和资料替换成一样，然后给群里所有的客服让他们发送我们内部的后台地址，想通过这种方式来寻找突破口，当然了这个算是里面的小菜鸟吧。&lt;/p&gt;
&lt;h3&gt;DDOS攻击&lt;/h3&gt;
&lt;p&gt;DDOS攻击我们也是遇到了很多次，确实也没有比较好办法，最后都是通过一些笨办法来尽量的避免，先说说我们的经历吧。有一次我正在敲代码，客服QQ又闪烁了起来，还没来得及打开查看信息，客服的经理电话就直接打了过来，我立刻就有一种不祥的预感，说官网打不开了，后台也登录不了。&lt;/p&gt;
&lt;p&gt;挂了电话，我在本机进行了测试果然不行，立刻准备登录VPN查看服务器各项指标，结果登录不上去，马上上楼找运维经理，他也登录不上，刚准备给机房打电话的时候，机房来电话了，说我们的一个IP正经历着1G多的流量访问，问我们是否正在做什么活动，刚话没有说完就说流量已经到5G，不到一分钟之后流量已经到达18G之多。因为我们的机房和集团公用了一个宽带入口，结果陆续的集团上面反馈他们的网站、服务也都出现了问题，机房方面害怕引起更大的冲击，直接把我们官网对外的IP封掉，集团的其它业务也才慢慢都恢复了过来，我们也紧急的更换了外网IP，重新切换了域名解析才恢复。&lt;/p&gt;
&lt;p&gt;事后我们根据apache分析了日志，流量来自N多个不同的IP地址根本无法应对，也正式因为这次攻击也才让我们领导重视了起来，将我们公司的机房网络层和公司集团彻底分离，这样的话不管那方受到大流量攻击都不会相互影响，我们也想了一些笨办法，因为上次我们更换了外网IP之后攻击也就停止了，那么我们认为肯定是针对我们外网来攻击的，所有我们就多准备了6个外网IP，当监控到对某一个外网进行攻击的时候马上切换到另一个外网地址，就这样跟他们玩，可以起到非常有限的一点作用，如果黑客真的想跟我们玩，这个办法就像是小孩子捉迷藏。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;周年庆的DDOS攻击&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;还有一次我们正在做周年庆活动，突然有人在QQ群里面给我们客服说了一句，叫你们的技术负责人来找我，然后我们的网站就挂了，我还保留了当时的一个截图如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/f9811b8c8c_hacker02.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;完了之后客服就来找我，然后按照往常的策略处理完之后，我根据客服给我的QQ号码加上了那个人，开口就来吓我，我依稀记当年的对话如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;黑客：你是平台的技术负责人吗？&lt;br&gt; 我：算是吧&lt;br&gt; 黑客：你信不信我可以让你们官网在5秒之内挂掉?&lt;br&gt; 我：…（沉默，还真害怕又把官网搞挂了）&lt;br&gt; 黑客：你们的官网漏洞很大&lt;br&gt; 我：如果有好的建议请您赐教&lt;br&gt; 黑客：你们的服务器是不是什么防护软件都没有装？&lt;br&gt; 我：…（继续沉默，这会在想不会是那个安全厂商来推广产品的吧，当然我们基础的防护肯定有）&lt;br&gt; 黑客：我们有非常多的肉鸡，想攻击谁，几秒之内肯定搞定&lt;br&gt; 我：…&lt;br&gt; 黑客：我们已经给很多互联网金融行业做了渗透测试，花点钱帮买你们平安，保证以后不会在出事情&lt;br&gt; 我：…&lt;br&gt; 黑客：免费的策略也有很多，比如360、百度云的安全产品可以免费低档10G左右的流量&lt;/p&gt;
&lt;p&gt;……(中间省略)&lt;/p&gt;
&lt;p&gt;黑客：我说了这多，你们也是不是给包烟钱，表示表示。&lt;/p&gt;
&lt;p&gt;……&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;后来也和领导进行了商议，坚决不能给他们钱，不能助涨这种嚣张气焰，实在不行就报警！&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;曝光一下当年使用的假QQ号，刚查了下变了个头像和描述，如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/f9811b8c8c_hacker01.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;后来我一直在想为什么DDOS攻击总是喜欢根据外网IP来攻击呢，慢慢好像是理解了如果针对域名来攻击的话，那不就是攻击到域名商的服务器了吗,一般域名商比较强大，黑客不太搞的定，也确实没有必要。当然记的前一段时间，某著名域名服务商被攻击，导致国外twitter等著名的互联网公司访问不断到达半天以上，还是很严重的。但是对于我们这些小公司，倒不至于搞这么大的动作。&lt;/p&gt;
&lt;p&gt;到底如何正确的防止DDOS攻击：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;第一种方案，隐藏服务器外网地址，服务器前端加CDN中转，免费的有百度云加速、360网站卫士、加速乐、安全宝等，如果资金充裕的话，可以购买高防的盾机，用于隐藏服务器真实IP，域名解析使用CDN的IP，所有解析的子域名都使用CDN的IP地址。此外，服务器上部署的其他域名也不能使用真实IP解析，全部都使用CDN来解析。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;第二种方案，买一些安全产品来进行流量清洗，主要是阿里云、腾讯云这种大厂商提供的一种服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;第三种方案，有很多的防火墙产品声称可以防止Ddos攻击，但是我个人使用感觉效果非常有限。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;SQL注入&lt;/h3&gt;
&lt;p&gt;我们的官网使用的是PHP开发，因为框架比较老旧的原因，存在着一些SQL注入的点，我们发现了一些进行了修补，没想到还是被一些黑客找到了突破点，这块还是比较感谢这些黑客的在漏洞盒子上面提交了bug(如下图)，最后我们根据提示进行了紧急修复，后来我们也在WAF防火墙配置了一些拦截SQL注入的策略，起到双保险的作用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/f9811b8c8c_sql.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;我一直在想为什么PHP一般比较容易出现SQL注入呢，而Java较少的暴漏出来SQL漏洞的情况，我估摸着有两方面的原因：第一，PHP一般会在前端使用的较多，受攻击的机会更多一些，Java一般做为后端服务攻击的可能性会比较少；第二，PHP框架较多而且很多早期的框架并没有特别考虑SQL注入的情况，Java大量普及了mybaits\hibernate这种orm框架，框架本身对常见的SQL注入有防止的功能，但不是说mybaits/hibernate框架就没有被sql注入的可能，大部分场景下是OK的。另外参数化查询可以有效的避免SQL注入。&lt;/p&gt;
&lt;p&gt;通过一段时间的学习，我发黑客一般先使用工具对网站做整体的扫描类似Acunetix，在根据扫描出来的漏洞做个大概的分析，但是比较深入的漏洞都需要根据网站的业务在进行调整，比如sql注入会根据页面的查询使用sqlmap等工具来进一步的渗透。当然我对这方面还是外行，描述的可能不够清晰。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其它攻击&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其它方面的攻击，主要是在业务方面，比如我们当初有一个很小的失误，有一个程序员在H5的小网页中将发送短信验证码返回了前端，最后被haker发现了，利用这个漏洞可以给任意的用户重置登录密码；短信攻击，现在的网站几乎都有发送短信或者短信验证码的功能，如果前端不做校验，haker会随便写一个for循环来发短信，一般系统的短信会进行全方位的防控，比如：1、前端加验证（字符验证码，有的是拖拽的动画）;2、后端根据用户或者IP加限制，比如用户一分钟只可以发送一条短信，忘记密码的短信一天只能发送10条、一个IP地址限制每天只能发送100条短信等。&lt;/p&gt;
&lt;h2&gt;BUG&lt;/h2&gt;
&lt;h3&gt;重复派息&lt;/h3&gt;
&lt;p&gt;15年的某一天看到一个新闻说是陆金所的一个用户发现自己银行里面突然多了很多钱，没过多久又被扣走了，然后收到陆金所那边的解释，说是给用户还本派息的时候程序出现了问题导致还本派息两次，当他们程序员发现了此问题后紧急进行了处理，用户当然闹了呀，就上了新闻，当然陆金所通道能力确实比较强可以直接从用户卡里面扣，当大家都兴致勃勃的谈论这个话题的时候，我却有一股淡淡的忧伤，为什么呢?因为这个错误我们也犯过，具体说就是我搞的，大家可不知道当时的心里压力有多大！&lt;/p&gt;
&lt;p&gt;事情是这样子的，我们使用的第三方支付的扣款接口不是特别的稳定，于是我们前期就对接了两种不通的扣款接口，平时前端投资的时候走一个接口，后端派息或者还本的时候走另外的一个接口，在初期的时候扣款接口不稳定，因此在给用户跑批的时候经常会有个别用户失败，需要手动给失败的用户二次派息。做为一个有志向的程序员当然觉得这种方式是低效的，于是将程序改造了一下，在后端派息的时候当第一种扣款失败的时候，自动再次调用第二种扣款接口进行扣款，当时想着这种方式挺好的，各个环境测试也没有问题，上线之后监控过一段时间也运行稳定。&lt;/p&gt;
&lt;p&gt;当我感觉一切都很美妙的时候，事故就来了，突然有一天客服反馈说有的用户说自己收到的利息感觉不对，好像是多了（真的是太感谢这个用户了），我登录后台看了一下派息的流水复核了一遍，果然利息被重复派了，一股冷水从头而下，把当天所有的用户派息记录和到期记录都进行了检查，影响了70多个用户，导致多派息了6万多元，幸亏只是派息出了问题，如果是到期的话金额会翻N倍，其中70多个人里面有几个进行了提现、几个进行了再次投资，绝大部分用户在我们发现的时候还不知情，金额也没有动。&lt;/p&gt;
&lt;p&gt;怎么处理呢，当然不能直接就动用户的钱了，给每个重复派息的用户打电话，说明原因赠送小礼物，请求谅解后我们把重复派过的利息在次调回来。大部分用户进行了核对之后都还是比较配合的，但是肯定有一些用户不干了，当然也不能怪客户，都是我的原因，有的客户需要上门赔礼道歉，有的客户需要公司出具证明材料，我们的老板亲自给客户打了N个电话被客户骂了N遍，我心里压力可想而知，其中有一个客户特别难缠，各种威胁说既然到了我的账户里面肯定是我的，你们的失误不应该让他来承担，折腾了很久，还是不能怪客户。可能会说有的互联网公司经常出现这种问题后就送给客户了，哎，我们是小公司呀！这个噱头玩不起。&lt;/p&gt;
&lt;p&gt;到底是什么原因呢，事后进行了复盘也给领导做了汇报，平时都是首先进行派息的定时任务，过一个小时之后进行到期的定时任务，当天的派息标的比较多，跑了一个半小时，就导致了派息和到期的两个定时任务同时进行，转账有了并发，第三方支付的接口不稳定给我们返回的失败，其实有的是成功的，就导致了我们进行了二次的扣款尝试引发了此问题。这个事情给我带来了非常大的教训，对于金融扣款的这种事情一定需要谨慎，哪怕付款引发报警之后在人工处理，也不能盲目重试可能引发雪崩效应。&lt;/p&gt;
&lt;h3&gt;杂七杂八&lt;/h3&gt;
&lt;p&gt;还有就是其它一些零碎的问题了，记的有一次对用户的登录过程进行优化，导致有一块判断少了一个括号结果用户在那两个小时内，只要输入账户，任意密码就可以登录了，幸好及时发现这个问题，正是这个问题才导致了我们正式确立了规范的上线流程，为以后的上线制度建定了基础。&lt;/p&gt;
&lt;p&gt;还有一次我们在模拟用户投资一种标的时候，留了一个入口通过http就可以调用，测试也没有问题，有一天正好给领导演示呢，就在次用http请求的方式在浏览器执行了一下，前端就会看到自动投标的过程，因为生产的数据有点多，投标的过程有点长，我们为了加快进度，找了好几个人同时来执行这http请求，导致最后出现了问题，最后发现写测试脚本的这个同事根本就没有考虑并发的情况，才导致出现了问题。&lt;/p&gt;
&lt;p&gt;也做了很多的活动，记得做一个网贷之家的一个活动的时候，活动上线比较紧张，我们团队曾经连续工作超过30个小时（一天一夜再一天），当天晚上我2点左右写完程序，测试从2两点测试到早上9点，最终确认没有任何问题，才进行投产。半夜公司没有暖气，我们实在冻的不行了，就在办公室跑步，从这头跑到那头，第二天上线之后，又害怕出现问题，监控了一天，确认没有任何问题，才到下午正常下班回家，那时候真是激情满满呀。&lt;/p&gt;
&lt;p&gt;说到做活动肯定少了羊毛党，说哪一家互金公司没有遇到过羊毛党那很少见，而且现在的羊毛党规模简直逆天了，我们用户里面就有一个羊毛党在两三天之内邀请了六七千位用户，如果说邀请一个用户送1元，那这个用户就可以搞几千块一次，而且有很多专业的网站、QQ群、微信公共账号都是他们的聚集地，那天那个平台有活动门清，他们写的淘羊毛操作手册有时候比我们官网的帮助文档还清晰，所以做活动的时候要考虑特别周全，各种限制，有封定、有预案、讲诚信，只要是符合我们活动规则的坚决按照流程走。&lt;/p&gt;
&lt;p&gt;还有一个有趣的事情，app推送，一次我在公交车上就看到xx盒子app弹出hhhhh的推送，这个事情我们也搞过，因为在调试的时候生产和测试就差了一个参数，有时候开发人员不注意就把生产参数部署到uat环境了，测试一发送就跑到生产了，这方面只能严格流产管理来防止了。&lt;/p&gt;
&lt;p&gt;其实还很多问题：mongodb集群和mysql的同步出现的一些状况、后台大量数据查询下的sql优化、golang使用mapreduce碰到的问题… 限于篇幅这里就不一一清晰的描述了。&lt;/p&gt;
&lt;p&gt;其实每次的出现问题都是对团队一次非常好的锻炼机会，通过发现问题，定位问题，解决问题，再次回过头来反思这些问题；重新梳理整个环节， 举一反三避免下次再次出现类似的问题。正是因为经历这些种种的困难、考验才让团队变的更强大更稳定，也更体现了流程的重要性，更是避免再次发生类似问题。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;古代对将军的要求是，心有万马奔腾而过，而面平静如湖水可照镜，在互联网行业对大牛的要求也同如此，特别是技术的负责人，在面对生产事故的时候，一定首先是安抚同事，静下下心来找到问题本质在去解决，而不是不断去施加压力催促解决，重压之下很多心里承受能力稍弱的队友，更加慌乱而不利于解决问题或者引发二次事故。&lt;/p&gt;
&lt;p&gt;在看淘宝双十一视频中，有一段特别受到感触，在双十一初期，虽然技术团队做了很多的准备，但是在零点过后流量瞬间涌入，服务被打垮，部分用户投诉刷新不出网页，紧接着隔壁同事也都反馈网站打不开，在大家都在慌乱中，xx一拍桌子大喊一声，大家都别动，三分钟之后再说，过了几分钟之后服务慢慢部分恢复了正常。后来回忆说，当时虽然服务瘫痪，但是监控还是有部分得业务成功，说明系统并没有被压垮，而此时的任何操作都有可能引发更大的问题，从此之后此人一战成名，成为阿里大将。&lt;/p&gt;
&lt;p&gt;互联网平台发展大抵都会经历三个阶段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、上线初期，此阶段问题最为繁多，生产事故不断，系统快速迭代优化。有人说为什么不测试到完全没有问题在投产吗？说实话在互联网行业这个很难，第一小公司很难做到生产环境和测试环境一致，成本太高；时间紧迫，一般都是很短的时间内要求上线，上线之后在快速迭代。另外互联网本就是一个快速试错的行业，错过半年时间可能风口早过；&lt;/li&gt;
&lt;li&gt;2、发展期，此阶段主要业务模式已经得到验证，系统出现问题的频繁度较少，低级错误减少，但此时是用户量和交易量不断爆发的时候，对系统性能、高并发的要求又上来了，所以此时出现的问题大多都是性能的问题；&lt;/li&gt;
&lt;li&gt;3、成熟期，发展期过后系统相对比较平稳，用户量和交易量都已经慢慢稳定下来，生产问题越来越少，出现问题几乎都是细小的bug，这个阶段也是公司最忽略技术得阶段，恰好我们公司就处于这个阶段，在这个阶段就需要静下心里，组织架构升级，补齐在初期和发展起所欠的技术债务，做好公司在升下一个量级的技术准备。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;所有的这些问题几乎都集中在14年底到15年初的这个阶段，15年后半年开始到现在平台慢慢稳定了下来，到现在几乎没有再出现过类似的问题，也因为几乎都是两年前的事情，有很多记的不是特别清楚了，写的比较粗糙望见谅。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.jobbole.com/110255/&quot;&gt;百亿互金平台救火故事&lt;/a&gt;，首发于&lt;a href=&quot;http://blog.jobbole.com&quot;&gt;文章 – 伯乐在线&lt;/a&gt;。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>如何用 OllyDbg 的跟踪功能分析虚拟机保护</title><link>https://blog.wo.ai/archives/1232/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1232/</guid><pubDate>Sat, 14 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;虚拟机保护已经是现代保护壳不可缺少的一环，虽然逆向方也发展出各种插件帮助分析，但只针对特定某款，通用性的方法却不多见。我总在想，既然虚拟机的结构是固定的，如果有一款工具能够记录指令流，那么按图索骥，也许能发展出一套通用的分析方法来。其实OD（OllyDbg）就有记录指令流的功能，叫跟踪（trace），也许是效果不好或者操作不便，用的人甚至知道的人不多。先介绍下怎么用。&lt;/p&gt;
&lt;p&gt;OD的跟踪功能原理很简单，就是每一步都自动下单步断点，然后记录断下来的指令信息。这项功能涉及到几项设置，第一项是缓存大小，不难想象，跟踪得到的这一些列的指令记录是需要占地方存储的，占多大可以设置，位置在调试选项（Debugging options）-&amp;gt;跟踪（Trace），如图1。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/eb2510c4c8_1a016cb4c1c92a441a0fd4ab9e6bd879.jpg&quot; alt=&quot;file0001&quot;&gt;&lt;/p&gt;
&lt;p&gt;图1&lt;/p&gt;
&lt;p&gt;第一项就是缓存的大小，内存允许的话，自然是多多益善，毕竟缓存越大，允许记录的信息越多。第二项是记录的内容，跟踪会自动记录地址模块等信息，此外可以选择是否记录指令、ESP和标志位的信息。设置位置紧接着缓存大小，见图2，可以按需勾选，本文只需要记录指令即可。最后一项是在调试（DEBUG）菜单中打开Trace。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/4e35159f3f_461237d368474b64ecfb6a277b900835.jpg&quot; alt=&quot;file0002&quot;&gt;&lt;/p&gt;
&lt;p&gt;图2&lt;/p&gt;
&lt;p&gt;现在Trace已经设置完毕了，按下Ctrl+F12，查看Trace窗口，应该已经开始记录执行过的指令。否则请检查前述设置和操作是否正确。&lt;/p&gt;
&lt;p&gt;那么，虚拟机保护要怎么入手分析呢？前面我提到，虚拟机是有固定结构的，既然要分析，那对应的找到这些结构应该就可以了。传统保护虚拟机的结构其实很简单，大致可以看成一只章鱼，有三个部分，分别是init（头），Dispatch（身）和Handle（触须），如图3：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/f248e63eb1_a0ec974c5655b05c9920257d38c5ff95.jpg&quot; alt=&quot;file0003&quot;&gt;&lt;/p&gt;
&lt;p&gt;图3&lt;/p&gt;
&lt;p&gt;Init主要完成虚拟机初始化工作，例如申请内存填写初始值之类，每次进入虚拟机，这个“头部”通常只执行一次。Dispatch是虚拟机的主体，可以看成一个主循环，它是每一条虚拟机指令的开始之处，也是结束之处，负责读取虚拟机指令，进入具体handle解释等工作。Handle就是虚拟机的“指令”了，实际完成各项虚拟机指令的功能。 我曾写过一篇《基于虚拟机的软件保护技术》较为详细的介绍过虚拟机保护技术，对基本结构还不太熟的同学，此文会对上述概念有更详细的说明。&lt;/p&gt;
&lt;p&gt;现在，我们就要在具体的软件中找这只“章鱼”了。以一个CrackMe为例，首先清理所有断点，打开Trace，Ctrl+F12跟踪步过运行，看到程序跑起来了，F12暂停，看Trace的窗口如下（图4）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/8ca497f3c3_54c7c30af4f097638622028ca5ae901b.jpg&quot; alt=&quot;file0004&quot;&gt;&lt;/p&gt;
&lt;p&gt;图4&lt;/p&gt;
&lt;p&gt;记录是从下往上看的，可以看出，在程序空间的最后一条支流，是00401534的一个call，调用了DialogBoxInDirectParamA，这是一个调出系统对话框的API，其中有一个参数DlgProc用来指明消息回调函数的位置，我们直接在反汇编窗口查看这个API，发现回调函数是0x401572（图5）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/b5846b312e_d965452de3ca2c5d42de23efbedbbbf7.jpg&quot; alt=&quot;file0005&quot;&gt;&lt;/p&gt;
&lt;p&gt;图5&lt;/p&gt;
&lt;p&gt;0x401572处代码不长，有好几条Call，但大部分都是系统Call，只有一处调用了程序空间的函数，这个函数就是虚拟机的入口。到这里，我们对虚拟机的分析的工作才刚刚开始。&lt;/p&gt;
&lt;p&gt;首先对虚拟机的入口下断，然后重新运行程序。目的是保证能够正确找到init。现在应该端在虚拟机的入口处，如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/b5846b312e_d125fc8a809b6de97cbdba3d35762398.jpg&quot; alt=&quot;file0006&quot;&gt;&lt;/p&gt;
&lt;p&gt;图6&lt;/p&gt;
&lt;p&gt;这是个非常简单的虚拟机，有经验的同学也许可以一眼就看出来图6包含了Init和Dispatch分别在哪里。当然也可以用Trace快速找出虚拟机的各个结构。现在去掉断点，打开Trace，Ctrl+F12跟踪步过，这时程序会跑起来，多点击几下按钮，目的是让主要分支得到更充分的执行（即增加获得执行的次数），然后F12暂停。回到Trace窗口，对着任意一行程序空间的指令点击右键，选择模块统计，结果如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://blog.wo.ai/wp-content/plugins/wp-o-matic/cache/858acb4c47_a4069cd955169adc70fa5ef0bc894680.jpg&quot; alt=&quot;file0007&quot;&gt;&lt;/p&gt;
&lt;p&gt;图7&lt;/p&gt;
&lt;p&gt;统计是以代码段来划分的，第一栏显示的是这段代码在刚才的跟踪执行中执行的次数，第二栏显示了某个代码段的首地址。我们先找执行了一次的指令首地址。可以找到第5行的地址就是虚拟机的入口地址，点击在反汇编窗口跟随，可以看到这段代码是从0x00401060到0x004010B9，这就是init：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00401060 $ 55 push ebp
00401061 . 8BEC mov ebp, esp
00401063 . 81C4 D0FEFFFF add esp, -0x130
00401069 . C745 E4 00000&amp;gt;mov dword ptr [ebp-0x1C], 0x0
00401070 . C745 E8 00000&amp;gt;mov dword ptr [ebp-0x18], 0x0
00401077 . C745 F1 00000&amp;gt;mov dword ptr [ebp-0xF], 0x0
0040107E . C645 FD 00 mov byte ptr [ebp-0x3], 0x0
00401082 . C645 FE 00 mov byte ptr [ebp-0x2], 0x0
00401086 . C745 F5 00000&amp;gt;mov dword ptr [ebp-0xB], 0x0
0040108D . 8D85 D0FEFFFF lea eax, dword ptr [ebp-0x130]
00401093 . 8945 F1 mov dword ptr [ebp-0xF], eax
00401096 . 8B45 14 mov eax, dword ptr [ebp+0x14]
00401099 . 8945 E0 mov dword ptr [ebp-0x20], eax
0040109C . 8B45 08 mov eax, dword ptr [ebp+0x8]
0040109F . 8945 D0 mov dword ptr [ebp-0x30], eax
004010A2 . 8B45 0C mov eax, dword ptr [ebp+0xC]
004010A5 . 8945 D8 mov dword ptr [ebp-0x28], eax
004010A8 . C745 DC 00000&amp;gt;mov dword ptr [ebp-0x24], 0x0
004010AF . C745 D4 00000&amp;gt;mov dword ptr [ebp-0x2C], 0x0
004010B6 . 8B45 10 mov eax, dword ptr [ebp+0x10]
004010B9 . 8945 EC mov dword ptr [ebp-0x14], eax
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着找Dispatch，刚才说过，它既是虚拟机指令的开始，又是结束，它得到的执行次数一定也最多。可以看到第三行的0x004010B9，这个地址在虚拟机入口地址之后，执行次数最多，同样的办法可以看到这段代码的终止位置是0x004010D9：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;004010BC &amp;gt; /FF45 EC inc dword ptr [ebp-0x14]
004010BF . |8B45 EC mov eax, dword ptr [ebp-0x14]
004010C2 . |8A00 mov al, byte ptr [eax]
004010C4 . |8845 F0 mov byte ptr [ebp-0x10], al
004010C7 . |B8 00204000 mov eax, 00402000
004010CC . |0FB65D F0 movzx ebx, byte ptr [ebp-0x10]
004010D0 . |C1E3 02 shl ebx, 0x2
004010D3 . |03C3 add eax, ebx
004010D5 . |FF20 jmp dword ptr [eax]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后是找这次执行虚拟机用到的handle。这个不难，虚拟机入口地址之后的代码段除了init和dispatch，其它都是handle，所有执行过的handle都会在里面出现。当然了，某条handle的具体作用，以及没有执行过的handle，就只能靠人肉分析了。还有就是，就分析虚拟机保护来说，了解执行了哪些handle，以及哪些handle更常用，这些信息都是十分有用的。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.jobbole.com/109803/&quot;&gt;如何用 OllyDbg 的跟踪功能分析虚拟机保护&lt;/a&gt;，首发于&lt;a href=&quot;http://blog.jobbole.com&quot;&gt;文章 – 伯乐在线&lt;/a&gt;。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>解决PKIX问题：unable to find valid certification path to requested target</title><link>https://blog.wo.ai/archives/1230/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1230/</guid><pubDate>Fri, 02 Dec 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;话说前几天在测试服务器上遇到了这么个异常&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;就是说找不着安全证书啥的等等烂码七糟的一大堆&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;接着就拜Google大神，发现一篇文章能被N个人转来转去的，关键文章还不怎么靠谱&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后来找到了一个办法，幸运的是在测试环境一弄， 这个问题看上去就被解决了&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我们要做的就是将所要访问的URL的安全认证证书导入到客户端&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;下面是获取安全证书的一种方法&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*
 * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &amp;quot;AS
 * IS&amp;quot; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class InstallCert {
	public static void main(String[] args) throws Exception {
		String host;
		int port;
		char[] passphrase;
		if ((args.length == 1) || (args.length == 2)) {
			String[] c = args[0].split(&amp;quot;:&amp;quot;);
			host = c[0];
			port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
			String p = (args.length == 1) ? &amp;quot;changeit&amp;quot; : args[1];
			passphrase = p.toCharArray();
		} else {
			System.out.println(&amp;quot;Usage: java InstallCert &amp;lt;host&amp;gt;[:port] [passphrase]&amp;quot;);
			return;
		}

		File file = new File(&amp;quot;jssecacerts&amp;quot;);
		if (file.isFile() == false) {
			char SEP = File.separatorChar;
			File dir = new File(System.getProperty(&amp;quot;java.home&amp;quot;) + SEP + &amp;quot;lib&amp;quot; + SEP + &amp;quot;security&amp;quot;);
			file = new File(dir, &amp;quot;jssecacerts&amp;quot;);
			if (file.isFile() == false) {
				file = new File(dir, &amp;quot;cacerts&amp;quot;);
			}
		}
		
		System.out.println(&amp;quot;Loading KeyStore &amp;quot; + file + &amp;quot;...&amp;quot;);
		InputStream in = new FileInputStream(file);
		KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
		ks.load(in, passphrase);
		in.close();

		SSLContext context = SSLContext.getInstance(&amp;quot;TLS&amp;quot;);
		TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
		SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
		context.init(null, new TrustManager[]{tm}, null);
		SSLSocketFactory factory = context.getSocketFactory();

		System.out.println(&amp;quot;Opening connection to &amp;quot; + host + &amp;quot;:&amp;quot; + port + &amp;quot;...&amp;quot;);
		SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
		socket.setSoTimeout(10000);
		try {
			System.out.println(&amp;quot;Starting SSL handshake...&amp;quot;);
			socket.startHandshake();
			socket.close();
			System.out.println();
			System.out.println(&amp;quot;No errors, certificate is already trusted&amp;quot;);
		} catch (SSLException e) {
			System.out.println();
			e.printStackTrace(System.out);
		}

		X509Certificate[] chain = tm.chain;
		if (chain == null) {
			System.out.println(&amp;quot;Could not obtain server certificate chain&amp;quot;);
			return;
		}

		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

		System.out.println();
		System.out.println(&amp;quot;Server sent &amp;quot; + chain.length + &amp;quot; certificate(s):&amp;quot;);
		System.out.println();
		MessageDigest sha1 = MessageDigest.getInstance(&amp;quot;SHA1&amp;quot;);
		MessageDigest md5 = MessageDigest.getInstance(&amp;quot;MD5&amp;quot;);
		for (int i = 0; i &amp;lt; chain.length; i++) {
			X509Certificate cert = chain[i];
			System.out.println(&amp;quot; &amp;quot; + (i + 1) + &amp;quot; Subject &amp;quot; + cert.getSubjectDN());
			System.out.println(&amp;quot;   Issuer  &amp;quot; + cert.getIssuerDN());
			sha1.update(cert.getEncoded());
			System.out.println(&amp;quot;   sha1    &amp;quot; + toHexString(sha1.digest()));
			md5.update(cert.getEncoded());
			System.out.println(&amp;quot;   md5     &amp;quot; + toHexString(md5.digest()));
			System.out.println();
		}

		System.out.println(&amp;quot;Enter certificate to add to trusted keystore or &amp;#39;q&amp;#39; to quit: [1]&amp;quot;);
		String line = reader.readLine().trim();
		int k;
		try {
			k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
		} catch (NumberFormatException e) {
			System.out.println(&amp;quot;KeyStore not changed&amp;quot;);
			return;
		}

		X509Certificate cert = chain[k];
		String alias = host + &amp;quot;-&amp;quot; + (k + 1);
		ks.setCertificateEntry(alias, cert);

		OutputStream out = new FileOutputStream(&amp;quot;jssecacerts&amp;quot;);
		ks.store(out, passphrase);
		out.close();

		System.out.println();
		System.out.println(cert);
		System.out.println();
		System.out.println(&amp;quot;Added certificate to keystore &amp;#39;jssecacerts&amp;#39; using alias &amp;#39;&amp;quot; + alias + &amp;quot;&amp;#39;&amp;quot;);
	}

	
	private static final char[] HEXDIGITS = &amp;quot;0123456789abcdef&amp;quot;.toCharArray();

	
	private static String toHexString(byte[] bytes) {
		StringBuilder sb = new StringBuilder(bytes.length * 3);
		for (int b : bytes) {
			b &amp;amp;= 0xff;
			sb.append(HEXDIGITS[b &amp;gt;&amp;gt; 4]);
			sb.append(HEXDIGITS[b &amp;amp; 15]);
			sb.append(&amp;#39; &amp;#39;);
		}
		return sb.toString();
	}

	
	private static class SavingTrustManager implements X509TrustManager {
		private final X509TrustManager tm;
		private X509Certificate[] chain;

		SavingTrustManager(X509TrustManager tm) {
			this.tm = tm;
		}

		public X509Certificate[] getAcceptedIssuers() {
			throw new UnsupportedOperationException();
		}

		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			throw new UnsupportedOperationException();
		}

		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			this.chain = chain;
			tm.checkServerTrusted(chain, authType);
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;编译InstallCert.java得到两个class文件，并执行InstallCert类&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;执行方式：java InstallCert hostname eg:java InstallCert &lt;a href=&quot;http://www.cebbank.com&quot;&gt;www.cebbank.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;接下来会看到下面的打印信息&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java InstallCert www.cebbank.com
Loading KeyStore /usr/java/jdk1.6.0_31/jre/lib/security/cacerts...
Opening connection to www.cebbank.com:443...
Starting SSL handshake...

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1731)
	at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:241)
	at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:235)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1206)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:136)
	at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:593)
	at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:529)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:925)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1170)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1197)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1181)
	at InstallCert.main(InstallCert.java:102)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:323)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:217)
	at sun.security.validator.Validator.validate(Validator.java:218)
	at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
	at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
	at InstallCert$SavingTrustManager.checkServerTrusted(InstallCert.java:198)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1198)
	... 8 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:318)
	... 14 more

Server sent 1 certificate(s):

 1 Subject CN=www.cebbank.com, OU=Terms of use at www.verisign.com/rpa (c)05, OU=CEB, O=&amp;quot;China Everbright Bank Co., Ltd&amp;quot;, L=Beijing
   Issuer  CN=VeriSign Class 3 Extended Validation SSL CA, OU=Terms of use at https://www.verisign.com/rpa (c)06, OU=VeriSign Trust Network
   sha1    5b d2 85 6e b3 a4 2b 07 a2 13 47 b3 be 3e 1f c9 d3 ce 46 57 
   md5     05 d8 ae ee f1 d9 51 63 6d 2f 11 e0 ac d0 e7 d7 

Enter certificate to add to trusted keystore or &amp;#39;q&amp;#39; to quit: [1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;然后输入 1 并回车，会看到类似下面的打印信息&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
[
  Version: V3
  Subject: CN=www.cebbank.com, OU=Terms of use at www.verisign.com/rpa (c)05, OU=CEB, O=&amp;quot;China Everbright Bank Co., Ltd&amp;quot;, L=Beijing
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits
  modulus: 30831246384548809540705228292841393062583732250993909916355780413722161557074568469738254573472093341710481517139910877
  public exponent: 65537
  Validity: [From: Mon Jul 02 08:00:00 CST 2012,
               To: Thu Jul 03 07:59:59 CST 2014]
  Issuer: CN=VeriSign Class 3 Extended Validation SSL CA, OU=Terms of use at https://www.verisign.com/rpa (c)06, OU=VeriSign Trust Network
  SerialNumber: [    5715ab25 6be8fa42 2fa28dd4 601bc732]

Certificate Extensions: 9
[1]: ObjectId: 1.3.6.1.5.5.7.1.1 Criticality=false
AuthorityInfoAccess [
  [
   accessMethod: 1.3.6.1.5.5.7.48.1
   accessLocation: URIName: http://ocsp.verisign.com, 
   accessMethod: 1.3.6.1.5.5.7.48.2
   accessLocation: URIName: http://EVSecure-aia.verisign.com/EVSecure2006.cer]
]

[2]: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: www.cebbank.com
]

[3]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: FC 8A 50 BA 9E B9 25 5A   7B 55 85 4F 95 00 63 8F  ..P...%Z.U.O..c.
0010: E9 58 6B 43                                        .XkC
]

]

[4]: ObjectId: 2.5.29.32 Criticality=false
CertificatePolicies [
  [CertificatePolicyId: [2.16.840.1.113733.1.7.23.6]
[PolicyQualifierInfo: [
  qualifierID: 1.3.6.1.5.5.7.2.1
  qualifier: 0000: 16 1C 68 74 74 70 73 3A   2F 2F 77 77 77 2E 76 65  ..https://www.ve
0010: 72 69 73 69 67 6E 2E 63   6F 6D 2F 63 70 73        risign.com/cps

]]  ]
]

[5]: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:false
  PathLen: undefined
]

[6]: ObjectId: 1.3.6.1.5.5.7.1.12 Criticality=false
Extension unknown: DER encoded OCTET string =
0000: 04 62 30 60 A1 5E A0 5C   30 5A 30 58 30 56 16 09  .b0`.^.\0Z0X0V..
0010: 69 6D 61 67 65 2F 67 69   66 30 21 30 1F 30 07 06  image/gif0!0.0..
0020: 05 2B 0E 03 02 1A 04 14   4B 6B B9 28 96 06 0C BB  .+......Kk.(....
0030: D0 52 38 9B 29 AC 4B 07   8B 21 05 18 30 26 16 24  .R8.).K..!..0&amp;amp;.$
0040: 68 74 74 70 3A 2F 2F 6C   6F 67 6F 2E 76 65 72 69  http://logo.veri
0050: 73 69 67 6E 2E 63 6F 6D   2F 76 73 6C 6F 67 6F 31  sign.com/vslogo1
0060: 2E 67 69 66                                        .gif


[7]: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
  clientAuth
]

[8]: ObjectId: 2.5.29.31 Criticality=false
CRLDistributionPoints [
  [DistributionPoint:
     [URIName: http://EVSecure-crl.verisign.com/EVSecure2006.crl]
]]

[9]: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  DigitalSignature
  Key_Encipherment
]

]
  Algorithm: [SHA1withRSA]
  Signature:
0000: 42 0A 89 BF 48 08 1E F4   98 F2 E5 DB 0D 83 EF 37  B...H..........7
0010: EC 27 6F 4D 81 69 C6 4A   4C 17 EC 57 F5 48 2A 14  .&amp;#39;oM.i.JL..W.H*.
0020: 3C 54 B2 C5 49 39 42 BA   EC 83 78 02 F9 96 6C 63  &amp;lt;T..I9B...x...lc
0030: 80 BC 60 61 BB 20 D1 AD   C3 D3 76 47 6F 0C 7B AC  ..`a. ....vGo...
0040: 76 B2 C7 2D B1 0A 7A 00   CA 40 38 86 FF 9F 12 F5  v..-..z..@8.....
0050: BE 5A E7 42 97 2F DF DE   0C 19 C5 F6 92 58 17 7A  .Z.B./.......X.z
0060: 9A 1D 2C 2C DA 8B 83 83   2D BE 07 58 56 36 92 E7  ..,,....-..XV6..
0070: B1 F8 A0 B5 00 F4 C3 30   D1 34 37 3D 94 75 28 04  .......0.47=.u(.
0080: A2 D8 C3 FE B1 E1 C2 2E   51 A8 6F D5 09 6D 49 DB  ........Q.o..mI.
0090: 2E 1D 4B F7 A8 06 30 B4   97 E7 C2 33 26 FD 6A DF  ..K...0....3&amp;amp;.j.
00A0: D6 B0 10 A1 F2 73 DD 5A   60 DE 51 5E EA 80 46 86  .....s.Z`.Q^..F.
00B0: 25 0B 53 FC C2 57 80 35   09 2D 31 55 28 35 EE 0F  %.S..W.5.-1U(5..
00C0: 62 50 4B 12 75 0B 02 9F   2F 0B D2 8A 0D 23 E3 C1  bPK.u.../....#..
00D0: 48 28 56 33 E1 DE 31 DD   72 78 15 96 EE 2B A5 1D  H(V3..1.rx...+..
00E0: 37 85 1B E5 88 53 80 88   02 6D 90 F3 E6 4A 74 AC  7....S...m...Jt.
00F0: D2 CA 0E 04 BC 46 A0 57   34 FA CF 9D E5 D7 0E 4B  .....F.W4......K

]

Added certificate to keystore &amp;#39;jssecacerts&amp;#39; using alias &amp;#39;www.cebbank.com-1&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;同时我们会在当面目录下发现已经生成了一个名为jssecacerts的证书&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;再将名为jssecacerts的证书拷贝\%JAVA_HONME%\\jre\\lib\\security\\目录中&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最后重启下应用的服务，证书就会生效了。。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**补充：**&lt;strong&gt;有人说生成证书后不用拷贝，直接代码里加句话就行，结果试了一下发现不管用&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;System.setProperty(&amp;quot;javax.net.ssl.trustStore&amp;quot;, &amp;quot;jssecacerts证书路径&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Technology</category><category>Uncategorized</category></item><item><title>[转]openssl的证书格式转换</title><link>https://blog.wo.ai/archives/1228/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1228/</guid><pubDate>Thu, 01 Dec 2016 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;证书转换&lt;/h2&gt;
&lt;p&gt;PKCS 全称是 Public-Key Cryptography Standards ，是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准，PKCS 目前共发布过 15 个标准。 常用的有：&lt;br&gt;PKCS#7 Cryptographic Message Syntax Standard&lt;br&gt;PKCS#10 Certification Request Standard&lt;br&gt;PKCS#12 Personal Information Exchange Syntax Standard&lt;br&gt;X.509是常见通用的证书格式。所有的证书都符合为Public Key Infrastructure (PKI) 制定的 ITU-T X509 国际标准。&lt;br&gt;PKCS#7 常用的后缀是： .P7B .P7C .SPC&lt;br&gt;PKCS#12 常用的后缀有： .P12 .PFX&lt;br&gt;X.509 DER 编码(ASCII)的后缀是： .DER .CER .CRT&lt;br&gt;X.509 PAM 编码(Base64)的后缀是： .PEM .CER .CRT&lt;br&gt;.cer/.crt是用于存放证书，它是2进制形式存放的，不含私钥。&lt;br&gt;.pem跟crt/cer的区别是它以Ascii来表示。&lt;br&gt;pfx/p12用于存放个人证书/私钥，他通常包含保护密码，2进制方式&lt;br&gt;p10是证书请求&lt;br&gt;p7r是CA对证书请求的回复，只用于导入&lt;br&gt;p7b以树状展示证书链(certificate chain)，同时也支持单个证书，不含私钥。 &lt;/p&gt;
&lt;h3&gt;1. CA证书&lt;/h3&gt;
&lt;p&gt;用openssl创建CA证书的RSA密钥(PEM格式)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl genrsa -des3 -out ca.key 1024
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 创建CA证书有效期为一年&lt;/h3&gt;
&lt;p&gt;用openssl创建CA证书(PEM格式,假如有效期为一年)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl req -new -x509 -days 365 -key ca.key -out ca.crt -config openssl.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;openssl是可以生成DER格式的CA证书的，最好用IE将PEM格式的CA证书转换成DER格式的CA证书。&lt;/p&gt;
&lt;h3&gt;3. x509转换为pfx&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. PEM格式的ca.key转换为Microsoft可以识别的pvk格式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pvk -in ca.key -out ca.pvk -nocrypt -topvk
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. PKCS#12 到 PEM 的转换&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;openssl pkcs12 -nocerts -nodes -in cert.p12 -out private.pem  验证   openssl pkcs12 -clcerts -nokeys -in cert.p12 -out cert.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 从 PFX 格式文件中提取私钥格式文件 (.key)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;openssl pkcs12 -in mycert.pfx -nocerts -nodes -out mycert.key
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. 转换 pem 到到 spc&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;   openssl crl2pkcs7 -nocrl -certfile venus.pem  -outform DER -out venus.spc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 -outform -inform 指定 DER 还是 PAM 格式。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl x509 -in Cert.pem -inform PEM -out cert.der -outform DER
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8. PEM 到 PKCS#12 的转换&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;openssl pkcs12 -export -in Cert.pem -out Cert.p12 -inkey key.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;IIS 证书&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd c:\openssl 　　　　　　　　　  set OPENSSL_CONF=openssl.cnf 　　　　　　　　　  openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;server.key和server.crt文件是Apache的证书文件，生成的server.pfx用于导入IIS&lt;/p&gt;
&lt;h3&gt;9. How to Convert PFX Certificate to PEM Format for SOAP&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ openssl pkcs12 -in test.pfx -out client.pem  Enter Import Password:  MAC verified OK  Enter PEM pass phrase:  Verifying - Enter PEM pass phrase:
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Technology</category><category>Uncategorized</category></item><item><title>Docker 笔记二：安装Nginx</title><link>https://blog.wo.ai/archives/1222/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1222/</guid><pubDate>Sat, 12 Nov 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;docker pull nginx&lt;/p&gt;
&lt;p&gt;I’d like to install nginx as a load balance server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http {
include       mime.types;
default_type  application/octet-stream;
#定义日志格式
#log_format  main  &amp;#39;$remote_addr - $remote_user [$time_local] $request &amp;#39;
#                  &amp;#39;&amp;quot;$status&amp;quot; $body_bytes_sent &amp;quot;$http_referer&amp;quot; &amp;#39;
#                  &amp;#39;&amp;quot;$http_user_agent&amp;quot; &amp;quot;$http_x_forwarded_for&amp;quot;&amp;#39;;
#access_log  off;
access_log  logs/access.log;
client_header_timeout  3m;
client_body_timeout    3m;
send_timeout           3m;
client_header_buffer_size    1k;
large_client_header_buffers  4 4k;
sendfile        on;
tcp_nopush      on;
tcp_nodelay     on;
#keepalive_timeout  75 20;
include    gzip.conf;
upstream localhost {
#根据ip计算将请求分配各那个后端tomcat，许多人误认为可以解决session问题，其实并不能。
#同一机器在多网情况下，路由切换，ip可能不同
#ip_hash;
server localhost:18081;
server localhost:18080;
}
server {
listen       80;
server_name  localhost;
location / {
proxy_connect_timeout   3;
proxy_send_timeout      30;
proxy_read_timeout      30;
proxy_pass http://localhost;
}
}


useful link
https://hub.docker.com/_/nginx/
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Uncategorized</category></item><item><title>I have crossed the oceans of time, to find you</title><link>https://blog.wo.ai/archives/1218/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1218/</guid><pubDate>Fri, 11 Nov 2016 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;I have crossed the oceans of time, to find you&lt;/h1&gt;
&lt;p&gt;对白台词，煽情的，经典的，听得也不少&lt;br&gt;很多早就过耳既忘&lt;br&gt;可是，这一句，却常常响起在耳畔&lt;br&gt;抑或早已铭刻于心&lt;br&gt;“我穿越了时间的瀚海来寻找你……”&lt;br&gt;时间，海洋，爱情，四百年，等待，轮回，&lt;br&gt;一幅幽远，广阔，空旷的场景&lt;br&gt;如果用时间来丈量爱情，四百年够不够长？&lt;br&gt;如果用空间来丈量爱情，无边的海洋够不够宽广？&lt;/p&gt;
&lt;p&gt;我却可以将时空的阻碍一一跨越，&lt;br&gt;纵使灵魂坠入无边的深渊&lt;br&gt;纵使绝望与痛苦的阴风霾雨要将我吞噬&lt;br&gt;只是,只是为了找到你&lt;/p&gt;
</content:encoded><category>English</category></item><item><title>Docker 笔记一：安装Tomcat</title><link>https://blog.wo.ai/archives/1220/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1220/</guid><pubDate>Thu, 10 Nov 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;docker pull tomcat&lt;/p&gt;
&lt;p&gt;Run the default Tomcat server (&lt;code&gt;CMD [&amp;quot;catalina.sh&amp;quot;, &amp;quot;run&amp;quot;]&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker run -it --rm tomcat:8.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can test it by visiting &lt;code&gt;http://container-ip:8080&lt;/code&gt; in a browser or, if you need access outside the host, on port 8888:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker run -it --rm -p 8888:8080 tomcat:8.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then go to &lt;code&gt;http://localhost:8888&lt;/code&gt; or &lt;code&gt;http://host-ip:8888&lt;/code&gt; in a browser.&lt;/p&gt;
&lt;p&gt;The default Tomcat environment in the image for versions 7 and 8 is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CATALINA_BASE:   /usr/local/tomcat
CATALINA_HOME:   /usr/local/tomcat
CATALINA_TMPDIR: /usr/local/tomcat/temp
JRE_HOME:        /usr
CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default Tomcat environment in the image for version 6 is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CATALINA_BASE:   /usr/local/tomcat
CATALINA_HOME:   /usr/local/tomcat
CATALINA_TMPDIR: /usr/local/tomcat/temp
JRE_HOME:        /usr
CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configuration files are available in &lt;code&gt;/usr/local/tomcat/conf/&lt;/code&gt;. By default, no user is included in the “manager-gui” role required to operate the “/manager/html” web application. If you wish to use this app, you must define such a user in &lt;code&gt;tomcat-users.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to map local file into the docker image, you can type following command with the parameter -v&lt;/p&gt;
&lt;p&gt;docker run -p 8080:8080 -v /home/tomcat/webapps:/usr/local/tomcat/webapps -v /home/tomcat/conf:/usr/local/tomcat/conf -v /home/tomcat/work:/usr/local/tomcat/work tomcat&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>洗心2:安心就能闲下来</title><link>https://blog.wo.ai/archives/1214/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1214/</guid><pubDate>Tue, 09 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;秋雨，毛毛雨，散文一样下着，下着光线，湿湿的光线。织布机织出来的一匹秋雨。而秋寒的灯光，湘绣一样，把窟窿绣在秋雨的布匹上。秋雨套在夜的身上，套在夜的里面做内衣，也套在夜的外面，作为披风。&lt;/p&gt;
&lt;p&gt;一盏灯，一颗心，一杯禅，一杯茶，无限的寂寞与孤独，超越了就无限自在，就像一位老人。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;秋雨夜眠&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;白居易&lt;/p&gt;
&lt;p&gt;凉冷三秋夜，安闲一老翁。&lt;/p&gt;
&lt;p&gt;卧迟灯灭后，睡美雨声中。&lt;/p&gt;
&lt;p&gt;灰宿温瓶火，香添暖被笼。&lt;/p&gt;
&lt;p&gt;晓晴寒未起，霜叶满阶红。&lt;/p&gt;
&lt;p&gt;此诗为大和六年秋（832），白居易任河南尹时作。这时他已经60多岁，体衰多病，官务清闲，加上亲密诗友元稹已经谢世，心情寂寞。该诗多少反映诗人暮年政治上的心灰意懒、生活上孤寂闲散的状况。&lt;/p&gt;
&lt;p&gt;全诗紧紧把握老翁秋雨夜安眠的特征，富有生活味道，实际上这时写面对生活历程最后的冷静淡定。&lt;/p&gt;
</content:encoded><category>Poem &amp; Reading</category></item><item><title>PHP中“＝＝”运算符的安全问题</title><link>https://blog.wo.ai/archives/1213/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1213/</guid><pubDate>Fri, 05 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;前言&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PHP是一种通用的开源脚本语言，它的语法混合了C，Java，以及Perl等优秀语言的语法。除此之外，它还提供了大量的函数库可供开发人员使用。但是，如果使用不当，PHP也会给应用程序带来非常大的安全风险。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在这篇文章中，我们将会对PHP应用程序中经常会出现的一些问题进行深入地分析，尤其是当我们使用“==”(比较运算符)来进行字符串比较时，可能会出现的一些安全问题。虽然近期有很多文章都围绕着这一话题进行过一些探讨，但我决定从“黑盒测试”的角度出发，讨论一下如何利用这个问题来对目标进行渗透和攻击。首先，我会对引起这个问题的根本原因进行分析，以便我们能够更加深入地理解其工作机制，这样才可以保证我们能够尽可能地避免这种安全问题的发生。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题的描述&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在2011年，PHP官方漏洞追踪系统发现，当字符串与数字在进行比较的时候，程序会出现某些非常奇怪的现象。从安全的角度出发，这个问题实际上并不能算是一个安全问题。比如说，你可以看到下面这段代码:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;php &amp;gt; var_dump(&amp;#39;0xff&amp;#39; == &amp;#39;255&amp;#39;);
bool(true)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际上，当使用类似“==”这样的比较运算符进行操作时，就会出现这样的情况。上面这个例子中出现的问题不能算是一个漏洞，因为它是PHP所提供的一种名为“类型转换”的功能。从本质上来分析，当我们使用特定的比较运算符(例如== ， !=， &amp;lt;&amp;gt;)来进行操作时，PHP首先会尝试去确定参与比较的数据类型。但是这样的一种类型转换机制将有可能导致计算结果与我们预期的结果有较大出入，而且也会带来非常严重的安全问题。安全研究专家在该问题的完整披露报告中写到:这种类型转化机制将有可能导致权限提升，甚至还会使程序的密码验证过程变得不安全。&lt;/p&gt;
&lt;p&gt;Gynvael写过一篇关于这一话题的经典文章，PHP等号运算符“==”所涵盖的数据类型非常广泛，我们给大家提供了一个较为完整的比较参考列表，并给出了一些示例，具体内容如下所示:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;1.00000000000000001&amp;quot; == &amp;quot;0.1e1&amp;quot; → bool(true)
&amp;quot;+1&amp;quot;    == &amp;quot;0.1e1&amp;quot; → bool(true)
&amp;quot;1e0&amp;quot;   == &amp;quot;0.1e1&amp;quot; → bool(true)
&amp;quot;-0e10&amp;quot; == &amp;quot;0&amp;quot; → bool(true)
&amp;quot;1000&amp;quot;  == &amp;quot;0x3e8&amp;quot; → bool(true)
&amp;quot;1234&amp;quot;  == &amp;quot;  	1234&amp;quot; → bool(true)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正如你所看到的，当我们使用“==”来比较这些数字字符串时，参与比较的就是字符串中数字的实际大小，从安全的角度出发，这就是一个非常有趣的问题了。在这种情况下，你可以使用科学计数法来表示一个数字，并将其放在一个字符串中，PHP将会自动把它作为一个数字类型来处理。我们之所以会得到这样的输出类型，是因为PHP使用了一种哈希算法(通常使用十六进制数值表示)来进行处理。比如说，如果一个数字为0，那么在进行松散比较的过程中，PHP会自动对其类型进行转换，但其值永远为0。对于一个给定的散列算法而言，密码就有可能会变成可以被替换的了。比如说，当密码的哈希值被转换成使用科学计数法来表示的数字时，将有可能正好与其他的密码哈希相匹配。这样一来，即使是一个完全不同的密码，也有可能可以通过系统的验证。但有趣的是，当某些采用科学计数法表示的数字在进行比较的时候，结果可能会让你意想不到:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;18372e0&amp;quot; == &amp;quot;492372e0000&amp;quot; → bool(false)
&amp;quot;2e6&amp;quot; == &amp;quot;8e2&amp;quot; → bool(false)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从“黑盒测试”的角度出发来考虑这个问题&lt;/p&gt;
&lt;p&gt;从静态分析的角度来看，这些安全问题就显得有些普通了。但如果我们从黑盒的角度来看待这些问题，我们能够得到什么样的启发呢?对于应用程序中的任何用户账号而言，如果应用程序使用了当前最为流行的哈希散列算法(例如SHA1和MD5)来对密码进行处理，而你在对密码哈希进行验证的时候使用了PHP的松散比较，那么此时就有可能出现安全问题。我们现在可以考虑进行一次典型的渗透测试，你可以创建一个普通的账号，将密码设置成哈希值类似的其中一个密码，然后使用其他的密码进行登录操作。很明显，系统的安全性完全取决于你所使用的散列算法。所以，我们假设你没有在散列算法中使用“Salt”值，那么你至少得使用两种不同的散列算法来对密码进行处理。&lt;/p&gt;
&lt;p&gt;现在，在我们去对这些密码组合进行研究之前，我们还应该考虑到一点——即密码的要求。因为我们在对这些密码和散列算法进行分析之前，首先得确保我们所设置的初始密码复合了密码复杂度的要求，否则我们的分析和研究将会没有任何的意义。因此，我们得确保我们的密码长度至少为八个字符，密码中包含有大小写字母，数字，以及至少一个特殊字符:具体如下所示:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import random
import hashlib
import re
import string
import sys
prof = re.compile(&amp;quot;^0+ed*$&amp;quot;) # you can also consider: re.compile(&amp;quot;^d*e0+$&amp;quot;)
prefix = string.lower(sys.argv[1])+&amp;#39;!&amp;#39;+string.upper(sys.argv[1])+&amp;quot;%s&amp;quot;
num=0
while True:
    num+=1
    b = hashlib.sha256(prefix % num).hexdigest()
    if (b[0]==&amp;#39;0&amp;#39; and prof.match(b)):
        print(prefix+str(num),b)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为此，我专门编写了一个Python脚本，虽然我没有竭尽全力去优化这个脚本的性能，但是在PyPy编译器的帮助下，这个精心编写的脚本可以在我的AMD FX8350所有可用的CPU核心中稳定运行。除此之外，我还使用到了hashlib库中的散列函数，而且为了避免遇到Python GIL的进程同步问题，我还生成了独立的进程来对密码数据进行处理。不仅如此，我还使用了非常复杂的技术来为每一个密码生成不同的前缀，正如上面这段代码所示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析结果&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在经过了一个多小时的分析之后，我得到了四个密码的SHA1值。令我感到惊讶的是，得到四个密码的MD5值所需的时间竟然更短。&lt;/p&gt;
&lt;p&gt;密码的计算结果十分相似，具体如下所示:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MD5
word	hash
c!C123449477	0e557632468345060543073989263828
d!D206687225	0e749889617409631915178731435707
e!E160399390	0e680455198929448171766997030242
f!F24413812	0e666889174135968272493873755352

SHA-1
word	hash
aA1537368460!	0e98690883042693380036268365370177656718
aA3539920368!	0e80128521090954700858853090442722395969
cC6593433400!	0e65495612893131014886449602893230063369
fF3560631665!	0e49205137236861153120561516430463247071
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可以随意选取两个密码来进行对比，对比的演示结果如下:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;php &amp;gt; var_dump(md5(&amp;#39;c!C123449477&amp;#39;) == md5(&amp;#39;d!D206687225&amp;#39;));
bool(true)
php &amp;gt; var_dump(sha1(&amp;#39;aA1537368460!&amp;#39;) == sha1(&amp;#39;fF3560631665!&amp;#39;));
bool(true)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你无法得到如上图所示的计算结果，那么你应该感到幸运。你可以尝试将用户名和密码捆绑在一起，然后使用带“salt”值的散列算法来进行计算。你只需要修改一小部分代码即可实现，点击“&lt;a href=&quot;http://www.digitalloft.org/init/plugin_wiki/attachment/4&quot;&gt;这里&lt;/a&gt;”获取修改后的脚本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;PHP给我们提供了一个解决方案，如果你想要对比哈希值，你应该使用password_verify()或hash_equals()这两个函数。它们会对数据进行严格比较，并排除一些其他的干扰因素。但是请你注意，hash_equals()函数也可以用于字符串的比较。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析结论&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;虽然我们的分析步骤执行起来有些过于复杂，但是从黑盒测试的角度出发，我们所描述的方法也许可以给大家提供一些有价值的信息。如果某个应用程序中的密码采用了这样的一种验证机制，那么它所带来的安全问题将会超出PHP数据类型转换本身所存在的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题远不止于此&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;这个问题给我们带来的影响远远不止于此。攻击者可以将这些密码添加到字典文件中，然后对应用程序中的所有用户进行暴力破解攻击。而且，如果应用程序的密码恢复机制中存在不安全的因素，攻击者还有可能对目标账号进行不限次数的攻击，直到攻击成功为止。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;源代码&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;点击“&lt;a href=&quot;http://www.digitalloft.org/init/plugin_wiki/attachment/4&quot;&gt;这里&lt;/a&gt;”获取Python源码。&lt;/p&gt;
&lt;h3&gt;相关文章&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/99458/&quot;&gt;php:给php增加函数强制类型返回&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/94475/&quot;&gt;PHP 底层的运行机制与原理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/98986/&quot;&gt; PHP并发IO编程之路&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/87956/&quot;&gt;PHP开发安全问题总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/23559/&quot;&gt;创建安全PHP应用程序的实用建议&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/88788/&quot;&gt;我用爬虫一天时间“偷了”知乎一百万用户，只为证明PHP是世界上最好的语言 &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/54201/&quot;&gt;PHP开发者应了解的24个库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/94518/&quot;&gt;Redis 未授权访问缺陷可轻易导致系统被黑&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/102235/&quot;&gt;理解 PHP 内部函数的定义&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/12371/&quot;&gt;10个实用的PHP代码片段推荐&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.jobbole.com/104107/&quot;&gt;PHP中“＝＝”运算符的安全问题&lt;/a&gt;，首发于&lt;a href=&quot;http://blog.jobbole.com&quot;&gt;文章 – 伯乐在线&lt;/a&gt;。&lt;/p&gt;
</content:encoded><category>Uncategorized</category></item><item><title>洗心1:用心洗干净心，用血洗干净血</title><link>https://blog.wo.ai/archives/1211/</link><guid isPermaLink="true">https://blog.wo.ai/archives/1211/</guid><pubDate>Mon, 01 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;泥土笑话了多少人间垃圾，大自然消化了多少人的黑暗与邪恶。泥土希望维持自己的高尚与干净，自然希望坚持自己的青青微笑去绿化人间。&lt;/p&gt;
&lt;p&gt;泥土愿意洗干净人的欲望，但人的很多不正当欲望太邪恶，泥土也深感辛苦，它没有受到人对它的尊重。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;颂七处心偈&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;西余净端&lt;/p&gt;
&lt;p&gt;七处征心心不遂，懵懂阿难不瞥地。&lt;/p&gt;
&lt;p&gt;直饶征得见无心，也是泥中洗土块。&lt;/p&gt;
&lt;p&gt;生命的本质就是要超越人间不自由的极限，去掉枷锁而得到真正的自在&lt;/p&gt;
</content:encoded><category>Poem &amp; Reading</category></item></channel></rss>