月度归档:2017年01月

Zephir用类似PHP的语法写PHP C扩展

Zephir是一个非常有意思的项目。它是专门针对编写PHP C扩展而创建的,是PHP C框架Phalcon的实现语言。Zephir提供了类似PHP的语法,然后转换成PHP C扩展代码,然后就是正常的C扩展编译和安装。

官网:http://zephir-lang.com/

Zephir依赖re2c来进行语法分析(PHP本身也使用re2c来进行语法分析),需要把Zephir代码转换为PHP C扩展的C代码,所以依赖PHP的头文件,然后需要编译,那么就必须有编译器(gcc):(https://docs.zephir-lang.com/en/latest/install.html)

# 安装PHP的头文件,如果是RPM安装,需要安装devel包
php70w-devel-7.0.13-1.w7.x86_64

# 安装re2c:http://re2c.org/
yum install re2c

# 安装编译器
yum install gcc make autoconf git

#安装
git clone https://github.com/phalcon/zephir
cd zehpir
./install -c   #拷贝zephir/bin/zephir 到  /usr/local/bin/zephir

zephir help

编译实例:

git clone --depth=1 https://github.com/phalcongelist/beanspeak.git
cd beanspeak
zephir build
# Or, for PHP 7 use
zephir build --backend=ZendEngine3

参考例子:

#https://github.com/phalcongelist/beanspeak/blob/master/beanspeak/client.zep
namespace Beanspeak;

/**
 * Beanspeak\Client
 *
 * Class to access the beanstalk queue service.
 *
 * Implements the beanstalk protocol spec 1.10.
 *
 * <code>
 * use Beanspeak\Client;
 *
 * // Each connection parameter is optional
 * $queue = new Client([
 *     'host'       => '127.0.0.1', // The beanstalk server hostname or IP address to connect to
 *     'port'       => 11300,       // The port of the server to connect to
 *     'timeout'    => 60,          // Timeout in seconds when establishing the connection
 *     'persistent' => true,        // Whether to make the connection persistent or not
 *     'wretries'   => 8,           // Write retries
 * ]);
 * </code>
 *
 * @link https://github.com/kr/beanstalkd/
 */
class Client
{
	/**
	 * The current socket connection.
	 * @var resource
	 */
	protected socket;

	/**
	 * The current connection options.
	 * @var array
	 */
	protected options = [];

	/**
	 * The current used tube.
	 * @var string
	 */
	protected usedTube = "default";

	/**
	 * The current watched tubes.
	 * @var array
	 */
	protected watchedTubes = [ "default" : true ];

	/**
	 * Beanspeak\Client constructor
	 */
	public function __construct(array options = null)
	{
		array defaults = [
			"host"       : "127.0.0.1",
			"port"       : "11300",
			"persistent" : true,
			"timeout"    : "60",
			"wretries"   : 8
		];

		if typeof options != "array" {
			let this->{"options"} = defaults;
		} else {
			let this->{"options"} = options + defaults;
		}
	}

	/**
	 * Makes a connection to the Beanstalk server.
	 *
	 * The resulting stream will not have any timeout set on it.
	 * Which means it can wait an unlimited amount of time until a packet
	 * becomes available.
	 *
	 * @throws Exception
	 */
	public function connect() -> resource
	{
		var e, options, socket, usedTube, tube;

		if this->isConnected() {
			this->disconnect();
		}

		let options = this->options;

		try {
			if options["persistent"] {
				let socket = pfsockopen(options["host"], options["port"], null, null, options["timeout"]);
			} else {
				let socket = fsockopen(options["host"], options["port"], null, null, options["timeout"]);
			}

			if typeof socket != "resource" {
				throw new Exception("Can't connect to Beanstalk server.");
			}
		} catch  \Exception, e {
			throw new Exception(e->getMessage());
		}

		stream_set_timeout(socket, -1, null);

		let this->{"socket"} = socket,
			usedTube = this->usedTube;

		if usedTube != "default" {
			this->useTube(usedTube);
		}

		for tube, _ in this->watchedTubes {
			if tube != "default" {
				unset(this->watchedTubes[tube]);
				this->watch(tube);
			}
		}

		if !isset this->watchedTubes["default"] {
			this->ignore("default");
		}

		return socket;
	}

	/**
	 * Closes the connection to the Beanstalk server.
	 *
	 * Will throw an exception if closing the connection fails, to allow
	 * handling the then undefined state.
	 *
	 * @throws Exception
	 */
	public function disconnect() -> boolean
	{
		var socket, status;

		if !this->isConnected() {
			return false;
		}

		let socket = this->socket,
			status = fclose(socket);

		if !status {
			throw new Exception("Failed to close connection.");
		}

		let this->{"socket"}       = null,
			this->{"usedTube"}     = "default",
			this->{"watchedTubes"} = [ "default" : true ];

		return true;
	}

	/**
	 * Whether the connection is established or not.
	 */
	public function isConnected() -> boolean
	{
		return typeof this->socket == "resource";
	}

	/**
	 * Inserts a job into the client's currently used tube.
	 *
	 * <code>
	 * $task = [
	 *     'recipient' => 'user@mail.com',
	 *     'subject'   => 'Welcome',
	 *     'content'   => $content,
	 * ];
	 *
	 * $put = $queue->pit($task, 999, 60 * 60, 3600);
	 * </code>
	 */
	public function put(var data, int priority = 1024, int delay = 0, int ttr = 86400) -> int|boolean
	{
		var status, response, serialized, length;

		// Data is automatically serialized before be sent to the server
		let serialized = serialize(data),
			length     = strlen(serialized);

		this->write("put " . priority . " " . delay . " " . ttr . " " . length . "\r\n" . serialized);

		let response = this->readStatus();

		if isset response[1] {
			let status = response[0];

			if status == "INSERTED" || status == "BURIED" {
				return intval(response[1]);
			}
		}

		return false;
	}

	/**
	 * Inserts a job into the desired tube.
	 *
	 * <code>
	 * $task = [
	 *     'recipient' => 'user@mail.com',
	 *     'subject'   => 'Welcome',
	 *     'content'   => $content,
	 * ];
	 *
	 * $queue->putInTube('tube-name', $task, 999, 60 * 60, 3600);
	 * </code>
	 */
	public function putInTube(string! tube, var data, int priority = 1024, int delay = 0, int ttr = 86400) -> boolean|int
	{
		var  response;

		let response = this->useTube(tube);
		if typeof response == "object" {
			return this->put(data, priority, delay, ttr);
		}

		return false;
	}

	/**
	 * Change the active tube.
	 *
	 * The "use" command is for producers. Subsequent put commands will put jobs
	 * into the tube specified by this command. If no use command has been issued,
	 * jobs will be put into the tube named "default".
	 *
	 * <code>
	 * $queue->useTube('mail-queue');
	 * </code>
	 *
	 * @throws Exception
	 */
	public function useTube(string! tube) -> <Client>
	{
		var response, status, used;

		let used = this->usedTube;
		if used == tube {
			return this;
		}

		this->write("use " . tube);

		let response = this->readStatus();

		if isset response[1] && response[0] == "USING" {
			let status = response[0];

			if status == "USING" {
				let this->{"usedTube"} = tube;
				return this;
			}
		}

		throw new Exception(
			"Unable to change the active tube. Server response: ". join(" ", response)
		);
	}

	/**
	 * Lets the client inspect a job in the system.
	 *
	 * <code>
	 * $peekJob = $queue->peek($jobId);
	 * </code>
	 */
	public function peekJob(int id) -> boolean|<Job>
	{
		var response;

		this->write("peek " . id);

		let response = this->readStatus();
		if isset response[2] && response[0] == "FOUND" {
			return new Job(this, response[1], unserialize(this->read(response[2])));
		}

		return false;
	}

	/**
	 * Return the delayed job with the shortest delay left.
	 *
	 * <code>
	 * $queue->peekDelayed();
	 * </code>
	 */
	public function peekDelayed() -> boolean|<Job>
	{
		var response;

		this->write("peek-delayed");

		let response = this->readStatus();
		if isset response[2] && response[0] == "FOUND" {
			return new Job(this, response[1], unserialize(this->read(response[2])));
		}

		return false;
	}

	/**
	 * Return the next job in the list of buried jobs.
	 *
	 * <code>
	 * $queue->peekBuried();
	 * </code>
	 */
	public function peekBuried() -> boolean|<Job>
	{
		var response;

		this->write("peek-buried");

		let response = this->readStatus();
		if isset response[2] && response[0] == "FOUND" {
			return new Job(this, response[1], unserialize(this->read(response[2])));
		}

		return false;
	}

	/**
	 * Inspect the next ready job.
	 *
	 * <code>
	 * $queue->peekReady();
	 * </code>
	 */
	public function peekReady() -> boolean|<Job>
	{
		var response;

		this->write("peek-ready");

		let response = this->readStatus();
		if isset response[2] && response[0] == "FOUND" {
			return new Job(this, response[1], unserialize(this->read(response[2])));
		}

		return false;
	}

	/**
	 * Moves jobs into the ready queue.
	 * The Kick command applies only to the currently used tube.
	 *
	 * <code>
	 * $queue->kick(3);
	 * </code>
	 */
	public function kick(int bound) -> boolean|int
	{
		var response;

		this->write("kick " . bound);

		let response = this->readStatus();
		if isset response[1] && response[0] == "KICKED" {
			return intval(response[1]);
		}

		return false;
	}

	/**
	 * Adds the named tube to the watch list, to reserve jobs from.
	 *
	 * <code>
	 * $count = $queue->watch($tube);
	 * </code>
	 *
	 * @throws Exception
	 */
	public function watch(string! tube) -> <Client>
	{
		var response, watchedTubes;

		let watchedTubes = this->watchedTubes;
		if !isset watchedTubes[tube] {
			this->write("watch " . tube);

			let response = this->readStatus();
			if !isset response[1] || response[0] != "WATCHING" {
				throw new Exception("Unhandled response: " . join(" ", response));
			}

			let this->watchedTubes[tube] = true;
		}

		return this;
	}

	/**
	* Adds the named tube to the watch list, to reserve jobs from, and
	* ignores any other tubes remaining on the watchlist.
	*
	* <code>
	* $count = $queue->watchOnly($tube);
	* </code>
	*
	* @throws Exception
	*/
	public function watchOnly(string! tube) -> <Client>
	{
		var watchedTubes, watchedTube;

		this->watch(tube);

		let watchedTubes = this->watchedTubes;
		for watchedTube, _ in watchedTubes {
			if watchedTube == tube {
				continue;
			}

			this->ignore(watchedTube);
		}

		return this;
	}

	/**
	 * Reserves/locks a ready job from the specified tube.
	 *
	 * <code>
	 * $job = $queue->reserve();
	 * </code>
	 *
	 * @throws Exception
	 */
	public function reserve(int timeout = -1) -> boolean|<Job>
	{
		var response;
		string command;

		if timeout >= 0 {
			let command = "reserve-with-timeout " . timeout;
		} else {
			let command = "reserve";
		}

		this->write(command);

		let response = this->readStatus();

		if response[0] != "RESERVED" || !isset response[2] {
			return false;
		}

		return new Job(this, response[1], unserialize(this->read(response[2])));
	}

	/**
	 * Reserves/locks a ready job from the specified tube.
	 *
	 * <code>
	 * $job = $queue->reserve();
	 * </code>
	 *
	 * @throws Exception
	 */
	public function reserveFromTube(string tube, int timeout = -1) -> boolean|<Job>
	{
		this->watch(tube);

		return this->reserve(timeout);
	}

	/**
	 * Removes the named tube from the watch list for the current connection.
	 *
	 * <code>
	 * $count = $queue->ignore('tube-name);
	 * </code>
	 *
	 * @throws Exception
	 */
	public function ignore(string! tube) -> <Client>
	{
		var response, watchedTubes;

		let watchedTubes = this->watchedTubes;
		if typeof watchedTubes != "array" {
			return this;
		}

		if isset watchedTubes[tube] {
			this->write("ignore " . tube);

			let response = this->readStatus();
			if response[0] == "NOT_IGNORED" {
				throw new Exception("Cannot ignore last tube in watchlist.");
			}

			if !isset response[1] || response[0] != "WATCHING" {
				throw new Exception("Unhandled response: " . join(" ", response));
			}

			unset(watchedTubes[tube]);
			let this->watchedTubes = watchedTubes;
		}

		return this;
	}

	/**
	 * Gives statistical information about the system as a whole.
	 *
	 * <code>
	 * $queue->stats();
	 * </code>
	 */
	public function stats() -> boolean|array
	{
		var response;

		this->write("stats");

		let response = this->readYaml();
		if response[0] != "OK" {
			return false;
		}

		return response[2];
	}

	/**
	 * Gives statistical information about the specified tube if it exists.
	 *
	 * <code>
	 * $stats = $queue->statsTube('process-bitcoin');
	 * </code>
	 */
	public function statsTube(string! tube) -> boolean|array
	{
		var response;

		this->write("stats-tube " . tube);

		let response = this->readYaml();
		if response[0] != "OK" {
			return false;
		}

		return response[2];
	}

	/**
	 * Returns a list of all existing tubes.
	 *
	 * <code>
	 * $tubes = $queue->listTubes();
	 * </code>
	 */
	public function listTubes() -> boolean|array
	{
		var response;

		this->write("list-tubes");

		let response = this->readYaml();
		if response[0] != "OK" {
			return false;
		}

		return response[2];
	}

	/**
	 * Returns the tube currently being used by the client.
	 *
	 * <code>
	 * $tube = $queue->listTubeUsed(); // local cache
	 * $tube = $queue->listTubeUsed(); // ask server
	 * </code>
	 *
	 * @throws Exception
	 */
	public function listTubeUsed(boolean ask = false) -> string
	{
		var response;

		if !ask {
			return this->usedTube;
		}

		this->write("list-tube-used");

		let response = this->readStatus();

		if isset response[1] && response[0] == "USING" {
			let this->{"usedTube"} = response[1];
			return response[1];
		}

		throw new Exception("Unhandled response form beanstalkd server: " . join(" ", response));
	}

	/**
	 * Returns a list tubes currently being watched by the client.
	 *
	 * <code>
	 * $tubes = $queue->listTubesWatched(); // local cache
	 * $tubes = $queue->listTubesWatched(true); // ask server
	 * </code>
	 *
	 * @throws Exception
	 */
	public function listTubesWatched(boolean ask = false) -> array
	{
		var response;

		if !ask {
			return array_keys(this->watchedTubes);
		}

		this->write("list-tubes-watched");

		let response = this->readYaml();
		if response[0] != "OK" {
			throw new Exception("Unhandled response form beanstalkd server: " . join(" ", response));
		}

		let this->{"watchedTubes"} = array_fill_keys(response[2], true);

		return this->watchedTubes;
	}

	/**
	 * Can delay any new job being reserved for a given time.
	 *
	 * <code>
	 * $queue->pauseTube('process-video', 60 * 60);
	 * </code>
	 */
	public function pauseTube(string! tube, int delay) -> boolean
	{
		var response;

		this->write("pause-tube " . tube . " " . delay);

		let response = this->readStatus();
		if !isset response[0] || response[0] != "PAUSED" {
			return false;
		}

		return true;
	}

	/**
	 * Resume the tube.
	 *
	 * <code>
	 * $queue->resumeTube('process-video');
	 * </code>
	 */
	public function resumeTube(string! tube) -> boolean
	{
		return this->pauseTube(tube, 0);
	}

	/**
	 * Simply closes the connection.
	 *
	 * <code>
	 * $queue->quit();
	 * </code>
	 */
	public function quit() -> boolean
	{
		this->write("quit");
		this->disconnect();

		return typeof this->socket != "resource";
	}

	/**
	 * Writes data to the socket.
	 * Performs a connection if none is available.
	 * @throws Exception
	 */
	public function write(string data) -> int
	{
		var socket, retries, written, step, length;

		if !this->isConnected() {
			this->connect();

			if !this->isConnected() {
				throw new Exception("Unable to establish connection with beanstalkd server.");
			}
		}

		let retries = this->options["wretries"],
			socket  = this->socket,
			data   .= "\r\n",
			step    = 0,
			written = 0;

		let length = strlen(data);

		while written < length {
			let step++;

			if step >= retries && !written {
				throw new Exception("Failed to write data to socket after " . retries . " tries.");
			}

			let written += fwrite(socket, substr(data, written));
		}

		return written;
	}

	/**
	 * Reads a packet from the socket.
	 * Performs a connection if none is available.
	 * @throws Exception
	 */
	public function read(int length = 0) -> boolean|string
	{
		var socket, data, meta;

		if !this->isConnected() {
			this->connect();

			if !this->isConnected() {
				return false;
			}
		}

		let socket = this->socket;

		if length {
			if feof(socket) {
				throw new Exception("Failed to read data from socket (EOF).");
			}

			let data = stream_get_line(socket, length + 2),
				meta = stream_get_meta_data(socket);

			if meta["timed_out"] {
				throw new Exception("Connection timed out upon attempt to read data from socket.");
			}

			if false === data {
				throw new Exception("Failed to read data from socket.");
			}

			let data = rtrim(data, "\r\n");
		} else {
			let data = stream_get_line(socket, 16384, "\r\n");
		}

		array errors = [
			"UNKNOWN_COMMAND" : "Unnown command.",
			"JOB_TOO_BIG"     : "Job data exceeds server-enforced limit.",
			"BAD_FORMAT"      : "Bad command format.",
			"OUT_OF_MEMORY"   : "Out of memory."
		];

		if isset errors[data] {
			throw new Exception(errors[data]);
		}

		return data;
	}

	/**
	 * Fetch a YAML payload from the Beanstalkd server.
	 */
	final public function readYaml() -> array
	{
		var response, status, data = [], bytes = 0;

		let response = this->readStatus();

		if isset response[0] {
			let status = response[0];
		} else {
			let status = "UNKNOWN";
		}

		if isset response[1] {
			let bytes = response[1],
				data  = this->yamlParse();
		}

		return [
			status,
			bytes,
			data
		];
	}

	/**
	 * Reads the latest status from the Beanstalkd server.
	 */
	final public function readStatus() -> array
	{
		var status;
		let status = this->read();
		if false === status {
			return [];
		}

		return explode(" ", status);
	}

	private function yamlParse() -> array
	{
		var data, lines, key, value, values, tmp, response = [];

		let data = this->read();

		if typeof data != "string" || empty(data) {
			return [];
		}

		if function_exists("yaml_parse") {
			let response = yaml_parse(data);

			return response;
		}

		let data  = rtrim(data),
			lines = preg_split("#[\r\n]+#", rtrim(data));

		if isset lines[0] && lines[0] == "---" {
			array_shift(lines);
		}

		if typeof lines != "array" || empty(lines) {
			trigger_error("YAML parse error.", E_USER_WARNING);
			return [];
		}

		for key, value in lines {
			if starts_with(value, "-") {
				let value = ltrim(value, "- ");
			} elseif strpos(value, ":") !== false {
				let values = explode(":", value);

				if !isset values[1] {
					trigger_error("YAML parse error for line: \"" . value . "\"", E_USER_WARNING);
				} else {
					let key   = values[0],
						value = ltrim(values[1], " ");
				}
			}

			if is_numeric(value) {
				let tmp = intval(value);

				if tmp == value {
					let value = tmp;
				} else {
					let value = doubleval(value);
				}
			}

			let response[key] = value;
		}

		return response;
	}
}

Ubuntu 16.x 装机参考

系统镜像可到:https://mirrors.aliyun.com/ubuntu-releases/下载

#更改限制
vi /etc/security/limits.conf
root soft nofile 65535
root hard nofile 65535
* soft nofile 65535
* hard nofile 65535

#解锁root
sudo passwd -u root
#为root设置密码
sudo passwd root

#切换到root
su root

为了避免麻烦,先关闭防火墙:

#查看启动了哪些服务
systemctl list-unit-files | grep enabled
systemctl stop ufw.service
systemctl disable ufw.service

默认没有安装openssh-server(Ubuntu 16.04默认安装时需要选中,否则需要自己安装)

apt install openssh-server

systemctl start sshd.service
systemctl enable sshd.service

默认安装的openssh-server是禁止root链接的,它的逻辑是先以一个普通用户登录,如果需要切换,再用su来进行。如果麻烦可以修改:

vi /etc/ssh/sshd_config

PermitRootLogin prohibit-password	#改为yes(prohibit-password禁止密码,就是使用公钥还是可以的)
PermitEmptyPasswords yes		#是否允许空密码,默认是no

#重启服务
systemctl restart sshd.service

更换软件源头,参考:http://wiki.ubuntu.org.cn/%E6%A8%A1%E6%9D%BF:16.04source

deb http://cn.archive.ubuntu.com/ubuntu/ xenial main restricted universe multiverse
deb http://cn.archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://cn.archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://cn.archive.ubuntu.com/ubuntu/ xenial-backports main restricted universe multiverse
##测试版源
deb http://cn.archive.ubuntu.com/ubuntu/ xenial-proposed main restricted universe multiverse
# 源码
deb-src http://cn.archive.ubuntu.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://cn.archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://cn.archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://cn.archive.ubuntu.com/ubuntu/ xenial-backports main restricted universe multiverse
##测试版源
deb-src http://cn.archive.ubuntu.com/ubuntu/ xenial-proposed main restricted universe multiverse
# Canonical 合作伙伴和附加
deb http://archive.canonical.com/ubuntu/ xenial partner
deb http://extras.ubuntu.com/ubuntu/ xenial main

这里的http://cn.archive.ubuntu.com/ubuntu/是阿里云资源,可以替换为http://mirrors.163.com/ubuntu/

安装桌面

apt update
apt upgrade

apt-get install --no-install-recommends ubuntu-desktop

add-apt-repository ppa:embrosyn/cinnamon
apt install cinnamon

SSH反向代理内网穿透

SSH可以用来做正向Socks代理外,还可以用来做反向代理。典型的应用场景是:让内网的机器对外网可见,常见手段是在路由器上做端口映射,但是由于路由器的IP地址会变化(除非你有固定不变的IP),所以还需要在内网或路由器上运行一个动态DNS的工具(这种工具实际就是往外网发送IP地址),然后通过域名获取到最新的IP,而SSH做反向代理就不需要这些步骤,当然,需要外网有一台机器作为接入点。

简单来说,就是内网机器和外网的机器建立一条TCP隧道,外网预期分配的端口通过SSHD服务转发到内网来,内网的数据出去也一样。TCP隧道,头尾都对应端口号,比如把外网的2222端口,通过TCP隧道跟内网的22端口接起来。

ssh -CNR 2222:127.0.0.1:22 -b 0.0.0.0 www@外网IP

然后输入外网IP的www用户的密码就可以建立一条TCP隧道。可以看到,这个时候外网IP的2222端口开放了,它是由SSHD服务开启的。这里的-b参数说明希望外网IP的监听地址是0.0.0.0,就是0.0.0.0:2222,不过可能看到的是127.0.0.1:2222,这个不是我们所期望的。为了让反向代理真正可靠的使用,还需要解决如下几个问题:

0 自动登录问题
这个问题最常见的是放置公钥,内网ssh通过-i来指定私钥文件。如果希望使用密码来登录,那么需要借助sshpass这个包装器,之所以需要它,那是因为ssh不支持通过参数指定密码:

yum install sshpass

#格式
sshpass -p "密码" ssh -CNR 3080:127.0.0.1:80 www@xx.xx.xx.xx

1 在外网监听任意地址
首先,内网ssh命令需要使用-b指定监听地址为0.0.0.0,然后外网的sshd服务的配置,需要修改GatewayPorts为yes:

vi /etc/ssh/sshd_config
GatewayPorts yes

可能还需要重启一下sshd服务。

2 TCP隧道长时保持连接
在内网上运行ssh命令时,加上-o TCPKeepAlive=yes来让TCP保持长时连接。

3 防止TCP被中断
发送大量的数据或长时没有数据发送时,都可能导致TCP连接被卡住或被中间的路由器杀掉。所以需要建立一个心跳检查,在内网运行ssh时添加-o ServerAliveInterval=10 -o ServerAliveCountMax=6来维持心跳,这里的意思是每10秒检测一次,如果连续6次都无法获取响应,那么进程将退出。

4 防止进程异常退出
通过设置TCP长链接和定时发送心跳数据来保存通道畅通,但是从实际使用来看,这些还是远远不够的。进程异常退出监控起来很简单,但是实际遇到过这种情况:通道实际已经无法通信,但是本地进程并未退出,这个情况可能是远程进程异常退出,但是本地进程并没有收到通知,或者中间路由的问题,所以为了解决这个问题,我们可以定时发送数据,根据响应来判断是否要重启本地进程,于是有了以下脚本:

#!/bin/bash

cmm="/usr/bin/kill"
if [ ! -f "$cmm" ]; then
    cmm="/bin/kill"
fi

#不等于空说明通道通畅
ping=`curl -s http://x.x.x.x:2222`
if [ "$ping" = "" ]; then
	ps aux | grep 'x.x.x.x' | grep -v grep | awk '{print $2"|"$6}' | cat | while read line
	do
    		pid=`echo $line | cut -d "|" -f 1`
    		rss=`echo $line | cut -d "|" -f 2`

        	kill=`$cmm -9 $pid`
	        date=`date "+%Y-%m-%d %H:%M:%S"`

	        echo $date' -- 'PID:$pid' - Was Killed.'
	done
fi

#反代
live=`ps -efH | grep 'ssh -CN -R 2222:127.0.0.1:22' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    ssh -CN -R 2222:127.0.0.1:22 -b 0.0.0.0 -o TCPKeepAlive=yes -o ServerAliveInterval=10 -o ServerAliveCountMax=6 sshproxy@x.x.x.x -p 22 > /dev/null 2>&1 &
fi

live=`ps -efH | grep 'ssh -CN -R 8000:127.0.0.1:8000' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    ssh -CN -R 8000:127.0.0.1:8000 -b 0.0.0.0 -o TCPKeepAlive=yes -o ServerAliveInterval=10 -o ServerAliveCountMax=6 sshproxy@x.x.x.x -p 22 > /dev/null 2>&1 &
fi

使用curl来发送数据,如果通道畅通就会有响应,否则就无响应,无响应则强制杀掉本地进程。紧接着重启进程。

这个方法虽然很粗暴,但是很实用。从运行来看,还是相当稳定的(实际我仅仅是把内网的一个服务映射出去,偶尔用一用,但是用的时候要保证能用,比如内网的Gitlab)。

Sockets代理 之 Shadowsocks

ShadowSocks – http://shadowsocks.org

实现一个Socks代理,很多语言都提供有内置支持或扩展包可用,需要编写的代码也不会太多。ShadowSocks这个工具提供了多种语言的实现,从http://shadowsocks.org/en/download/servers.html可以看到,有Python、Go、C with libev、C++ with Qt, 这些实现版本中,都应该包含服务端和客户端工具,不过也有一些专门的客户端(主要提供图形操作界面),比如
Windows客户端(https://github.com/shadowsocks/shadowsocks-windows/releases)
Linux下的客户端(https://github.com/shadowsocks/shadowsocks-qt5/releases)
Mac OS X下的客户端(https://github.com/shadowsocks/ShadowsocksX-NG/releases),Android下的客户端(https://github.com/shadowsocks/shadowsocks-android,在Android下可打开https://play.google.com/store/apps/details?id=com.github.shadowsocks进行安装)。这个代理软件与一般的Socks5代理稍不一样的是:它不是一个纯粹的代理,它加入了加密的功能。 这个工具开启的代理功能和SSH开启的代理有本质不一样,SSH开启的代理会在本地和服务端之间建立一条持久的TCP链接,而shadowsocks只是在服务器开启了监听端口,客户端的链接随到随链。所以,对于需要HOLD大量链接的情况,C with libev是一个很好的选择。

注:
由于shadowsock不是一个纯的Sockets代理,所以必须搭配客户端使用。

服务端,这里关注C实现的版本:https://github.com/shadowsocks/shadowsocks-libev,它提供了比较完整的功能。客户端关注Windows下的版本,它是.NET软件,可能需要升级你的.NET版本。

在CentOS 7.x上,可以使用了一个yum源来安装(参考https://copr.fedorainfracloud.org/coprs/librehat/shadowsocks/):

#https://copr.fedorainfracloud.org/coprs/librehat/shadowsocks/repo/epel-7/librehat-shadowsocks-epel-7.repo
#vi /etc/yum.repos.d/librehat-shadowsocks.repo

[librehat-shadowsocks]
name=Copr repo for shadowsocks owned by librehat
baseurl=https://copr-be.cloud.fedoraproject.org/results/librehat/shadowsocks/epel-7-$basearch/
type=rpm-md
skip_if_unavailable=True
gpgcheck=1
gpgkey=https://copr-be.cloud.fedoraproject.org/results/librehat/shadowsocks/pubkey.gpg
repo_gpgcheck=0
enabled=1
enabled_metadata=1

安装:

yum install shadowsocks

rpm -qa | grep shadowsocks
shadowsocks-libev-2.5.5-1.el7.centos.x86_64

rpm -ql shadowsocks-libev-2.5.5-1.el7.centos.x86_64
/etc/default/shadowsocks-libev
/etc/shadowsocks-libev/config.json
/usr/bin/ss-local
/usr/bin/ss-manager
/usr/bin/ss-nat
/usr/bin/ss-redir
/usr/bin/ss-server
/usr/bin/ss-tunnel
/usr/lib/systemd/system/shadowsocks-libev.service
/usr/lib64/libshadowsocks-libev.so

编译安装(新版本可能有问题)

# Ubuntu
apt-get install --no-install-recommends build-essential automake autoconf libtool libssl-dev libpcre3-dev asciidoc xmlto
# CentOS
yum install gcc autoconf libtool automake make zlib-devel openssl-devel asciidoc xmlto

#克隆源码,或直接下载压缩包
git clone https://github.com/shadowsocks/shadowsocks-libev.git
cd shadowsocks-libev
./configure --prefix=/usr/local/ss
make
make install

#######################################
# CentOS下编译安装新版本参考:https://shadowsocks.be/4.html,以下是从脚本中提取的安装步骤
#安装必须的软件包
yum install epel-release
yum install -y unzip openssl openssl-devel gettext gcc autoconf libtool automake make asciidoc xmlto udns-devel libev-devel pcre pcre-devel git c-ares-devel

#下载安装
wget https://github.com/jedisct1/libsodium/releases/download/1.0.13/libsodium-1.0.13.tar.gz
tar zxf libsodium-1.0.13.tar.gz
cd libsodium-1.0.13
./configure —prefix=/usr && make && make install

wget http://dl.teddysun.com/files/mbedtls-2.6.0-gpl.tgz
tar xf mbedtls-2.6.0-gpl.tgz
cd mbedtls-2.6.0-gpl
make SHARED=1 CFLAGS=-fPIC
make DESTDIR=/usr install

wget https://c-ares.haxx.se/download/c-ares-1.12.0.tar.gz
tar zxvf c-ares-1.12.0.tar.gz
cd c-ares-1.12.0
./configure
make && make install

ldconfig

wget https://github.com/shadowsocks/shadowsocks-libev/releases/download/v3.1.0/shadowsocks-libev-3.1.0.tar.gz
tar zxf shadowsocks-libev-3.1.0.tar.gz
cd shadowsocks-libev-3.1.0
./configure --disable-documentation
make && make install

默认配置在/etc/default/shadowsocks-libev,配置文件/etc/shadowsocks-libev/config.json,服务启动脚本/usr/lib/systemd/system/shadowsocks-libev.service。默认实际使用到的就是/usr/bin/ss-server(如果要启动客户端,修改为ss-local即可),查看配置文件:

{
    "server":"120.xx.xx.28",
    "server_port":8388,
    "local_port":1080,
    "password":"xxxxxxxx",
    "timeout":60,
    "method":"aes-256-cfb"
}

不管是使用ss-local和ss-server,这几个参数都一样,注意:作为服务端,local_port应该是没有用的。客户端和服务端,都预置了密码和对称加密使用的算法,简单来说,它们通过密码认证,然后使用预置的算法产生一个秘钥,用这个秘钥来加解密传输的数据。不难看出,它跟传统的CA不同,秘钥的交换不是通过加密隧道来交换的。如果需要安全,不要使用这个工具。

这个包也包括了其它工具:

/usr/bin/ss-local	客户端,和服务端的配置一样
/usr/bin/ss-manager	用来管理服务端,比如在服务端开启多个端口(动态拉起ss-server)
/usr/bin/ss-nat		管理NAT映射
/usr/bin/ss-redir	客户端工具,用来实现透明代理
/usr/bin/ss-server	服务端
/usr/bin/ss-tunnel	用来实现本地端口转发,比如目的地址符合条件时,转发到本地某个端口

对于客户端,最典型的应用,就是在内网架设一个服务器,上面安装ShadowSocks-livev,然后启动客户端(也可以把参数写入配置,用c指定读取的配置文件):

ss-local -s 120.xx.xx.x -p 8388 -b 192.168.1.250 -l 1080 -k xxxxx -m aes-256-cfb

需要代理的机器,指定为192.168.1.250即可,这样就可以避免在每个终端上安装客户端。不过,如果需要连接多个代理服务器,就需要启动多个ss-local进程,并且指定不同的端口。

注:可以在服务器上设置多用户(实际是启动多个ss-server,用端口区分),客户端可以配置多个远程服务器,但是需要指定默认,需要更换时,需要切换一下默认远程服务器,从这里可知,一个客户端要对应哪个远程是随意的,目前看起来缺少的是在客户端管理远程服务器的工具(比如只启动一个客户端,动态或安装一定规则对应到不同远程服务器)

Window下的客户端(https://github.com/shadowsocks/shadowsocks-windows/releases):
ss

当然,这个客户端还提供了 系统代理模式(就是直接修改系统代理) 系统代理模式(全局与PAC) 切换服务器等功能。

Linux下的客户端不支持配置多个端口(不同端口对应不同的服务端),所以可以启动多个客户端来解决,比如分别对应建立配置文件,然后写一段简单粗暴的定时脚本,就可以Hold它:

#!/bin/bash

live=`ps -efH | grep '/etc/shadowsocks-libev/config_4441.json' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    /usr/bin/ss-local -a root -c /etc/shadowsocks-libev/config_4441.json -u 2>&1 > /dev/null &
fi

live=`ps -efH | grep '/etc/shadowsocks-libev/config_4442.json' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    /usr/bin/ss-local -a root -c /etc/shadowsocks-libev/config_4442.json -u 2>&1 >> /dev/null &
fi
 
live=`ps -efH | grep '/etc/shadowsocks-libev/config_4443.json' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    /usr/bin/ss-local -a root -c /etc/shadowsocks-libev/config_4443.json -u 2>&1 >> /dev/null &
fi

live=`ps -efH | grep '/etc/shadowsocks-libev/config_4444.json' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    /usr/bin/ss-local -a root -c /etc/shadowsocks-libev/config_4444.json -u 2>&1 >> /dev/null &
fi

exit 0

需要说明的是shadowsocks-qt5是一个跨平台的客户端(https://github.com/shadowsocks/shadowsocks-qt5/releases),以下链接是安装和使用说明:https://github.com/shadowsocks/shadowsocks-qt5/wiki:

Ubuntu通过PPA方式安装:

sudo add-apt-repository ppa:hzwhuang/ss-qt5
sudo apt-get update
sudo apt-get install shadowsocks-qt5

这个ppa对应的链接是:https://launchpad.net/~hzwhuang/+archive/ubuntu/ss-qt5
ss-ubuntu

而对应的Window版本长这个样子:
ss-window

最后:
作为服务端,如果使用的是Linux(CentOS或Ubuntu),最好的选择是shadowsocks-libev,而对于Windows,看起来只有一个选择(https://github.com/shadowsocks/libQtShadowsocks/releases,提供一个叫libQtShadowsocks的工具,既可做服务端,也可以做客户端); 作为客户端,在Windows和Ubuntu下https://github.com/shadowsocks/shadowsocks-qt5/releases这个较好。

这是一个百花齐放的开源产品。