JaguarJack's Blog

做人呢最重要的就是开心

0%

Websocket

websocket作为h5新的API, 基于HTTP的新协议, 具体资料请自行搜索

Websocket标准头信息

GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:8000
Origin: http://127.0.0.1:8000
Sec-WebSocket-Key: hj0eNqbhE/A0GkBXDRrYYw==
Sec-WebSocket-Version: 13

Sec-WebSocket-Key是由客户端(浏览器)发起的, 主要就是要根据这个来进行握手

Websocket标准response

HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept:secret

握手的关键就在这个响应的Sec-WebSocket-Accept,服务要通过Sec-WebSocket-Key和常量字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11合并成新的字符串之后进行sha1加密, 然后在进行base64加密之后得到Sec-WebSocket-Accept, 返回给客户端认证握手。

GO实现

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

import (
"net"
"fmt"
"strings"
"crypto/sha1"
"io"
"encoding/base64"
)

const (
WEBSOCKET_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
)

func main() {
connect()
}


func connect() {
//监听
ln, err := net.Listen("tcp", ":8000")
if err != nil {
fmt.Println(err)
}

for {
//接受客户端连接
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
}

for {
handConnect(conn)
}
}
}

func handConnect(conn net.Conn) {
content := make([]byte, 1024)
n, err := conn.Read(content)
if err != nil {
fmt.Println(err)
}
fmt.Println(fmt.Sprintf("读取%d个字节", n))

header := parseHeaders(string(content))
fmt.Println(header["Sec-WebSocket-Key"])

secret := getSecret(header["Sec-WebSocket-Key"])

response := "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
response += "Upgrade: websocket\r\n"
response +="Connection: Upgrade\r\n"
response +="Sec-WebSocket-Accept: " + secret +"\r\n"
response += "\r\n"

conn.Write([]byte(response))

}

func parseHeaders(content string) map[string]string {
h := strings.Split(content, "\r\n")
header := make(map[string]string, 0)
for _, value := range h {
v := strings.Split(value, ":")
//注意这里切片长度一定要大于2
if len(v) >= 2 {
//空格也必须去除
header[strings.Trim(v[0], " ")] = strings.Trim(v[1], " ")
}
}
return header
}

func getSecret(key string) string{
key += WEBSOCKET_KEY
res := sha1.New()
io.WriteString(res, key)
return base64.StdEncoding.EncodeToString(res.Sum(nil))
}

上面就是简单的实现, 然后就是如何解析frame。

Laravel

问题可能出现的场景 可能就是你没有自己定义登录中间件, 使用默认的auth的时候。

当我在使用了Laravel的默认的用户认证Trait时候, 并且已经在auth配置文件配置了guards。重写该门面方法。

1
2
3
4
protected function guard()
{
return Auth::guard('admin');
}

照理讲当你自定义完该方法, 并且切换了guard之后, 就应该可以了, 可是在并不能通过,尝试了找到了登录验证方法

1
2
3
4
5
6
7
8
9
10
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();

$this->clearLoginAttempts($request);

return $this->authenticated($request, $this->guard()->user())
?: redirect()->intended($this->redirectPath());
}

我通过打印$this->gurad()->user()之后发现已经验证成功, 可是并不能通过, 所以问题出现在哪里呢?登录之后应该会自动跳转到$this->redirectPath();就是你自定义跳转的页面, 查看该页面的路由, 发现我用了auth的中间件,来看一下auth中间件的做的事儿
发现这个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);

return $next($request);
}

/**
* Determine if the user is logged in to any of the given guards.
*
* @param array $guards
* @return void
*
* @throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
}

foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}

throw new AuthenticationException('Unauthenticated.', $guards);
}

…$gurad可变参数,这里就没错了, 在auth中间还需要认证guard, 这里在authenticate也可以发现,auth中间件默认使用的是auth配置里面default的门面。具体可以查看Auth源码。 这里就不去解释了。但是如果你切换了guard的话, 你就必须加上认证的guards。
在你的路由文件这样写 ->middleware(‘auth:admin’);当然从这个中间件来看, 支持多个guard。 具体在文档的 中间件可以查看

在看到 go 字符串的时候, 偶然看到 []rune(s), 它可以将字符串转化成 unicode 码点。那么它和 **[]byte(s)**有什么区别呢?来测试一下

1
2
3
first := "fisrt"
fmt.Println([]rune(first))
fmt.Println([]byte(first))

[102 105 115 114 116] //输出结果[]rune
[102 105 115 114 116] //输出结果[]byte

从输出来看, 没有任何区别, 作者不可能无缘无故弄出两个相同的东西,那么到底区别在哪里呢?翻看源码才知道

1
2
3
4
5
6
7
8
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

原来是 byte 表示一个字节,rune 表示四个字节,那么就可以得出了结论了,来看一段代码,使用中文字符串

1
2
3
first := "社区"
fmt.Println([]rune(first))
fmt.Println([]byte(first))

[31038 21306] //输出结果[]rune
[231 164 190 229 140 186]//输出结果[]byte

这里也可以很清晰的看出这里的中文字符串每个占三个字节, 区别也就一目了然了。
说道这里正好可以提一下 Go 语言切割中文字符串,Go 的字符串截取和切片是一样的s[n:m]左闭右开的原则, 看一下例子

1
2
s := "golangcaff"
fmt.Println(s[:3])

gol //输出, 看起来没问题, 顺利截取了三个字符

如果换成中文的呢?来看一下例子

1
2
3
s := "截取中文"
//试试这样能不能截取?
fmt.Println(s[:2])

?? //输出 在预料之中, 输出了常见的??

那么该如何截取呢?这里就需要将中文利用 []rune 转换成 unicode 码点, 再利用 string 转化回去, 来看一下例子。

1
2
3
4
s := "截取中文"
//试试这样能不能截取?
res := []rune(s)
fmt.Println(string(res[:2]))

截取 //输出, 顺利截取了

当然你可以使用 []byte 来截取, 但是这样你就需要知道你的中文字符到底占几个字节, 似乎这种方法不可取, 因为你无法得知。
为什么s[:n]无法直接截取呢, 通过实验我猜测如果直接截取的话,底层会将中文转化成[]byte, 而不是**[]rune**。你可以尝试一下:

1
2
3
s := "截取中文"
//试试这样能不能截取?
fmt.Println(s[:3])

截 //输出

当然这只是我猜测的, 没有看源码具体实现。

VIM

准备阶段, 一台空的centos7系统。如果你所使用的环境不是初始化的服务器的话, 以下请适当安装需要的依赖。安装这个果然是很难啊,我不得不说太难了, 翻阅了很多 资料, 各种google。 尤其是在centos上, 简直了, 如果是乌班图Ubuntu的话, 可能还好一点,因为在编译的YcmCompleteMe的时候, 自动下载的clang是Ubuntu的,所以之后需要centos需要自己编译安装

安装一些基础的依赖

yum -y update
yum -y install git
yum -y install ncurses-devel
yum install python-devel libffi-devel graphviz-devel elfutils-libelf-devel readline-devel libedit-devel libxml2-devel protobuf-devel gtext-devel doxygen swig -y

升级gcc & vim

yum install centos-release-scl -y
yum install devtoolset-3-toolchain -y
yum install gcc-c++
scl enable devtoolset-3 bash

安装最新版vim, 最好是8.0以上, 不然会出现问题。
vim //查看你的vim版本

git clone https://github.com/vim/vim.git
cd vim/src/
./configure –with-features=huge -enable-pythoninterp –with-python-config-dir=/usr/lib/python2.7/config
make && make install

安装Vundle 插件管理

git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim //提示版本可能会更新, 请于github查看
vim ~/.vimrc //没有就将下面粘贴进去, 如果有错误, 请到github获取官方配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
set nocompatible              " be iMproved, required
filetype off " required=

set nocompatible " be iMproved, required
filetype off " required
" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()
" alternatively, pass a path where Vundle should install plugins
"call vundle#begin('~/some/path/here')

" let Vundle manage Vundle, required
Plugin 'VundleVim/Vundle.vim'

" The following are examples of different formats supported.
" Keep Plugin commands between vundle#begin/end.
" plugin on GitHub repo
Plugin 'tpope/vim-fugitive'
" plugin from http://vim-scripts.org/vim/scripts.html
" Plugin 'L9'
" Git plugin not hosted on GitHub
Plugin 'git://git.wincent.com/command-t.git'
" git repos on your local machine (i.e. when working on your own plugin)
" Plugin 'file:///home/gmarik/path/to/plugin'
" The sparkup vim script is in a subdirectory of this repo called vim.
" Pass the path to set the runtimepath properly.
Plugin 'rstacruz/sparkup', {'rtp': 'vim/'}
" Install L9 and avoid a Naming conflict if you've already installed a
" different version somewhere else.
" Plugin 'ascenator/L9', {'name': 'newL9'}

" All of your Plugins must be added before the following line
call vundle#end() " required
filetype plugin indent on " required
" To ignore plugin indent changes, instead use:
"filetype plugin on
"
" Brief help
" :PluginList - lists configured plugins
" :PluginInstall - installs plugins; append `!` to update or just :PluginUpdate
" :PluginSearch foo - searches for foo; append `!` to refresh local cache
" :PluginClean - confirms removal of unused plugins; append `!` to auto-approve removal
"
" see :h vundle for more details or wiki for FAQ
" Put your non-Plugin stuff after this line

vim
:PluginInstall //安装插件

安装YouCompleteMe

vim ~/.vimrc

1
2
3
4
5
all vundle#begin()  
. . .
Plugin 'Valloric/YouCompleteMe’ //加入 YouCompleteMe
. . .
call vundle#end()

继续vim 然后:PluginInstall

升级Cmake

wget https://cmake.org/files/v3.11/cmake-3.11.3.tar.gz
tar xvf cmake-3.11.3.tar.gz && cd cmake-3.11.3/
./bootstrap
gmake
gmake install
//查看版本
/usr/local/bin/cmake –version
//移除自带的版本
yum remove cmake -y
//新建软连接
ln -s /usr/local/bin/cmake /usr/bin/

到这里第一部分算是完成了, 千万不能立即编译YcmCompleteMe, 因为centos根本就不会自动下载clang所以这里我们需要自己安装, 过程非常漫长, 要等待很长时间, 所以务必耐心等待

下载文件

http://releases.llvm.org/download.html#6.0.0首先到这个页面Source下面除了**LLVM Test Suite (.sig)**以外的全部下载下来

准备 LLVM 源码

LLVM 的各个子组件必须放在 LLVM 源码的固定位置,使用固定名称。
首先解压 llvm-6.0.0.src.tar.xz,生成目录 llvm-6.0.0.src
解压 tar.xz 文件使用 tar Jxvf filename.tar.xz -C 指定路径
解压文件到指定文件夹tar Jxvf filename.tar.xz -C 指定路径
mv 源文件 新文件名来修改文件名

放在 llvm-6.0.0.src/tools 目录下的组件:

解压 cfe-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/tools/clang
解压 lld-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/tools/lld
解压 polly-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/tools/polly
解压 lldb-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/tools/lldb

放在 llvm-6.0.0.src/projects 目录下的组件:

解压 openmp-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/projects/openmp
解压 libcxx-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/projects/libcxx
解压 libcxxabi-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/projects/libcxxabi
解压 libunwind-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/projects/libunwind
解压 compiler-rt-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/projects/compiler-rt

clang 的子组件:

解压 clang-tools-extra-6.0.0.src.tar.xz,重命名为 llvm-6.0.0.src/tools/clang/tools/extra

使用 CMake 生成 Makefile

  1. cd llvm-6.0.0.src //首先进入解压的目录
  2. mkdir build && cd build
  3. cmake .. -DLLVM_OPTIMIZED_TABLEGEN=ON -DLLVM_INCLUDE_DOCS=OFF -DLLVM_ENABLE_RTTI=ON -DLLVM_ENABLE_EH=ON -DLLVM_INSTALL_UTILS=ON -DWITH_POLLY=ON -DLINK_POLLY_INTO_TOOLS=ON -DLLVM_TARGETS_TO_BUILD=X86 -DLIBOMP_ARCH=x86_64 -DBUILD_SHARED_LIBS=ON -DLIBOMP_ENABLE_SHARED=ON -DLLVM_ENABLE_LIBCXX=ON -DLLDB_RELOCATABLE_PYTHON=ON -DLLVM_ENABLE_FFI=ON -DCMAKE_BUILD_TYPE=Release -DCLANG_DEFAULT_CXX_STDLIB=libc++ -DCLANG_DEFAULT_RTLIB=compiler-rt -DCLANG_INCLUDE_TESTS=OFF -DENABLE_LINKER_BUILD_ID=ON -DENABLE_X86_RELAX_RELOCATIONS=ON -DLIBCXXABI_INCLUDE_TESTS=OFF -DLIBCXX_INCLUDE_BENCHMARKS=OFF -DLIBCXX_INCLUDE_TESTS=OFF -DLIBOMP_TSAN_SUPPORT=ON -DLLDB_BUILD_INTEL_PT=OFF -DLLDB_INCLUDE_TESTS=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_GO_TESTS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INSTALL_BINUTILS_SYMLINKS=ON -DLLVM_TOOL_LLGO_BUILD=ON -DLLVM_TOOL_PARALLEL_LIBS_BUILD=ON -DLLVM_ENABLE_PIC=ON -DLIBUNWIND_ENABLE_SHARED=OFF -DLIBCXX_ENABLE_PEDANTIC=ON -DLIBCXXABI_LIBDIR_SUFFIX=64 -DLIBCXX_LIBDIR_SUFFIX=64 -DLIBUNWIND_LIBDIR_SUFFIX=64 -DLLVM_LIBDIR_SUFFIX=64

编译(时间较长, 注意等待)

make -j4 // -j4 表示并发执行 4 个任务,这个数字指定 CPU 核心数为佳
make install

在编译阶段遇到的问题

这里的问题是以我本身环境所决定, 具体的如果你遇到请自行搜索查阅

内存不足

这里如果你的机器内存太小, 比如我的内存只有1G, 所以需要借助swap
dd if=/dev/zero of=/swapfile bs=64M count=16 //bs代表块大小, count需要多少块
mkswap /swapfile
swapon /swapfile
// 编译结束以后一定要删除
swapoff /swapfile
rm /swapfile

遇到未找到的问题

/usr/bin/ld: cannot find -lgtest
如果是遇到这个问题, 是越少googletest框架, 参考https://stackoverflow.com/questions/13513905/how-to-setup-googletest-as-a-shared-library-on-linux

git clone https://github.com/google/googletest.git
cd googletest
cmake -DBUILD_SHARED_LIBS=ON .
make
cp -a googletest/include/gtest /usr/include
cp -a googlemock/gtest/libgtest_main.so googlemock/gtest/libgtest.so /usr/lib/

遇到Can’t locate Data/Dumper.pm in @INC…问题

wget http://www.cpan.org/modules/by-module/Data/Data-Dumper-2.154.tar.gz
tar xvzf Data-Dumper-2.154.tar.gz
cd Data-Dumper-2.121
perl Makefile.PL
make
make install
//如果遇到Can’t locate ExtUtils/MakeMaker.pm…该错误
yum install perl-ExtUtils-CBuilder perl-ExtUtils-MakeMaker cpan

下面就是编译YcmCompleteMe

由于你不是直接编译, 而是通过另外编译Clang, 所以不能使用install.sh脚本。

mkdir ~/ycm_build
cd ~/ycm_build
cmake -G “Unix Makefiles” -DEXTERNAL_LIBCLANG_PATH=/usr/local/lib64/libclang.so . ~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp
// 这里需要你自己查找机器libclang的地方, find / -name “libclang”

生成ycm_core

cmake –build . –target ycm_core –config Release

至此,YouCompleteMe已经算是安装成功!
注意:这时候,ycm_build目录可以删除啦!

安装成功后,ycm_build以及ycm_temp目录都可以删除,不影响YouCompleteMe插件的使用。

如果vim 出现
NoExtraConfDetected: No .ycm_extra_conf.py file detected, so no compile flags are available. Thus no semantic support for C/C++/ObjC/ObjC++. Go READ THE DOCS NOW, DON’T file a bug r…
https://github.com/Valloric/ycmd/blob/master/cpp/ycm/.ycm_extra_conf.py该页面下载, 然后修改进行修改
cd ~/.vim/bundle/YouCompleteMe
查看有没有cpp文件夹, 没有就创建mkdir cpp/ycm, 然后将下载.ycm_extra_conf.py放进该文件夹下, 然后编辑

1
2
3
4
5
6
7
'-isystem',
'/usr/include',
'-isystem',
'/usr/include/c++/4.8.5',//注意这是你自己版本
'-isystem',
'/usr/include',
'/usr/include/x86_64-linux-gnu/c++', //这个也是你机器实际的位置

再次打开 .vimrc 配置YCM,如下:

“YouCompleteMe
let g:ycm_global_ycm_extra_conf=’~/.vim/bundle/YouCompleteMe/cpp/ycm/.ycm_extra_conf.py’

这样就算完成了, 可以自动补全了, 安装这个真的太费劲了, 简直不能忍受啊。 真的太繁琐了。

在安装phalcon的时候遇到内存不足的问题,需要swap增加内存,可是在docker里面遇到

1
swapon failed: Operation not permitted

该问提百思不得其姐,最后在docker官网找到了解释,最后说一下,搜索还是google靠谱

Hi,
As each running docker container uses the host Kernel, they also use the memory and swap of the host. If this is a one of requirement its better to increase the host swap space.
If you want to still add swap from the container you have two options.
Run container in privileged mode
In this case you will have to run the container with –privileged option.

Example

docker run -it –rm –privileged centos:6
Running container with privileged mode gives container full privilege on the host. If you read the manpage of swapon you can see that for swapon to run the process should have CAP_SYS_ADMIN capability. In Docker you can add a particular capability selectively to the container using the –cap-add argument.

Example

docker run -it –rm –cap-add SYS_ADMIN centos:6
If you run the container in either of the above two modes you can achieve what you are trying.
Now the problem with this approach is , when you create swap inside the container and start using that, its actually the Host Kernel that is using it, as a result when you exit the container without a swapoff the host kernel will be still using the file, and you wont get a clean exit of the container. You will see a dead status for the container.

嗨,
由于每个运行的docker容器都使用主机内核,它们还使用主机的内存和交换。如果这是要求更好的增加主机交换空间的一个。
如果你想从容器中添加交换,你有两个选择。
以特权模式运行容器
在这种情况下,您将必须使用–privileged选项运行容器。
#示例
docker run -it –rm –privileged centos:6
运行具有特权模式的容器可以为主机提供容器完整权限。如果您阅读swapon的联机帮助页面,您可以看到,对于swapon来运行该进程应该具有CAP_SYS_ADMIN功能。在Docker中,您可以使用-cap-add参数有选择地向容器添加特定的功能。
#示例
docker run -it –rm –cap-add SYS_ADMIN centos:6
如果您以上述两种模式运行容器,您可以实现您正在尝试的功能。
现在,这种方法的问题是,当您在容器内创建交换并开始使用它时,实际上是使用它的主机内核,因此当您退出容器而不进行swapoff时,主机内核将仍然使用该文件,你不会得到一个干净的出口的容器。您将看到容器的死亡状态。

有时候docker拉镜像的确有很慢哈,官方也考虑到这一点了,所以呢,允许你建立自己的私有仓库,以下只是简单创建,没有考虑安全性问题

三步骤

拉取

1
docker pull registry

根据下载的镜像创建并运行一个容器

1
docker run -p 5000:5000 -v /data/registry:/var/lib/registry --name registry -d registry
  • -p选项用于将宿主机的端口映射到容器的端口,这样就可以通过宿主机的地址访问容器服务
  • -v选项用于将宿主机的目录挂在到容器的目录,便于直接在宿主机上查看上传的镜像

访问 http://xxx.xxxx.xxx:5000/v2,你会在网页看到一个{},说明建立成功了

上传镜像到私有仓库

通过 docker tag 将该镜像标志为要推送到私有仓库

docker tag 镜像名[:标签] 镜像仓库服务器地址/命名空间/镜像发布名:发布标签,
运行 docker pus 将镜像 push 到我们的私有仓库中

docker push 镜像仓库服务器地址/命名空间/镜像发布名:发布标签
本例中,我们操作如下

1
2
docker tag php xxx.xxx.xxx:5000/php
docker push xxx.xxx.xxx:5000/php

如果遇到一下问题

http: server gave HTTP response to HTTPS client

对应的解决方法

请修改改文件 /etc/docker/daemon.json

{ “insecure-registries”:[“myregistry.example.com:5000”] }

1
2
3
systemctl restart docker.service

docker restart registry

然后在此 push,完毕后

然后在 /data/registry/docker/registry/v2/repositories/php 查看到改该镜像
以上假设是在某台服务器上的操作,现在另外一台服务准备拉取镜像,不经过官方的

1
docker pull xxx.xxx.xxx:5000/php

可能会出现上面同样的错误解决办法是一样的

http: server gave HTTP response to HTTPS client

这样就可以了

FFMPEG

CentOS 7,具有完全 root 访问权限。

注意:此方法的 ffmpeg 安装也适用于 centos 6.x,cpanel,directadmin
从 repo 导入 GPG 密钥:

1
rpm --import http://packages.atrpms.net/RPM-GPG-KEY.atrpms

安装 ATRPMS Repo:

1
rpm -ivh http://dl.atrpms.net/all/atrpms-repo-6-7.el6.x86_64.rpm

Ffmpeg 需要 libdc1394-devel,它在 epel 库中可用。 执行以下命令安装 epel 存储库:

1
rpm -Uvh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm

ATRPMS 安装 FFMpeg 存储库

1
yum -y --enablerepo = atrpms install ffmpeg ffmpeg-devel

验证ffmpeg版本:

1
2
3
4
5
6
7
8
9
10
11
12
ffmpeg -version
ffmpeg version 2.2.1
built on Jun 17 2014 01:25:46 with gcc 4.8.2 (GCC) 20140120 (Red Hat 4.8.2-16)
configuration: --prefix=/usr --libdir=/usr/lib64 --shlibdir=/usr/lib64 --mandir=/usr/share/man --enable-shared --enable-runtime-cpudetect --enable-gpl --enable-version3 --enable-postproc --enable-avfilter --enable-pthreads --enable-x11grab --enable-vdpau --disable-avisynth --enable-frei0r --enable-libdc1394 --enable-libgsm --enable-libmp3lame --enable-libnut --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librtmp --enable-libspeex --enable-libtheora --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libxavs --enable-libxvid --extra-cflags='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --disable-stripping
libavutil 52. 66.100 / 52. 66.100
libavcodec 55. 52.102 / 55. 52.102
libavformat 55. 33.100 / 55. 33.100
libavdevice 55. 10.100 / 55. 10.100
libavfilter 4. 2.100 / 4. 2.100
libswscale 2. 5.102 / 2. 5.102
libswresample 0. 18.100 / 0. 18.100
libpostproc 52. 3.100 / 52. 3.100

使用方法摘自国外博客,有兴趣的可以看看
FFMPEG 博客

watermarking-videos-from-the-command-line-using-ffmpeg-filters/
在某些情况下,您可能不知道要加入水印的视频的确切尺寸。 幸运的是,有一些变量可以用来更好地定位水印,这取决于视频的大小。 这些变量包括:

  • main_h - 视频的高度
  • main_w - 视频的宽度
  • overlay_h - 重叠广告的高度
  • overlay_w - 重叠式广告的宽度

使用这些变量,我们可以将水印定位在视频的中心,如下所示:

1
2
ffmpeg -i birds.mp4 -i watermark.png
-filter_complex "overlay=x=(main_w-overlay_w)/2:y=(main_h-overlay_h)/2" birds2.mp4

如果我们想要为剪辑添加品牌或水印,但不覆盖现有视频,我们可以使用 pad 过滤器为剪辑添加一些填充,然后将我们的水印放在填充上,如下所示:

1
2
3
ffmpeg -i birds.mp4 -i watermark2.png
-filter_complex "pad=height=ih+40:color=#71cbf4,overlay=(main_w-overlay_w)/2:main_h-overlay_h"
birds3.mp4

一旦你开始得到这个的概念之后,你甚至可以让你的水印动起来

1
2
ffmpeg -i birds.mp4 -i watermark.png
-filter_complex "overlay='if(gte(t,1), -w+(t-1)*200, NAN)':(main_h-overlay_h)/2" birds4.mp4

如果遇到这个 error

1
The encoder 'aac' is experimental but experimental codecs are not enabled, add '-strict -2' if you want to use it.

那就添加个参数吧

1
ffmpeg -i yii.mp4 -i logo.jpg -strict -2 -filter_complex "overlay=x=(main_w-overlay_w)/2:y=(main_h-overlay_h)/2" birds2.mp4

如果遇到其他错误,就去查查资料吧

前端代码,不一定适合你的项目要求,可以做一定量修改!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
var fileMd5;
//监听分块上传过程中的三个时间点
WebUploader.Uploader.register({
"before-send-file":"beforeSendFile",
"before-send":"beforeSend",
"after-send-file":"afterSendFile",
},{
//时间点1:所有分块进行上传之前调用此函数
beforeSendFile:function(file){
var deferred = WebUploader.Deferred();
//1、计算文件的唯一标记,用于断点续传
(new WebUploader.Uploader()).md5File(file)
.progress(function(percentage){
$('#item1').find("p.state").text("正在读取文件信息...");
})
.then(function(val){
fileMd5=val;
$('#item1').find("p.state").text("成功获取文件信息...");
//获取文件信息后进入下一步
deferred.resolve();
});
return deferred.promise();
},
//时间点2:如果有分块上传,则每个分块上传之前调用此函数
beforeSend:function(block){
var deferred = WebUploader.Deferred();

$.ajax({
type:"POST",
url:"uploadVideo.php",
data:{
//文件唯一标记
fileMd5:fileMd5,
//当前分块下标
chunk:block.chunk,
//当前分块大小
chunkSize:block.end-block.start ,
'option':'checkChunk',
},
dataType:"json",
success:function(response){
if(response.status == 'error'){
//分块存在,跳过
deferred.reject();
}else{
//分块不存在或不完整,重新发送该分块内容
deferred.resolve();
}
}
});

this.owner.options.formData.fileMd5 = fileMd5;
deferred.resolve();
return deferred.promise();
},
//时间点3:所有分块上传成功后调用此函数
afterSendFile:function(block){
//如果分块上传成功,则通知后台合并分块
$.ajax({
type:"POST",
url:"uploadVideo.php",
data:{
fileMd5:fileMd5,
'option':'merge',
},
success:function(response){
if (response.status == 'success') {
$('input[name=url]').val(response.data.video_path);
$('.webuploader-pick').html('视频上传成功');
$('#item1').html(' ');
} else {
layui.msg(response.msg);
}
}
});
}
});

var uploader = WebUploader.create({
// swf文件路径
swf: '/Public/webuploader/Uploader.swf',
// 文件接收服务端。
server:"uploadVideo.php",
// 选择文件的按钮。可选。
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
pick: {id: '#filePicker',multiple:true},
// 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
resize: true,
auto:true,
//开启分片上传
chunked: true,
chunkSize: 2 * 1024 * 1024,

accept: {
//限制上传文件为MP4
extensions:'mp4',
mimeTypes: 'video/mp4',
},
});

// 文件上传过程中创建进度条实时显示。
uploader.on( 'uploadProgress', function( file, percentage ) {
$('.webuploader-pick').html('视频正在上传中...'+Math.round(percentage * 100) + '%');
});

uploader.on( 'uploadError', function( file ) {
$('.webuploader-pick').html('视频出错了');
$('#item1').html('');

});

后端利用的是php,不一定适合你的项目,可以做修改这里只是提供思路,可到github看官方的列子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
class uploadVideo
{
//视频块的大小
//设置上传的目录
private $upload_path = './VideoTemp/';
//文件格式
private $exts = ['mp4'];
private $tem_path;
private $allow_host = '';
//视频地址
private $host = '';
//资源大小
private $size = 2;

public function __construct() {
// header('Access-Control-Allow-Origin:' . $this->allow_host);
header('Access-Control-Allow-Origin:*');//项目要求跨域,解决跨域请求
}

public function upload()
{
$data = $_POST;
//检查块是否上传
if ($data['option'] == 'checkChunk') {
$this->checkChunk();
//合并视频块
} else if ($data['option'] == 'merge') {
$this->merge();
//上传资源
} else {
$this->uploadFile();
}

}

protected function uploadFile()
{
$data = $_POST;

$file = $_FILES['file'];
//临时目录,以上传文件md5加密
$tem_path = $this->upload_path.$data['fileMd5'].'/';

//检测上传视频的格式
if (!$ext = $this->checkExt($file['name'])) {
$this->ajaxError('视频格式只能mp4');
}

//创建临时目录
if (!is_dir($tem_path)) {
mkdir($tem_path,0777,true);
}

//组装块名
$file_name = $data['fileMd5'] . '_' . $data['chunk'];
$file_name = $file_name.'.' .$ext;

//上传块
$result =move_uploaded_file($file['tmp_name'], $tem_path.$file_name);

return $result == false ? $this->ajaxError('文件上传失败') : $this->ajaxSuccess('文件上传成功');

}


private function checkExt($filename)
{
$ext = array_pop(explode('.',$filename));

return in_array($ext, $this->exts) ? $ext : false;
}

protected function checkChunk()
{
$data = $_POST;

$file_name = $data['fileMd5'] . '_' . $data['chunk'] . '.mp4';

//视频块路径
$blocks_path = $this->upload_path.$data['fileMd5'].'/';
return file_exists($blocks_path.$file_name) ? $this->ajaxError('该块已经上传过') : $this->ajaxSuccess('上传成功');
}

protected function ajaxError($msg='', $fields=array())
{
header('Content-Type:application/json; charset=utf-8');
$data = array('status'=>'error', 'msg'=>$msg, 'fields'=>$fields);
echo json_encode($data);
exit;
}
protected function ajaxSuccess($msg, $_data=array())
{
header('Content-Type:application/json; charset=utf-8');
$data = array('status'=>'success', 'msg' => $msg ,'data'=>$_data);
echo json_encode($data);
exit;
}

protected function merge()
{
$data = $_POST;

//视频块临时目录
$block_path = $this->upload_path.$data['fileMd5'].'/';

//chunks数量
$chunksArray = glob($block_path.'*.mp4');
$numbers = count($chunksArray);

//合并视频地址
$mergeFileName = $this->upload_path . $data['fileMd5'] .'.mp4';

//每次读取资源大小2M
$filezie = $this->size * 1024 * 1024;

//处理视频块地址
$fp2 = fopen($mergeFileName,"wb+");//二进制方法打开

if ( flock($fp2, LOCK_EX) ) {
for($key = 0 ; $key < $numbers ; $key++)
{
$path = $block_path . $data['fileMd5'] . '_' . $key . '.mp4';

$fp = fopen($path, "rb");

while ($buff = fread($fp, $filezie)) {
fwrite($fp2, $buff);
}

unset($buff);

fclose($fp);

}

flock($fp2, LOCK_UN);
}

fclose($fp2);

//验证视频的散列值是否一致
if (md5_file($mergeFileName) == $data['fileMd5']) {
foreach ($chunksArray as $vo) {
//删除视频块
unlink($vo);
}
//删除临时文件夹
rmdir($block_path);

$this->ajaxSuccess('视频上传成功',['video_path' => $this->host . ltrim($mergeFileName,'.')]);
} else {
$this->ajaxError('视频上传失败');
}

}
}

$uploadVideo = new uploadVideo2();

$uploadVideo->upload();

centos7 默认使用firewalld防火墙配置,以及修改iptables

开启服务

1
systemctl start firewalld.service

关闭防火墙

1
systemctl stop firewalld.service

开机自动启动

1
systemctl enable firewalld.service

关闭开机制动启动

1
systemctl disable firewalld.service

查看状态

1
systemctl status firewalld

查看状态

1
firewall-cmd --state    //running 表示运行

获取活动的区域

1
firewall-cmd --get-active-zones

获取所有支持的服务

1
firewall-cmd --get-service

在不改变状态的条件下重新加载防火墙:

1
firewall-cmd --reload

防火墙预定义的服务配置文件是xml文件 目录在 /usr/lib/firewalld/services/

在 /etc/firewalld/services/ 这个目录中也有配置文件 但是/etc/firewalld/services/目录 优先于 /usr/lib/firewalld/services/ 目录

修改配置文件后 使用命令重新加载

1
firewall-cmd --reload

启用某个服务

1
2
firewall-cmd --zone=public --add-service=https   //临时
firewall-cmd --permanent --zone=public --add-service=https //永久

开启某个端口

1
2
firewall-cmd --permanent --zone=public --add-port=8080-8081/tcp  //永久
firewall-cmd --zone=public --add-port=8080-8081/tcp //临时

使用命令加载设置

1
firewall-cmd --reload

查看开启的端口和服务

1
2
firewall-cmd --permanent --zone=public --list-services    //服务空格隔开  例如 dhcpv6-client https ss   
firewall-cmd --permanent --zone=public --list-ports //端口空格隔开 例如 8080-8081/tcp 8388/tcp 80/tcp

在每次修改 端口和服务后 /etc/firewalld/zones/public.xml 文件就会被修改 所以也可以在文件中之间修改 然后重新加载

删除上面设置的规则

1
#firewall-cmd --permanent --zone=public --remove-rich-rule="rule family="ipv4"  source address="192.168.0.4/24" service name="http" accept"

启动服务

1
2
3
4
systemctl start firewalld.service  //开启服务
systemctl enable firewalld.service //开机制动启动
systemctl stop firewalld.service //关闭服务
systemctl disable firewalld.service //禁止开机启动

安装iptables防火墙(在这之前关闭firewalld)

1
2
3
4
yum install iptables-services #安装
vi /etc/sysconfig/iptables #编辑防火墙配置文件
systemctl restart iptables.service #最后重启防火墙使配置生效
systemctl enable iptables.service #设置防火墙开机启动