文章归档

学习笔记

kubernetes nginx ingress custom 401/500 page

在k8s中,通常会使用ingress的方式将内部服务进行暴露,与此同时会加上鉴权的功能,如:Basic Auth、Oauth2和JWT等等。

使用方法

在Ingress资源的metadata的annotations中增加nginx.ingress.kubernetes.io/auth-url来指定鉴权的地址。如:nginx.ingress.kubernetes.io/auth-url: http://10.11.0.123:8081/api-auth

这样一来,我们在编写/api-auth这个接口的时候,可以根据请求头拿到客户端的认证信息,进而判断是否对其请求进行放行,返回状态码为200即为放行,nginx会将客户端的请求转发到对应的后端服务;返回状态码为401即表示没有授权,nginx直接对其拦截,并返回给客户端默认的响应。

响应Header头:

1
2
3
4
HTTP/2 401
date: Mon,21 Feb 2022 06:42:41 GMT
content-type: text/html
content-length: 574

响应Body体:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->

但是客户端仅知道请求是因为授权问题被拒绝了,但是不知道具体的原因,如果需要得知具体的拒绝原因,可以按照如下方法进行优化。

优化方法

因为nginx中没有直接获取upsream返回结果的方法(可通过重新编译,用Lua实现),但是可获取到响应头,所以我们利用响应头来返回拒绝的原因。

首先,在/api-auth中,增加一个响应头my_auth_error,内容可以直接填写您需要返回给客户端的原因,我这里会填入一个json串的Base64编码值。

然后我们回到nginx-ingress的配置文件,增加两个声明:nginx.ingress.kubernetes.io/configuration-snippet和nginx.ingress.kubernetes.io/server-snippet。

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
metadata:
# 此处省略N个字
annotations:
nginx.ingress.kubernetes.io/auth-url: http://10.11.0.123:8081/api-auth
nginx.ingress.kubernetes.io/configuration-snippet: |
error_page 401 = @ingress_service_custom_error_401;

auth_request_set $my_auth_error $upstream_http_wwd_auth_error;
auth_request_set $my_auth_error_content_type $upstream_http_content_type;
auth_request_set $my_auth_status $upstream_status;
nginx.ingress.kubernetes.io/server-snippet: |
location @ingress_service_custom_error_401 {
internal;

# Decode auth response header
set_decode_base64 $my_auth_error_decoded $my_auth_error;

# Return the error from auth service if any
if ($my_auth_error_decoded != ""){
add_header Content-Type $my_auth_error_content_type always;
return 401 $my_auth_error_decoded;
}

# Fall back to default nginx response
return 401;
}
# 此处省略N个字

配置解释

configuration-snippet用来对location进行自定义配置,上面对401的错误页面定义了一个@ingress_service_custom_error_401,然后增加了3个变量my_auth_error、my_auth_error_content_type和my_auth_status分别取值自定义响应头、响应类型和响应状态码。

server-snippet用来对server块进行自定义配置,上面相当于在server块增加了一个location,该location即为401时的默认响应,其中会对my_auth_error进行base64解码,然后判断解码内容,若其内容为空,则直接返回最初的默认401页面,否则在响应头中增加Content-Type为具体的自定义类型,并返回401状态码和自定义响应Body。

如此一来,就可以实现nginx-ingress根据auth接口响应的结果来执行不同的行为。最终实现不同的效果。

响应头:

1
2
3
4
5
HTTP/2 401
date: Mon, 21 Feb 2022 07:14:36 GMT
content-type: text/html
content-length: 51
content-type: application/json

响应Body体:

1
2
3
4
5
{
"message": "用户未登录",
"code": 14,
"data": null
}

参考资料

Custom response for Ingress-Nginx External Authentication

阅读剩下更多

默认配图
学习笔记

记一次使用CGo的Golang项目交叉编译遇到的问题及解决步骤

本篇文章对我在交叉编译EasyDarwin项目到Arrch64架构的Linux上做了简单的记录和总结,总体上遇到的问题分为那么几类: 1. 项目依赖库的缺失和更新 2. syscall.Dup2方法不存在 3. CGO的交叉编译支持 4. 执行二进制文件却提示:No such file or directory

阅读剩下更多

记一次使用CGo的Golang项目交叉编译遇到的问题及解决步骤
学习笔记

利用FFmpeg处理鱼眼视频分成4个方向的视野画面

介绍

首先,FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它的功能强大,这里就不多做阐述了。

本次实验主要是为了学习FFmpeg的Filter功能,它提供了很多音视频的特效处理,比如:视频的缩放、翻转、叠加等等。常用的Filter有:

  • Scale:画面的缩放
  • Overlay:画面的叠加
  • Crop:画面的裁剪
  • More:查看更多

我们可以通过命令查看全部Filter的简介:

1
ffmpeg -filters

实验

原始材料

我们手里现在有一段鱼眼镜头录制的视频源文件,命名:original.mp4。我们先来看看它长什么样子:

out

尝试球形转方形

这一步,我暂时还没找到一个很好的方法把其展平成一个广角镜头全景的画面,但是我使用v360这个filter通过不断修改参数,得到了一个比较满意的部分画面的方形画面。

该Filter的基本参数格式如下:

1
v360=input:output:format_1:arg_name=value

其中input和output分别是原始投影格式和目标投影格式,这里我们是从鱼眼视频转换为常规视频,所以要填入的参数为:

1
v360=input=fisheye:output=flat # 都有哪些投影格式,可以通过ffmpeg -h filter=v360获取查看

format_1是指具体要设置的格式,这里我并没有用到。arg_name和value指具体的参数项和值。

我的使用例子:

1
v360=input=fisheye:output=flat:ih_fov=180:iv_fov=180:h_fov=90:v_fov=60:w=720:h=360:pitch=45

解释一下,其中ih_fov和iv_fov分别表示设置的输入横向视野范围和输入纵向视野范围,我这里都设置为180度;h_fov和v_fov分别表示设置的输出横向视野范围和输出纵向视野范围,我这里设置横向90度,纵向60度。然后还设置了输出画面的宽高为720x360,俯仰旋转角(pitch)为45度。

我的个人理解(个人瞎猜),就如同人眼可接受光的范围,理论上横向和纵向都能够覆盖180度的范围,但是我们在眼球不转动的情况下,有效的水平视野大概是90度,纵向视野60度。因为这个是我具体调参的出来的个人觉得比较好的参数条件,所以不具有权威性!哈哈!

那么现在能输出一个什么样的画面呢?

up

Nice!这就已经很像一个普通摄像头的画面了。那么如何得到其它几个方向的画面呢?前面我们有提到,FFmpeg可以使画面旋转!如果我先将原始画面旋转一下,让其它几个方向的画面都转到上面,然后转换画面不就行了吗?

画面旋转

FFmpeg有个Filter叫做transpose。它可以非常简单的完成画面的旋转或翻转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 输入ffmpeg -h filter=transpose查一下说明
Filter transpose
Transpose input video.
slice threading supported
Inputs:
#0: default (video)
Outputs:
#0: default (video)
transpose AVOptions:
dir <int> ..FV...... set transpose direction (from 0 to 7) (default cclock_flip)
cclock_flip 0 ..FV...... rotate counter-clockwise with vertical flip
clock 1 ..FV...... rotate clockwise
cclock 2 ..FV...... rotate counter-clockwise
clock_flip 3 ..FV...... rotate clockwise with vertical flip
passthrough <int> ..FV...... do not apply transposition if the input matches the specified geometry (from 0 to INT_MAX) (default none)
none 0 ..FV...... always apply transposition
portrait 2 ..FV...... preserve portrait geometry
landscape 1 ..FV...... preserve landscape geometry

简单来说:

  • transpose=0逆时针旋转90度并纵向翻转一次
  • transpose=1顺时针旋转90度
  • transpose=2逆时针旋转90度
  • transpose=3顺时针旋转90度并纵向翻转一次

我们分别使用如下参数,可获得的画面:

1
2
3
#顺时针旋转90度
transpose=1,v360=input=fisheye:output=flat:ih_fov=180:iv_fov=180:h_fov=90:v_fov=60:w=720:h=360:pitch=45
#得到左方镜头画面

left

1
2
3
#逆时针旋转90度
transpose=2,v360=input=fisheye:output=flat:ih_fov=180:iv_fov=180:h_fov=90:v_fov=60:w=720:h=360:pitch=45
#得到右方镜头画面

right

1
2
3
#顺时针旋转90度2次
transpose=1,transpose=1,v360=input=fisheye:output=flat:ih_fov=180:iv_fov=180:h_fov=90:v_fov=60:w=720:h=360:pitch=45
#得到后方镜头画面

down

画面拼接

做到上面的效果已经可以将鱼眼镜头的视频流拆分成4个不同方向的视频分别输出到4个流了,但是我还想将它们合成一个视频流,同时展示4个画面!

这里我们就需要用到overlay(画面叠加)了!

具体的思路如下:

  1. 创建一张画布,大小为1440x720。
  2. 将画布分成4个区域,分别叫做upperleft、upperright、lowerleft、lowerright,并放置视频的左、上、右、下。

上面用到语法叫做filterchain,是将一个或多个filter用,号分隔,这里还需要用到的语法叫做:filtergraph,是将filterchain用;号分隔。

最终的命令如下:

1
ffmpeg -i original.mp4 -filter_complex "nullsrc=size=1440x720 [base]; [0:v] transpose=1,v360=input=fisheye:output=flat:ih_fov=180:iv_fov=180:h_fov=90:v_fov=60:w=720:h=360:pitch=45 [upperleft]; [0:v] v360=input=fisheye:output=flat:ih_fov=180:iv_fov=180:h_fov=90:v_fov=60:w=720:h=360:pitch=45 [upperright]; [0:v] transpose=2,v360=input=fisheye:output=flat:ih_fov=180:iv_fov=180:h_fov=90:v_fov=60:w=720:h=360:pitch=45 [lowerleft]; [0:v] transpose=1,transpose=1,v360=input=fisheye:output=flat:ih_fov=180:iv_fov=180:h_fov=90:v_fov=60:w=720:h=360:pitch=45 [lowerright]; [base][upperleft] overlay [tmp1]; [tmp1][upperright] overlay=720:0 [tmp2]; [tmp2][lowerleft] overlay=0:360 [tmp3];[tmp3][lowerright] overlay=720:360" -c:v libx264 -an -y out.mp4

叠加的步骤如下:

  1. 创建一个空的画布[base],size=1440x720
  2. 将输入的视频[0:v]分别通过transpose和v360转换成[upperleft]、[upperright]、[lowerlet]和[lowerright]4个流
  3. 将[base]和[upperleft]叠加成[tmp1],偏移量(x=0, y=0)
  4. 将[tmp1]和[upperright]叠加成[tmp2],偏移量(x=720, y=0)
  5. 将[tmp2]和[lowerleft]叠加成[tmp3],偏移量(x=0, y=360)
  6. 将[tmp3]和[lowerright]叠加成最终视频,偏移量(x=720, y=360)

最终画面展示:

dewarp

参考文献

阅读剩下更多

利用FFmpeg处理鱼眼视频分成4个方向的视野画面
学习笔记

自建CA服务器学习笔记

​ 我人生中的第一次遇到证书问题并加以解决,还是在大约3年前,那时还专门写了一篇文章《解决Chrome更新到63后.dev/.app后缀域名强制HTTPS的问题》。当时是因为本地开发环境一直都是用的.dev后缀结尾的域名,一直都是以http://www.test.dev的方式进行本地web开发。那天浏览器突然提示访问不安全的网站,此处黑人问号?? Chrome强制.dev后缀的域名都得走https,无奈之下,不想更换后缀的我走上了自签发https证书的道路,但当时直接使用了开源工具,并没有深入研究。

​ 日月如梭,今时今日的我已经不再是那个年少的小孩!日常工作中免不了与SSL/TLS这玩意儿打交道,除了访问web用https外,只要与建立加密连接有关的统统都少不了证书和秘钥。这次需要自建CA服务器并签发下级证书以达到加密访问的目的,那么这次的笔记将会成为以后温习的主要文献了。(哈哈哈)

阅读剩下更多

默认配图
学习笔记

ESP-Touch智能配网是如何实现的

写在前面

近年来物联网发展迅速,目前依旧是比较热门的领域,现今世界处处都是物联网,可谓无处不物联(哈哈,扯远了!)。我接触物联网这块比较晚,去年才开始入门,最早的实战项目就是将传感器上云,借助腾讯的物联网通信平台,我可以对传感器进行远程控制以及读取传感器采集到的数据。当然,这些都离不开网络(Internet)。设备上云的首要前提就是接入互联网,而接入互联网的方式有很多种(如:有线网络、无线WiFi、NB-IoT、移动蜂窝2G/3G/4G等),不同场景下自然有各自合适的网络接入方式。

我们现在生活中的很多智能家电都是通过WiFi接入的,不用我说大家也能举很多例子(像小米智能家居有小爱同学、空气净化器、电视、冰箱、洗衣机等blablabla~),这些设备刚到手的时候你一般都需要按照指导说明去下载个APP,然后绑定设备给设备配网,给设备配网可不像给手机连上WiFi那么容易,毕竟你买的小爱同学不能接鼠标键盘,也没有操作系统给你用。那么配网是如何完成的呢?

快速配网的两种途径:

  1. SoftAP配网
  2. SmartConfig配网

第一种很好理解,就类似给自己家里的路由器配置网络,你首先通过WiFi或者网线连接你家路由器,然后进入http://192.168.xxx.xxx进行Web可视化配置,也有一些小型单片机,自己搭载了一个WiFi模组以AP模式运行,你通过指定的APP就可以与其建立通信并发送配置信息过去。但是这种配网方式比较繁琐,用户体验较差。

第二种就比较高级了,你只需要打开设备的配网开关,并在你的手机上连接即将给设备配置的WiFi网络,然后在App或者小程序上输入对应的密码并点击开始,设备能够在很短的时间内(大约7~30s)就可以完成网络的配置。

这篇文章我将以我自己的学习成果来讲述SmartConfig配网的整个过程

阅读剩下更多

ESP-Touch智能配网是如何实现的
Web

腾讯云物联网通信平台设备动态注册鉴权流程

前置条件

物联网平台

  1. 在物联网平台创建新产品(普通产品),认证方式选用“秘钥认证”。
  2. 在产品设置中打开动态注册配置开关,并允许自动创建设备。
  3. 记录产品ID和产品秘钥(ProductID/ProductSecret)。

设备端

  1. 命名设备的唯一标识(如设备MAC、IMEI号、芯片ID等)即DeviceName。
  2. 将物联网平台获取的产品ID和产品秘钥烧录至设备中。

注册流程

第一步:生成验签参数

1
2
3
4
deviceName: #设备名称
nonce: #随机整数
productId: #产品ID
timestamp: #时间戳

拼接字符串“deviceName=%s&nonce=%d&productId=%s&timestamp=%d”,使用HMAC_SHA1算法,ProductSecret作为Key对字符串进行摘要,然后使用Base64编码该摘要获得验签参数(Signature)的值

阅读剩下更多

默认配图
算法

线性插值算法之一维数组矩阵

此篇文章用于记录工作中所需的数据插值算法:线性插值(以两个相邻数据的均值作为中间值插入两数之间)

惊喜预览图:

原始数据:原始采集动图一次插值数据:1次插值数据动图二次插值数据:2次插值数据动图

现有数据是一个8x8的温度矩阵(值为真实温度的100倍),存放在一个长度为64的一维数组中,测试数据如下:

1
2275,2250,2350,2350,2325,2350,2325,2400,2325,2275,2350,2350,2350,2350,2325,2350,2275,2300,2325,2350,2325,2300,2350,2325,2200,2275,2250,2275,2325,2275,2300,2275,2250,2250,2225,2250,2275,2325,2325,2350,2200,2250,2275,2250,2300,2275,2300,2325,2200,2250,2200,2250,2375,2325,2250,2250,2100,2125,2225,2300,2450,2325,2200,2225

问题分析

将8x8的矩阵利用插值算法转化成15x15(8个数据中插入7个虚拟值)矩阵根本思路:

  1. 将8x8的数据进行横向插值为一个15x8的数组;
  2. 将15x8的数字进行纵向插值为一个15x15的数组;

转化公式的分析:

先将问题简化,分析一个4x4的矩阵:

阅读剩下更多

默认配图
Linux

Linux配置网卡(静态)IP地址

上一篇记录了在OrangePi-Zero连接WIFI的过程,今天试着给自带默认网卡配置一下静态IP地址,以方便在整个局域网内能够访问设备。

首先

还是得通过ssh等方式登录Server,然后查看网卡的名称。执行ifconfig命令,找到eth开头的网卡,我这里是eth0。

再者

使用vim编辑文件/etc/network/interfaces,增加新行如下:

阅读剩下更多

默认配图
学习笔记

OrangePi Zero 简单连接WIFI的方法

有幸拿到可爱的小玩意儿“OrangePi_Zero”,也烧写了多个系统去体验,其中可以直接刷写OpenWrt,然后通过web管理界面启用WIFI功能。不过我需要的不是路由器,我需要的是能够连接WIFI的小型Server。

首先

通过ssh等方式登录server,然后查看无线网卡的名称。这里使用iwconfig命令,如果无线网卡正常安装且驱动正常的话,你能够看到一个以wlan开头+数字的的网卡,记住这个名字(我这里是wlan5)。

再者

使用vim编辑文件/etc/network/interfaces,内容如下:

阅读剩下更多

默认配图
学习笔记

利用Dockerfile创建PHP7.3环境镜像

关于Docker以及Dockerfile

Docker容器就不详细介绍了,把它理解成类似“虚拟机”的一种介质吧。至于如何创建一个镜像,主要有两种方法:1. 从现有的镜像基础上,创建容器并自定义后Commit成镜像;2. 利用Dockerfile,根据自己的需要,如同写shell脚本一般,将自己需要搭建的容器环境所需的指令一条一条的汇集成指令集,然后让Docker根据Dockerfile来自动创建你想要的镜像

创建自己的PHP7.3镜像

下载PHP源码:https://www.php.net/downloads.php

我这里下载了php-7.3.6.tar.bz2

阅读剩下更多

默认配图
PHP

引用符号'&'在foreach循环中的惊喜

工作中总会遇到一些奇奇怪怪的由前人所写下的不可置疑的代码,如果你仅仅跟着眼前所见的代码去理解他人的思路,Well You have fell into a terrible situation.

贴一段示例代码:

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
<?php
$students = [
['name'=>'Jackson','age'=>15],
['name'=>'Jerry','age'=>14],
['name'=>'Amy','age'=>14]
];
$classes = [
'CL001'=>['name'=>'Language','classTime'=>30],
'CL002'=>['name'=>'PE','classTime'=>13]
];

/*
* 循环$students,为其添加一个属性'name-age'
* 这里使用了&$v以获得数组内部元素的引用,使的可以在循环体内修改元素内容
*/
foreach ($students as $k=>&$v){
$v['name-age'] = $v['name'] . '-' .$v['age'];
}

$classString = ''; //定义一个空串
/*
* 循环$classes,将所有课程ID和名称联结成串
*/
foreach ($classes as $k=>$v){
$classString .= 'ClassID:'.$k.'-'.$v['name'].PHP_EOL;
}
echo $classString;
var_dump($students); //Think here!

你也许会回答这样一个输出结果:

阅读剩下更多

默认配图
算法

算法时间复杂度的表达-渐进符号与主定理(转载)

渐进符号是分析算法时间复杂度的常用记号,对于某个规模为n的问题,当n足够大时,就可以忽略掉复杂度表达式中的低阶项和最高次项的系数,由此引出“渐进复杂度”,并且用渐进符号来对“渐进复杂度”进行表达。

一、渐进符号

1、O(大O符号):上界
定义:若存在两个正的常数 c 和 n0 , 对于任意 n≥n0 , 都有 T( n)≤cf( n) ,则称T( n) = O( f( n) )(或称算法在 O( f( n))中)。

大 O 符号用来描述增长率的上限,表示 T( n)的增长最多像 f( n)增长的那样快,也就是说, 当输入规模为 n时, 算法消耗时间的最大值,这个上限的阶越低, 结果就越有价值。上界是对算法效率的一种承诺。

大O符号的含义如下图所示:
大O符号的含义

2、Ω(大Ω符号):下界
定义:若存在两个正的常数 c和 n0 ,对于任意 n≥ n0 , 都有 T( n)≥cg( n) ,则称T( n) = Ω( g( n) )(或称算法在 Ω( g( n) )中)。

大 Ω符号用来描述增长率的下限, 也就是说, 当输入规模为 n 时,算法消耗时间的最小值。与大 O 符号对称, 这个下限的阶越高,结果就越有价值。

阅读剩下更多

默认配图
学习笔记

软考选择题-索引文件地址项

【选择题】

设文件索引节点中有8个地址项,每个地址项大小为4字节,其中5个地址项为直接地址索引,2个地址项是一级间接地址索引,1个地址项是二级间接地址索引,磁盘索引块和磁盘数据块大小为1KB。若要访问文件的逻辑块号分别为5和518,则系统应分别采用____;而且可表示的单个文件最大长度是____KB。

第一空选项:

  1. 直接地址索引和一级间接地址索引
  2. 直接地址索引和二级间接地址索引
  3. 一级间接地址索引和二级间接地址索引
  4. 一级间接地址索引和一级间接地址索引

第二空选项:

  1. 517
  2. 1029
  3. 16513
  4. 66053

解答:

由题所知,索引快大小1KB,每个地址项4Byte,则每个索引块包含地址项为1KB/4Byte=256个。

直接地址索引实际拥有5*1个块,块号0~4

一级间接地址索引实际拥有2*256个块,块号5260,261516

二级间接地址索引实际拥有1*256*256个块,块号517~66052

逻辑块号为5的在一级间接地址索引,逻辑块号为518的在二级间接地址索引。

如上计算,文件索引节点中可以有66053个索引块,即最大文件长度为66053*1KB

阅读剩下更多

默认配图
网络安全

免费获得SSL证书-Let's Encrypt

第一步,安装certbot

MacOS版本:

1
brew install certbot

Ubuntu版本:

1
2
3
4
5
6
sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot python-certbot-nginx

第二步,运行certbot

type-1.仅仅制作证书

阅读剩下更多

默认配图
PHP

PHP加解密算法使用openssl替换mcrypt扩展

PHP版本从7.2开始不再支持mcrypt扩展,所以我们需要使用OpenSSl对其进行替换。本文仅列出部分算法的替换示例,所以不在本文出现的算法或模式需要自行尝试,顺水推舟。

本文替换案例:

  1. MCRYPT_RIJNDAEL_128 | MCRYPT_MODE_ECB => AES-128-ECB
  2. MCRYPT_DES | MCRYPT_MODE_CBC => DES-CBC
  3. MCRYPT_RIJNDAEL_128 | MCRYPT_MODE_CBC => AES-128-CBC
  4. MCRYPT_XTEA | MCRYPT_MODE_CBC

在使用 MCRYPT_RIJNDAEL_128 的地方,如果秘钥长度分别为16、24、32,则加密算法用 AES-128-ECB、AES-192-ECB、AES-256-ECB,BlockSize为16、24、32。

首先列出需要用到的数据填充方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function ZeroPadding($str, $block = 16) {
$pad = $block - (strlen($str) % $block);
if($pad == $block) return $str;
return $str.str_repeat(chr(0),$pad);
}
function ZeroUnPadding($str) {
return rtrim($str, "\0");
}


function PKCS7Padding($str, $block_size) {
$padding_char = $block_size - (strlen($str) % $block_size);
$padding_str = str_repeat(chr($padding_char),$padding_char);
return $str.$padding_str;
}
function PKCS7UnPadding($str) {
$char=substr($str,-1,1);
$num=ord($char);
if($num>0 && $num <= strlen($str)) {
$str = substr($str, 0, -1 * $num);
}
return $str;
}

阅读剩下更多

默认配图
学习笔记

Go-Server/Client以及PHP-Client之间的GRPC初次尝试

写这篇笔记的目的

为了应对后续开发生涯中可能遇到的种种情况以及分布式计算的趋势(讲白了就是后续对工作会很有帮助)。如若总是依赖http-api/restful编写并提供外部调用接口,当接口数量不断上升,文档内容不断增加,这给设计者和使用者都带来非常不好的体验,而RPC在这体就现出了非常大的优势。我将自己的理解和体会以及学习的过程记录在这里,以便今后遇到问题能够从这儿获得些许的线索以及提供一个参考给同道中人。

前提

不妨思考这样一个情形:作为接口设计者,我早已经定义好的接口的请求方式(RESTFul)和返回结构(json),但是每个接口我还需要另外维护一个文档来说明各个接口的用法(请求参数)和解释返回的结果(字段描述)。而对于接口的调用者而言,不但要去文档中查找自己需要的接口并阅读说明,在实际调用中,还要以防接口提供者返回非既定结构的结果而导致的报错。

介绍

Remote Procedure Call(远程过程调用),简称RPC。它可以使得调用远程服务接口如同调用本地方法一样简单。虽然实质还是通过网络通信,但是相比http请求api的网络开销还是极小的,其原因简单来说,HTTP协议每次请求都需要建立TCP连接,就会涉及3次握手的网络开销问题以及冗余报文,而rpc直接使用TCP多路复用(gRPC基于HTTP/2)无需重复建立连接。就文档方面而言,编写一份开发文档足矣,因为接口的定义由接口定义语言(IDL)来完成,而阅读IDL便可理解所有接口,并且通过编译器可以将IDL编译成不同的语言实现源码(gRPC通过protoc编译器将protobuf编译)。其他优点例如:注册、监控、发布等的这里不做论述。

Go-Server

GRPC的官方资源:go get google.golang.org/grpc

GRPC的镜像资源:https://github.com/grpc/grpc

官方的相关示列可以在grpc/examples/中找到。

定义服务

使用protocol buffers去定义gRPC service和方法 request以及 response 的类型。

新增并编辑文件:something.proto

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
syntax = "proto3";

// 声明包/作用域
package something;

// 定义一个服务,名为Wiwider
service Wiwider {
rpc FindUser (UserRequest) returns (UserReply) {} // 声明一个方法
rpc FindUsers (UserRequest) returns (UsersReply) {}
}

// 定义一个请求消息
message UserRequest {
string name = 1;
}

// 定义一个响应消息
message UserReply {
int64 id = 1;
string name = 2;
int32 age = 3;
sexes sex = 4;

enum sexes {
Male = 0;
Female = 1;
}
}

// 定义一个包含消息的消息
message UsersReply {
repeated UserReply users = 1;
}

生成代码

Go-代码

1
protoc --go_out=plugins=grpc:. something.proto

运行这个命令后,会在当前目录生成一个something.pb.go文件,内容包含:

  • 所有用于填充,序列化和获取我们请求和响应消息类型的 protocol buffer 代码
  • 一个为客户端调用定义在Wiwider服务的方法的接口类型(或者 存根 )
  • 一个为服务器使用定义在Wiwider服务的方法去实现的接口类型(或者 存根 )

PHP-代码

1
protoc --proto_path=./ --php_out=./ --grpc_out=./ --plugin=protoc-gen-grpc=/Users/anthony/git/grpc/bins/opt/grpc_php_plugin ./something.proto

其中/Users/anthony/git/grpc/bins/opt/grpc_php_plugin对应从git中获取的<grpc-git-path>/bins/opt/grpc_php_plugin

运行这个命令后,会在当前目录生成如下:

  • GPBMetadata/something.php
  • Something/UserReply/sexes.php
  • Something/UserReply.php
  • Something/UserReply_sexes.php
  • Something/UserRequest.php
  • Something/UsersReply.php
  • Something/WiwiderClient.php

创建服务器

首先我们需要实现服务定义的服务接口:

something/something.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
package something

import (
"Wiwide/cmi"
"context"
)
type UserServer struct {

}

func (s *UserServer) FindUser(ctx context.Context, in *UserRequest) (*UserReply, error) {
name := in.GetName()
rs, err := cmi.Db("local").Table("users").Where("name", name).First() // 从数据库查询
if err != nil || len(rs) == 0 {
return &UserReply{},err
}
rId := rs["id"].(int64)
rName := rs["name"].(string)
rAge := int32(rs["age"].(int64))
rSex := UserReplySexes(rs["sex"].(int64))
rep := &UserReply{Id:rId,Name:rName,Age:rAge,Sex:rSex}
return rep, nil
}

func (s *UserServer) FindUsers(ctx context.Context, in *UserRequest) (*UsersReply, error) {
name := in.GetName()
rs, err := cmi.Db("local").Table("users").Where("name","like","%"+name+"%").Get() // 从数据库查询
if err != nil {
return &UsersReply{}, err
}
list := make([]*UserReply, 0)
for _, v := range rs {
rId := v["id"].(int64)
rName := v["name"].(string)
rAge := int32(v["age"].(int64))
rSex := UserReplySexes(v["sex"].(int64))
list = append(list, &UserReply{Id:rId,Name:rName,Age:rAge,Sex:rSex})
}
rep := &UsersReply{Users: list}
return rep, nil
}

然后运行一个gRPC服务器,注册我们的服务并监听来自客户端的请求:

server.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
package main

import (
"Wiwide/rpc/something"
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"log"
"net"
)

var (
port = ":50051"
)
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
something.RegisterWiwiderServer(s, &something.UserServer{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

运行服务器

简单执行命令:go run server.go,然后等待客户端请求。

创建客户端

Go-Client

创建文件client.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
package main

import (
"Wiwide/rpc/something"
"context"
"google.golang.org/grpc"
"log"
"os"
"time"
)

const(
address = "localhost:50051"
defaultName = "World"
)

func main(){
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()

name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
s := something.NewWiwiderClient(conn)
u, err := s.FindUser(ctx, &something.UserRequest{Name:name})
if err != nil {
log.Fatalf("could not Find: %v", err)
}
us, err := s.FindUsers(ctx, &something.UserRequest{Name:name})
if err != nil {
log.Fatalf("could not Find users: %v", err)
}
log.Printf("Find user: %v", u)
log.Printf("Find users: %v", us)
}

PHP-Client

PHP首先要安装grpc的php扩展,下载地址:http://pecl.php.net/package/gRPC

直接使用phpize安装:

1
2
3
4
5
6
7
8
tar -zxf grpc-1.17.0.tgz
cd grpc-1.17.0
phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make
make install
# 在php.ini中增加
extension = grpc.so

执行php -m | grep grpc 应该会输出”grpc”,就代表成功了。

创建客户端文件,这里需要用到composer获取两个包,其中composer.json内容为:

1
2
3
4
5
6
7
8
{
"name": "grpc/grpc-demo",
"description": "gRPC example for PHP",
"require": {
"grpc/grpc": "^v1.3.0",
"google/protobuf": "^v3.3.0"
}
}

something.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
require dirname(__FILE__).'/vendor/autoload.php';

include_once dirname(__FILE__).'/Something/UserReply.php';
include_once dirname(__FILE__).'/Something/UsersReply.php';
include_once dirname(__FILE__).'/Something/UserRequest.php';
include_once dirname(__FILE__).'/Something/WiwiderClient.php';
include_once dirname(__FILE__).'/GPBMetadata/Something.php';

$client = new Something\WiwiderClient('127.0.0.1:50051', ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$req = new Something\UserRequest();
$name = !empty($argv[1]) ? $argv[1] : 'world';
$req->setName($name);
list($reply, $status) = $client->FindUser($req)->wait();
var_dump($reply->getId(), $reply->getName(), $reply->getAge(), $reply->getSex(), $status);

list($reply, $status) = $client->FindUsers($req)->wait();
$uers = $reply->getUsers();
$users = array();
foreach ($uers as $key => $value) {
array_push($users, array('id'=>$value->getId(),'name'=>$value->getName(),'age'=>$value->getAge(),'sex'=>$value->getSex()));
}
var_dump($users);

运行客户端

Go-Client

我们先编译一下:go build client.go,输出可执行文件client,然后直接运行:

  • ./client Anthony
  • ./client ny

分别输出如下:

1
2
2018/12/02 13:40:15 Find user: id:1 name:"Anthony" age:24 sex:Female 
2018/12/02 13:40:15 Find users: users:<id:1 name:"Anthony" age:24 sex:Female >
1
2
2018/12/02 13:40:25 Find user: 
2018/12/02 13:40:25 Find users: users:<id:1 name:"Anthony" age:24 sex:Female > users:<id:4 name:"funny" age:15 sex:2 >

PHP-Client

直接执行PHP文件:php something.php Anthony和php something.php ny,结果如下:

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
int(1)
string(7) "Anthony"
int(24)
int(1)
object(stdClass)#8 (3) {
["metadata"]=>
array(0) {
}
["code"]=>
int(0)
["details"]=>
string(0) ""
}
array(1) {
[0]=>
array(4) {
["id"]=>
int(1)
["name"]=>
string(7) "Anthony"
["age"]=>
int(24)
["sex"]=>
int(1)
}
}
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
int(0)
string(0) ""
int(0)
int(0)
object(stdClass)#8 (3) {
["metadata"]=>
array(0) {
}
["code"]=>
int(0)
["details"]=>
string(0) ""
}
array(2) {
[0]=>
array(4) {
["id"]=>
int(1)
["name"]=>
string(7) "Anthony"
["age"]=>
int(24)
["sex"]=>
int(1)
}
[1]=>
array(4) {
["id"]=>
int(4)
["name"]=>
string(5) "funny"
["age"]=>
int(15)
["sex"]=>
int(2)
}
}

结束

本次笔记接近结尾了,内容很简单,主要记录使用grpc的大体流程。为后续的测试做一点点的准备。

参考资料

阅读剩下更多

默认配图
编程语言

Go语言包-path/filepath

一天一个Golang包,慢慢学习之“path/filepath”

上一篇学习了path包,了解了几个对路径处理的方法。今天继续完成它的子包“filepath”,包中的函数会根据不同平台做不同的处理,如路径分隔符、卷名等。

阅读文档:

官方pkg地址:https://golang.org/pkg/path/filepath

包方法

func Base(path string) string
func Clean(path string) string
func Dir(path string) string
func Ext(path string) string
func IsAbs(path string) bool
func Join(elem ...string) string
func Match(pattern, name string) (matched bool, err error)
func Split(path string) (dir, file string)
// 以上8种方法跟path包同名方法功能类似


func Abs(path string) (string, error)
// 返回相对当前路径的path的绝对路径

1
2
3
4
5
6
7
8
9
10
11
12
fmt.Println(filepath.Abs(""))
fmt.Println(filepath.Abs("name.txt"))
fmt.Println(filepath.Abs("/name.txt"))
fmt.Println(filepath.Abs("../name.txt"))
fmt.Println(filepath.Abs("~/../name.txt"))
/*
/Users/anthony/Workspaces/GoLand/src/application/learn/path/filepath <nil>
/Users/anthony/Workspaces/GoLand/src/application/learn/path/filepath/name.txt <nil>
/name.txt <nil>
/Users/anthony/Workspaces/GoLand/src/application/learn/path/name.txt <nil>
/Users/anthony/Workspaces/GoLand/src/application/learn/path/filepath/name.txt <nil>
*/

func EvalSymlinks(path string) (string, error)
// 返回Path的实际路径(如果path是个软链接的话)

1
2
fmt.Println(filepath.EvalSymlinks("/etc"))		// /private/etc <nil>
fmt.Println(filepath.EvalSymlinks("/usr/local"))// /usr/local <nil>

func ToSlash(path string) string
func FromSlash(path string) string
// 以上两种方法作主要用于Windows平台。
// 将path中的平台相关的路径分隔符(或’/‘)转换为’/‘(或平台相关的路径分隔符)


func Glob(pattern string) (matches []string, err error)
// 列出与指定模式pattern完全匹配的文件或目录,匹配原则同Match一样。

1
2
fmt.Println(filepath.Glob("/usr/*"))
// [/usr/bin /usr/include /usr/lib /usr/libexec /usr/local /usr/sbin /usr/share /usr/standalone] <nil>

func HasPrefix(p, prefix string) bool
// 该方法已弃用,不再被建议使用。


func Rel(basepath, targpath string) (string, error)
// 返回targpath相对于basepath的路径。
// 要求二者必须同为“相对路径”或“绝对路径”

1
2
3
4
fmt.Println(filepath.Rel("/usr/local","/usr/local/bin/go"))			// bin/go <nil>
fmt.Println(filepath.Rel("/usr/local","/usr/local/../local/bin/go"))// bin/go <nil>
fmt.Println(filepath.Rel("/usr/local","/usr/bin/go")) // ../bin/go <nil>
fmt.Println(filepath.Rel("/usr/local","/")) // ../.. <nil>

func SplitList(path string) []string
// 将路径序列path分割为多条独立路径。
// path类似Unix/Linux下的环境变量PATH。

1
2
fmt.Printf("%q\n", filepath.SplitList("/bin:/sbin:/usr/bin:/usr/sbin  : /usr/local/bin"))
// ["/bin" "/sbin" "/usr/bin" "/usr/sbin " " /usr/local/bin"]

func VolumeName(path string) string
// 返回路径字符中的卷名
// Windows 中的 C:\Windows 会返回”C:”
// Linux 中的 //dev/host/dir 会返回 //dev/host


func Walk(root string, walkFn WalkFunc) error
// 遍历指定目录root(包括子目录),对遍历到的项目用walkFn函数进行处理。
// walkFn返回nil,则walkFn继续遍历,如果返回SkipDir,则Walk函数跳过当前目录,继续遍历下一目录。
// 如果返回其他错误,则Walk函数终止。
// 在Walk遍历过程中,如遇到错误,则会将错误通过err传递给walkFn,同时跳过出错的项目,继续处理后续项目。

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
// 打印目录及目录下所有文件及大小
fn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Printf("%q, %q, %d\n", path, info.Name(), info.Size())
return nil
}

filepath.Walk("/Users/anthony/Downloads/conf", fn)
/*
"/Users/anthony/Downloads/conf", "conf", 224
"/Users/anthony/Downloads/conf/0x4f5da2.cn.key", "0x4f5da2.cn.key", 1700
"/Users/anthony/Downloads/conf/0x4f5da2.cn_bundle.crt", "0x4f5da2.cn_bundle.crt", 3323
"/Users/anthony/Downloads/conf/world.cert.key.pem", "world.cert.key.pem", 1675
"/Users/anthony/Downloads/conf/world.dev.bundle.crt", "world.dev.bundle.crt", 2802
"/Users/anthony/Downloads/conf/world.local.bundle.crt", "world.local.bundle.crt", 2863
*/

// WalkFunc 函数:
// 列出含有 *.txt 文件的目录(不是全部,因为会跳过一些子目录)
findTxtDir := func(path string, info os.FileInfo, err error) error {
ok, err := filepath.Match(`*.txt`, info.Name())
if ok {
fmt.Println(filepath.Dir(path), info.Name())
// 遇到 txt 文件则继续处理所在目录的下一个目录
// 注意会跳过子目录
return filepath.SkipDir
}
return err
}
// 列出含有 *.txt 文件的目录(不是全部,因为会跳过一些子目录)
err := filepath.Walk(`/usr`, findTxtDir)
fmt.Println(err)
/*
/usr/local/Cellar/chromaprint/1.4.3 NEWS.txt
/usr/local/Cellar/fontconfig/2.13.1/share/doc/fontconfig fontconfig-devel.txt
/usr/local/Cellar/frei0r/1.6.1 AUTHORS.txt
/usr/local/Cellar/game-music-emu/0.6.2 changes.txt
...
<nil>
*/

// WalkFunc 函数:
// 列出所有以 ab 开头的目录(全部,因为没有跳过任何项目)
findabDir := func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
ok, err := filepath.Match(`[aA][bB]*`, info.Name())
if err != nil {
return err
}
if ok {
fmt.Println(path)
}
}
return nil
}
// 列出所有以 ab 开头的目录(全部,因为没有跳过任何项目)
err = filepath.Walk(`/usr`, findabDir)
fmt.Println(err)
/*
/usr/local/Cellar/node/11.0.0/libexec/lib/node_modules/npm/node_modules/abbrev
/usr/local/Homebrew/Library/Taps/homebrew/homebrew-services/.git/objects/ab
/usr/local/lib/node_modules/gulp/node_modules/resolve/test/dotdot/abc
/usr/local/lib/node_modules/hexo-cli/node_modules/abbrev
...
<nil>
*/


阅读剩下更多

默认配图
编程语言

Go语言包-path

一天一个Golang包,慢慢学习之“path”

上一篇学习了bytes包,内容还是有点多,花的时间也多了一点。所以今天补充点小内容-path包。当然,path还有子包:filepath,这个下次再继续。

阅读文档:

官方pkg地址:https://golang.org/pkg/path/

包方法

func Base(path string) string
// 返回最后一个元素(目录或文件)的路径

1
2
3
4
5
6
7
8
fmt.Println(path.Base("/a/b/"))	// b
fmt.Println(path.Base("/a/b")) // b
fmt.Println(path.Base("./a/b")) // b
fmt.Println(path.Base("../a/b")) // b
fmt.Println(path.Base("/")) // /
fmt.Println(path.Base("./")) // .
fmt.Println(path.Base(".")) // .
fmt.Println(path.Base("")) // .

func Clean(path string) string
// 返回最洁净的路径,在path比较复杂的情况下使用,可以简化path。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
paths := []string{
"a/c",
"a//c",
"a/c/.",
"a/c/b/..",
"/../a/c",
"/../a/b/../././/c",
"",
}
for _, p := range paths {
fmt.Printf("Clean(%q) = %q\n", p, path.Clean(p))
}
// Clean("a/c") = "a/c"
// Clean("a//c") = "a/c"
// Clean("a/c/.") = "a/c"
// Clean("a/c/b/..") = "a/c"
// Clean("/../a/c") = "/a/c"
// Clean("/../a/b/../././/c") = "/a/c"
// Clean("") = "."

func Dir(path string) string
// 返回元素(目录或文件)的目录路径

1
2
3
4
5
6
fmt.Println(path.Dir("/a/b/c"))	// /a/b
fmt.Println(path.Dir("a/b/c")) // a/b
fmt.Println(path.Dir("/a/")) // /a
fmt.Println(path.Dir("a/")) // a
fmt.Println(path.Dir("/")) // /
fmt.Println(path.Dir("")) // .

func Ext(path string) string
// 返回path下文件名的后缀

1
2
3
4
fmt.Println(path.Ext("/a/b/c/bar.css"))	// .css
fmt.Println(path.Ext("/a/b/c/bar.tar.gz")) // .gz
fmt.Println(path.Ext("/")) //
fmt.Println(path.Ext("")) //

func IsAbs(path string) bool
// 判断path是否是绝对路径

1
2
3
fmt.Println(path.IsAbs("/dev/null"))	// true
fmt.Println(path.IsAbs("dev/null")) // false
fmt.Println(path.IsAbs("./dev/null")) // false

func Join(elem ...string) string
// 将多个路径元素连接成一个,空元素会被忽略。

1
2
3
4
5
6
fmt.Println(path.Join("a", "b", "c"))	// a/b/c
fmt.Println(path.Join("a", "b/c")) // a/b/c
fmt.Println(path.Join("a/b", "c")) // a/b/c
fmt.Println(path.Join("", "")) //
fmt.Println(path.Join("a", "")) // a
fmt.Println(path.Join("", "a")) // a

func Match(pattern, name string) (matched bool, err error)
// 判断name是否符合pattern规则,并返回err

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// pattern:
// { term }
// term:
// '*' matches any sequence of non-/ characters
// '?' matches any single non-/ character
// '[' [ '^' ] { character-range } ']'
// character class (must be non-empty)
// c matches character c (c != '*', '?', '\\', '[')
// '\\' c matches character c

// character-range:
// c matches character c (c != '\\', '-', ']')
// '\\' c matches character c
// lo '-' hi matches character c for lo <= c <= hi

fmt.Println(path.Match("abc", "abc")) // true <nil>
fmt.Println(path.Match("a*", "abc")) // true <nil>
fmt.Println(path.Match("a*d", "abcd")) // true <nil>
fmt.Println(path.Match("a*/b", "a/c/b")) // false <nil>
fmt.Println(path.Match("a/[a-f]/b", "a/c/b")) // true <nil>

func Split(path string) (dir, file string)
// 返回path的目录和文件名

1
2
3
fmt.Println(path.Split("static/myfile.css"))	// static/ myfile.css
fmt.Println(path.Split("myfile.css")) // myfile.css
fmt.Println(path.Split("")) //

子包:filepath

下一篇来学习:path/filepath

阅读剩下更多

默认配图
编程语言

Go语言包-bytes

一天一个Golang包,慢慢学习之“bytes”

今天学习bytes包,其实之前就已经使用了bytes包的很多方法了,这次主要就是再次熟悉和认识这个包里面的方法。

阅读文档:

官方pkg地址:https://golang.org/pkg/bytes/

包方法

func Compare(a, b []byte) int
// 比较a和b两个字节数组。
// if a==b return 0;
// if a < b return -1;
// if a > b return 1;

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
var a, b []byte
a = []byte{65,66,67}
b = []byte{65,66,67}
fmt.Printf("%s, %s, %d\n", a, b, bytes.Compare(a, b))

a = []byte{65,66,67}
b = []byte{65,66,68}
fmt.Printf("%s, %s, %d\n", a, b, bytes.Compare(a, b))

a = []byte{65,66,67}
b = []byte{65,66,66}
fmt.Printf("%s, %s, %d\n", a, b, bytes.Compare(a, b))

a = []byte{65,66,67}
b = []byte{65,66,67,68}
fmt.Printf("%s, %s, %d\n", a, b, bytes.Compare(a, b))

a = []byte{65,66,67,68}
b = []byte{65,66,67}
fmt.Printf("%s, %s, %d\n", a, b, bytes.Compare(a, b))
// ABC, ABC, 0
// ABC, ABD, -1
// ABC, ABB, 1
// ABC, ABCD, -1
// ABCD, ABC, 1

func Contains(b, subslice []byte) bool
// 检查subslice是不是包含在b中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a := []byte{65, 66, 67, 68, 69, 70}
b := []byte{65, 66, 67, 68, 69, 70}
fmt.Printf("%s, %s, %v\n", a, b, bytes.Contains(a, b))

b = []byte{65, 66, 67, 68, 69}
fmt.Printf("%s, %s, %v\n", a, b, bytes.Contains(a, b))

b = []byte{65, 66, 67, 68, 69, 70, 71}
fmt.Printf("%s, %s, %v\n", a, b, bytes.Contains(a, b))

b = []byte{66, 67, 68, 69, 70, 71}
fmt.Printf("%s, %s, %v\n", a, b, bytes.Contains(a, b))
// ABCDEF, ABCDEF, true
// ABCDEF, ABCDE, true
// ABCDEF, ABCDEFG, false
// ABCDEF, BCDEFG, false

func ContainsAny(b []byte, chars string) bool
// 检查b中是否包含chars中的任意字符

1
2
3
4
5
6
a := []byte{65, 66, 67, 68}
fmt.Printf("%v\n", bytes.ContainsAny(a, "abcd") // false
fmt.Printf("%v\n", bytes.ContainsAny(a, "a")) // false
fmt.Printf("%v\n", bytes.ContainsAny(a, "aB")) // true
fmt.Printf("%v\n", bytes.ContainsAny(a, "aBcd")) // true
fmt.Printf("%v\n", bytes.ContainsAny(a, "abcd ABC")) // true

func ContainsRune(b []byte, r rune) bool
// 判断b中是否包含r字符

1
2
3
a := []byte{65, 66, 67 ,68}
fmt.Printf("%v\n", bytes.ContainsRune(a, 'b')) // false
fmt.Printf("%v\n", bytes.ContainsRune(a, 'B')) //true

func Count(s, sep []byte) int
// sep在s中的重复次数(sep不重叠),即s=ababab,sep=abab则返回1

1
2
3
4
5
a := []byte("ababab")
sep := []byte("ab")
fmt.Printf("%d\n", bytes.Count(a, sep)) // 3
sep = []byte("abab")
fmt.Printf("%d\n", bytes.Count(a, sep)) // 1

func Equal(a, b []byte) bool
// 判断a和b是否相等,nil等同于[]byte()

1
2
3
4
5
a := []byte("ABC")
b := []byte{65, 66, 67}
fmt.Printf("%s, %s, %v\n", a, b, bytes.Equal(a, b)) // ABC, ABC, true
b = []byte{65 ,66}
fmt.Printf("%s, %s, %v\n", a, b, bytes.Equal(a, b)) // ABC, AB, false

func EqualFold(s, t []byte) bool
// 判断s和t是否相似,忽略大小写和标题三种格式

1
2
3
4
5
a := []byte("abc")
b := []byte("ABC")
fmt.Printf("%s, %s, %v\n", a, b, bytes.EqualFold(a, b)) // abc, ABC, true
b = []byte("ABCC")
fmt.Printf("%s, %s, %v\n", a, b, bytes.EqualFold(a, b)) // abc, ABCC, false

func Fields(s []byte) [][]byte
// 以连续空白为分隔符切割s成多个子串(不含分隔符)

1
2
3
a := []byte("abc def  ABC  CBA")
fmt.Printf("%s, %v\n", a, bytes.Fields(a))
// abc def ABC CBA, [[97 98 99] [100 101 102] [65 66 67] [67 66 65]]

func FieldsFunc(s []byte, f func(rune) bool) [][]byte
// 以符合f方法的字符为分隔符切割s成多个子串(不含分隔符)

1
2
3
f := func(r rune) bool { return r == '#'}
a := []byte("This is # and @")
fmt.Printf("%s, %q\n", a, bytes.FieldsFunc(a, f)) // This is # and @, ["This is " " and @"]

func HasPrefix(s, prefix []byte) bool
// 检测s字节切片是否以prefix开头

1
2
3
4
5
6
7
s := []byte("Hello World!")
prefix := []byte("hello")
fmt.Printf("%s, %s, %v\n", s, prefix, bytes.HasPrefix(s, prefix)) // Hello World!, hello, false
prefix = []byte("Hello")
fmt.Printf("%s, %s, %v\n", s, prefix, bytes.HasPrefix(s, prefix)) // Hello World!, Hello, true
prefix = []byte("")
fmt.Printf("%s, %s, %v\n", s, prefix, bytes.HasPrefix(s, prefix)) // Hello World!, , true

func HasSuffix(s, suffix []byte) bool
// 检测s字节切片是否以suffix结尾

1
2
3
4
5
6
7
s := []byte("Hello World!")
suffix := []byte("world!")
fmt.Printf("%s, %s, %v\n", s, suffix, bytes.HasSuffix(s, suffix)) // Hello World!, world!, false
suffix = []byte("Hello World!")
fmt.Printf("%s, %s, %v\n", s, suffix, bytes.HasSuffix(s, suffix)) // Hello World!, World!, true
suffix = []byte("")
fmt.Printf("%s, %s, %v\n", s, suffix, bytes.HasSuffix(s, suffix)) // Hello World!, , true

func Index(s, sep []byte) int
// 返回sep在s中的起始位置,如果sep不在s中则返回-1

1
2
3
4
5
6
7
8
9
s := []byte("Hello World!")
sep := []byte("Hello")
fmt.Printf("'%s' of '%s' is %d\n", sep, s, bytes.Index(s, sep)) // 'Hello' of 'Hello World!' is 0
sep = []byte("World")
fmt.Printf("'%s' of '%s' is %d\n", sep, s, bytes.Index(s, sep)) // 'World' of 'Hello World!' is 6
sep = []byte("Bob")
fmt.Printf("'%s' of '%s' is %d\n", sep, s, bytes.Index(s, sep)) // 'Bob' of 'Hello World!' is -1
sep = []byte("")
fmt.Printf("'%s' of '%s' is %d\n", sep, s, bytes.Index(s, sep)) // '' of 'Hello World!' is 0

func IndexAny(s []byte, chars string) int
// 返回chars中任意字符在s中出现的第一个位置,chars为空或找不到则返回-1.

1
2
3
4
5
6
7
8
9
10
11
s := []byte("Hello World!")
chars := "abcdefg"
fmt.Printf("%s, %s, %d\n", s, chars, bytes.IndexAny(s, chars)) // Hello World!, abcdefg, 1
chars = "aabbcc"
fmt.Printf("%s, %s, %d\n", s, chars, bytes.IndexAny(s, chars)) // Hello World!, aabbcc, -1
chars = "abcd"
fmt.Printf("%s, %s, %d\n", s, chars, bytes.IndexAny(s, chars)) // Hello World!, abcd, 10
chars = ""
fmt.Printf("%s, %s, %d\n", s, chars, bytes.IndexAny(s, chars)) // Hello World!, , -1
chars = " "
fmt.Printf("%s, %s, %d\n", s, chars, bytes.IndexAny(s, chars)) // Hello World!, , 5

func IndexByte(b []byte, c byte) int
// 同Index,sep变为c字节


func IndexFunc(s []byte, f func(r rune) bool) int
// 返回符合f方法的字符首次出现的位置,找不到则返回-1


func IndexRune(s []byte, r rune) int
// 同Index,sep变为r字符


func Join(s [][]byte, sep []byte) []byte
// 以sep为连接符将子串列表连接成一个字节串

1
2
3
s := [][]byte{{65,66},{67,68}}
sep := []byte{124,124}
fmt.Printf("%q, %s, %s\n", s, sep, bytes.Join(s, sep)) // ["AB" "CD"], ||, AB||CD

func LastIndex(s, sep []byte) int
// 查找s中最后一次出现sep的位置,找不到返回-1

1
2
3
4
5
6
7
s := []byte("abcddabcd")
sep := []byte("abc")
fmt.Printf("%s, %s, %d\n", s, sep, bytes.LastIndex(s, sep)) // abcddabcd, abc, 5
sep = []byte("bcd")
fmt.Printf("%s, %s, %d\n", s, sep, bytes.LastIndex(s, sep)) // abcddabcd, bcd, 6
sep = []byte("")
fmt.Printf("%s, %s, %d\n", s, sep, bytes.LastIndex(s, sep)) // abcddabcd, , 9

func LastIndexAny(s []byte, chars string) int
// 查找s中最后一次出现chars中任意字符的位置,找不到返回-1


func LastIndexByte(s []byte, c byte) int
// 查找s中最后一次出现c字节的位置,找不到返回-1


func LastIndexFunc(s []byte, f func(r rune) bool) int
// 查找s中符合f方法的字符位置,找不到返回-1


func Map(mapping func(r rune) rune, s []byte) []byte
// 将s中的r替换成mapping(r)返回的字符,如果mapping返回负值,则丢弃

1
2
3
4
5
6
7
8
9
10
11
rot13 := func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z':
return 'A' + (r-'A'+13)%26
case r >= 'a' && r <= 'z':
return 'a' + (r-'a'+13)%26
}
return r
}
fmt.Printf("%s\n", bytes.Map(rot13, []byte("'Twas brillig and the slithy gopher...")))
// 'Gjnf oevyyvt naq gur fyvgul tbcure...

func Repeat(b []byte, count int) []byte
// 返回子串b重复count次后的串

1
fmt.Printf("%s\n", bytes.Repeat([]byte("Abc"), 3))	// AbcAbcAbc

func Replace(s, old, new []byte, n int) []byte
// 将s中的前n个old替换成new,如果n<0则替换全部

1
2
3
4
s := []byte("Bob takes you to Bob's house.")
fmt.Printf("%s\n", bytes.Replace(s, []byte("Bob"), []byte("Lily"), 0)) // Bob takes you to Bob's house.
fmt.Printf("%s\n", bytes.Replace(s, []byte("Bob"), []byte("Lily"), 1)) // Lily takes you to Bob's house.
fmt.Printf("%s\n", bytes.Replace(s, []byte("Bob"), []byte("Lily"), -1)) // Lily takes you to Lily's house.

func Runes(s []byte) []rune
// 将s转换成[]rune型


func Split(s, sep []byte) [][]byte
// 以sep作为分隔符将s拆分成多个子串。
// 如果sep为空,则将s拆分成Unicode字符列表


func SplitN(s, sep []byte, n int) [][]byte
// 同Split,但可指定拆分次数n,超出n的部分不进行拆分


func SplitAfter(s, sep []byte) [][]byte
// 同Split,但是每个子串都包含分隔符sep


func SplitAfterN(s, sep []byte, n int) [][]byte
// 同SplitAfter,但可指定拆分次数n。


func Title(s []byte) []byte
// 将s中所有单词的首字符修改为Title格式返回
// Bug: 不能很好的处理Unicode标点符号分隔的单词。


func ToLower(s []byte) []byte
// 将s中的字符全部替换成小写并返回


func ToUpper(s []byte) []byte
// 将s中的字符全部替换成大小并返回


func ToTitle(s []byte) []byte
// 将s中的字符全部替换成标题并返回


func ToLowerSpecial(c unicode.SpecialCase, s []byte) []byte
// 使用指定的映射表将 s 中的所有字符修改为小写格式返回。


func ToUpperSpecial(c unicode.SpecialCase, s []byte) []byte
// 使用指定的映射表将 s 中的所有字符修改为大写格式返回。


func ToTitleSpecial(c unicode.SpecialCase, s []byte) []byte
// 使用指定的映射表将 s 中的所有字符修改为标题格式返回。


func Trim(s []byte, cutset string) []byte
func TrimLeft(s []byte, cutset string) []byte
func TrimRight(s []byte, cutset string) []byte
// 以上方法返回去掉s两边(左边、右边)包含在cutset中字符的切片


func TrimFunc(s []byte, f func(r rune) bool) []byte
func TrimLeftFunc(s []byte, f func(r rune) bool) []byte
func TrimRightFunc(s []byte, f func(r rune) bool) []byte
// 以上方法返回去掉s两边(左边、右边)符合f方法字符的切片


func TrimPrefix(s, prefix []byte) []byte
// 去掉s的前缀prefix


func TrimSuffix(s, suffix []byte) []byte
// 去掉s的后缀suffix


func TrimSpace(s []byte) []byte
// 去掉两边的空白


func NewBuffer(buf []byte) *Buffer
// 将buf包装成bytes.Buffer对象


func NewBufferString(s string) *Buffer
// 将s转换成[]byte后,包装成bytes.Buffer对象


func NewReader(b []byte) *Reader
// 将b包装成bytes.Reader对象


Buffer结构体

1
2
3
4
5
6
7
8
9
10
11
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
buf []byte // contents are the bytes buf[off : len(buf)]
off int // read at &buf[off], write at &buf[len(buf)]
bootstrap [64]byte // memory to hold first slice; helps small buffers avoid allocation.
lastRead readOp // last read operation, so that Unread* can work correctly.

// FIXME: it would be advisable to align Buffer to cachelines to avoid false
// sharing.
}

Buffer的方法:

func (b *Buffer) Bytes() []byte
// 引用未读取部分的数据切片(不移动读取位置)


func (b *Buffer) Cap() int
// 返回缓冲区容量


func (b *Buffer) Grow(n int)
// 自动增加缓存容量,以保证有n字节的剩余空间
// 如果n<0或者无法增加容量则会报Panic


func (b *Buffer) Len() int
// 返回未读缓冲区的字节长度,b.Len()==len(b.Bytes())


func (b *Buffer) Next(n int) []byte
// 返回缓冲区中的下n个字节数据的切片,并推进读取位置(类似于返回的字节数据已经被读取)。
// 如果n大于缓冲区长度,则返回整个缓冲区数据。
// 返回的切片仅在下一次调用Read或Write方法前有效。


func (b *Buffer) Read(p []byte) (n int, err error)
// 从缓冲区读取len(p)个字节或整个缓冲区到p中,返回读取的字节数n,如果没有数据可返回,则err=io.EOF。


func (b *Buffer) ReadByte() (byte, error)
// 从缓冲区读取一个字节并返回,如果没有可返回的数据,err=io.EOF。


func (b *Buffer) ReadBytes(delim byte) (line []byte, err error)
// 从缓存中读取数据直到遇到delim时停止并返回包含delim的切片。
// 如果期间遇到错误,则返回遇到错误之前读取的数据并返回err。


func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error)
// 从r中读取数据直到遇到EOF并且写入缓存区,必要时会增大缓存。
// 返回读取到的字节数量n,遇到除了EOF的其他错误也会返回结果。
// 如果缓存变的太大将会报一个ErrTooLarge的Panic错误。


func (b *Buffer) ReadRune() (r rune, size int, err error)
// 从缓存中读取并返回下一个UTF8编码字符,没有合适的字节则返回io.EOF。


func (b *Buffer) ReadString(delim byte) (line string, err error)
// 从缓存中读取直到第一次遇到delim字节,并作为string返回这个包含delim的数据。


func (b *Buffer) Reset()
// 将b重置并清空数据,如同Truncate(0)。


func (b *Buffer) String() string
// 将缓冲区所有未读数据作为string返回,如果b是nil,则返回”“。


func (b *Buffer) Truncate(n int)
// 从缓冲区中丢弃第n个未读数据之后数据,如果n<0或n>Cap则报Panic。


func (b *Buffer) UnreadByte() error
// 撤销最近一次的读操作中的最后字节数据。
// 如果在这之前执行了write或者最后一次读取返回了错误或者最后读取了空字节,则该方法会返回一个错误。


func (b *Buffer) UnreadRune() error
// 类似UnreadByte(),但是严格要求最后一次读取是Rune,否则报错。


func (b *Buffer) Write(p []byte) (n int, err error)
// 将p中的数据写入缓存,并返回写入的字节数n和可能发生的错误。


func (b *Buffer) WriteByte(c byte) error
// 往缓存中写入一个字节c并返回可能发生的错误(通常是nil)。
// 如果缓存扩容太大,则会报一个ErrTooLarge的Panic。


func (b *Buffer) WriteRune(r rune) (n int, err error)
// 同WriteByte,返回写入长度和可能发生的错误(通常是nil)。


func (b *Buffer) WriteString(s string) (n int, err error)
// 同WriteByte,将s的内容写入缓存,返回写入长度和可能发生的错误。


func (b *Buffer) WriteTo(w io.Writer) (n int64, err error)
// 将缓存数据写入w直到写入完毕或发生错误。


Buffer的示列:

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
package bytes

import (
"bytes"
"fmt"
)

func Buffer(){
bt := make([]byte, 0)
buffer := bytes.NewBuffer(bt)
buffer.Write([]byte("Hello"))
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // Hello, 5, 5

buffer.WriteString(" World")
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // Hello World, 16, 11

buffer.WriteByte('!')
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // Hello World!, 16, 12

buffer.WriteRune('$')
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // Hello World!$, 16, 13

r, size, _ := buffer.ReadRune()
fmt.Printf("%q, %d\n", r, size) // 'H', 1
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // ello World!$, 16, 12

buffer.UnreadRune()
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // Hello World!$, 16, 13

b , _ :=buffer.ReadByte()
fmt.Printf("%q\n", b) // 'H'
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // ello World!$, 16, 12

buffer.UnreadByte()
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // Hello World!$, 16, 13

line, _ := buffer.ReadBytes('l')
fmt.Printf("%q\n", line) // "Hel"
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // lo World!$, 16, 10

buffer.UnreadByte()
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // llo World!$, 16, 11

str, _ := buffer.ReadString('o')
fmt.Printf("%q\n", str) // "llo"
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // World!$, 16, 8

buffer.UnreadByte()
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // o World!$, 16, 9

next := buffer.Next(2)
fmt.Printf("%q\n", next) // "o "
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // World!$, 16, 7

btRead := make([]byte, 2)
n, _ := buffer.Read(btRead)
fmt.Printf("%d\n", n) // 2
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // rld!$, 16, 5

buffer.Grow(3)
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // rld!$, 16, 5
buffer.Grow(4)
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // rld!$, 36, 5

buffer.Truncate(4)
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // rld!, 36, 4

buffer.Reset()
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // , 36, 0

bt1 := []byte("I see you.")
bf1 := bytes.NewReader(bt1)
n64, _ :=buffer.ReadFrom(bf1)
fmt.Printf("%d\n", n64) // 10
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // I see you., 584, 10

bt2 := make([]byte, 0)
bf2 := bytes.NewBuffer(bt2)
n64, _ = buffer.WriteTo(bf2)
fmt.Printf("%d, %q, %q\n", n64, bf2, bt2) // 10, "I see you.", ""
fmt.Printf("%s, %d, %d\n", buffer, buffer.Cap(), buffer.Len()) // , 584, 0
}

Reader结构体

1
2
3
4
5
6
7
8
9
10
11
// bytes.Reader 实现了如下接口:
// io.ReadSeeker
// io.ReaderAt
// io.WriterTo
// io.ByteScanner
// io.RuneScanner
type Reader struct {
s []byte
i int64 // current reading index
prevRune int // index of previous rune; or < 0
}

Reader的方法:

func (r *Reader) Len() int
// 返回未读部分的数据长度。


func (r *Reader) Size() int64
// 返回底层数据的总长度。


func (r *Reader) Reset(b []byte)
// 将底层数据切换为b,同时复位所有标记。


func (r *Reader) Read(b []byte) (n int, err error)
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
func (r *Reader) ReadByte() (byte, error)
func (r *Reader) ReadRune() (ch rune, size int, err error)
func (r *Reader) Seek(offset int64, whence int) (int64, error)
func (r *Reader) UnreadByte() error
func (r *Reader) UnreadRune() error
func (r *Reader) WriteTo(w io.Writer) (n int64, err error)

Reader的示列:

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
package bytes

import (
"bytes"
"fmt"
)

func Reader(){
b1 := []byte("你好,世界!")
b2 := []byte("Hello World!")
buf := make([]byte, 9)
rd := bytes.NewReader(b1)
n, _ := rd.Read(buf)
fmt.Printf("%q, %d, %d, %d\n", buf, rd.Len(), rd.Size(), n) // "你好,", 9, 18, 9
n, _ = rd.Read(buf)
fmt.Printf("%q, %d, %d, %d\n", buf, rd.Len(), rd.Size(), n) // "世界!", 0, 18, 9

rd.Reset(b2)
buf = make([]byte, 6)
n, _ = rd.Read(buf)
fmt.Printf("%q, %d, %d, %d\n", buf, rd.Len(), rd.Size(), n) // "Hello ", 6, 12, 6
n, _ = rd.Read(buf)
fmt.Printf("%q, %d, %d, %d\n", buf, rd.Len(), rd.Size(), n) // "World!", 0, 12, 6

rd.UnreadByte()
n, _ = rd.Read(buf)
fmt.Printf("%q, %d, %d, %d\n", buf, rd.Len(), rd.Size(), n) // "!orld!", 0, 12, 1
// UnreadByte使得最后一个字符'!'回到了rd中,在rd.Read写入buf时,则将'!'写入了buf[0]中。
}

阅读剩下更多

默认配图
编程语言

Go语言包-bufio

一天一个Golang包,慢慢学习之“bufio”

今天学习带缓存的I/O操作,基础包“bufio”中有bufio.go和scan.go两个文件

阅读文档:

官方pkg地址:https://golang.org/pkg/bufio/

包方法

func NewReadWriter(r *Reader, w *Writer) *ReadWriter
// 将r和w封装为一个bufio.Readwriter对象

func NewReader(rd io.Reader) *Reader
// 将rd封装为一个bufio.Reader对象(缓存大小默认4096)

func NewReaderSize(rd io.Reader, size int) *Reader
// 将rd封装成一个拥有size大小缓存的bufio.Reader对象

func NewScanner(r io.Reader) *Scanner
// 创建一个Scanner来扫描r

func NewWriter(w io.Writer) *Writer
// 将w封装成一个bufio.Writer对象(缓存大小默认4096)

func NewWriterSize(w io.Writer, size int) *Writer
//将w封装成一个拥有size大小缓存的bufio.Writer对象

Reader结构体

1
2
3
4
5
6
7
8
9
// Reader 实现了对io.Reader对象的缓冲功能
type Reader struct {
buf []byte
rd io.Reader // reader provided by the client
r, w int // buf read and write positions
err error
lastByte int
lastRuneSize int
}

func (b *Reader) Buffered() int
// 返回缓存中的数据长度

func (b *Reader) Discard(n int) (discarded int, err error)
// 丢弃接下来的n个字节,返回丢弃的字节长度

func (b *Reader) Peek(n int) ([]byte, error)
// 返回前n字节的缓存切片

func (b *Reader) Read(p []byte) (n int, err error)
// 读取len(p)个字节到p中。
// 如果len(p)>缓存大小,且缓存不为空,则读取全部缓存。
// 若缓存为空,则从底层io.Reader直接读取

func (b *Reader) ReadByte() (byte, error)
// 读出一个字节并返回

func (b *Reader) ReadBytes(delim byte) ([]byte, error)
// 查找delim并读取delim及其之前的所有数据(byte切片)
// 如果在找到delim之前发生错误,则返回发生错误之前的数据和error。
// 当找不到delim的时候,err!=nil

func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
// 返回一个单行数据(切片),不包括行尾标记。
// 如果在缓存中找不到行尾标记,isPrefix为true,表示查找未完成,否则isPrefix为false

func (b *Reader) ReadRune() (r rune, size int, err error)
// 读取一个UTF8字符和字符的编码长度。
// 如果UTF8序列无法解码出一个正确的Unicode字符,则只读出一个字节,并返回U+FFFD字符,size返回1。

func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
// 查找delim并读取delim及其之前的所有数据(byte切片-引用)

func (b *Reader) ReadString(delim byte) (string, error)
// 查找delim并读取delim及其之前的所有数据(字符串)

func (b *Reader) Reset(r io.Reader)
// 丢弃任何缓存数据,重置所有状态并切换io.Reader到r

func (r *Reader) Size() int
// 返回(底层)缓存的字节长度

func (b *Reader) UnreadByte() error
// 撤销最后一次读出的一个字节

func (b *Reader) UnreadRune() error
// 撤销最后一次读出的Unicode字符
// 如果最后一次执行的不是ReadRune(),则返回一个错误

func (b *Reader) WriteTo(w io.Writer) (n int64, err error)
// 实现了io.WriteTo接口。
// 可以(可能)对底层Reader的Read方法进行多次调用。
// 如果底层Reader支持WriteTo方法,则直接调用底层方法,无需缓存。

Reader的示例:

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
package bufio

import (
"bufio"
"bytes"
"fmt"
)

func Reader(){
testB := bytes.NewReader([]byte("abcdefg\nABCDEFG\nh i j k\nH I J K\n\nline."))
bufRd := bufio.NewReader(testB)
fmt.Println(bufRd.Buffered(), bufRd.Size())
// 0 4096

b, _ := bufRd.ReadByte()
fmt.Println(bufRd.Buffered(), b, string(b))
// 37 97 a

bs, _ := bufRd.ReadBytes('d')
fmt.Println(bufRd.Buffered(), bs, string(bs))
// 34 [98 99 100] bcd

discard, _ := bufRd.Discard(1)
fmt.Println("discard:", discard, "bufferd:", bufRd.Buffered())
// discard: 1 bufferd: 33

b, _ = bufRd.ReadByte()
fmt.Println(bufRd.Buffered(), b, string(b))
// 32 102 f

bs, _ = bufRd.Peek(7)
fmt.Println(bufRd.Buffered(), bs, string(bs))
// 32 [103 10 65 66 67 68 69] g
// ABCDE

line, isPrefix, _ := bufRd.ReadLine()
fmt.Println(bufRd.Buffered(), line, string(line), isPrefix)
// 30 [103] g false

r, size, _ := bufRd.ReadRune()
fmt.Println(bufRd.Buffered(), r, string(r), size)
// 29 65 A 1

slice, _ := bufRd.ReadSlice('D')
fmt.Println(bufRd.Buffered(), slice, string(slice))
// 26 [66 67 68] BCD

str, _ := bufRd.ReadString('G')
fmt.Println(bufRd.Buffered(), str)
// 23 EFG

bufRd.Reset(bytes.NewReader([]byte("Hello World.\nMy World.")))

str, _ = bufRd.ReadString('r')
fmt.Println(bufRd.Buffered(), str)
// 13 Hello Wor

bufRd.UnreadByte()
fmt.Println(bufRd.Buffered())
// 14

str, _ = bufRd.ReadString('\n')
fmt.Println(bufRd.Buffered(), str)
// 9 rld.
//

r, size, _ = bufRd.ReadRune()
fmt.Println(bufRd.Buffered(), r, string(r), size)
// 8 77 M 1

bufRd.UnreadRune()
fmt.Println(bufRd.Buffered())
// 9

str, _ = bufRd.ReadString('\n')
fmt.Println(bufRd.Buffered(), str)
// 0 My World.

}

Writer结构体

1
2
3
4
5
6
7
8
9
// Writer 实现了对io.Writer对象的缓冲功能
// 如果在写入Writer时发生错误,则会停止继续写入和后续操作并返回这个错误。
// 在所有数据被写入后,应当调用Flush方法来保证所有数据都被写入底层Writer。
type Writer struct {
err error
buf []byte
n int
wr io.Writer
}

func NewWriter(w io.Writer) *Writer
// 将w封装成一个bufio.Writer对象,缓存大小为4096.

func NewWriterSize(w io.Writer, size int) *Writer
// 将w封装成一个拥有size大小缓存的bufio.Writer对象

func (b *Writer) Available() int
// 返回缓存中的可用空间

func (b *Writer) Buffered() int
// 返回缓存中未提交的数据长度

func (b *Writer) Flush() error
// 将缓存中的数据提交到底层io.Writer中

func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)
// 实现了io.ReaderFrom接口。
// 如果底层writer支持ReadFrom方法且b的缓存数据为空,则直接调用底层的ReadFrom方法,不使用缓存。

func (b *Writer) Reset(w io.Writer)
// 丢弃所有未被提交的缓存数据,重置所有状态并切换io.Writer到w

func (b *Writer) Size() int
// 以字节为单位返回(底层)缓冲区大小

func (b *Writer) Write(p []byte) (nn int, err error)
// 将p中的数据写入b中,返回写入的字节数
// 如果写入的字节数小于p的长度,则返回错误

func (b *Writer) WriteByte(c byte) error
// 写入一个字节

func (b *Writer) WriteRune(r rune) (size int, err error)
// 写入一个UTF8编码字符

func (b *Writer) WriteString(s string) (int, error)
// 写入一个字符串,返回写入的字节数

Writer的示列:

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
package bufio

import (
"bufio"
"bytes"
"fmt"
)

func Writer(){
bt := make([]byte, 0)
Bt := bytes.NewBuffer(bt)
bufWt := bufio.NewWriter(Bt)

fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4096 0

// Write
nn, _ := bufWt.Write([]byte{65, 66})
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4094 2
fmt.Println(nn, Bt) // 2 "空"
bufWt.Flush()
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4096 0
fmt.Println(nn, Bt) // 2 AB

// WriteByte
bufWt.WriteByte('C')
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4095 1
fmt.Println(Bt) // AB
bufWt.Flush()
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4096 0
fmt.Println(Bt) // ABC

// WriteRune
size, _ := bufWt.WriteRune('D')
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4095 1
fmt.Println(size, Bt) // 1 ABC
bufWt.Flush()
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4096 0
fmt.Println(Bt) // ABCD

// WriteString
n, _ := bufWt.WriteString("EFG")
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4093 3
fmt.Println(n, Bt) // 3 ABCD
bufWt.Flush()
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4096 0
fmt.Println(Bt) // ABCDEFG

fmt.Println(bufWt.Size()) // 4096

// ReadFrom without buffered
some := bytes.NewReader([]byte("HIJKLMN"))
nChar, _ := bufWt.ReadFrom(some)
fmt.Println(bufWt.Available(), bufWt.Buffered()) //4096 0
fmt.Println(nChar, Bt) //7 ABCDEFGHIJKLMN
// 这里不用调用flush,因为在ReadForm之前缓存中没有数据,直接调用底层的ReadFrom写入了数据
// 下面看下有缓存的情况,先写入一个换行
bufWt.WriteByte('\n')
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4095 1
fmt.Println(Bt) // ABCDEFGHIJKLMN
// 紧接着不Flash而是直接ReadForm
some2 := bytes.NewReader([]byte("OPQRST"))
nChar, _ = bufWt.ReadFrom(some2)
fmt.Println(bufWt.Available(), bufWt.Buffered()) //4089 7
fmt.Println(nChar, Bt) //6 ABCDEFGHIJKLMN
// 现在Flush然后看各个变量
bufWt.Flush()
fmt.Println(bufWt.Available(), bufWt.Buffered()) // 4096 0
fmt.Println(Bt) // ABCDEFGHIJKLMN"换行"OPQRST
}

Scanner结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Scanner为读取数据提供了便捷的接口,这些数据包括但不限于有换行分界符的文本文件。
// 连续调用Scan方法将会遍历文件的'Token'(指定部分)且跳过指定部分之间的字节。
// 指定部分的格式由SplitFunc类型的函数定义,默认的切分函数根据行尾将输入拆分成行但不包括行尾
// 本包下定义并提供的切分函数用于将文件扫描成line,bytes,UTF-8Encoded runes和空格拆分的单词。用户也可以使用自己的切分函数来替换它。
// 当扫描遇到EOF、第一次I/O错误或'指定部分'太大无法存入缓冲区时停止。
// 当一次扫描停止时,读取器有可能早已超出'指定部分'很远。如果程序想要对数据进行更多的控制,如错误处理或扫描更大的'指定部分'或顺序扫描,则应当使用bufio.reader来代替。
type Scanner struct {
r io.Reader // 用户提供的reader.
split SplitFunc // 拆分token的方法.
maxTokenSize int // token的上限
token []byte // split返回的最后token.
buf []byte // 作为参数给split的缓冲区.
start int // 缓冲区起始位.
end int // 缓冲区终止位.
err error // Sticky error.
empties int // 连续出现空token的次数.
scanCalled bool // 扫描是否开始.
done bool // 扫描完毕.
}

func (s *Scanner) Buffer(buf []byte, max int)
// 用于设置自定义缓存以及可扩展范围,如果max小于len(buf),则buf的尺寸将固定不可调。
// Buffer必须在第一次Scan之前设置,否则引发Panic。
// 默认情况下,Scanner将会使用一个4096-bufio.MaxScanTokenSize大小的内部缓存。

func (s *Scanner) Bytes() []byte
// 将最后一次扫描出的‘指定部分’作为切片(引用)返回
// 下一次的扫描会覆盖本结果

func (s *Scanner) Err() error
// 返回扫描过程中遇到的非EOF错误

func (s *Scanner) Scan() bool
// 扫描数据中的‘指定部分’

func (s *Scanner) Split(split SplitFunc)
// 设置Scanner的分切函数,必须在Scan方法之前调用

func (s *Scanner) Text() string
// 最后一次扫描出的‘指定部分’作为String返回

Scanner的示例:

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
package bufio

import (
"bufio"
"bytes"
"fmt"
)

func Scan(){
b1 := bytes.NewReader([]byte("ABC\nDEF\r\nGHI\nJKL"))
bs := bufio.NewScanner(b1)
for bs.Scan() {
fmt.Printf("%s %v\n", bs.Bytes(), bs.Text())
}
// ABC ABC
// DEF DEF
// GHI GHI
// JKL JKL
b2 := bytes.NewReader([]byte("ABC\nDEF GHI JKL"))
bs = bufio.NewScanner(b2)
bs.Split(bufio.ScanWords)
for bs.Scan() {
fmt.Println(bs.Text())
}
// ABC
// DEF
// GHI
// JKL
b3 := bytes.NewReader([]byte("Hello 世界!"))
bs = bufio.NewScanner(b3)
bs.Split(bufio.ScanRunes)
for bs.Scan() {
fmt.Printf("%v ", bs.Text())
}
// H e l l o 世 界 !
fmt.Println()
b4 := bytes.NewReader([]byte("我的天哪!"))
bs = bufio.NewScanner(b4)
bs.Split(bufio.ScanBytes)
for bs.Scan() {
fmt.Printf("%v ", bs.Bytes())
}
// [230] [136] [145] [231] [154] [132] [229] [164] [169] [229] [147] [170] [33]
fmt.Println()
}

另外的参考:

阅读剩下更多

默认配图
编程语言

Go语言包-archive/zip

一天一个Golang包,慢慢学习之“archive/zip”

昨天阅读了archive包的tar包,对Go语言操作压缩包有了一定的基础,今天就继续把另外一个包“zip”也来熟悉一下。

阅读文档:

官方pkg地址:https://golang.org/pkg/archive/zip/

今天同样从官方的包文档中提取几个主要的内容出来分享一下:

常量

1
2
3
4
const (
Store uint16 = 0 // no compression
Deflate uint16 = 8 // DEFLATE compressed
)

变量

1
2
3
4
5
var (
ErrFormat = errors.New("zip: not a valid zip file") // 不合法的zip文件
ErrAlgorithm = errors.New("zip: unsupported compression algorithm") // 不支持的压缩算法
ErrChecksum = errors.New("zip: checksum error") //校验错误
)

FileHeader结构体

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
type FileHeader struct {
Name string // 文件名,必须是相对路径,不能以设备或斜杠开始,分隔符用'/'表示

Comment string

NonUTF8 bool // Go 1.10

CreatorVersion uint16
ReaderVersion uint16
Flags uint16

Method uint16

Modified time.Time // Go 1.10
ModifiedTime uint16 // Deprecated: Legacy MS-DOS date; use Modified instead.
ModifiedDate uint16 // Deprecated: Legacy MS-DOS time; use Modified instead.

CRC32 uint32
CompressedSize uint32 // Deprecated: Use CompressedSize64 instead.
UncompressedSize uint32 // Deprecated: Use UncompressedSize64 instead.
CompressedSize64 uint64 // Go 1.1
UncompressedSize64 uint64 // Go 1.1
Extra []byte
ExternalAttrs uint32 // Meaning depends on CreatorVersion
}

FileHeader相关的方法:

func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) // 通过传入的fi填充一个FileHeader并返回,由于os.FileInfo的Name方法只返回文件描述的Name,所以实际使用有可能需要自行修改为完整的全路径

func (h *FileHeader) FileInfo() os.FileInfo // 返回一个os.FileInfo

func (h *FileHeader) ModTime() time.Time // 修改时间

func (h *FileHeader) Mode() (mode os.FileMode) // 权限和模式位

func (h *FileHeader) SetModTime(t time.Time) // 设置修改时间

func (h *FileHeader) SetMode(mode os.FileMode) // 设置权限

ReadCloser结构体

1
2
3
4
type ReadCloser struct {
Reader
// contains filtered or unexported fields
}

ReadCloser的相关方法:

func OpenReader(name string) (*ReadCloser, error) // 传入文件名(路径),返回一个ReadCloser指针(继承了Reader)

func (rc *ReadCloser) Close() error // 关闭ReadCloser

Reader结构体

1
2
3
4
5
type Reader struct {
File []*File
Comment string
// contains filtered or unexported fields
}

Reader的相关方法:

func NewReader(r io.ReaderAt, size int64) (*Reader, error) // 从r中读取size个字节并返回一个新的Reader

func (z *Reader) RegisterDecompressor(method uint16, dcomp Decompressor)

Writer结构体

1
2
3
type Writer struct {
// contains filtered or unexported fields
}

Writer的相关方法:

func NewWriter(w io.Writer) *Writer // 创建并返回一个将zip文件写入w的Writer

func (w *Writer) Close() error // 关闭Writer

func (w *Writer) Create(name string) (io.Writer, error) // 使用name添加一个文件到zip文件中,返回一个Writer。name必须是相对路径,不能以设备或斜杠开头,用’/‘作为分隔符。新增文件内容必须在下一次调用CreateHeader、Create或Close方法前全部写入。

func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) // 使用fh所描述的元数据添加一个文件到zip文件中,并返回一个用于写入数据的io.Writer。

func (w *Writer) Flush() error // 将缓存写入底层IO

等等..


实现一个跟昨天一样的需求:简单的创建和读取压缩包,传送门

代码实现

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
package zip

import (
"archive/zip"
"fmt"
"io"
"log"
"os"
"time"
)

var(
filePath = "/tmp/go/learn/data/zipArchive.zip"
)

func CreateArchive(){
fmt.Println("This is function CreateArchive called")
var files = []struct{
Name, Body string
}{
{"Readme.md", "## Hello World\n1. line 1\n2. line 2\n3. line 3"},
{"gopher.txt", "Gopher names:\nGorge\nGeoffrey\nGonzo"},
{"todo.txt", "Get animal handling licence.\nWrite more examples"},
{"myDir/file.txt", "This is a sub dir's file"},
}
fi, err := os.Create(filePath)
defer fi.Close()
if err != nil {
log.Fatal(err)
}
zw := zip.NewWriter(fi)
for _, file := range files {
fh := &zip.FileHeader{
Name: file.Name,
Modified: time.Now(),
}
// 这里也可以使用 f, err := zw.Create(file.Name); 快速创建文件
if f, err := zw.CreateHeader(fh); err != nil{
log.Fatal(err)
}else{
if _, err := f.Write([]byte(file.Body));err != nil {
log.Fatal(err)
}
}
}
if err = zw.Close(); err != nil {
log.Fatal(err)
}
fmt.Println("Created Archive file is stored in:", filePath)
}

func ReadArchive(){
fmt.Println("This is function ReadeArchive called")
zr, err := zip.OpenReader(filePath)
if err != nil {
log.Fatal(err)
}
defer zr.Close()
for _, f := range zr.File {
fmt.Printf("\tContents of %s:\n",f.Name)
if rc, err := f.Open();err != nil {
log.Fatal(err)
}else{
if _, err := io.Copy(os.Stdout, rc, ); err != nil {
log.Fatal(err)
}
rc.Close()
fmt.Println()
}
}
}

通过今天的学习,突然发现了昨天的tar并没有涉及到压缩,所以它应该就是个普通的归档,而zip中带有了压缩算法,所以它可以被称为压缩包。不过我今天也悄悄的看了一下tar.gz的相关内容,非常简单!这里就不多说,感兴趣的自行移步官方文档。

阅读剩下更多

默认配图
编程语言

Go语言包-archive/tar

一天一个Golang包,慢慢学习之“archive/tar”

今天了解一下归档(压缩包)中的tar包,我们对压缩包其实并不陌生,像是”某某.tar”,自然能想到应该用压缩包工具打开并解压出来使用。

阅读文档:

官方pkg地址:https://golang.org/pkg/archive/tar/

从官方的包文档中,我提取几个主要的内容出来分享一下:

常量

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
const (
// Type '0' indicates a regular file.(普通文件)
TypeReg = '0'
TypeRegA = '\x00' // Deprecated: Use TypeReg instead.

// Type '1' to '6' are header-only flags and may not have a data body.
TypeLink = '1' // Hard link(硬链接)
TypeSymlink = '2' // Symbolic link(软链接/符号链接)
TypeChar = '3' // Character device node(字符设备节点)
TypeBlock = '4' // Block device node(块设备节点)
TypeDir = '5' // Directory(目录)
TypeFifo = '6' // FIFO node

// Type '7' is reserved.(保留项)
TypeCont = '7'

// Type 'x' is used by the PAX format to store key-value records that
// are only relevant to the next file.
// This package transparently handles these types.
TypeXHeader = 'x' // 可扩展头部

// Type 'g' is used by the PAX format to store key-value records that
// are relevant to all subsequent files.
// This package only supports parsing and composing such headers,
// but does not currently support persisting the global state across files.
TypeXGlobalHeader = 'g' // 全局扩展头部

// Type 'S' indicates a sparse file in the GNU format.
TypeGNUSparse = 'S' // 稀疏文件

// Types 'L' and 'K' are used by the GNU format for a meta file
// used to store the path or link name for the next file.
// This package transparently handles these types.
TypeGNULongName = 'L'
TypeGNULongLink = 'K'
)

变量(主要用于错误输出)

1
2
3
4
5
6
var (
ErrHeader = errors.New("archive/tar: invalid tar header") // 无效的tar头部
ErrWriteTooLong = errors.New("archive/tar: write too long") // 写入数据太长
ErrFieldTooLong = errors.New("archive/tar: header field too long") // 头部太长
ErrWriteAfterClose = errors.New("archive/tar: write after close") // 关闭后写入
)

Header结构体

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
type Header struct {
Typeflag byte // the type of header entry.(文件类型)

Name string // 文件名称
Linkname string // 链接名称 (适用于硬链接和软链接)

Size int64 // 文件的字节大小
Mode int64 // 权限,如:0600
Uid int // 用户ID
Gid int // 组ID
Uname string // 用户名
Gname string // 组名

ModTime time.Time // 最后一次修改文件或目录的时间
AccessTime time.Time // 最后一次访问文件或目录的时间
ChangeTime time.Time // 最后一次改变文件或目录(改变的是原数据即:属性)的时间

Devmajor int64 // Major device number (valid for TypeChar or TypeBlock)(字符设备或块设备的主设备号)
Devminor int64 // Minor device number (valid for TypeChar or TypeBlock)(字符设备或块设备的次设备号)

Xattrs map[string]string // Go 1.3

PAXRecords map[string]string // Go 1.10

Format Format // Go 1.10
}

Header的相关方法:

func FileInfoHeader(fi os.FileInfo, link string)(*Header, error) //该方法通过os.FileInfo来创建一个tar.Header,用在对已有文件打包十分方便

func (h *Header) FileInfo() os.FileInfo // 该方法获取Header的os.FileInfo信息

Reader结构体

1
2
3
4
5
6
7
8
9
10
11
type Reader struct {
r io.Reader
pad int64 // Amount of padding (ignored) after current file entry
curr fileReader // Reader for current file entry
blk block // Buffer to use as temporary local storage

// err is a persistent error.
// It is only the responsibility of every exported method of Reader to
// ensure that this error is sticky.
err error
}

Reader结构体有以下方法:
func NewReader(r io.Reader) *Reader // 用r创建新的tar.Reader

func (tr *Reader) Next() (*Header, error) // 使tr指向下一个文件实体并返回实体的Header,到最后会返回err为io.EOF

func (tr *Reader) Read(b []byte) (int, error) // 读取当前实体到b,读取到最后时返回err为io.EOF

阅读到这儿,基本就可以解析一个tar文件了,从上述的方法我们可以得知,要想获得压缩包的内容,我们可以使用tar.Reader来完成,而tar.Reader的创建可以通过使用tar.NewReader()方法,该方法需要提供一个实现了io.Reader接口的对象,此对象可以通过os包的方法如os.Open()或者os.OpenFile()等方法获得。根据这个思路,我们便可以解析获得tar文件的所有内容了。

Writer结构体

1
2
3
4
5
6
7
8
9
10
11
12
type Writer struct {
w io.Writer
pad int64 // Amount of padding to write after current file entry
curr fileWriter // Writer for current file entry
hdr Header // Shallow copy of Header that is safe for mutations
blk block // Buffer to use as temporary local storage

// err is a persistent error.
// It is only the responsibility of every exported method of Writer to
// ensure that this error is sticky.
err error
}

Writer结构体有如下方法:
func NewWriter(w io.Writer) *Writer // 用w创建新的tar.Writer

func (tw *Writer) Close() error // 关闭tar文件,并将未写入的数据写入底层writer
func (tw *Writer) Flush() error // 完成当前写入

func (tw *Writer) Write(b []byte) (int, error) // 将b写入当前实体,如果写入长度大于WriteHeader所描述的Size,则会返回err为ErrWriteTooLong的报错

func (tw *Writer) WriteHeader(hdr *Header) error // 将hdr写入tar文件中并准备接受Write()方法的写入,hdr.Size决定写入的文件字节大小。如果未完全写入,则会报错。写入hdr之前会隐式调用Flash()。

下面我们假设这样一个需求:

需求描述:

  1. 创建一个压缩包文件,命名为:”tarArchive.tar”。
  2. 压缩包中存有3个文本文档,分别命名:”readme.md”、”gopher.txt”、”todo.txt”,内容随意。
  3. 压缩包中还有一个目录(命名:dir),其中有一个文件:dirFile.txt,内容随意
  4. 将压缩包保存在”/tmp/go/learn/data”目录中。
  5. 读取压缩包内容,并打印文件(或目录)以及文件内容。

求解过程:

  1. 最终结果是要创建一个文件,所以我们先创建这个文件:这里用到f, err := os.OpenFile()方法来获取一个文件指针
  2. 然后创建一个tar的Writer对象来实现对文件的写入:tw := tar.NewWriter(f),获得的tw对象就是一个写入器,他有Write方法。
  3. 构建一个文件列表(任何方式,为了方便用循环添加文件)。
  4. 使用tw.WriteHeader()和tw.Write()方法向压缩包写入文件。
  5. 关闭文件和写入器:f.Close()、tw.Close(),tar文件创建完毕。
  6. 读取tar文件,并使用tar.NewReader()获得Reader对象。
  7. 循环Next()方法获得hdr并打印hdr.Name以及Reader的内容
  8. 判断io.EOF结束并关闭文件。

完整代码

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
package tar

import (
"archive/tar"
"fmt"
"io"
"log"
"os"
)
var(
filePath = "/tmp/go/learn/data/tarArchive.tar"
)

func CreateArchive(){
f, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
log.Fatal(err)
}
defer f.Close()
tw := tar.NewWriter(f)
var files = []struct{
Name, Body string
}{
{"Readme.md", "This is a readme markdown file."},
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
{"todo.txt", "Get animal handling license."},
{"dir/dirFile.txt", "it's content is in directory"},
}
// 创建一个名字叫做myDir2的目录,用于区分上述的dir/dirFile.txt中的目录
if err = tw.WriteHeader(&tar.Header{Name: "myDir2",Mode: 0766,Typeflag:tar.TypeDir}); err != nil{
log.Fatal(err)
}
for _, file := range files {
hdr := &tar.Header{
Name: file.Name,
Mode: 0600,
Size: int64(len(file.Body)),
}
if err := tw.WriteHeader(hdr); err != nil {
log.Fatal(err)
}
if _, err := tw.Write([]byte(file.Body)); err != nil {
log.Fatal(err)
}
}
if err := tw.Close(); err != nil {
log.Fatal(err)
}
}

func ReadArchive(){
f, err := os.OpenFile(filePath, os.O_RDONLY, 0)
if err != nil {
log.Fatal(err)
}
defer f.Close()
tr := tar.NewReader(f)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Printf("Contents of %s:\n",hdr.Name)
if _, err := io.Copy(os.Stdout, tr); err != nil {
log.Fatal(err)
}
fmt.Println("\n====")
}
}

注意:代码中有关myDir目录和myDir2目录的创建方式不一样,但是效果差异不大

阅读剩下更多

默认配图
学习笔记

判断IP地址是否内网IP

当你的服务同时开放于公网和内网,子服务却仅允许内网访问,那么则会涉及到IP白名单的功能。但是如果名单太多或者服务进行了迁移,那么维护起来会相当的麻烦,最简单的就是判断访问的来源IP是否内网IP地址,从而直接屏蔽掉公网IP。

首先,我们认识了3类私有地址:

A类:10.0.0.0-10.255.255.255

B类:172.16.0.0-172.31.255.255

C类:192.168.0.0-192.168.255.255

还有一个本机地址:127.0.0.1

他们就是我们平时所谓的内网IP地址。

方法一: PHP自带函数

filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)

如果$IP是内网IP则会返回false,否则返回ip字符串。

1
2
3
4
5
6
7
$IP = get_client_ip(); //该方法自己实现,返回客户端的IP地址
$is_publicIP = filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if($is_publicIP){
echo '公网IP';
}else{
echo '内网IP';
}

方法二: 适合有网络基础的胖友阅读

关于IP地址的知识,这里就不讲太多了,下面的方法就是根据IP地址的定义和网段的划分等专业知识进行判定的。懂的就自然懂啦!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function isLocal($ip){
$long=ip2long($ip);
$data=array(
24=>'10.255.255.255',
20=>'172.31.255.255',
16=>'192.168.255.255'
);
foreach($data as $k=>$v){
if($long >> $k === ip2long($v)>>$k){
return true;
}
}
return false;
}

方法三:正则匹配

更加不用解释了。

1
2
3
4
<?php
function isLocal($ip){
return preg_match('%^127\.|10\.|192\.168|172\.(1[6-9]|2|3[01])%',$ip);
}

参考:

  1. http://php.net/manual/zh/function.filter-var.php
  2. https://www.51-n.com/t-4360-1-1.html
  3. https://blog.csdn.net/z_qifa/article/details/75497577

阅读剩下更多

默认配图
学习笔记

更新升级MacOSX到10.14 Mojave (MacBook Air 13-inch early 2014)

写在前面的话:

如果不是真的很需要,其实最好是不去更新系统,以免带来更多的麻烦。如果新的系统对你真的很重要,我也建议你备份现有数据,然后全新安装新的系统版本。我就是那个单纯的升级系统,导致原有的软件无法正常运行的傻逼,下面虽然记录了我的问题,但我最终的解决方案还是把软件卸载后重新安装和配置。

既然App Store推送了本次更新,我也就傻傻的更新了,谁知道会发生什么呢?

很不开心的是,第二天上班来发现系统更新好了,直接登录!一切安好。然鹅!!!发现了好几个惊喜:

  1. svn和git命令无法执行,提示xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
  2. Apache和PHP配置全部恢复成初始状态,所有已安装的扩展没了
  3. OSSRS(Simple-RTMP-Server)无法运行了,提示:illegal hardware instruction,目前没找到解决方案,提了Issue看怎么说。

解决第一个问题网上给了答案,安装XCode开发者工具就好了:
xcode-select --install

Apache和PHP的配置的恢复,主要是指httpd.conf的覆盖和php.ini的丢失,以及PHP扩展丢失,这样我就不得不重新编写配置文件和编译安装开发所需要的全部扩展了。

这里记录一下我的操作过程,因为之前更新系统也遇到这种问题,但是没有做笔记,导致现在又遇到问题时又花了很多时间去找资料。

安装Memcache扩展

从Github直接clone下来:https://github.com/websupport-sk/pecl-memcache

执行phpize出现问题:

1
2
3
4
5
6
7
grep: /usr/include/php/main/php.h: No such file or directory
grep: /usr/include/php/Zend/zend_modules.h: No such file or directory
grep: /usr/include/php/Zend/zend_extensions.h: No such file or directory
Configuring for:
PHP Api Version:
Zend Module Api No:
Zend Extension Api No:

百度出来的解决方案:

1
sudo ln -s /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/ /usr/include

好的,遇到了就算使用sudo还是提示权限不足的情况。 不怕,之前有经验,需要关掉系统的保护机制。操作方法就是重启电脑,按住command+R直到进入新界面,通过实用工具打开终端,输入csrutil disable关闭保护,然后reboot重启

重启完打开终端,傻傻的执行ln命令后发现还是不行!phpize依旧报错, 怪我有点笨,然后才去读这行命令的意思,发现MacOSX10.11.sdk这个目录不存在,只有一个MacOSX10.14.sdk的目录。很傻,根据自己的系统来嘛!

所以下面的完整命令解决phpize的问题

1
sudo ln -s /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/ /usr/include

然后是./configure 接着make && sudo make install

执行完后再修改/etc/php.ini增加extension=memcache.so。然后重启apache:sudo apachectl -k restart

安装mcrypt扩展

这个我是直接去php官网下载系统当前版本的PHP源码(php-7.1.19.tar.gz),然后解压进入ext/mcrypt目录,如上所述的执行phpize和./configure以及make && sudo make install,修改php.ini增加extension=mcrypt.so。重启apache搞定。

重启打开安全策略csrutil enable

电脑重启后发现memcache和mcrypt扩展无法加载,提示了文件签名的问题。无解!

所以,重点强调!!! 直接舍弃系统自带的PHP和Apache吧,用brew全新安装一个,管理起来也非常的方便,系统更新后也不会导致软件丢失!

不过,遇到了新的问题,brew在安装cmake的时候出现了错误,提示如下:

1
2
3
4
Error: You are using macOS 10.14.
We do not provide support for this pre-release version.
You may encounter build failures or other breakages.
Please create pull-requests instead of filing issues.

很明显,说当前系统版本时预发布版本,他们不支持,不支持!!??

百度了,无解,无奈就去问了Google,然后说要去下载Command Line Tools (macOS 10.14) for Xcode 10,原文:https://intellij-support.jetbrains.com/hc/en-us/community/posts/360000898980-CMake-not-compiling-after-macOS-10-14-Mojave-update

写在最后

官宣,作为一个需要稳定开发环境的开发人员和开发机器来说,不是闲的蛋疼就别随便的更新系统了吧,一个上午就折腾这事儿去了,真是满脸的不愉快,晚上8点还在重装软件,今儿可是1024啊,过节啊!!

阅读剩下更多

默认配图
学习笔记

Linux下的安装Ngrok服务器的故事

使用场景

公司A是一家服务提供商,公司B是一家知名企业。
现在公司B中的一个软件服务出现了故障,需要公司A的专业人员来排查。由于两家公司相隔甚远,他们采用了远程排查方案。但是公司B的这个软件部署在内网环境的机器C上,层层网关以及种种原因(和谐部分)导致无法将机器C映射到公司B的公网端口。为了解决这种需要打通从一个局域网到另外一个局域网的网络通路,我们叫它‘内网穿透’。

总所周知,处在同一个局域网的所有机器是可以互通的,而不同局域网中的机器是无法跨域访问的。解决这种问题,我能想到的有:

  1. 交换机和路由
  2. 使用公网VPN服务器
  3. 使用内网穿透服务器

配置交换机和路由往往非常复杂,遇到复杂的组网环境,还是杀了我吧!

公网VPN服务器我也试过了,使用简单,但是稳定性得不到保证。

所以我最终尝试了内网穿透服务器:Ngrok。(当然还有别的比如:frp)

Ngrok简介

Ngrok是一款使用GO语言开发的开源软件(仅针对1.x,2.x不开源),是一个反向代理软件。它可以在你本地运行的服务和公网服务器端点建立一条安全的隧道,这样任何一个和公网连通的客户端都可以访问和使用你的本地服务。Ngrok还可以捕获和分析所有通道上的流量,对后期的分析和处理也有相当好的支持。

Ngrok的git仓库地址:https://github.com/inconshreveable/ngrok

需要的宝贝

云服务器(有公网IP的服务器)

提供这种服务的像阿里云/腾讯云/百度云等等,随便挑

域名

随便什么域名,只要是你的域名,你能对其配置解析(最好能泛解析)。

我的域名解析

环境要求

Linux服务器:CentOS、Ubuntu

1
2
3
4
5
#centos
yum -y install zlib-devel openssl-devel perl hg cpio expat-devel gettext-devel curl curl-devel perl-ExtUtils-MakeMaker hg wget gcc gcc-c++ git golang

#ubuntu
#自行对应缺啥用'apt-get'补啥,openssl golang make 等等的都得有

安装服务器

克隆Ngrok到本地

1
git clone https://github.com/inconshreveable/ngrok.git

制作和生成证书

1
2
3
4
5
6
7
8
9
cd ngrok
mkdir cert
cd cert
NGROK_DOMAIN="ngrok.imsry.cn"
openssl genrsa -out base.key 2048
openssl req -new -x509 -nodes -key base.key -days 10000 -subj "/CN=$NGROK_DOMAIN" -out base.pem
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=$NGROK_DOMAIN" -out server.csr
openssl x509 -req -in server.csr -CA base.pem -CAkey base.key -CAcreateserial -days 10000 -out server.crt

覆盖证书到Ngrok中

1
2
3
cp base.pem ../ngrok/assets/client/tls/ngrokroot.crt
cp server.crt ../ngrok/assets/server/tls/snakeoil.crt
cp server.key ../ngrok/assets/server/tls/snakeoil.key

编译生成Ngrok服务端

1
2
cd ../ngrok
GOOS=linux GOARCH=amd64 make release-server

启动和运行服务端(可以写成bash脚本)

1
2
3
4
5
6
7
./bin/ngrokd -tlsKey="assets/server/tls/snakeoil.key" -tlsCrt="assets/server/tls/snakeoil.crt" -domain="ngrok.imsry.cn"  -httpAddr=":8081" -httpsAddr=":8082" -tunnelAddr=":8083"

#参数说明:
#-domain 访问ngrok是所设置的服务地址生成证书时那个
#-httpAddr http协议端口 默认为80
#-httpsAddr https协议端口 默认为443 (可配置https证书)
#-tunnelAddr 通道端口 默认4443

客户端的编译和使用

1
2
3
4
5
6
7
8
9
10
11
#window-64bit如下:
GOOS=windows GOARCH=amd64 make release-client
#编译成功后会在ngrok/bin/下面生成一个windows_amd64目录下面有ngrok.exe

#Linux 平台 32 位系统:GOOS=linux GOARCH=386
#Linux 平台 64 位系统:GOOS=linux GOARCH=amd64
#Windows 平台 32 位系统:GOOS=windows GOARCH=386
#Windows 平台 64 位系统:GOOS=windows GOARCH=amd64
#MAC 平台 32 位系统:GOOS=darwin GOARCH=386
#MAC 平台 64 位系统:GOOS=darwin GOARCH=amd64
#ARM 平台:GOOS=linux GOARCH=arm

编写配置文件ngrok.cfg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server_addr: "ngrok.imsry.cn:8083"
trust_host_root_certs: false

tunnels:
http:
subdomain: "test"
proto:
http: "80"

https:
subdomain: "test"
proto:
https: "443"

tcp:
remote_port: 5555
proto:
tcp: "8001"

ssh:
remote_port: 2222
proto:
tcp: "22"

启动客户端

1
2
3
4
./ngrok -config=ngrok.cfg start-all #启动所有服务

./ngrok -config=ngrok.cfg start ssh #启动SSH服务
./ngrok -config=ngrok.cfg start ssh http #启动SSH和HTTP服务

出现下面内容,表示链接成功:

1
2
3
4
5
6
7
8
9
10
11
ngrok

Tunnel Status online
Version 1.7/1.7
Forwarding http://test.ngrok.imsry.cn8081 -> 127.0.0.1:80
Forwarding https://test.ngrok.imsry.cn:8082 -> 127.0.0.1:443
Forwarding tcp://test.ngrok.imsry.cn:5555 -> 127.0.0.1:8001
Forwarding tcp://test.ngrok.imsry.cn:2222 -> 127.0.0.1:22
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

开始访问你的服务吧!

直接访问上面的地址就能取得相应的服务
如果是ssh和tcp的话,直接使用如:ssh [email protected] -p 2222

阅读剩下更多

默认配图
编程语言

不同语言下文件读取和排序测试

排序时间对比图

测试机器配置:

  • MacBook Air (13-inch, Early 2014)
  • 处理器:1.4 GHz Intel Core i5
  • 内存:4 GB 1600 MHz DDR3

PHP

1
2
3
PHP 7.1.16 (cli) (built: Mar 31 2018 02:59:59) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
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
<?php
$t1 = microtime(true);

$fileName = "Data.txt";
if(($file=fopen($fileName,'r')) === false){
echo '读取文件:'.$fileName."失败\n";
exit;
}
if(empty($file)){
echo "文件为空!\n";
exit;
}
$data = array();
while(!feof($file)){
$lineStr = fgets($file);
array_push($data, trim($lineStr));
}
$t2 = microtime(true);
echo 'Read time:' . ($t2 - $t1)*1000 . "ms\n";
BubbleSort($data);
var_dump($data);
fclose($file);

$t3 = microtime(true);

echo 'Sort time:' . ($t3 - $t2)*1000 . "ms\n";
echo 'Finished time:' . ($t3 - $t1)*1000 . "ms\n";

function BubbleSort(&$data){
$len = count($data);
for($i=0;$i<$len;$i++){
$flag = 0;
for($j=1;$j<($len-$i);$j++){
if($data[$j]<$data[$j-1]) {
$tmp = $data[$j];
$data[$j] = $data[$j-1];
$data[$j-1] = $tmp;
$flag = 1;
}
}
if($flag == 0){
return;
}
}
}
?>

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
~/Sites/languageDiff/sort » php sort.php
Read time:20.021915435791ms
Sort time:219256.12616539ms
Finished time:219276.14808083ms
-------------------------------------------------------
~/Sites/languageDiff/sort » php sort.php
Read time:19.091129302979ms
Sort time:229677.68502235ms
Finished time:229696.77615166ms
-------------------------------------------------------
~/Sites/languageDiff/sort » php sort.php
Read time:21.70205116272ms
Sort time:228220.97086906ms
Finished time:228242.67292023ms
-------------------------------------------------------
~/Sites/languageDiff/sort » php sort.php
Read time:20.930051803589ms
Sort time:206329.57100868ms
Finished time:206350.50106049ms
-------------------------------------------------------
~/Sites/languageDiff/sort » php sort.php
Read time:18.213033676147ms
Sort time:201813.07411194ms
Finished time:201831.28714561ms

Java

1
2
3
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
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
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class sort {

public static void main(String[] args) {
try {
long t1 = System.currentTimeMillis();
int[] data = new int[50000];
BufferedReader br = new BufferedReader(new FileReader("Data.txt"));

String line = null;
int index = 0;
while ((line = br.readLine()) != null) {
data[index] = Integer.parseInt(line);
index++;
}
if (br != null) {
br.close();
}
long t2 = System.currentTimeMillis();
System.out.println("Read time: " + String.valueOf(t2 - t1) + "ms");

BubbleSort(data);
long t3 = System.currentTimeMillis();
System.out.println("Sort time: " + String.valueOf(t3 - t2) + "ms");
System.out.println("Finished time: " + String.valueOf(t3 - t1) + "ms");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public static void BubbleSort(int[] a) {
int j, flag;
int temp;
for (int i = 0; i < a.length; i++) {
flag = 0;
for (j = 1; j < a.length - i; j++) {
if (a[j] < a[j-1]) {
temp = a[j];
a[j] = a[j - 1];
a[j - 1] = temp;
flag = 1;
}
}
if (flag == 0) {
return;
}
}
}
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
~/Sites/languageDiff/sort » java sort
Read time: 56ms
Sort time: 5286ms
Finished time: 5342ms
-------------------------------------------------------
~/Sites/languageDiff/sort » java sort
Read time: 47ms
Sort time: 4911ms
Finished time: 4958ms
-------------------------------------------------------
~/Sites/languageDiff/sort » java sort
Read time: 46ms
Sort time: 4903ms
Finished time: 4949ms
-------------------------------------------------------
~/Sites/languageDiff/sort » java sort
Read time: 47ms
Sort time: 4953ms
Finished time: 5000ms
-------------------------------------------------------
~/Sites/languageDiff/sort » java sort
Read time: 49ms
Sort time: 5335ms
Finished time: 5384ms

Go

1
go version go1.10.3 darwin/amd64
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
package main

import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"time"
)

func main() {
fileName := "Data.txt"
data := make([]int, 0, 50000)
start := time.Now()
fi, err := os.Open(fileName)
if err != nil {
fmt.Printf("Error: %s\n", err)
return
}
defer fi.Close()
br := bufio.NewReader(fi)
for {
a, _, c := br.ReadLine()
if c == io.EOF {
break
}
num, _ := strconv.Atoi(string(a))
data = append(data, num)
}
endReadTime := time.Now()
fmt.Printf("Read time: %fs\n", endReadTime.Sub(start).Seconds())
BubbleSort(data)
endTime := time.Now()
fmt.Printf("Sort time: %fs\n", endTime.Sub(endReadTime).Seconds())
fmt.Printf("finished time: %fs\n", endTime.Sub(start).Seconds())
}

func BubbleSort(arr []int) {
length := len(arr)
var flag int
var tmp int
for i := 0; i < length; i++ {
flag = 0
for j := 1; j < (length - i); j++ {
if arr[j] < arr[j-1] {
tmp = arr[j]
arr[j] = arr[j-1]
arr[j-1] = tmp
flag = 1
}
}
if flag == 0 {
return
}
}
}

测试结果:

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
~/Sites/languageDiff/sort » go build sort.go
~/Sites/languageDiff/sort » ./sort
Read time: 0.006603s
Sort time: 5.207361s
finished time: 5.213964s
-------------------------------------------------------
~/Sites/languageDiff/sort » ./sort
Read time: 0.005319s
Sort time: 5.167615s
finished time: 5.172934s
-------------------------------------------------------
~/Sites/languageDiff/sort » ./sort
Read time: 0.005648s
Sort time: 5.317512s
finished time: 5.323160s
-------------------------------------------------------
~/Sites/languageDiff/sort » ./sort
Read time: 0.005842s
Sort time: 5.267731s
finished time: 5.273573s
-------------------------------------------------------
~/Sites/languageDiff/sort » ./sort
Read time: 0.005585s
Sort time: 5.442154s
finished time: 5.447738s

JavaScript(NodeJS)

1
2
~/Sites » node -v
v9.11.1
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
var t1 = (new Date()).getTime();  
var readline = require('readline');
var fs = require('fs');
var os = require('os');

var fReadName = './data.txt';
var fRead = fs.createReadStream(fReadName);


var objReadline = readline.createInterface({
input: fRead,
});


var data = [];
objReadline.on('line', (line)=>{
data.push(Number(line));
});

objReadline.on('close', ()=>{
var t2 = (new Date()).getTime();
console.log("Read time:" + (t2 - t1) + "ms");
var res = BubbleSort(data);
// console.log(res);
var t3 = (new Date()).getTime();
console.log("Sort time:" + (t3 - t2) + "ms");
console.log("finished time:" + (t3 - t1) + "ms");
});

function BubbleSort(arr){
for (var i = 0; i < arr.length; i++) {
var flag = 0;
for(var j = 1;j< arr.length-i;j++){
if(arr[j]<arr[j-1]){
var tmp = arr[j];
arr[j] = arr[j-1]
arr[j-1] = tmp
flag = 1;
}
}
if(flag == 0){
return arr;
}
}
return arr;
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
~/Sites/languageDiff/sort » node sort.js
Read time:55ms
Sort time:6067ms
finished time:6122ms
-------------------------------------------------------
~/Sites/languageDiff/sort » node sort.js
Read time:38ms
Sort time:6064ms
finished time:6102ms
-------------------------------------------------------
~/Sites/languageDiff/sort » node sort.js
Read time:37ms
Sort time:6339ms
finished time:6376ms
-------------------------------------------------------
~/Sites/languageDiff/sort » node sort.js
Read time:42ms
Sort time:6367ms
finished time:6409ms
-------------------------------------------------------
~/Sites/languageDiff/sort » node sort.js
Read time:40ms
Sort time:7110ms
finished time:7150ms

Python

1
2
~/Sites » python -V
Python 2.7.10
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
import time

def BubbleSort(a):
length = len(a)
for i in range(0,length):
flag = 0
for j in range(1,length-i):
if a[j] < a[j-1]:
tmp = a[j]
a[j] = a[j-1]
a[j-1] = tmp
flag = 1
if flag == 0:
return a


t1 = time.time()
fileName = "Data.txt"
data = []
for line in open(fileName):
data.append(int(line.strip()))
t2 = time.time()
print 'Read time: %.02fs' % (t2 - t1)
res = BubbleSort(data)
t3 = time.time()
print 'Sort time: %.02fs' % (t3 - t2)
print 'finished time: %.02fs' % (t3 - t1)

测试结果:

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
-------------------------------------------------------
~/Sites/languageDiff/sort » python sort.py
Read time: 0.05s
Sort time: 282.76s
finished time: 282.82s
-------------------------------------------------------
~/Sites/languageDiff/sort » python sort.py
Read time: 0.07s
Sort time: 263.54s
finished time: 263.61s
-------------------------------------------------------
~/Sites/languageDiff/sort » python sort.py
Read time: 0.06s
Sort time: 346.26s
finished time: 346.32s
-------------------------------------------------------
~/Sites/languageDiff/sort » python sort.py
Read time: 0.06s
Sort time: 298.12s
finished time: 298.18s
-------------------------------------------------------
~/Sites/languageDiff/sort » python sort.py
Read time: 0.06s
Sort time: 251.43s
finished time: 251.49s

目前这几种编程语言看来,排序如下:IO时长,排序时长,总时长

  1. Java 平均 0.0490s 5.078s 5.127s
  2. Go 平均 0.0058s 5.281s 5.286s
  3. NodeJs 平均 0.0424s 6.389s 6.432s
  4. PHP 平均 0.0200s 217.059s 217.079s
  5. Python 平均 0.0600s 288.422s 288.484s

Ps: 如果你希望看到自己喜欢的语言也排进来,欢迎提供代码!

阅读剩下更多

不同语言下文件读取和排序测试
编程语言

不同语言对网络IO的效率对比

网络IO时间对比图

测试机器配置:

  • MacBook Air (13-inch, Early 2014)
  • 处理器:1.4 GHz Intel Core i5
  • 内存:4 GB 1600 MHz DDR3

PHP

1
2
3
PHP 7.1.16 (cli) (built: Mar 31 2018 02:59:59) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$t1 = microtime(true);

for($i=0; $i<20; $i++){
$url = "http://2018.ip138.com/ic.asp?count=".$i;
$data = file_get_contents($url);
// echo 'get:'.$i." ".$data."\r\n";
}

$t2 = microtime(true);

echo 'php time:' . ($t2 - $t1)*1000 . "ms\n";

?>

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/Sites/languageDiff/io » php main.php
php time:2112.9629611969ms
-------------------------------------------------------
~/Sites/languageDiff/io » php main.php
php time:1960.746049881ms
-------------------------------------------------------
~/Sites/languageDiff/io » php main.php
php time:2055.7940006256ms
-------------------------------------------------------
~/Sites/languageDiff/io » php main.php
php time:2052.6430606842ms
-------------------------------------------------------
~/Sites/languageDiff/io » php main.php
php time:1990.2958869934ms

Java

1
2
3
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {

public static void main(String[] args) {
long t1 = System.currentTimeMillis();

for(int i=0; i<20; i++){
String s=HttpRequest.sendGet("http://2018.ip138.com/ic.asp", "count="+i);
// System.out.println(""+i+" : "+s);
}

long t2 = System.currentTimeMillis();

System.out.println("java time: " + String.valueOf(t2 - t1) + "ms");
}
}

HttpRequst.java CodeOrigin:https://www.cnblogs.com/guoyinli/p/7192839.html

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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;

public class HttpRequest {
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
/*for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}*/
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}

/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!"+e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/Sites/languageDiff/io » java Main
java time: 1416ms
-------------------------------------------------------
~/Sites/languageDiff/io » java Main
java time: 1472ms
-------------------------------------------------------
~/Sites/languageDiff/io » java Main
java time: 1328ms
-------------------------------------------------------
~/Sites/languageDiff/io » java Main
java time: 1348ms
-------------------------------------------------------
~/Sites/languageDiff/io » java Main
java time: 1336ms

Go

1
go version go1.10.3 darwin/amd64
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
package main

import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"sync"
"time"
)

func main() {
wg := sync.WaitGroup{}
start := time.Now()
for k := 0; k < 2; k++ {
wg.Add(1)
go func(k int) {
for i := 0; i < 10; i++ {
HttpGet("http://2018.ip138.com/ic.asp?count=" + strconv.Itoa(i*2+k))
}
wg.Done()
}(k)
}
wg.Wait()
fmt.Printf("cost time: %f\n", time.Now().Sub(start).Seconds())
}

func HttpGet(url string) string {
resp, err := http.Get(url)
if err != nil {
return err.Error()
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err.Error()
}
return string(body)
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/Workspaces/go » go run main.go
cost time: 0.952737
-------------------------------------------------------
~/Workspaces/go » go run main.go
cost time: 0.983901
-------------------------------------------------------
~/Workspaces/go » go run main.go
cost time: 0.967799
-------------------------------------------------------
~/Workspaces/go » go run main.go
cost time: 0.972302
-------------------------------------------------------
~/Workspaces/go » go run main.go
cost time: 0.980509

JavaScript(NodeJS)

1
2
~/Sites » node -v
v9.11.1
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
var t1 = (new Date()).getTime();  
var job = 0;
for(var i=0; i<20; i++){
get(i);
}

var t2 = (new Date()).getTime();

console.log("nodejs time:" + (t2 - t1) + "ms");


function get(i){

var http=require('http');
//get 请求外网
http.get('http://2018.ip138.com/ic.asp?count='+i,function(req,res){
var html='';
req.on('data',function(data){
html+=data;
});
req.on('end',function(){
job++
// console.info(html);
if(job == 20){
var t2 = (new Date()).getTime();
console.log("finished time:" + (t2 - t1) + "ms");
}
});
});
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~/Sites/languageDiff/io » node main.js
nodejs time:25ms
finished time:236ms
-------------------------------------------------------
~/Sites/languageDiff/io » node main.js
nodejs time:21ms
finished time:220ms
-------------------------------------------------------
~/Sites/languageDiff/io » node main.js
nodejs time:22ms
finished time:230ms
-------------------------------------------------------
~/Sites/languageDiff/io » node main.js
nodejs time:21ms
finished time:227ms
-------------------------------------------------------
~/Sites/languageDiff/io » node main.js
nodejs time:20ms
finished time:221ms

Python

1
2
~/Sites » python -V
Python 2.7.10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time,urllib2

def get(url):
req = urllib2.Request(url)

res_data = urllib2.urlopen(req)
res = res_data.read()
return res


t = time.time()

for i in xrange(0, 20):
# print "Get("+str(i)+"):"
data = get("http://2018.ip138.com/ic.asp?count="+str(i))
# print data

print 'Python time: %.02fs' % (time.time() - t)

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~/Sites/languageDiff/io » python main.py
Python time: 1.98s
-------------------------------------------------------
~/Sites/languageDiff/io » python main.py
Python time: 1.98s
-------------------------------------------------------
~/Sites/languageDiff/io » python main.py
Python time: 2.01s
-------------------------------------------------------
~/Sites/languageDiff/io » python main.py
Python time: 2.07s
-------------------------------------------------------
~/Sites/languageDiff/io » python main.py
Python time: 2.20s
-------------------------------------------------------
~/Sites/languageDiff/io » python main.py
Python time: 2.08s

目前这几种编程语言看来,排序如下:

  1. NodeJs 平均 0.23s
  2. Go 平均 0.97s
  3. Java 平均 1.38s
  4. PHP 平均 2.03s
  5. Python 平均 2.05s

Ps: 如果你希望看到自己喜欢的语言也排进来,欢迎提供代码!

阅读剩下更多

不同语言对网络IO的效率对比
编程语言

不同语言的基本性能测试

计算时间对比图
测试机器配置:

  • MacBook Air (13-inch, Early 2014)
  • 处理器:1.4 GHz Intel Core i5
  • 内存:4 GB 1600 MHz DDR3

PHP

1
2
3
PHP 7.1.16 (cli) (built: Mar 31 2018 02:59:59) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
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
<?php
$t1 = microtime(true);

for($i=0; $i<10000000; $i++){
aaa($i);
}

$t2 = microtime(true);

echo 'php time:' . ($t2 - $t1)*1000 . "ms\n";

function aaa($i){
$a = $i + 1;
$b = 2.3;
$s = "abcdefkkbghisdfdfdsfds";

if($a > $b){
++$a;
}else{
$b = $b + 1;
}

if($a == $b){
$b = $b + 1;
}

$c = $a * $b + $a / $b - pow($a, 2);
$d = substr($s, 0, strpos($s, 'kkb')) . strval($c);
}
?>

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~/Sites » php tes.php
php time:8196.1958408356ms
-------------------------------------------------------
~/Sites » php tes.php
php time:7649.4419574738ms
-------------------------------------------------------
~/Sites » php tes.php
php time:7908.7510108948ms
-------------------------------------------------------
~/Sites » php tes.php
php time:7708.6410522461ms
-------------------------------------------------------
~/Sites » php tes.php
php time:7515.1500701904ms
-------------------------------------------------------
~/Sites » php tes.php
php time:7468.3780670166ms

Java

1
2
3
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
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
public class Main {

public static void main(String[] args) {
long t1 = System.currentTimeMillis();

for(int i=0; i<10000000; i++){
aaa((float)i);
}

long t2 = System.currentTimeMillis();

System.out.println("java time: " + String.valueOf(t2 - t1) + "ms");
}

static void aaa(float i) {
float a = i + 1;
float b = 2.3f;
String s = "abcdefkkbghisdfdfdsfds";

if(a > b){
++a;
}else{
b = b + 1;
}

if(a == b){
b = b + 1;
}

float c = (float)(a * b + a / b - Math.pow(a, 2));
String d = s.substring(0, s.indexOf("kkb")) + String.valueOf(c);
}
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
~/Sites » java Main
java time: 1537ms
-------------------------------------------------------
~/Sites » java Main
java time: 1497ms
-------------------------------------------------------
~/Sites » java Main
java time: 1466ms
-------------------------------------------------------
~/Sites » java Main
java time: 1610ms
-------------------------------------------------------
~/Sites » java Main
java time: 1488ms
-------------------------------------------------------
~/Sites » java Main
java time: 1503ms
-------------------------------------------------------
~/Sites » java Main
java time: 1475ms

Go

1
go version go1.10.3 darwin/amd64
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
package main

import (
"fmt"
"math"
"strconv"
"strings"
"time"
)

func main() {
t1 := time.Now()
for i := 0; i < 10000000; i++ {
Aaa(float64(i))
}
t2 := time.Now()
fmt.Printf("Go time: %f s\n", t2.Sub(t1).Seconds())
}

func Aaa(i float64) {
var a float64 = i + 1
var b float64 = 2.3
s := "abcdefkkbghisdfdfdsfds"

if a > b {
a++
} else {
b = b + 1
}

if a == b {
b = b + 1
}

c := a*b + a/b - math.Pow(a, 2)
d := s[0:strings.Index(s, "kkb")] + strconv.FormatFloat(c, 'E', -1, 64)
_ = d
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~/Workspaces/GoLand/src/Aaa » go build main.go
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 4.083166 s
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 4.095651 s
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 4.154200 s
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 4.175203 s
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 4.111375 s

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
package main

import (
"fmt"
"math"
"strconv"
"strings"
"sync"
"time"
)

func main() {
wg := sync.WaitGroup{}
t1 := time.Now()
for k := 0; k < 2; k++ {
wg.Add(1)
go func() {
for i := 0; i < 5000000; i++ {
Aaa(float64(i))
}
wg.Done()
}()
}
wg.Wait()
t2 := time.Now()
fmt.Printf("Go time: %f s\n", t2.Sub(t1).Seconds())
}

func Aaa(i float64) {
var a float64 = i + 1
var b float64 = 2.3
s := "abcdefkkbghisdfdfdsfds"

if a > b {
a++
} else {
b = b + 1
}

if a == b {
b = b + 1
}

c := a*b + a/b - math.Pow(a, 2)
_ = s[0:strings.Index(s, "kkb")] + strconv.FormatFloat(c, 'E', -1, 64)
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~/Workspaces/GoLand/src/Aaa » go build main.go
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 2.326077 s
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 2.270769 s
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 2.277345 s
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 2.252540 s
-------------------------------------------------------
~/Workspaces/GoLand/src/Aaa » ./main
Go time: 2.255374 s

JavaScript(NodeJS)

1
2
~/Sites » node -v
v9.11.1
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
var t1 = (new Date()).getTime();  

for(var i=0; i<10000000; i++){
aaa(i);
}

var t2 = (new Date()).getTime();

console.log("nodejs time:" + (t2 - t1) + "ms");


function aaa(i){
var a = i + 1;
var b = 2.3;
var s = "abcdefkkbghisdfdfdsfds";

if(a > b){
++a;
}else{
b = b + 1;
}

if(a == b){
b = b + 1;
}

var c = a * b + a / b - Math.pow(a, 2);

var d = s.substring(0, s.indexOf("kkb")) + c.toString();
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~/Sites » node main.js
nodejs time:4898ms
-------------------------------------------------------
~/Sites » node main.js
nodejs time:5567ms
-------------------------------------------------------
~/Sites » node main.js
nodejs time:4846ms
-------------------------------------------------------
~/Sites » node main.js
nodejs time:4853ms
-------------------------------------------------------
~/Sites » node main.js
nodejs time:4955ms
-------------------------------------------------------
~/Sites » node main.js
nodejs time:5113ms

Python

1
2
~/Sites » python -V
Python 2.7.10
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
import sys, time, math

def aaa(i):
a = i + 1
b = 2.3
s = "abcdefkkbghisdfdfdsfds"

if a > b:
a = a + 1
else:
b = b + 1

if a == b:
b = b + 1

c = a * b +a / b - math.pow(a, 2)

d = s[0: s.find("kkb")] + str(c)


t = time.time()

for i in xrange(0, 10000000):
aaa(i)

print 'Python time: %.02f s' % (time.time() - t)

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/Sites » python main.py
Python time: 20.34
-------------------------------------------------------
~/Sites » python main.py
Python time: 19.90
-------------------------------------------------------
~/Sites » python main.py
Python time: 20.07
-------------------------------------------------------
~/Sites » python main.py
Python time: 19.88
-------------------------------------------------------
~/Sites » python main.py
Python time: 22.89

目前这几种编程语言看来,排序如下:

  1. Java 平均 1.51s (稳居第一)
  2. Go 平均 3.94s (并发2.28s)
  3. NodeJs 平均 5.03s
  4. PHP 平均 7.65s
  5. Python 平均 20.62s (差距大应该是我对这语言不熟,密集运算应该有优化方案)

Ps: 如果你希望看到自己喜欢的语言也排进来,欢迎提供代码!

阅读剩下更多

不同语言的基本性能测试
编程语言

Go语言中的切片(Slice)是引用类型还是值类型?

在最近的学习和实践中,照着别人的代码敲,仅仅只是把别人写在方法内的片段直接拿出来用,结果却出人意料,所以很纳闷!

示例代码:

1
2
3
4
5
6
7
8
func CryptBlocks(dst, src []byte) {
// ...省略内容
for len(src) > 0 {
encrypt(dst, src[:16]) //类似copy
dst = dst[16:]
src = src[16:]
}
}

这个分块处理切片数据的方法很巧妙,我第一次看见的时候(认定切片是传引用),认为这个方法错了,最终dst变成了空切片。

先看一个Slice的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import(
"fmt"
)

func main () {
var slice []int
slice = make([]int, 6)
slice = []int{0,1,2,3,4,5}
fmt.Println("Slice: ",slice)
changeSlice(slice)
fmt.Println("Changed Slice: ", slice)

}

func changeSlice(s []int) {
s[2] = 100
}

输出:

1
2
Slice:  [0 1 2 3 4 5]
Changed Slice: [0 1 100 3 4 5]

我们会发现,在方法changeSlice中对形参的修改即真实修改。如果这时我们将其定性为引用传值,为之过早!

我们将代码稍微修改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import(
"fmt"
)

func main () {
var slice []int
slice = make([]int, 6)
slice = []int{0,1,2,3,4,5}
fmt.Printf("SliceAddr: %p , Slice: %v\r\n",&slice, slice)
changeSlice(slice)
fmt.Printf("Changed SliceAddr: %p , Changed Slice: %v\r\n", &slice, slice)

}

func changeSlice(s []int) {
fmt.Printf("In func's sliceAddr: %p\r\n", s)
s[2] = 100
}

输出:

1
2
3
SliceAddr: 0xc82000e0a0 , Slice: [0 1 2 3 4 5]
In func's sliceAddr: 0xc8200460c0
Changed SliceAddr: 0xc82000e0a0 , Changed Slice: [0 1 100 3 4 5]

由此可见,在方法内的那个s(地址:0xc8200460c0)并非传入的那个slice(地址:0xc82000e0a0),但是为什么它的修改能影响到原来的数据呢?

阅读剩下更多

默认配图
学习笔记

mysql中几种Log和打开方法

Mysql中常见的log有:错误日志log_error、慢查询日志slow_query_log、二进制日志bin_log和通用查询日志general_log

错误日志(error_log)

这个日志记载mysql服务器的启动和关闭以及运行过程中发生的故障或异常情况,如果你发现mysql无法正常启动,那么你首先应该查看这个日志。

配置:

1
log_error = error.log

服务器上查询log位置:show variables like 'log_error'

慢查询日志(slow_query_log)

为了改善数据库性能,需要减少慢查询SQL的使用次数,那么哪些SQL需要改善呢?通过这个日志可以更清楚如何去改善。

配置:

1
2
3
slow_query_log = 1
lone_query_time = 10
slow_query_log_file = slow_query.log

二进制查询日志(bin_log)

Mysql的binlog日志作用是用来记录mysql内部增删改查等对mysql数据库有更新的内容的记录(对数据库的改动),对数据库的查询select或show等不会被binlog日志记录;主要用于数据库的主从复制以及增量恢复。

配置:

1
log_bin = mysql_bin.log

通用查询日志(general_log)

会记录mysql运行期间的所有sql语句

配置:

1
2
3
log_output=[none|file|table|file,table]  #通用查询日志输出格式
general_log=[on|off] #是否启用通用查询日志
general_log_file[=filename] #通用查询日志位置及名字

阅读剩下更多

默认配图
学习笔记

skip-name-resolve可以解决远程连接mysql数据库时候的缓慢或超时

  咳咳,迁了新的机器,全新的机器但是环境还是跟以前一样,基本没变过什么配置。但是远程连接mysql却很慢,甚至10s超时都还没连上!!! 当然,仅仅只是建立连接的时候,连接成功后一切操作都行如流水。

  所以最快最简单的方法就是直接在数据库的配置文件my.cnf|my.ini中添加一行skip-name-resolve来解决。

  其实mysql在用户登录建立连接的时候,会对客户端进行DNS反查,如果遇到服务器的DNS解析出现问题,那么就可能导致客户端迟迟连接不上。此方法可禁止mysql进行DNS反查从而解决了这个问题。

阅读剩下更多

默认配图
学习笔记

Laravel5.5 发送邮件遇到Connection could not be established with host xxxxx

Laravel输出的错误页面如下图:
Laravel异常捕获-mail

服务器:阿里云香港节点(B区) CentOS6.8 64bit

lamp环境:PHP7.2.1

Laravel .env mail部分:

1
2
3
4
5
6
7
8
MAIL_DRIVER=smtp
MAIL_HOST=hwsmtp.exmail.qq.com
MAIL_PORT=465
[email protected]
MAIL_PASSWORD=XXXXXXXX
[email protected]
MAIL_FROM_NAME=noreply
MAIL_ENCRYPTION=ssl

问题发生前景:本地测试完全正常,相同配置在服务器上就被抛出异常。

因为之前遇到过由于端口没有放行而造成无法建立连接的问题,在此问题上我也做了一样的尝试,在安全组配置里面对465端口进行放行(无论是出口还是入口)。但是结果表示并不是这个问题,而我在没有配置放行的情况下,telnet host:port 是通的。

经过大约两天的间接性排查和尝试,最终提交工单咨询阿里,他们给了我处理意见:尝试他们的Demo!

尝试阿里云提供的Demo测试发件是没有问题的,这就令我很纳闷的,于是我去看该Demo是如何建立连接的。

通过一步一步的探索,发现该Demo也是同样使用了stream_socket_client方法来建立连接,那为什么它可以,Laravel内的却不行?说明不是该环境的问题!

问题逐渐定位到了stream_socket_client这个方法上,查询PHP.NET来认识一下它。

1
resource stream_socket_client ( string $remote_socket [, int &$errno [, string &$errstr [, float $timeout = ini_get("default_socket_timeout") [, int $flags = STREAM_CLIENT_CONNECT [, resource $context ]]]]] )

方法描述上,只有一个参数可能会不一样,就是最后那个$context,它是一个Resource,由stream_conntext_create方法创建并返回。

我注意到Laravel中使用的Swift_Mailer所引用的StreamBuffer类中,在stream_socket_client方法前面加上了@符号来屏蔽error,而在下面自己判断来抛出异常。

那么我果断将@符号去掉! 真正的错误爆出来了!!!!

1
2
stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages:
error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed

乍眼一看,证书校验失败????

上面的废话只是简单描述我遇到这个问题是如何应对的

解决方案来了! 既然是走SSL协议(465端口),且程序抛出错误为证书校验失败,那么就去直接搜索有关Laravel/SwitEmailer的证书校验问题。

在stackOverflow上找到两个相关的问题:

  1. localhost and “stream_socket_enable_crypto(): SSL operation failed with code 1”
  2. how to fix stream_socket_enable_crypto(): SSL operation failed with code 1

他们都提出了一个配置:

1
2
3
4
5
6
7
8
9
//	在/config/mail.php中增加

'stream' => [
'ssl' => [
'allow_self_signed' => true,
'verify_peer' => false,
'verify_peer_name' => false,
],
],

然后测试,Bingo!!!

当然,既然是SSL必然使用校验才是最安全的,但是我这里仅满足能够发件即可,至于如何进行证书校验,请移步:使用PHPMailer 中的报错解决 “Connection failed. Error #2: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:”

阅读剩下更多

默认配图
Linux

Apache设置目录的访问权限(用户名和密码)

  最近重新搞了个阿里云香港ECS用来科学上网,另外使用LAMP一键安装包搭环境,搭建完成后开放云盾端口即可通过IP访问到web默认页,这默认页有些实用的小工具,如:探针、MyAdmin啥的。如图:

  

  然后我想让这默认页只有我自己能看到,其他人最好不要看到,毕竟其他人看到也没啥用,最多给服务器施压而已,然后就想弄个如下图的效果:

  

  下面来讲讲如何实现此效果的步骤,假设需要设置权限的目录(DIR)为”/data/www/default”

  • 修改apache的配置文件(可以是httpd.conf,也可以是具体的vhost的单独配置文件),内容如下:
1
2
3
4
5
6
<Directory "/data/www/default">
Options Indexes
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
  • 在DIR下新增.htaccess文件,内容如下
1
2
3
4
AuthName "sys"
AuthType Basic
AuthUserFile /data/www/default/.htpasswd
require user username
  • 使用Apache的htpasswd创建.htpasswd文件
1
2
/usr/local/apache/bin/htpasswd -c /data/www/default/.htpasswd username
##这里会提示你输入两次密码##

  完成上述步骤后,重启Apache即可

阅读剩下更多

默认配图
网络安全

解决Chrome更新到63后.dev/.app后缀域名强制HTTPS的问题

  又是周一,上班的日子总是来的太快,来到公司的第一件事,打开IDE并更新代码库。看看上周做到哪里了,还有什么BUG是待修复的?

  然鹅,意想不到的事情就这么发生了,为什么本地环境访问不了?
请求被拒绝

  localhost能访问,本地服务器没问题;其他的本地域名可以访问,Apache配置没动过应该不是这个问题;试了试公司项目的其他域名,都是同样的报错;然后我注意了一下URL,被加上了https协议,去掉s再回车没解决;换个浏览器,行了?? 好吧,可能谷歌抽风了,我试了清空缓存和浏览历史,完全不行。后来发现问题没那么简单,网上也很难找到相关的结果,发现自己遇到了难题。

  然后我发现了SegmentFault有人问了相同的问题,才在回答里面找到了答案:

Chrome update news

  原文地址:Chrome 63 now forces .dev domains to HTTPS

  既然如此,强大的谷歌对自己拥有的顶级域名(.dev和.app)进行了合法干涉,那么我们也只能认怂了吧。 默默的把原来的.dev改成了.local后缀。

  但是有强迫症的我有点不甘心,相信有很多人跟我一样,都是执着的没事干的人!不找出一个能在chrome上用.dev的后缀的方法,心里面还是稍微有那么一点难受的(^_-!)

  在github上有一个工具:Fishdrowned的自签泛域名证书,MacOS用户可以使用我FORK过来的修改版(原作者已做兼容2018/01/02)

  咱们可以自己在本地生成一个xxx.dev的域名证书来直接让TA支持https访问!!!! 具体的使用方法在上面链接有介绍

  服务器端如何配置支持https自行搜索,这里不做解释了。

  最终实现.dev的https访问,如图:

url地址栏

浏览器证书   

阅读剩下更多

默认配图
PHP

Pazzle on array_column

  在PHP中内置的对数组操作的方法(函数)有一个叫做’array_column’的非常实用,它可以用作返回一个二维数组的指定列。

  先介绍一下它!

  好比这样一个用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$students = array(
array(
'id'=>1,
'name'=>'Jerry'
),
array(
'id'=>3,
'name'=>'Tom'
),
array(
'id'=>6,
'name'=>'Lily'
),
array(
'id'=>11,
'name'=>'Bob'
),
);
$student_names = array_column($students,'name');
var_dump($student_names);

输出结果:

1
2
3
4
5
array (size=4)
0 => string 'Jerry' (length=5)
1 => string 'Tom' (length=3)
2 => string 'Lily' (length=4)
3 => string 'Bob' (length=3)

  这样就得到了一个所有名字的数组,想必对于一个PHPER来说并不陌生。但是今天要讲的重点是array_column的另外一个用法,如果给它第三个参数,那么它将以第三个参数对应的数组中的值作为返回数组的键,用例如下:

阅读剩下更多

默认配图
学习笔记

完全源码编译LNMP环境

环境:

软件:

依赖软件:

阅读剩下更多

默认配图
学习笔记

【干货】本地git对应多个远程仓库

当Githuber发现GitOSChina能发布项目演示,想把自己的项目也同步到OSChina上去时,同时维护两个本地仓库或者remote显得太过麻烦

提要

  首先,为本地git仓库添加远程仓库,我们使用命令:git remote add origin [url],这样一来,本地仓库就对应有了一个远程仓库的地址,包括push和fetch源,如下图所示:

remote-v

  但是,我们如何才能做到既能将本地git仓库PUSH到Github,也能够PUSH到OSChina上呢?

  可能有以下方式是你想到的:

  1. 本地创建两个仓库,一一对应(你不嫌麻烦的话的确可行)
  2. 添加多个远程库,即git remote add [another-origin](那每次都要PUSH两次咯?)

  其实,git的一个远程库本来就可以配置成多个地址,即一个origin拥有多个url。

敲黑板了(实现)

  前提条件是,我们已经为本地仓库添加了一个origin远程库(没有添加的自行添加,方法前面说了)。我们现在为本地库继续添加url地址:https://git.oschina.net/anthony_box/hitools.git

  这个命令是:git remote set-url --add origin [url](url替换成如上地址)

set-url

擦黑板了(原理)

  其实该条命令执行就是在git项目的config文件里的[remote "origin"]配置新增一条记录,我们打开config文件看看,输入git config -e(文件路径:[repo]/.git/config):

config-e

  所以说,执行git remote set-url -add origin和编辑config文件,一码子事。

阅读剩下更多

默认配图
学习笔记

神奇的空格符

  当我通过表单提交带空格的数据并保存到数据库,然后通过再次渲染出的页面修改该数据,意想不到的事情发生了,再也不同通过这个值检索到这条数据了。

  首先,我们先写一个PHP的测试文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if(isset($_POST['model'])){
echo $_POST['model'];
echo "<br>";
echo str_replace(' ','&nbsp;',$_POST['model']);
echo "<br>";
echo urlencode($_POST['model']);
}
?>
<form method='post'>
<input name='model' value='' />
<button type='submit'>ttttt</button>
</form>

  可以看到,这里面有个表单,提交到自己这个页面,然后由PHP获取到POST过来的参数model,第一行直接输出它的值,第二行输出防SQL注入对空格的转化后的值,第三行输出encode之后的值(为什么?我们最后说TA的用途^_^)。

  我们现在试着输入并提交Sumsang S8:

结果1

  由此可见,后台获取到的是一个Sumsang+空格+S8,第二行跟上面一样(显示一样,源码不一样),第三行空格变成加号是urlencode后的转义,那么看下源码:

源码1

  当然,很多情况下,为了防止SQL注入,我们会将空格转化成&nbsp;存库,然后取数据的时候在逆回来。


  紧接着,我们模拟数据的修改,基本过程是:从数据库取出这条数据–>填充到input标签的value属性中(没有做逆处理)–>渲染页面,渲染的页面源码如下:   

阅读剩下更多

默认配图
杂谈

毕业了

转眼间,青春不再年华

  其实,很早就要写这样一篇文章的,只是一直都不知道如何表达,即使现在也还是一样。

  毕业,经历也不止那么一两次了,从来都没有感觉到有什么特别。人们总说,离别是伤感的,因为知道以后的相逢是微乎其微的,因为不知道的,太多了。我从来都没有想到,竟然在我即将离开校园的那一天,舍友一个个从宿舍离开的那一天,我热泪盈眶,竟说不出话来。一想到平日里我们朝夕相处,如今却落得个你南我北他中原。

  正式步入社会,成为万千就业者中的一员,也就是说,我们不得不考虑和选择更多的事情。入京工作已过半年之久,以前从未想过会来京都工作,我永远都会记得每一次面试当面试官问我想去哪个城市工作时,我的回答只强调了一个重点,那就是“除了北京”。原因很简单,离家太远和空气环境差,我更愿意去上海、深圳、成都甚至是长沙。但最终我实习和工作的地方,就是北京。Orz…

  既然来了,便也学会适应并努力完善自我,尽管生存环境不尽人意。相信自己能够在这个不曾向往的地方体现自我价值,创造人生价值。

两年,或许是个新开始

  是的,不管怎么样,我决定至少认真的对待这两年,其实两年对于一个刚刚毕业的菜鸟是很重要的一个过渡期,这意味着今后的路将怎么走,应当展望一个什么样的未来,找到自己所处的位置和自己将要达到的下一个高度。我希望两年后,我会是一个全新的我,完全从现在的自己蜕变而出,所及之处无遗憾,稳而渐入佳境。

总有那么一个时刻,你背负着Newbie的称号。

阅读剩下更多

默认配图
学习笔记

The Problems When Python2.6 to 2.7 on CentOS6.5

当Python从2.6升级到2.7后,pip找不到了?

  对的,基本上都能遇到这个问题。我很暴力的直接去下载一个pip然后用Python去安装它。

1
python setup.py install

  却被告知ImportError: No module named setuptools,想通过yum直接给安装,却出现下图所示:

  没办法,去下载一个setuptools的包自己来装吧(我试过了下载setuptools-35.0.1.zip,这个不适用)。所以下面的这个亲测有效:

1
2
wget https://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg  --no-check-certificate
sh setuptools-0.6c11-py2.7.egg

阅读剩下更多

默认配图
PHP

array_walk中的$this报错

Fatal Error:Using $this when not in object context on line 54

  今天在完成一个小功能开发并提交SVN后,收到前端的求助,说是更新了我的代码后页面打不开了。很着急啊,我这里都是测试成功才提交版本的。现在只能麻溜的跑过查看了。

  首先,出现的情况是空白页面(对,没有任何输出)。报错肯定没打开,帮他打开E_ALL后就看到了今天的重点报错提示了。说是在54行有个$this不在对象环境中?

  然后我去看了,那个$this出现在array_walk方法的function中,从语句上来看是完全没有问题的,在类的方法里面用$this肯定没毛病啊,可是为什么他那里就报错呢?

  我突然想起来,如果要在array_walk里面使用外面的变量的时候需要使用use方法。所以改进一下:

1
2
3
4
$user_id = $this->user->id;
array_walk($arr,function(&$v) use($user_id) {
$v['user_id']=$user_id;
});

  这样就没毛病了。看来这个array_walk还挺调皮啊,不得不说这个方法的使用还真有许多需要注意的地方呢。

  通过查资料,得知这个$this在PHP版本低于5.4的情况下是会出现问题的。高版本的不会出现。

参考:Using $this when not in object context error within array_walk

阅读剩下更多

默认配图
PHP

PHP时区默认为Europe/zurich且修改php.ini无效的困惑

为何我的date(“Y/m/d H:i:s”)相差了7个小时?

环境:MacOS X EI Capitan / PHP5.6.25

  今天在校验Crontab的计划执行时,利用PHP输出执行时间却发现输出的时间和当地事件相差了7个小时,对!是7不是8,并不是默认的UTC,呵呵了~

  好吧,一贯思路,我去把php.ini中的date.timezone修改成PRC。嗯,肯定正常了吧。

  要能正常我也不会写本文了^_~!

  不论是修改成PRC还是Asia/Shanghai又或者UTC,全都未然。   


  1. apache输出phpinfo(),查看date区域它的Default Timezone还是没变。
    Phpinfo的date区域
  2. 在控制台查看php -i | grep zone也还是同样没变
    控制台输出timezone
  3. 核对加载的php.ini文件是否正确并且配置信息格式是否正确

  4. 控制台使用php -a交互模式,调用ini_get('date.timezone'),检查配置的INI是否生效

  全都没有任何问题啊!!!为什么结果却出乎意料呢??百度,谷歌我都问遍了,大家没人出现我这样的情况啊。正当我发愁的时候,小伙伴过来给我支招了。

阅读剩下更多

默认配图
PHP

php上传进度之uploadprogress

我的学习是项目驱动,这次遇到的需求是上传进度条。

注意:此方法仅在Apache下运行PHP时有效,如果您采用FastCGI方式(如:Nginx+fpm),这个不适用!

通过查PHP手册,了解到PHP版本5.4+有一个新特性uploadprogress,也可以说是新扩展吧!

1
2
当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。

将这个扩展启用,需要开启PHP.INI支持:

session.upload_progress.enabled = On
session.upload_progress.cleanup = On
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"

  当然,我按照官方的示例操作了N久,就是读不到此类session的值。一开始以为是公司框架的问题,后来自己在本地单独写文件来测试,结果还是一样!

  在网上查找各种此类问题的解决办法,虽说有相同提问的,但是解答问题的人根本就没找到根本的原因。其实很简单:上传的文件Size太小

阅读剩下更多

默认配图
学习笔记

Learning-Markdown

MarkDown的学习

这里面就是一些简单的MarkDown语法,提供给我自己看一下,刚开始还是需要不断练习来接受这种语法,必然能够在码字的过程中体验乐趣!

下面开始啦:

阅读剩下更多

默认配图
杂谈

Hi! Github Pages

时间 事件
2017/01/17 考虑到服务器5个月后到期,无法提供一个月65元的费用(因为性价比),寻求新的方式
2017/01/18 快速学习Hexo与Github Pages,初步学习如此方式的博客
2017/01/19 配置Hexo和域名解析,完成以前的文章的迁移(图片还没找到合适的方法),imsry.cn域名正式使用!

感谢大家的支持与厚爱!

注:dshui.wang暂时并存一段时间,以免部分朋友找不到入口。请大家记住新的地址(www.imsry.cn –对不起.cn/抱歉.cn/我是抱歉.cn)

阅读剩下更多

默认配图
学习笔记

我是如何将mac OS X(10.11.6)的PHP版本升级到5.6.29的


由于项目和学习的需要,本机曾经升级到了5.5.38已经不够折腾了,所以时候升级一波5.6了(没必要到7的时候,我是不会去升级的)。
首先直接去搜索引擎找便捷的方法呗,直接就选中了一款来自“简书”的文章,因为它就简单的一条命令:

1
curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6

看到这个地址,我心里就放心了!因为上一个版本也是liip的。
我首先看了一下这个install.sh的内容,确定没有什么危险,里面其实就是检测一下设备的系统版本和cpu类型是否可以升级。
环境检测没问题就去下载了一个叫做“packager.tgz”的压缩包,然后解压,运行里面的pythony脚本。

1
2
#TYPE = 5.6-10.10
sudo packager.py install $TYPE-frontenddev

阅读剩下更多

默认配图
杂谈

2017年的开篇,居然是这个!

今天一天其实可以说,相~当~累! 因为去了一趟八达岭长城,徒步北长城让我精疲力竭。回来已经晚上9点了,收拾收拾东西,洗漱完。坐着等2017的到来。
然后就干了这么一件事:
20170206148636027922625.png

然后产生的结果是:
20170206148636024291000.png

对于这个,2017年1月1日 00时00分01秒,我露出了无奈的笑容~

阅读剩下更多

默认配图
杂谈

JetBrains家的IDEA真不便宜

先贴出来正版购买链接!

JetBrains公司的那些IDE真的是非常的好用,No.1的水准!一直都非常喜欢这家的产品,也难怪价格会稍微贵那么不止一点点了!
IntelliJ IDEA专为Java打造,当然还有为PHP打造的PHPStorm,为javascript打造的WebStorm,为Python打造的PyCharm等等。
不过今天不是来吹嘘JetBrains家的产品的,我是来吐槽自己买不起的事实。[/摊手]

阅读剩下更多

默认配图
数据库

初探MongoDB安全之RBAC(基于角色的访问控制)

写在开头的一段话:我是一名忠实的SQL玩家,虽说MySQL并没有达到精通的级别,但在日常使用中还是会经常出现“这个表要是NoSQL的话就好办多了”的想法。所以说,我认为无论是站在关系型数据库还是非关系性数据库的立场上,二者并不是相对立的,而且我觉得应该用“相辅相成”来形容二者的关系。

通过在《MongoDB 教程|菜鸟教程》轻量级的了解了一下MongoDB的基础知识,我在自己电脑上安装并使用了这款NoSQL软件。很明显地,读别人的教程只能是迅速的、简单的和容易理解的。也正是这样,我发现了其中有一块并没有提及!那就是并没有说要登录数据库啊。

阅读剩下更多

默认配图
算法

实现寻找两个字符串的最大公子串的方法

昨天在做某兔的校招笔试题的时候遇到的题目,就这一个编程题,然而当时却没有拿下,把它和字符串匹配中的子串包含给弄混了,哎!
废话少说,上代码!

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
<?php
function MaxSubCommonStr($str1,$str2){
$a = str_split($str1); //字符串分割成数组
$b = str_split($str2);
$len1 = strlen($str1); //字符串的长度
$len2 = strlen($str2);
$maxlen = 0; //最大计数器
for($i=0;$i<$len1;$i++){
for($j=0;$j<$len2;$j++){
if($a[$i] == $b[$j]){ //找到第一个相等的字符
$as = $i; //拷贝字符串1的起点
$bs = $j; //拷贝字符串2的起点
$count = 1; //有一个相等的字符了
while ($as + 1 < $len1 && $bs + 1 < $len2 && $a[++$as] == $b[++$bs])
$count++; //往后比较,每匹配一个计数+1,直到其中一个查完或者出现不相等
if($count > $maxlen){ //当本次计数长度大于最大记录时
$maxlen = $count; //更新最大计数长度
$start1 = $i; //更新本次比较的字符串1起点
$start2 = $j; //更新本次比较的字符串2起点
}
}
}
}
return substr($str1,$start1,$maxlen); //直接返回字符串1,从$start1起点往后$maxlen最大匹配长度个数的子串
}

$str1 = 'abcdefgabc';
$str2 = 'defghijabc';
echo MaxSubCommonStr($str1,$str2);
?>

没什么含金量,只是写出来练练手,思路照搬过来的。

阅读剩下更多

默认配图
杂谈

[合集]面试中遇到的那些提问

参加了好几家公司的面试了,一直也没来得及将这些问题整理一下,今天正好不想到处跑了,所以先在这里写下这一篇文章,以后会不定期更新。

非技术篇:

  1. 先来一个自我介绍吧(废话! =_=!)
  2. 用3个词组评价你自己
  3. 说一说你一生中感到最有成就感的一件事
  4. 说一说你曾经遇到过的最沮丧的一件事
  5. 说说你的优缺点
  6. 说说你的父母都是做什么的?
  7. 你有多少个兄弟姐妹?
  8. 你为什么选择来参加我们公司的招聘
  9. 你对加班的看法和出差的看法,以及你对加班和出差的最大忍耐极限
  10. 你觉得你在哪一方面比较优秀?
  11. 别人都是怎么评价你的?
  12. 在同一个Team中,当别人的意见和自己的意见冲突的时候,你会怎么办?
  13. 拿一个你最熟悉的项目来讲讲,它的架构、设计、遇到的问题以及是如何解决的
  14. 你对我们的开发团队有什么期望吗?
  15. 最后,你有什么想要问我的?
    …

阅读剩下更多

默认配图
算法

PHP常用算法的方法实现(冒泡、选择、插入、快排、二分查找)

以前说起写算法,基本上都是拿C语言来写,因为用C可以更清楚的理解各种排序算法和数据结构。今天遍换成使用PHP语言来写几个常用的算法。
这次要写的算法包括:

  • 冒泡排序
  • 插入排序
  • 选择排序(直接)
  • 快速排序
  • 二分查找

冒泡:

<?php
$arr = array(4,3,5,6,8,0,10,15,11);
echo implode(' ',$arr);
//冒泡排序 最坏 平均O(N^2) 最好O(N)
function BubbleSort($arr){
	$length = count($arr);
	if($length <= 1){
		return $arr;
	}
	for($i=0;$i<$length;$i++){
		for($j=0;$j<$length-$i-1;$j++){
			if($arr[$j] > $arr[$j+1]){
				$tmp = $arr[$j];
				$arr[$j] = $arr[$j+1];
				$arr[$j+1] = $tmp;
			}
		}
	}
	return $arr;
}
echo "\nBubbleSort:\n";
echo implode(' ',BubbleSort($arr))."\n";
?>

阅读剩下更多

默认配图
PHP

一种较好的实现PHP异步发送邮件的实现

前些天在写我的TR-System(社团招新管理系统)的时候,用到了邮箱验证用户注册,也就是注册成功后,需要访问收到的邮件中的URL进行用户激活。这个事件发生在用户提交注册请求之后,系统需要立即发送一封邮件用于激活,但是系统在调用PHPMailer的时候需要进行SMTP连接邮箱系统(用的腾讯企业邮),往往在这个时候会出现等待的情况。如果让用户来承担这个等待时间,体验也太差了!,没见过哪个系统上注册个用户,还需要慢慢等待系统告诉我到底有没有注册成功。
一开始在写的时候,直接在POST请求中处理,写入数据库后就调用sendEmail方法,情况是这样的:
1.提交注册 --> 2.写入数据库 --> 3.发送Email --> 4.注册成功。
这样在用户看来是有一个漫长的等待的。
实际上应该为:
1.提交注册 --> 2.写入数据库 --> 3.注册成功。 ||||||||||||||||||-->发送Email
这样的才是一个拥有良好用户体验的系统 ^_^ 。
若是在Java等环境下,咱们可以很好利用多线程,分出来一个任务让另一个线程去完成。
但是!PHP这个单线程环境下,没有new Thread来搞这个事情啊。 不过,换一个角度,线程不行的,我就给个新进程呗。当然不是在当前进程创建新进程。

阅读剩下更多

默认配图
算法

通用笔试-PHP测试编程题[2题]

水仙花数

题目描述:
春天是鲜花的季节,水仙花就是其中最迷人的代表,数学上有个水仙花数,他是这样定义的: “水仙花数”是指一个三位数,它的各位数字的立方和等于其本身,比如:153=1^3+5^3+3^3。 现在要求输出所有在m和n范围内的水仙花数。

输入
输入数据有多组,每组占一行,包括两个整数m和n(100<=m<=n<=999)。

输出
对于每个测试实例,要求输出所有在给定范围内的水仙花数,就是说,输出的水仙花数必须大于等于m,并且小于等于n,如果有多个,则要求从小到大排列在一行内输出,之间用一个空格隔开; 如果给定的范围内不存在水仙花数,则输出no; 每个测试实例的输出占一行。

样例输入
100 120 300 380

样例输出
no 370 371

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
<?php
echo "Please Input\n";
$in = '';
while($in != "\n"){
$first = false;
// $stdin = fopen("php://stdin",'r');
// $in = fgets($stdin);
$in = fgets(STDIN);//使用标准输入
$args[] = explode(' ',trim($in));
}
// fclose($stdin);
array_pop($args);
foreach ($args as $item) {
if($item[0] <100 || $item[1]>999 || $item[0]>=$item[1]) {
echo "输入参数不合法!\n";
exit(0);
}
$start = $item[0];
$end = $item[1];
$res = array();
while($start <= $end){
$a = floor($start/100);
$b = floor(($start-$a*100)/10);
$c = $start%10;
if($start == ($a*$a*$a+$b*$b*$b+$c*$c*$c)){
$res[] = $start;
}
$start += 1;
}
echo empty($res)?"No\n":implode(' ',$res)."\n";
}
?>

第二题:

阅读剩下更多

默认配图
学习笔记

You have new mail - Mac OS

奇了怪了,莫名其妙!平时打开终端都没怎么注意,今天发现打开终端的时候,除了常规的上次登录提示,居然有一句“You have new mail”紧接着在下面出现了。
平时都不用自带的邮件.app的 打开邮件,却没有啥。
好的,本着好奇的心理,开始摸索。

阅读剩下更多

默认配图
Linux

[修复]wordpress WP_Image_Editor_Imagick 指令注入漏洞

漏洞描述:该修复方案为临时修复方案,可能存在兼容性风险,为了防止WP_Image_Editor_Imagick扩展的指令注入风险,将wordpress的默认图片处理库优先顺序改为GD优先,用户可在/wp-includes/media.php的_wp_image_editor_choose()函数中看到被修改的部分
云盾不提供自动修复,咱们自己来修复吧。根据提示,我们打开media.php 找到相关函数的内容:

$implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ) );

做如下顺序调整:

$implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_GD', 'WP_Image_Editor_Imagick' ) );

正如上面的描述所说,临时方案嘛。Wordpress官方不做处理的话,你每次升级了程序,都会出现这样的报错,每次都得自己去修改。不过如果不在乎这个提示的话,不理也行!

附当时的图片一张
wp-Image 注入漏洞

阅读剩下更多

默认配图
Linux

nginx+php-fpm环境下的mysqld痛不欲生

问题背景

今天突然发现自己的服务器又出现了Mysql服务器的崩溃,重启一下便恢复了。
但是!下午2点的时候重启的,晚上回来一看,数据库又挂了!!!十分恼火啊!再重启一下,什么?直接启动不了啦!
发生报错:

[root@iZ28l1ca1vhZ ~]# service mysqld start
Starting MySQL.The server quit without updating PID file (/usr/local/mysql/data/iZ28l1ca1vhZ.pid).                                         [FAILED]
[root@iZ28l1ca1vhZ ~]# service mysqld restart

排查过程

查看mysqld的日志:

阅读剩下更多

默认配图
Web

js判断两个块元素的相交与否

之前在做阿里笔试的时候,其中就有那么一个题,要求你用js写一个函数,用来判断两个Div是否相交,当时在那个上面并没有很快的写出来,结果时间到了。回头好好又想想,还是把他写一下,就算是最笨的方法,也得实现。

阅读剩下更多

默认配图
编程语言

C(C++)、Java、PHP区别函数参数传递

这篇文章,我专门测试了一下C、Java、PHP三种编程语言中对于函数(方法)参数传递的问题

一、C/C++篇

首先说明一下,这里仅在C语言下测试,C++类似,只不过在C++中多了一个传引用(&),其性质还是指针,传递过程中可以看作是使用别名。
看代码:

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
//
// main.c
// TestOfVlaue
//
// Created by anthony on 16/4/16.
// Copyright © 2016年 Anthony. All rights reserved.
//

#include <stdio.h>
//C语言测试
void prt1(int v) {
printf("方法内执行后的结果:%d\n",v);
}
void prt2(int v[],short int len) {
for (int i=0; i<len; i++) {
printf("a[%d]:%d\n",i,v[i]);
}
}
void ch1(int v) { //按值传递
++v;
prt1(v);
}
void ch2(int *v) { //按地址(指针)传递
++(*v);
prt1(*v);
}
//void ch3(int &v) { //引用传递 C里面木有&(引用)C++中可用,底层还是指针
// ++v;
// prt1(*v);
//}
void ch4(int v[],short int len) { //改变数组的值
for (int i=0; i<len; i++) {
v[i]++;
}
}
void test1(int v) { //测试整型变量
printf("测试单个值:类型:int,值为:%d\n",v);
ch1(v);
printf("执行ch1()的结果:%d\n",v);
ch2(&v);
printf("执行ch2()的结果:%d\n\n",v);
}
void test2(int v[],short int len) { //测试整型数组
printf("测试数组:类型:int,值为:\n");
prt2(v, len);
ch4(v,len);
printf("方法内执行后的结果:\n");
prt2(v, len);
printf("\n");
}
int main(int argc, const char * argv[]) {
int a = 10;
int b[] = {1,2,3};
test1(a); //测试变量a++
test2(b, 3); //测试数组b各元素++
test1(b[0]); //测试数组b[0]++
return 0;
}

运行结果:

阅读剩下更多

默认配图
学习笔记

《XML实用教程》课后实验1-7章文件

由于我使用的是MacOS,所以编码均采用UTF-8,书上均是gb2312的。

第一章:

使用Altova XMLSpy 2010软件编写XML文档

[实验指导1-1.xml](/uploads/2016/04/实验指导 1-1.xml)

第二章:

创建通讯录的XML文档

tongxun.xml

第三章:

3-1:使用内部DTD编写动物园内动物信息的XML文档

[实验 3-1.xml](/uploads/2016/04/实验 3-1.xml)

3-2:结合已知外部DTD创建XML文档

3-2.dtd
3-2.xml

阅读剩下更多

默认配图
Linux

CentOS 6下更新PHP版本到5.5后与Nginx发生的小事情

今天在写API的时候用到了php中新版本的json_encode()方法,在5.4以后支持了不对中文进行Unicode编码,而我在服务器上的php版本还停留在yum源包自带的5.3版本,只有升级来满足需要了。
首先,找了一些关于升级PHP的文章,各不相同,我还是坚持简单、易操作的方法,使用yum来更新。
当然,yum update php 是完全不能解决的,因为它告诉我5.3是最新的版本了。不能怪它,因为没人告诉它今年是哪一年了^_^!
这个不行,肯定要换一个源包了。。
不过首先是要卸载已经安装的php。在这之前,先看看当前安装的PHP有哪些依赖包吧:

1
yum list installed | grep php

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@iZ28l1ca1vhZ scuec]# yum list installed | grep php
php.x86_64 5.3.3-46.el6_6 @updates
php-bcmath.x86_64 5.3.3-46.el6_6 @updates
php-cli.x86_64 5.3.3-46.el6_6 @updates
php-common.x86_64 5.3.3-46.el6_6 @updates
php-fpm.x86_64 5.3.3-46.el6_6 @updates
php-gd.x86_64 5.3.3-46.el6_6 @updates
php-imap.x86_64 5.3.3-46.el6_6 @updates
php-ldap.x86_64 5.3.3-46.el6_6 @updates
php-mbstring.x86_64 5.3.3-46.el6_6 @updates
php-mcrypt.x86_64 5.3.3-4.el6 @epel
php-mysql.x86_64 5.3.3-46.el6_6 @updates
php-odbc.x86_64 5.3.3-46.el6_6 @updates
php-pdo.x86_64 5.3.3-46.el6_6 @updates
php-pear.noarch 1:1.9.4-4.el6 @base
php-xml.x86_64 5.3.3-46.el6_6 @updates
php-xmlrpc.x86_64 5.3.3-46.el6_6 @updates

上面这些本来有的,安装新版本后还是加上去比较好办,对!
接着卸载php吧

阅读剩下更多

默认配图
Linux

Debian中搭建VPN(PPTP协议)实战,实现VPN上网

首先讲一讲为什么会突然想到弄一下VPN呢?这是因为在我的学校,校园网的认证采用的是VPN(L2TP协议),既然这样,我是不是可以通过学校的虚机来自建一个认证系统来实现上网呢?抱着这种疑问,开始做一些事情吧。

另外提一下,PPTP, L2TP/IPSec 和 OpenVPN这三种协议里,PPTP比较容易配置,所以我采用了这种方法。

1、所需工具和资源

说到底,还是离不开一台校内的机器,而且能够上网并拥有独立IP(看上去很难有这个),多亏有师生这层美妙的关系,对!一台虚机就这样有了。软件方面的东西直接在虚机上下载就行了。

本次实战的机器是Debian系统,软件源包使用apt-get,本地电脑Macbook(自带ssh连接端)

2、具体步骤

首先连接上远程计算机,通过命令行进行安装:

阅读剩下更多

默认配图
PHP

分享一个PHP修改ini配置文件的类

.ini配置文件 有简单类型 和 复杂类型:
简单的不带节点,如:
username=myname
userage=21
userinfo=i’m a boy

另一种带有节点,如mysql的配置文件my.ini(windows下):
[mysqld]
port = 3306
socket = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 16M
max_allowed_packet = 1M
table_open_cache = 64

阅读剩下更多

默认配图
学习笔记

有关MacBook从10.9.5升级到10.11(EI capitan)后的问题及解决方法

看到别人的MAC在用最新的EI Capitan系统之后,我终于打破了自己坚持使用旧系统的原则,本着更好的体验和更高的性能,我将自己的本子也更新到最新,既然是更新升级上来的,不免有些小问题会出现,下面就逐个来说说我遇到的问题吧。

问题一:Apache无法正常启动?

首先遇到这个问题时,当然是去找日志了,因为在终端输入启动命令:sudo apachectl start之后,没有任何报错和提示,只是本地浏览器访问localhost or 127.0.0.1失败。但是,这个错误日志真的不好找,最后我干脆去配置文件etc/apache2/httpd.conf中寻找。

嗯!找到了! ErrorLog “/private/var/log/apache2/error_log” 好的,我去看看到底是什么原因。可是,“/private/var/log/apache2/” 目录根本没有任何文件!想着自己给它创建这个错误日志文件吧,创建好了之后使用命令启动apache,结果文件里面没有任何内容!

这也不行,看来通过错误日志是行不通了,于是乎,在网上查找各种资料,终于看到有个人在网页上写到:现在尝试启动一下Apache: sudo apachectl -k start。看到这个,带着好奇我也输入了一遍,好奇心真的不会害死猫!终于出现了报错了!第一次这么开心地看见报错信息,于是乎根据提示找到错误点并屏蔽掉了这个错误的配置。

阅读剩下更多

默认配图
Linux

Apache 反向代理 Tomcat实战

apache

近期突然有个想法,既然有多出来的服务器,空着也是空着,不如拿出来用作演示项目。带着这样的心情,我开始在自己的服务器上动起了手脚。

一、现有情况:

  1. 我的博客地址:http://www.dshui.wang/
  2. 演示用的服务器地址(Tomcat):http://xxx.xx.xx.xx:8080/ (由于暴露IP和端口带来了攻击,现在取消了)
  3. 演示所用域名:demo.dshui.wang 解析至博客所在服务器(非演示用服务器)

二、预期效果:

  1. 访问http://demo.dshui.wang/tomcat/时,展示的页面为http://xxx.xx.xx.xx:8080/所显示的页面
  2. 更近一层,http://demo.dshui.wang/tomcat/* 译为http://xxx.xx.xx.xx:8080/*  (*代表通配符

    阅读剩下更多

默认配图
学习笔记

[作业练习]带数据库的简易购物车

在前一次作业的基础上([作业]JavaBean+Jsp简易购物车实现),加上数据库来管理商品和购买记录。
最后结果展示:http://demo.dshui.wang/tomcat/ShopCartDemo/
首先,建立数据表:

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
#用户表
CREATE TABLE `cart_users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(80) NOT NULL DEFAULT '',
`password` varchar(220) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
<!--more-->

#商品表
CREATE TABLE `cart_goods` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`gname` varchar(50) NOT NULL DEFAULT '',
`gprice` double NOT NULL,
`gdname` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

#购买记录
CREATE TABLE `cart_lists` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(80) NOT NULL DEFAULT '',
`gid` int(11) unsigned DEFAULT NULL,
`gnumber` double DEFAULT NULL,
`gtprice` double DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `users` (`username`),
KEY `goodid` (`gid`),
CONSTRAINT `goodid` FOREIGN KEY (`gid`) REFERENCES `cart_goods` (`id`),
CONSTRAINT `users` FOREIGN KEY (`username`) REFERENCES `cart_users` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8;

写入测试数据:

阅读剩下更多

默认配图
学习笔记

[作业]JavaBean+Jsp简易购物车实现

本次作业要求使用到JavaBean+纯Jsp,带有登陆功能的购物车系统。
登陆功能这里就不多讲了,前面的聊天室已经做过,这边直接拷贝就能使用了。
另外,本次作业中我加入了BootStrap来做一个小小风格美化。
不多说,直接上代码!
index.jsp 首页,也是登陆界面

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
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%
Cookie[] cookies = request.getCookies();
String uname = "";
String upwd = "";
if(cookies!=null){
for(int i=0;i<cookies.length;i++)
{
Cookie cookie=cookies[i];
if(cookie.getName().equals("remname"))
{
uname = cookie.getValue();
}
else if(cookie.getName().equals("rempwd"))
{
upwd = cookie.getValue();
}
}
}
%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>请登录ShopCartDemo</title>
<link href="bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<form class="form-horizontal" action="dologin.jsp" method="post" name="loginform">
<div class="form-group">
<label for="inputusername" class="col-sm-5 control-label">用户名</label>
<div class="col-sm-2">
<input class="form-control" id="inputusername" type="text" name="username" value="<%=uname%>" placeholder="username" />
</div>
</div>
<div class="form-group">
<label for="inputpassword" class="col-sm-5 control-label">密码</label>
<div class="col-sm-2">
<input class="form-control" id="inputpassword" type="password" name="password" value="<%=upwd%>" placeholder="password" />
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-5 col-sm-2">
<div class="checkbox">
<label>
<input type="checkbox" name="remember" value="true"> 记住我
</label>
</div>
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-5 col-sm-2">
<button type="submit" class="btn btn-default">登陆</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>

</form>
</div>
</body>
</html>

dologin.jsp 登陆信息处理页面

阅读剩下更多

默认配图
Linux

linux下mysql的root密码忘记解决方法

1.首先确认服务器出于安全的状态,也就是没有人能够任意地连接MySQL数据库。
因为在重新设置MySQL的root密码的期间,MySQL数据库完全出于没有密码保护的
状态下,其他的用户也可以任意地登录和修改MySQL的信息。可以采用将MySQL对
外的端口封闭,并且停止Apache以及所有的用户进程的方法实现服务器的准安全
状态。最安全的状态是到服务器的Console上面操作,并且拔掉网线。
2.修改MySQL的登录设置:

vi /etc/my.cnf

在[mysqld]的段中加上一句:skip-grant-tables
例如:
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
skip-grant-tables
保存并且退出vi。
3.重新启动mysqld

/etc/init.d/mysqld restart

Stopping MySQL: [ OK ]
Starting MySQL: [ OK ]
4.登录并修改MySQL的root密码

/usr/bin/mysql

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3 to server version: 3.23.56
Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the buffer.
mysql> USE mysql ;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> UPDATE user SET Password = password ( ‘new-password’ ) WHERE User = ‘root’ ;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 2 Changed: 0 Warnings: 0
mysql> flush privileges ;
Query OK, 0 rows affected (0.01 sec)
mysql> quit
Bye
5.将MySQL的登录设置修改回来

vi /etc/my.cnf

将刚才在[mysqld]的段中加上的skip-grant-tables删除
保存并且退出vi。
6.重新启动mysqld

/etc/init.d/mysqld restart

Stopping MySQL: [ OK ]
Starting MySQL: [ OK ]
文章转自:http://www.cnblogs.com/allenblogs/archive/2010/08/12/1798247.html

阅读剩下更多

默认配图
算法

各种排序算法介绍

1.直接插入排序算法(Straight Insertion Sort):
基本思想:
将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
算法的实现:

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
//
// main.cpp
// Straight Insertion Sort
//
// Created by anthony on 15-10-8.
// Copyright (c) 2015年 anthony. All rights reserved.
//

#include <iostream>
using namespace std;

void print(int a[], int n ,int i){
cout<<i <<":";
for(int j= 0; j<n; j++){
cout<<a[j] <<" ";
}
cout<<endl;
}
void InsertSort(int a[], int n)
{
for(int i= 1; i<n; i++){
if(a[i] < a[i-1]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
int j= i-1;
int x = a[i]; //复制为哨兵,即存储待排序元素
a[i] = a[i-1]; //先后移一个元素
while(x < a[j]){ //查找在有序表的插入位置
a[j+1] = a[j];
j--; //元素后移
}
a[j+1] = x; //插入到正确位置
}
print(a,n,i); //打印每趟排序的结果
}
}

int main(int argc, const char * argv[]) {
int a[8] = {5,6,1,3,2,8,9,7};
InsertSort(a,8);
print(a,8,8);
return 0;
}

运行结果:

1:5 6 1 3 2 8 9 7

2:1 5 6 3 2 8 9 7

3:1 3 5 6 2 8 9 7

4:1 2 3 5 6 8 9 7

5:1 2 3 5 6 8 9 7

6:1 2 3 5 6 8 9 7

7:1 2 3 5 6 7 8 9

8:1 2 3 5 6 7 8 9

Program ended with exit code: 0

时间复杂度:O(n^2).

2.选择排序—简单选择排序(Simple Selection Sort):

基本思想:

在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。

操作方法:

阅读剩下更多

默认配图
学习笔记

[作业]Servlet简易聊天室

简单例子-Servlet聊天室,所用工具:NetBeans IDE(配备GlassFish)
本次例子重在思路,页面未做任何美化。
最后结果展示:http://demo.dshui.wang/tomcat/First_chat_room/
首先,需要一个登陆页面。
Login.jsp

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
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%
Cookie[] cookies = request.getCookies();
String uname = "";
String upwd = "";
if(cookies!=null){
for(int i=0;i<cookies.length;i++)
{
Cookie cookie=cookies[i];
if(cookie.getName().equals("remname"))
{
uname = cookie.getValue();
}
else if(cookie.getName().equals("rempwd"))
{
upwd = cookie.getValue();
}
}
}
//上面代码用来判断用户是否曾保存过登陆信息
%>
<!DOCTYPE html>
<html>
<head>
<title>牛逼的登陆界面</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>
<form action="Main" method="post" name="loginform">
<table>
<tr>
<td>用户名:</td>
<td>
<input type="text" name="username" value="<%=uname%>" />
</td>
</tr>
<tr>
<td>密码:</td>
<td>
<input type="password" name="password" value="<%=upwd%>" />
</td>
</tr>
<tr>
<td colspan="2" style="text-align: right;">
记住信息<input name="remember" type="checkbox" value="true" />
<input type="submit" name="submit" value="登录"/>
<input type="reset" name="" value="重置"/>
</td>
</tr>
</table>

</form>
</div>
</body>
</html>

新建一个用来处理登录信息的Servlet: Main.java 所有的处理都在DoPost()完成

阅读剩下更多

默认配图
学习笔记

Discuz的数据库的迁移

如果WEB服务器迁移,除了将论坛所有的文件转移到新的服务器上之外,还需要将数据库一同迁移。文件转移完毕之后,并不能算完成了,因为这里还有数据库的配置没有修改。
Discuz论坛,如果需要迁移数据库,那么我们在转移的数据库的所有数据之后,还需要重新配置一下Discuz的配置文件:
论坛连接Ucenter的配置:config/config_ucenter.php
论坛的配置:config/config_global.php
Ucenter的配置:uc_server/data/config.inc.php
在这三个文件里面,修改对应的数据库地址、数据库名称、用户名、密码。
全部配置完毕之后,保存就可以了。

阅读剩下更多

默认配图
Linux

Linux之CentOS 6.7 下无网络? 原来是网卡没启动!

CentOS-Network-eth0
今天用Vbox安装了CentOS 6.7 准备测试游戏服务器,需要下载一个服务端,使用wget时发现无wget命令,聪明的我马上想到使用yum install wget 来安装一个,谁知道出现了无法连接镜像列表的报错,通过查看ifconfig发现,原来是网卡没有启动,于是查看了资料:

1
vi /etc/sysconfig/network-scripts/ifcfg-eth0

修改开启自启:

1
2
3
4
5
6
7
DEVICE=eth0
HWADDR=00:0C:29:2E:37:F0
TYPE=Ethernet
UUID=69cd9740-184f-49b7-857f-e397e57f265b
ONBOOT=yes //修改此处
NM_CONTROLLED=yes //修改此处
BOOTPROTO=dhcp

然后重启网络:

1
2
3
//重启网络
/etc/init.d/network restart
//查看IP

阅读剩下更多

默认配图
杂谈

SAMP-LS中心医院围栏+自动门

20170206148636277144832.png
下面是围栏的创建,写入main()或者OnGameModeInit()函数即可;

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
CreateObject(966, 1209.32813, -1384.90000, 12.24180,   0.00000, 0.00000, 0.00000);//障碍底座
CreateObject(967, 1210.39294, -1384.90000, 12.24560, 0.00000, 0.00000, -90.00000);//看守台
CreateObject(970, 1187.95776, -1384.87561, 12.78820, 0.00000, 0.00000, 0.00000);//前面大门围栏
CreateObject(970, 1200.26697, -1384.90002, 12.78820, 0.00000, 0.00000, 0.00000);//前面大门围栏
CreateObject(970, 1192.11816, -1384.90002, 12.78820, 0.00000, 0.00000, 0.00000);//前面大门围栏
CreateObject(970, 1196.20007, -1384.90002, 12.78820, 0.00000, 0.00000, 0.00000);//前面大门围栏
CreateObject(1411, 1210.96069, -1383.11963, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1377.84949, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1372.57935, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1367.30921, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1362.03907, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1356.76893, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1351.49879, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1346.22865, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1340.95851, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1335.68837, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1330.41823, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1325.14809, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1319.87795, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1314.60781, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1309.33767, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1304.06753, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1298.79739, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(1411, 1210.96069, -1293.52725, 13.86000, 0.00000, 0.00000, 90.00000);//侧面围栏
CreateObject(978, 1206.48462, -1290.66870, 13.21470, 0.00000, 0.00000, 0.00000);//后面障碍
CreateObject(978, 1197.15100, -1290.66504, 13.21470, 0.00000, 0.00000, 0.00000);//后面障碍
CreateObject(978, 1187.76526, -1290.66870, 13.21470, 0.00000, 0.00000, 0.00000);//后面障碍

新建障碍门变量:

1
2
new hospitalgate1;//门关
new hospitalgate2;//门开
1
2
//写入OnGameModeInit()函数
hospitalgate1 = CreateObject(968, 1209.4478, -1384.9000, 13.0276, 0.00000, -90.00000, 0.00000);
1
2
3
4
5
6
7
public GateCloseHspt()
{
DestroyObject( hospitalgate2 );
hospitalgate1 = CreateObject(968, 1209.4478, -1384.9000, 13.0276, 0.00000, -90.00000, 0.00000);
lshospital = 0;
return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//此段代码在输入指令/go(开门的指令)相关代码里面加入
else if(PlayerInfo[playerid][pMember] == 4 || PlayerInfo[playerid][pLeader] == 4)//医院
{
if (IsPlayerInRangeOfPoint(playerid, 15,1209.4478,-1384.9000,12.9476))
{
if(lshospital == 1) { SendClientMessage(playerid, COLOR_GREY, "** 医院大门已经打开了"); return 1; }
DestroyObject( hospitalgate1 );
hospitalgate2 = CreateObject(968, 1209.4478, -1384.9000, 13.0276, 0.00000, 0.00000, 0.00000);
//MoveObject(hospitalgate1,1209.44775,-1385.21558,12.94760,2,0.00000,0.00000,0.0000);
SetTimer("GateCloseHspt", 6000, 0);
SendClientMessage(playerid, COLOR_BLUE,"医院门打开了并将在6秒后关闭.");

format(string, sizeof(string), "* %s 打开了 医院大门.", sendername);
ProxDetector(30.0, playerid, string, COLOR_CHAT1,COLOR_CHAT2,COLOR_CHAT3,COLOR_CHAT4,COLOR_CHAT5);
lshospital = 1;
}
else
{
SendClientMessage(playerid, COLOR_GREY,"* 你附近没有可以打开的门!.");
return 1;
}
}

阅读剩下更多

默认配图
杂谈

SAMP-地图-新鞋停车场围栏Obj

![2017020614863629785667.png](../uploads/qiniu/2017020614863629785667.png)
下面是围栏的创建,写入OnGameModeInit()函数即可;
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
	CreateObject(985, 1407.17517, -2267.35474, 12.53770,   0.00000, 0.00000, 180.00000);
CreateObject(985, 1415.00610, -2267.35474, 12.53770, 0.00000, 0.00000, 180.00000);
CreateObject(985, 1399.33862, -2267.35474, 12.53770, 0.00000, 0.00000, 180.00000);
CreateObject(985, 1399.33862, -2267.35474, 12.53770, 0.00000, 0.00000, 180.00000);
CreateObject(985, 1391.50842, -2267.35474, 12.53770, 0.00000, 0.00000, 180.00000);
CreateObject(985, 1383.69531, -2267.35474, 12.53770, 0.00000, 0.00000, 180.00000);
CreateObject(985, 1371.91162, -2263.42041, 12.53770, 0.00000, 0.00000, 90.00000);
CreateObject(985, 1371.91162, -2255.59082, 12.53770, 0.00000, 0.00000, 90.00000);
CreateObject(985, 1371.91162, -2247.79932, 12.53770, 0.00000, 0.00000, 90.00000);
CreateObject(985, 1371.91162, -2240.01318, 12.53770, 0.00000, 0.00000, 90.00000);
CreateObject(985, 1371.91162, -2232.20728, 12.53770, 0.00000, 0.00000, 90.00000);
CreateObject(985, 1371.91162, -2224.41846, 12.53770, 0.00000, 0.00000, 90.00000);
CreateObject(985, 1371.91162, -2216.63135, 12.53770, 0.00000, 0.00000, 90.00000);
CreateObject(985, 1371.91162, -2208.81470, 12.53770, 0.00000, 0.00000, 90.00000);
CreateObject(985, 1383.63245, -2204.85791, 12.53770, 0.00000, 0.00000, 0.00000);
CreateObject(985, 1391.45520, -2204.85791, 12.53770, 0.00000, 0.00000, 0.00000);
CreateObject(985, 1399.27893, -2204.85791, 12.53770, 0.00000, 0.00000, 0.00000);
CreateObject(985, 1407.10815, -2204.85791, 12.53770, 0.00000, 0.00000, 0.00000);
CreateObject(985, 1418.95215, -2263.45361, 12.53770, 0.00000, 0.00000, -90.00000);
CreateObject(985, 1418.95215, -2255.62061, 12.53770, 0.00000, 0.00000, -90.00000);
CreateObject(985, 1418.95215, -2247.78955, 12.53770, 0.00000, 0.00000, -90.00000);
CreateObject(985, 1418.95215, -2239.96265, 12.53770, 0.00000, 0.00000, -90.00000);
CreateObject(985, 1418.95215, -2224.43652, 12.53770, 0.00000, 0.00000, -90.00000);
CreateObject(985, 1418.95215, -2208.79883, 12.53770, 0.00000, 0.00000, -90.00000);
CreateObject(985, 1418.95215, -2216.62427, 12.53770, 0.00000, 0.00000, -90.00000);
CreateObject(985, 1375.85364, -2267.35474, 12.53770, 0.00000, 0.00000, 180.00000);
CreateObject(985, 1375.78442, -2204.85791, 12.53770, 0.00000, 0.00000, 180.00000);
接下来是遥控门的代码:(左右打开的门)
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
/* 首先声明两个门的变量: */
new xxgate1;
new xxgate2;

/* 一个函数: */
forward GateCloseXx();

/* 初始化动态对象 写在public OnGameModeInit()函数体里面 */
xxgate1 = CreateObject(988,1419.0482,-2235.0635,13.5822,0.00000,0.00000,90.00000);
xxgate2 = CreateObject(988,1419.0477,-2229.5605,13.5822,0.00000,0.00000,90.00000);

/* 函数代码实例: */
public GateCloseXx()
{
//DestroyDynamicObject( xxgate2 );
MoveObject(xxgate1,1419.0482,-2235.0635,13.5822,2,0.00000,0.00000,90.0000);
MoveObject(xxgate2,1419.0477,-2229.5605,13.5822,2,0.00000,0.00000,90.0000);
lsxinxie = 0;
return 1;
}

/* 开门指令: */
/* 找到下面的代码 */
if(!strcmp(cmdtext, "/go", true)) // By CuervO_NegrO
{
if(IsAPDMember(playerid) || IsAFreecop(playerid))
{
.......
}
...
...
...
/* 下面是新增的 */
else if(PlayerInfo[playerid][pMember] == 11 || PlayerInfo[playerid][pLeader] == 11)
{
if (IsPlayerInRangeOfPoint(playerid, 15,1419.3103,-2232.0325,12.8972))
{
if(lsxinxie == 1) { SendClientMessage(playerid, COLOR_GREY, "** 新鞋停车场门已经打开了"); return 1; }
MoveObject(xxgate1,1419.0482,-2238.0635,13.5822,2,0.00000,0.00000,90.0000);
MoveObject(xxgate2,1419.0477,-2225.5605,13.5822,2,0.00000,0.00000,90.0000);
SetTimer("GateCloseXx", 6000, 0);
SendClientMessage(playerid, COLOR_BLUE,"新鞋停场大门打开了并将在6秒后关闭.");

format(string, sizeof(string), "* %s 打开了 新鞋停车场大门.", sendername);
ProxDetector(30.0, playerid, string, COLOR_CHAT1,COLOR_CHAT2,COLOR_CHAT3,COLOR_CHAT4,COLOR_CHAT5);
lsxinxie = 1;
}
else
{
SendClientMessage(playerid, COLOR_GREY,"* 你附近没有可以打开的门 新鞋!.");
return 1;
}
}
/* 上面是新增的 */
/* 下面是已经有的 */
else
{
SendClientMessage(playerid, COLOR_GREY,"* 你没有开门的遥控器.");
}
return 1;
}

阅读剩下更多

默认配图
数据库

数据库系统的基本概念

  1. 数据(Data):数据是数据库中存储的基本对象,它有多种表现形式。数据是描述事物的符号记录,这些符号可以是文字、图形、声音和图像等。

  2. 数据库(DataBase,DB):数据库是一个长期存储在计算机内的,有组织的、可共享的、统一管理的数据集合。

  3. 数据库管理系统(DataBase Management,DBMS):数据库管理系统是为数据库的建立、使用和维护而配置的系统软件。

    • 数据定义功能
    • 数据操作功能
    • 数据库的运行管理功能
    • 数据库的建立和维护功能
  4. 数据库管理员(DataBase Administrator,DBA):数据库管理员是负责管理和维护数据库服务器的人员

    1. DBA应参与数据库和应用系统的设计
    2. DBA应参与决定数据库的存储结构和存取策略的工作
    3. DBA要负责定义数据的安全性要求和完整性要求
    4. DBA负责监视和控制数据库系统的运行以及系统的维护和数据恢复工作
    5. DBA负责数据库的改进和重组
  5. 数据库系统(Database System,DBS):数据库系统是指在计算机系统中引入数据库后的系统,一般由数据库、数据库管理系统、应用系统、数据库管理员和用户构成。

阅读剩下更多

默认配图
学习笔记

利用PHP实现QQ的登陆和发送消息

利用CURL抓取3GQQ的网页实现的。
这里只是简单实现了功能,可以修改代码实现更多可能。
如:定时发送消息、机器人客服等高级功能。
展示代码,分两个步骤完成。

第一步:登录获取sid,有sid是发送消息的前提。

//发送方QQ账号和密码
$qq  = '';
$pwd = '';
//准备要POST数据
$data = array(
    'qq'            => $qq,
    'pwd'           => $pwd,
    'bid_code'      => '3GQQ',
    'toQQchat'      => true,
    'login_url'     => 'http://pt.3g.qq.com/s?aid=nLoginnew&q_from=3GQQ',
    'q_from'        => '',
    'modifySKey'    => 0,
    'loginType:'    => 1,
    'aid'           => 'nLoginHandle',
    'i_p_w'         => 'qq|pwd|',
);
//开始CURL模拟登录
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,'http://pt.3g.qq.com/psw3gqqLogin');
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_POST,true);
curl_setopt($ch,CURLOPT_POSTFIELDS,http_build_query($data));
$file = curl_exec($ch);
curl_close($ch);
//分析登录是否成功
preg_match('%sid=(.*?)&%si',$file,$sid);
$sid = $sid[1];
if($sid){
    echo '登陆成功!'.$sid;
}else{
    echo '登陆失败!请检查用户名和密码是否正确!';
    exit();
}

获取到sid后就可以发送消息了。
第二步:发送消息,利用上一步获取到的sid。

$sid='';
$qq   = "909047801";
$text = "你好,豆豆!";
//准备要POST的数据
$data = array(
    'u'         => $qq,
    'saveUrl'   => 0,
    'do'        => 'send',
    'on'        => 1,
    'aid'       => '发送',
    'msg'       => $text,
);
//开始CURL模拟发送
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,'http://q16.3g.qq.com/g/s?sid=' . $sid);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_POST,true);
curl_setopt($ch,CURLOPT_POSTFIELDS,http_build_query($data));
$file = curl_exec($ch);
curl_close($ch);
//分析发送是否成功
preg_match('%

(.*?) %si',$file,$callback); $callback = $callback[1]; echo $callback;

阅读剩下更多

默认配图
学习笔记

Mac OS(苹果电脑) 如何清空DNS缓存?

很多时候,因为域名的解析长时间未生效,我们无法通过域名访问刚刚解析的主机,最快的方法就是清空本地DNS缓存。
我们都知道,在Windows下面,想要清空本地dns缓存只需要执行一条CMD命令:

ipconfig/flushdns
但是在使用苹果这样高大上的Mac系统的时候,就得用它独特的命令了。不过,不同的MAC系统版本命令也不一样!
Tiger或更低版本 Mac OS:

sudo lookupd -flushcache

Leopard和Snow Leopard:

sudo dscacheutil -flushcache

而到了Lion、Mountain Lion和Mavericks:

sudo killall -HUP mDNSResponder

然后是Yosemite:

sudo discoveryutil mdnsflushcache

最后就到了EI Caption:

sudo dscacheutil -flushcache

另外,提一下,Linux下通用命令:

sudo /etc/init.d/dns-clean start

阅读剩下更多

默认配图
Linux

安装CentOS7之后win7引导没了,Grub解决

因为学习linux需要,虚拟机安装又显得太简单,所以在实验室win7电脑上装个windows和linux的双系统,自然采用最新的centos7来试着玩一玩咯!但是没想到装完cnetos7之后,windows进不去了(没有windows的引导),上网查了各种资料,最终确定了修改Grub的配置文件来增加对windows的引导。
首先我们要登陆Centos,打开Grub的配置文件:

执行命令:
vi /boot/grub2/grub.cfg
找到 ### BEGIN /etc/grub.d/30_os-prober ### 在下面添加:

1
2
3
4
5
menuentry "Windows 7 Loader On Dev/sda1" {
insmod ntfs
set root=(hd0,1)
chainloader +1
}

关键代码:set root=(hd0,1) 其中 hd0 表示硬盘,1 表示C盘 ,因为我这里win7是安装在C盘的,所以是1,如果你不是C盘(第一个分区),那么就要需改成你对应的分区号了。

阅读剩下更多

默认配图
学习笔记

作业4-2(带用户的留言板)单页面实现!

作业4-2要求
演示页面:http://www.dshui.wang/html/board2.php
本次作业基于作业4-1完成,部分文件在4-1中给出:
所需要的数据表:

1
2
3
4
5
6
7
CREATE TABLE `msg` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`content` varchar(254) NOT NULL DEFAULT '',
`username` char(10) DEFAULT '',
`stime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
1
2
3
4
5
6
7
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` char(20) NOT NULL DEFAULT '',
`pwd` varchar(20) NOT NULL DEFAULT '',
`registtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

文件board2.php

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
161
162
163
164
165
166
167
168
169
<?php
include './mysqlconfig.php';
session_start();
if($_POST['do']=="login"){
$username = $_POST['username'];
$pwd = $_POST['passwd'];
$sql = "select * from user where username='$username' and pwd='$pwd';";
$res = mysql_query($sql);
$row = mysql_fetch_assoc($res);
if ($row['id'] <> ''){
$_SESSION['username'] = $row['username'];
echo "<script>alert('登陆成功!')</script>";
}else{
echo "<script>alert('登录失败!用户名或密码错误!')</script>";
}
header ( "refresh:0;url=" );
}elseif($_POST['do']=="registe"){
$rname = $_POST['rname'];
$rpwd = $_POST['rpasswd'];
$sql = "insert into user(username,pwd) values ('$rname','$rpwd');";
if(mysql_query($sql)){
$_SESSION['username'] = $rname;
echo "<script>alert('注册成功!已经为您自动登录!')</script>";
}else{
echo "<script>alert('注册失败!')</script>";
}
header ( "refresh:0;url=" );
}elseif($_POST['do']=="submit"){
$content = $_POST['content'];
echo $content;
$sql = "insert into msg(content,username) values (\"{$content}\",'".$_SESSION['username']."');";
if(mysql_query($sql)){
echo "<script>alert('留言成功!')</script>";
header ( "refresh:0;url=" );
}else{
echo "<script>alert('不知道怎么的,留言失败了。')</script>";
}
}elseif($_POST['do']=="logout"){
session_unset();
session_destroy();
echo "<script>alert('成功退出登陆!')</script>";
header ( "refresh:0;url=" );
}
$sql = "select * from msg order by id desc;";
$result = mysql_query($sql);
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>简易留言板(带用户验证)</title>
<style type="text/css">
#tab {
BORDER-BOTTOM: #c2130e 3px solid; HEIGHT: 60px
}
#tab p{
FLOAT: left; LINE-HEIGHT: 21px; text-align:center
}
#tab p.hit {
BACKGROUND: #dcdcdc; BORDER-LEFT: #f2f2f2 1px solid; WIDTH: 40px; CURSOR: pointer
}
#tab p.nhit {
BACKGROUND: #c2130e; BORDER-LEFT: #f2f2f2 1px solid; WIDTH: 40px; COLOR: #fff
}
</style>
</head>

<body bgcolor="#56FF00">
<div style="margin:0 auto;width:400px;">
<?php if(!isset($_SESSION['username']) || $_SESSION['username']==''){?>
<div id="usertip" class="login">

注意:只有登陆之后才能进行留言哦!

<div id="tab">

登陆

注册

</div>
<div id="login">
<form name="login" method="post">
<input type="hidden" name="do" value="login">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username" ></td>
</tr>
<tr>
<td>密&nbsp;&nbsp;&nbsp;&nbsp;码:</td>
<td><input type="password" name="passwd" ></td>
</tr>
<tr>
<td></td>
<td align="right"><input type="submit" value="登陆" /></td>
</tr>
</table>
</form>
</div>
<div id="reg" style="display: none;">
<form name="registe" method="post">
<input type="hidden" name="do" value="registe">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="rname" ></td>
</tr>
<tr>
<td>密&nbsp;&nbsp;&nbsp;&nbsp;码:</td>
<td><input type="password" name="rpasswd" ></td>
</tr>
<tr>
<td></td>
<td align="right"><input type="submit" value="注册" /></td>
</tr>
</table>
</form>
</div>
</div>
<?php }else{?>
<div class="form">

当前用户:<?php echo $_SESSION['username'];?>
<form method="post">
<input type="hidden" name="do" value="logout">
<input type="submit"value="退出登录">
</form>

<form method="post">
<textarea name="content" cols="45" rows="5" style="width: 400px; height: 88px;"></textarea>

<input type="hidden" name="do" value="submit">
<div align="right"><input type="submit" value="留言" />&nbsp;</div>
</form>
</div>
<?php }?>
<div class="msg">
<table border="1" bordercolor="#FFFFFF">
<?php while ($rows = mysql_fetch_assoc($result)){?>
<tr>
<td><span><?php echo $rows['username'];?> </span><span class="datetime">(<?php echo $rows['stime'];?>)</span>
<span class="content"><?php echo $rows['content'];?></span></td>
</tr>
<?php }?>
</table>
</div>
</div>
<script>
var Tags=document.getElementById('tab').getElementsByTagName('p');
var usertip=document.getElementById('usertip').getElementsByTagName('div');
var len=Tags.length;
var flag=0;//修改默认值
for(i=0;i<len;i++){
Tags[i].value = i;
Tags[i].onmouseover=function(){changeNav(this.value)};
}
Tags[flag].className='nhit';
function changeNav(v){
Tags[flag].className='hit';
usertip[flag+1].style.display="none";
flag=v;
Tags[v].className='nhit';
usertip[v+1].style.display="";
}
</script>
</body>
</html>

阅读剩下更多

默认配图
学习笔记

作业4-1(简易留言板)

作业4-1(简易留言板)
演示页面:http://www.dshui.wang/html/board1.php
首先我们得建立一个数据表用来存放留言信息:(由于之前作业有建立数据库’XSGL’,我们就直接用这个数据库啦)

1
2
3
4
5
6
7
CREATE TABLE `msg` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`content` varchar(254) NOT NULL DEFAULT '',
`username` char(10) DEFAULT '',
`stime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

建立一个mysql的连接文件: mysqlconfig.php

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
header ( "Content-type:text/html;charset=utf-8" );
$dbhost = 'localhost';
$dbname = 'XSGL';
$user = 'root';
$pwd = '1234';
$conn = mysql_connect($dbhost,$user,$pwd) or die ( "could not connect mysql" );
mysql_select_db ( $dbname, $conn ) or die ( "could not open database" );
mysql_query ( "set names utf8;" );
?>

新建一个留言本程序:board1.php

1
2
3
4
5
6
7
8
9
10
11
<?php
include './mysqlconfig.php';
if($_POST['issubmit']!=null){
$content = $_POST['content'];
$sql = "insert into msg(content) values ('".$content."');";
$res = mysql_query($sql);
header ( "refresh:0;url=" );
}
$sql = "select * from msg order by id desc;";
$result = mysql_query($sql);
?>
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
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>简易留言板</title>
</head>

<body bgcolor="#56FF00">
<div style="margin:0 auto;width:400px;">
<div class="form">
<form method="post">
<textarea name="content" cols="45" rows="5" style="width: 400px; height: 88px;"></textarea>

<div align="right"><input type="submit" name="issubmit" value="留言" />&nbsp;</div>
</form>
</div>
<div class="msg">
<table border="1" bordercolor="#FFFFFF">
<?php while ($rows = mysql_fetch_assoc($result)){?>
<tr>
<td><span class="datetime">(<?php echo $rows['stime'];?>)</span>
<span class="content"><?php echo $rows['content'];?></span></td>
</tr>
<?php }?>
</table>
</div>
</div>
</body>
</html>

阅读剩下更多

默认配图
学习笔记

作业3-1(完成MYSQL操作)

作业3-1要求

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
第一步,创建数据库'XSGL': 
create database XSGL;

第二步,创建学生表'student':
create table student(
Sno char(7) NOT NULL,
Sname char(10) NOT NULL,
Ssex enum('男','女') NULL,
Sage tinyint(4) NULL,
Sdept char(20) NULL default '计算机系',
PRIMARY KEY(Sno)
)engine=innodb default charset=utf8;

第三步,插入数据:
insert into student(Sno,Sname,Ssex,Sage,Sdept) values ('9512101','李勇','男','19','计算机系'),('9512102','刘晨','男','20','计算机系'),('9512103','王敏','女','20','计算机系'),('9521101','张立','男','22','信息系'),('9521102','吴兵','女','21','信息系'),('9521103','张海','男','20','信息系'),('9531101','钱小平','女','18','数学系'),('9531102','王大力','男','19','数学系');

第四步,删除学号为'9531102'的记录:
delete from student where Sno='9531102';

第五步,将计算机系的学生年龄统一加'1':
update student set Sage=Sage+1 where Sdept='计算机系';

第六步,显示学生的学号和姓名两列,这两列分别起别名为“ID”和“NAME”, 要求只列出查询结果的前 5 条记录:
select Sno as ID,Sname as NAME from student limit 5;

第七步,查询信息系所有男同学的所有信息:
select * from student where Sdept='信息系' and Ssex='男';

第八步,查询 student 表所有的数据,按照年龄排序,年龄相同则按照学号降序排序:
select * from student order by Sage ASC,Sno DESC;

第九步,查询所有姓“王”的同学的基本信息:
select * from student where Sname LIKE '王%';

第十步,查询每个院系学生的人数,要求列出院系名称和相应人数:
select COUNT(*) as '人数',Sdept from student GROUP BY Sdept;
//如果一张表里面学号有重复的,必须去重!使用下面语句:
select COUNT(DISTINCT Sno) as '人数',Sdept from student GROUP BY Sdept;

到此,作业3-1就做完了!

阅读剩下更多

默认配图
学习笔记

作业2-2(PHP表单数据提交与接收)

作业2-2
展示页面:http://www.dshui.wang/html/work2.php

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
<?php
#Designed By Anthony_Box
#Date: 2015-05-07
#201321092028 OF SCUEC
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Second Class Work</title>
</head>
<!---
#Designed By Anthony_Box
#Date: 2015-05-07
#201321092028 OF SCUEC
--->
<body>
<?php if ($_POST['submit']){?>
姓名:<?php echo $_POST['name'];?>

兴趣:<?php echo $_POST['intrest'];?>

性别:<?php echo $_POST['sex'];?>

血型:<?php echo $_POST['blood'];?>

擅长语言:<?php echo implode(',',$_POST['lang']);?>

最擅长的操作系统:<?php echo $_POST['system'];?>

留言:<?php echo $_POST['note'];?>

<input type="submit" value="确认">
<?php }else{?>
<form method="post">
<label>姓名:<input type="text" name="name" /></label>

<label>兴趣:<input type="text" name="intrest" /></label>

<label>性别:<input type="radio" name="sex" value="男" />男 </label><label><input type="radio" name="sex" value="女"/>女</label>

<label>血型:<input type="radio" name="blood" value="A"/>A </label><label><input type="radio" name="blood" value="B"/>B </label><label><input type="radio" name="blood" value="AB"/>AB </label><label><input type="radio" name="blood" value="O"/>O</label>

请选择您擅长的一种或几种编程设计语言:

<label><input type="checkbox" name="lang[]" value="C++"/>C++ </label><label><input type="checkbox" name="lang[]" value="PHP"/>PHP </label><label><input type="checkbox" name="lang[]" value="Shell"/>Shell </label><label><input type="checkbox" name="lang[]" value="VBscript"/>VBscript</label>

请选择您最擅长的操作系统:<select name="system"><option>Linux</option><option>Windows</option><option>Unix</option></select>

如果您有什么建议,敬请留言:

<textarea name="note"></textarea>

<input type="submit" name="submit" value="提交"/> &nbsp;&nbsp;<input type="reset" value="重置"/>
</form>
</body>
</html>
<?php }?>

阅读剩下更多

默认配图
学习笔记

作业2-1(PHP程序画n*n正方形矩阵)

作业2-1要求
展示页面:http://www.dshui.wang/html/work1.php
可以在地址后面加参数哟!
如:http://www.dshui.wang/html/work1.php?colnum=7 试试看吧!最大31

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
#Designed By Anthony_Box
#Date: 2015-05-07
#201321092028 OF SCUEC
$colnum = $_GET['colnum']?$_GET['colnum']:5;
if($colnum>31){
$colnum = 5;
}
echo "<table border='1px' bordercolor='yellow'>\n\t<tr>\n";
for($i=0;$i<$colnum*$colnum;$i++){
if(!($i%$colnum)){
echo "\n\t</tr>\n\t<tr>\n";
}
if($i%2){
echo "\t\t<td style='text-align: center;'>";
}else{
echo "\t\t<td style='background-color: red;text-align: center;'>";
}
echo $i+1;
echo "</td>";
}
echo "\n\t</tr></table>";
?>

阅读剩下更多

默认配图
学习笔记

作业1-2(编写一个HTML静态网页,完整网页包括DIV定位)

作业1-2要求
展示页面:http://www.dshui.wang/html/firstwork2.html

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
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>学院概括</title>
</head>
<!---
#Designed By Anthony_Box
#Date: 2015-05-07
#201321092028 OF SCUEC
--->
<body name='top' link="#1F94F0" vlink="#85B2BE" leftmargin="10px" topmargin="20px" style="width:800px;margin:0 auto;">
[学院简介](#jianjie-content)

[现任领导](#leader-content)

[组织机构](#dpt-content)

<div id="jianjie-content">![](images/fish.png) _**<font color="#00AEFF">学院简介</font>**_

* * *
</div>
<div>&nbsp;&nbsp;电子信息工程学院的前身是1981年学校重建后恢复的物理系,是学校历史最悠久 教学单位之一;1988年,物理系更名为电子信息工程系并开始招收电子技术专业本科生;由于那什么的什么的那啥,老师这里给了省略。。。。。。。。。。。
&nbsp;&nbsp;计算机科学学院的明天一定会更加美好@哈哈!</div>
<div align="right">

[返回TOP](#top)
</div>
<div id="leader-content">![](images/fish.png) _**<font color="#00AEFF">现任领导</font>**_

* * *
</div>
<div>&nbsp;&nbsp;院党委:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;党委书记:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;巴某某(027-67841987)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;党委副书记:&nbsp;&nbsp;&nbsp;&nbsp;张&nbsp;&nbsp;&nbsp;某(027-678428317)

&nbsp;&nbsp;院行政:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;院&nbsp;&nbsp;&nbsp;&nbsp;长:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;娃某某(负责全面工作,027-67841987)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;副院长:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;黑&nbsp;&nbsp;&nbsp;某(027-678428317)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;副院长:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;白&nbsp;&nbsp;&nbsp;某(027-678428317)</div>
<div align="right">

[返回TOP](#top)
</div>
<div id="dpt-content">![](images/fish.png) _**<font color="#00AEFF">组织机构</font>**_

* * *
</div>
<div>
&nbsp;&nbsp;教研室:

<table border="1px" bgcolor="#02B928" width="500px" style="text-align:center">
<tr style="height:50px">
<td width="50px">序号</td><td width="100px">教研室名称</td><td width="90px">教研室主任</td>
</tr>
<tr>
<td>1</td><td>应用电子技术</td><td>陈琨</td>
</tr>
<tr>
<td>2</td><td>信号处理</td><td>周某</td>
</tr>
<tr>
<td>3</td><td>通信工程</td><td>朱某</td>
</tr>
<tr>
<td>4</td><td>计算机</td><td>田某某</td>
</tr>
<tr>
<td>5</td><td>光信息科学与技术</td><td>钟某某</td>
</tr>
<tr>
<td>6</td><td>电路与系统</td><td>朱某某</td>
</tr>
<tr>
<td>7</td><td>大学物理</td><td>姚某某</td>
</tr>
<tr>
<td>8</td><td>综合实验室</td><td>彭某某(正)伊某某(副)</td>
</tr>
</table>
<div align="right">

[返回TOP](#top)
</div>
</div>
<div style="height:800px;background-color:#DFDADA">

此处只是为了拉长页面

</div>
</body>
</html>

阅读剩下更多

默认配图
学习笔记

作业1-1(编写一个HTML静态网页,表单)

作业1-1要求
展示页面:/html/firstwork1.html

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
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>First Class Work</title>
</head>
<!---
#Designed By Anthony_Box
#Date: 2015-05-07
#201321092028 OF SCUEC
--->
<body>
<form method="post">
<label>姓名:<input type="text" name="name" /></label>

<label>兴趣:<input type="text" name="intrest" /></label>

<label>性别:<input type="radio" name="sex" value="男" />男 </label><label><input type="radio" name="sex" value="女"/>女</label>

<label>血型:<input type="radio" name="blood" value="A"/>A </label><label><input type="radio" name="blood" value="B"/>B </label><label><input type="radio" name="blood" value="AB"/>AB </label><label><input type="radio" name="blood" value="O"/>O</label>

请选择您擅长的一种或几种编程设计语言:

<label><input type="checkbox" name="lang[]" value="C++"/>C++ </label><label><input type="checkbox" name="lang[]" value="PHP"/>PHP </label><label><input type="checkbox" name="lang[]" value="Shell"/>Shell </label><label><input type="checkbox" name="lang[]" value="VBscript"/>VBscript</label>

请选择您最擅长的操作系统:<select name="system"><option>Linux</option><option>Windows</option><option>Unix</option></select>

如果您有什么建议,敬请留言:

<textarea name="note"></textarea>

<input type="submit" name="submit" value="提交"/> &nbsp;&nbsp;<input type="reset" value="重置"/>
</form>
</body>
</html>

阅读剩下更多

默认配图
学习笔记

DEDECMS自定义表单联动类型不可用的解决方法 -

原因是官方模板文件中注释掉了相关选项,修改方式如下:

需要修改两个文件,

第一个:dede/templets/diy_field_edit.htm 124行 官方把 “<option value="stepselect">联动类型</option–>”这句给注释掉了,取消注释。

第 二个:dede/templets/diy_field_add.htm 145 行 官方把“<div><input type=’radio’ value="stepselect">联动类型</div> ”这句给注释了,取消注释。

改好后,保存就ok了!

当然,你在添加自定义表单中的字段时,数据类型一定要选择“联动类型”!

阅读剩下更多

默认配图
Linux

apache域名跳转法简单完成重定向

用.htaccess做域名跳转
apache默认是开启.htaccess的。
但是为了成功率还是检查一下配置有没有真的支持
编码apache的配置文件httpd.conf,把相应的内容修改为:

Options FollowSymLinks
AllowOverride All
LoadModule rewrite_module modules/mod_rewrite.so 这一行的注释要去掉

然后重启动apache服务器。

编辑apahce中web根目录下.htaccess文件(如题没有此文件,则直接新建立一个既可)。

RewriteEngine On
RewriteCond %{HTTP_HOST} ^fandouzi.com [NC]
RewriteRule ^(.*) http://www.fandouzi.com/ [R=301,L]

访问fandouzi.com同样会转向到www.fandouzi.com。

阅读剩下更多

默认配图
Linux

linux下使用yum安装Apache+php+Mysql+phpMyAdmin [转载]

适用redhat于32位及64位,前提架设好本地源。在这里不再赘述。

1 安装Apache+php+Mysql

a、安装Apahce, PHP, Mysql, 以及php连接mysql库组件 yum -y install httpd php mysql mysql-server php-mysql

b、安装mysql扩展 yum -y install mysql-connector-odbc mysql-devel libdbi-dbd-mysql

或一次性粘贴安装:

yum -y install httpd php mysql mysql-server php-mysql httpd-manual mod_ssl mod_perl mod_auth_mysql php-mcrypt php-gd php-xml php-mbstring php-ldap php-pear php-xmlrpc mysql-connector-odbc mysql-devel libdbi-dbd-mysql

c、设置mysql数据库root帐号密码。
mysqladmin -u root password ‘newpassword’ [引号内填密码]

d、 让mysql数据库更安全
mysql -u root -p [此时会要求你输入刚刚设置的密码,输入后回车即可

mysql> DROP DATABASE test; [删除test数据库]
mysql> DELETE FROM mysql.user WHERE user = ”; [删除匿名帐户]
mysql> FLUSH PRIVILEGES; [重载权限]

e、 按照以上的安装方式, 配置出来的默认站点目录为/var/www/html/新建一个php脚本:

2、安装phpMyAdmin
进入phpMyAdmin官方下载(不要最新版本,下phpMyAdmin 2.11.9.5就行了,3.1以上需php 5.2以上),上传到你的网站目录下,然后进行配置。只需几步即可搞定。
I. config.sample.inc.php更名为config.inc.php;
II. 打开config.inc.php文件,进行以下修改;
// $cfg[‘Servers’][$i][‘controluser’] = ‘pma’;
// $cfg[‘Servers’][$i][‘controlpass’] = ‘pmapass’;
// $cfg[‘Servers’][$i][‘pmadb’] = ‘phpmyadmin’;
// $cfg[‘Servers’][$i][‘bookmarktable’] = ‘pma_bookmark’;
// $cfg[‘Servers’][$i][‘relation’] = ‘pma_relation’;
// $cfg[‘Servers’][$i][‘table_info’] = ‘pma_table_info’;
// $cfg[‘Servers’][$i][‘table_coords’] = ‘pma_table_coords’;
// $cfg[‘Servers’][$i][‘pdf_pages’] = ‘pma_pdf_pages’;
// $cfg[‘Servers’][$i][‘column_info’] = ‘pma_column_info’;
// $cfg[‘Servers’][$i][‘history’] = ‘pma_history’;
// $cfg[‘Servers’][$i][‘designer_coords’] = ‘pma_designer_coords’;

去掉每行前面的//;
II.$cfg[‘blowfish_secret’] = ”; |修改为| $cfg[‘blowfish_secret’] = ‘http’;
IV.$cfg[‘Servers’][$i][‘controluser’] = ‘pma’; |把’pma’修改为你的帐号|$cfg[‘Servers’][$i][‘controlpass’] = ‘pmapass’; |把’pmapass设置为你的mysql登录密码|
V. $cfg[‘blowfish_secret’] = ”; | 添加短语密码例如:$cfg[‘blowfish_secret’] = ‘onohot’;

3、//安装php的扩展
yum -y install php-gd php-xml php-mbstring php-ldap php-pear php-xmlrpc

4、//安装apache扩展

yum -y install httpd-manual mod_ssl mod_perl mod_auth_mysql

5、 配置防火墙
添加允许访问HTTP、FTP端口

iptables -I RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 21 -j ACCEPTiptables -I RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT
重启iptables: service iptables restart
转载于:linux下使用yum安装Apache+php+Mysql+phpMyAdmin - - ITeye技术网站.

阅读剩下更多

默认配图
Linux

Linux上vi(vim)编辑器使用教程 - 转载

vi(vim)是上Linux非常常用的编辑器,很多Linux发行版都默认安装了vi(vim)。vi(vim)命令繁多但是如果使用灵活之后将会大大提高效率。vi是“visual interface”的缩写,vim是vi IMproved(增强版的vi)。在一般的系统管理维护中vi就够用,如果想使用代码加亮的话可以使用vim。下面vps侦探整理一下vi的使用教程:包含vi的基本介绍、使用模式、文件的打开关闭保存、插入文本或新建行、移动光标、删除、恢复字符或行、搜索等等,算是一篇比较适合新手学习vi的教程。

vi有3个模式:插入模式、命令模式、低行模式。

插入模式:在此模式下可以输入字符,按ESC将回到命令模式。
命令模式:可以移动光标、删除字符等。
低行模式:可以保存文件、退出vi、设置vi、查找等功能(低行模式也可以看作是命令模式里的)。

一、打开文件、保存、关闭文件(vi命令模式下使用)

vi filename       //打开filename文件
:w       //保存文件
:w vpser.net //保存至vpser.net文件
:q          //退出编辑器,如果文件已修改请使用下面的命令
:q!        //退出编辑器,且不保存
:wq         //退出编辑器,且保存文件

二、插入文本或行(vi命令模式下使用,执行下面命令后将进入插入模式,按ESC键可退出插入模式)


a      //在当前光标位置的右边添加文本
i       //在当前光标位置的左边添加文本
A     //在当前行的末尾位置添加文本
I      //在当前行的开始处添加文本(非空字符的行首)
O     //在当前行的上面新建一行
o     //在当前行的下面新建一行
R    //替换(覆盖)当前光标位置及后面的若干文本
J    //合并光标所在行及下一行为一行(依然在命令模式)

三、移动光标(vi命令模式下使用)

1、使用上下左右方向键

2、命令模式下:h 向左、j 向下 、k 向上、l 向右。
空格键 向右、Backspace 向左、Enter 移动到下一行首、- 移动到上一行首。
四、删除、恢复字符或行(vi命令模式下使用)


x         //删除当前字符
nx         //删除从光标开始的n个字符
dd      //删除当前行
ndd   //向下删除当前行在内的n行
u       //撤销上一步操作
U      //撤销对当前行的所有操作

五、搜索(vi命令模式下使用)


/vpser     //向光标下搜索vpser字符串
?vpser     //向光标上搜索vpser字符串
n           //向下搜索前一个搜素动作
N         //向上搜索前一个搜索动作

六、跳至指定行(vi命令模式下使用)


n+        //向下跳n行
n-         //向上跳n行
nG        //跳到行号为n的行
G           //跳至文件的底部

七、设置行号(vi命令模式下使用)


:set  nu     //显示行号
:set nonu    //取消显示行号

八、复制、粘贴(vi命令模式下使用)


yy    //将当前行复制到缓存区,也可以用 "ayy 复制,"a 为缓冲区,a也可以替换为a到z的任意字母,可以完成多个复制任务。
nyy   //将当前行向下n行复制到缓冲区,也可以用 "anyy 复制,"a 为缓冲区,a也可以替换为a到z的任意字母,可以完成多个复制任务。
yw    //复制从光标开始到词尾的字符。
nyw   //复制从光标开始的n个单词。
y^      //复制从光标到行首的内容。  VPS侦探
y$      //复制从光标到行尾的内容。
p        //粘贴剪切板里的内容在光标后,如果使用了前面的自定义缓冲区,建议使用"ap 进行粘贴。
P        //粘贴剪切板里的内容在光标前,如果使用了前面的自定义缓冲区,建议使用"aP 进行粘贴。

九、替换(vi命令模式下使用)


:s/old/new      //用new替换行中首次出现的old
:s/old/new/g         //用new替换行中所有的old
:n,m s/old/new/g     //用new替换从n到m行里所有的old
:%s/old/new/g      //用new替换当前文件里所有的old

十、编辑其他文件

:e otherfilename    //编辑文件名为otherfilename的文件。

十一、修改文件格式

:set fileformat=unix   //将文件修改为unix格式,如win下面的文本文件在linux下会出现^M。

总结:vi(vim)教程虽然有比较多的命令,但是只要勤加练习,相信你会很快的熟练掌握,同时也会给你的工作或学习带来更高的效率。当如果不知道自己处在什么模式时可以按2次Esc键即可回到命令模式,会有最后提醒一点:注意大小写!!!
文章来源:Linux上vi(vim)编辑器使用教程 - VPS侦探.

阅读剩下更多

默认配图
学习笔记

MySQL新建用户,授权,删除用户,修改密码 - 转载

首先要声明一下:一般情况下,修改MySQL密码,授权,是需要有mysql里的root权限的。

注:本操作是在WIN命令提示符下,phpMyAdmin同样适用。
用户:phplamp 用户数据库:phplampDB

1.新建用户。

//登录MYSQL
@>mysql -u root -p
@>密码
//创建用户
mysql> insert into mysql.user(Host,User,Password) values("localhost","phplamp",password("1234"));
//刷新系统权限表
mysql>flush privileges;

这样就创建了一个名为:phplamp 密码为:1234 的用户。

然后登录一下。

mysql>exit;
@>mysql -u phplamp -p
@>输入密码

mysql>登录成功

2.为用户授权。


//登录MYSQL(有ROOT权限)。我里我以ROOT身份登录.
@>mysql -u root -p
@>密码
//首先为用户创建一个数据库(phplampDB)
mysql>create database phplampDB;
//授权phplamp用户拥有phplamp数据库的所有权限。
>grant all privileges on phplampDB.* to phplamp@localhost identified by '1234';
//刷新系统权限表
mysql>flush privileges;
mysql>其它操作

/*
如果想指定部分权限给一用户,可以这样来写:
mysql>grant select,update on phplampDB.* to phplamp@localhost identified by '1234';
//刷新系统权限表。
mysql>flush privileges;
*/

3.删除用户。

@>mysql -u root -p
@>密码
mysql>DELETE FROM user WHERE User="phplamp" and Host="localhost";
mysql>flush privileges;
//删除用户的数据库
mysql>drop database phplampDB;

4.修改指定用户密码。

@>mysql -u root -p
@>密码
mysql>update mysql.user set password=password('新密码') where User="phplamp" and Host="localhost";
mysql>flush privileges; 

转自:MySQL新建用户,授权,删除用户,修改密码 - analyzer - 博客园.

阅读剩下更多

默认配图
Linux

linux下允许外网对mysql的访问

从阿里云镜像市场安装的ubuntu(Apache+Mysql+VsFtp)里面Mysql默认是外网无法访问的,而在实际开发过程中需要对数据库的可视化比对操作,这样我们可以通过本地计算机来事实查改服务器的数据库。


1、修改表,登录mysql数据库,切换到mysql数据库,使用sql语句查看
"select host,user from user ;"
\mysql -u root -p
\mysql>use mysql; 
\mysql>update user set host = '%' where user ='root'; 
\mysql>select host, user from user; 
\mysql>flush privileges;  (使修改生效,必须执行)

2、授权用户,你想root使用密码从任何主机连接到mysql服务器
\mysql>GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'admin1234' WITH GRANT OPTION;
\mysql>flush privileges;  (使修改生效,必须执行)

阅读剩下更多

默认配图
PHP

上传Excel表格写入数据表(上传订单功能)

代码包:ExcelToMysql
此项功能的实现依赖PHPExcelReader,下载压缩包可得。
下面贴出PHP主要实现代码:

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
<form id="form1" name="form1" method="post" action="">
<label>
<input name="file" type="file" id="file13"/>
<input type="submit" name="Submit" value="提交" />
</label>
</form>
<p>
<?php
require_once 'reader.php';
$Reader = new Spreadsheet_Excel_Reader();
$Reader->setOutputEncoding('gbk');
$conn= mysql_connect('localhost','root','root') or die("Can not connect to database.");
mysql_query("set names 'gbk'");//设置编码输出
mysql_select_db('my_db'); //选择数据库
if($_POST['Submit'])
{
$Reader->read($_POST['file']);

for ($i = 2; $i <= $Reader->sheets[0]['numRows']; $i++) {
//将EXCEL里面从第二行开始写入数据表'my_table'中
$sql = "INSERT INTO my_table VALUES (null,'".$Reader->sheets[0]['cells'][$i][1]."','".$Reader->sheets[0]['cells'][$i][2]."','".$Reader->sheets[0]['cells'][$i][3]."','".$Reader->sheets[0]['cells'][$i][4]."','".$Reader->sheets[0]['cells'][$i][5]."','".$Reader->sheets[0]['cells'][$i][6]."','".$Reader->sheets[0]['cells'][$i][7]."','".$Reader->sheets[0]['cells'][$i][8]."','".$Reader->sheets[0]['cells'][$i][9]."')";

$query=mysql_query($sql);
if($query)
{
echo "<script type=\"text/javascript\"><!--
alert('数据已经提交成功');window.top.location='a.php'
// --></script>";
}else{
echo "<script type=\"text/javascript\"><!--
alert('数据已经提交失败');window.top.location='a.php'
// --></script>";
}
}
}
?>

阅读剩下更多

默认配图
学习笔记

百度新闻代码(去LOGO方法)- JS实现

获取代码请到:http://news.baidu.com/newscode.html 按自己的需要获取

1
2
3
4
5
6
7
8
9
10
11
//展现新闻的DIV框架
<div id="news">
<!-- @百度新闻代码 start! -->
<style type=text/css>
div{font-size:12px;font-family:arial}.baidu{font-size:12px;line-height:20px;font-family:arial}
a,a:link{color:#00bbbb;} .baidu span{color:#6f6f6f;font-size:12px} a.more{color:#008000;}a.blk{color:#000;font-weight:bold;}
</style>
<script language="JavaScript" type="text/JavaScript" src="http://news.baidu.com/n?cmd=1&class=socianews&pn=1&tn=newsbrofcu">
</script>
<!-- @百度新闻代码 end! -->
</div>

QQ20140926-10

这样不做修改的代码展现出来是带有百度新闻LOGO的,有时候我们不想这个LOGO出来,所以这里来教你们怎么去掉这个LOGO!
在页面中加入JS函数:

1
2
3
4
5
6
7
8
<script type="text/javascript">
//函数定义(DIV框架ID,第i个子DIV框架)
function removebdlogo(obj, i) {
a = document.getElementById(obj).getElementsByTagName("div"); //获取LOGO的DIV节点
a[i].style.display = "none"; //将其设置为隐藏
return false;
}
</script>

然后我们在id=”news”的DIV框架外面执行这个函数:

1
2
<!-- id='news'框架下面的第0个DIV框架 -->
<script type="text/javascript" >removebdlogo("news",0);</script>

这样我们展现出来的效果就好多了!
QQ20140926-11

当然,我们也可以通过修改STYLE来改变一些元素属性,比如字体大小、颜色等等

阅读剩下更多

默认配图
学习笔记

做侧边菜单之弹出二级菜单-position定位不影响其他DIV

二级展现菜单
常常使用position用于层的绝对定位,比如我们让一个层位于一个层内具体什么位置,为即可使用position:absolute和position:relative实现。

position:absolute;position:relative绝对定位使用通常是父级定义position:relative定位,子级定义position:absolute绝对定位属性,并且子级使用left或right和top或bottom进行绝对定位。

例子:
首先是JS实现二级菜单的展现

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
<script language="javascript">
function tab(tag, className, isDisplay) {
var list = document.getElementsByTagName(tag);
for (var i = 0,
len = list.length; i < len; i++) {
if (list[i].className == className) {
if (isDisplay == 1) {
list[i].style.display = "block"
} else {
list[i].style.display = "none"
}
}
}
}
</script>
</pre>
页面代码:
<pre lang="html" line="1" escaped="true">
<div style="float:left; position:relative">

##学校
<span class="bianh">
<a herf="#">[切换]</a></span>
<div style="position:absolute; left:40px;" onmousemove="tab('div','selectschool',1)" onclick="tab('div','selectschool',2)">
<a herf="#">中南民族大学</a>
<a herf="#">XXXX大学</a>
</div>
</div>

阅读剩下更多

默认配图
MacOS

删除Mac中所有 .DS_Store 隐藏文件&不再产生.DS_Store文件

120031avxwmscar1wg6w1zMac OS X系统下,几乎绝大部分文件夹中都包含 .DS_Store 隐藏文件,这里保存着针对这个目录的特殊信息和设置配置,例如查看方式,图标大小以及这个目录的一些附属元数据。

在Mac下这些 .DS_Store 文件默认是隐藏的,看不见。不过当将文件转移共享到Windows的时候,那么就是可见的了,并且感觉像是一些垃圾文件一样的存在。

操作方法摘自Macx.cn 和 **onmac.net **

**
1.删除系统中所有的.DS_Store文件:**
(a)、Spotlight搜索“终端”,打开[终端]程序
(b)、输入如下代码并回车:
sudo find / -name “.DS_Store” -depth -exec rm {} ;
等待个1分钟左右,删除就完成了!

**
2.不让系统生成.DS_Store文件:**
(a)、打开[终端]程序
(b)、输入以下代码并回车:(提示需要输入密码就输入 电脑密码)
defaults write com.apple.desktopservices DSDontWriteNetworkStores true
(c)、sudo cp Library/Preferences/com.apple.desktopservices.plist /Library/Preferences/
(d)、sudo chmod 777 /Library/Preferences/com.apple.desktopservices.plist
(e)、重新启动计算机。

参考资料:
http://forum.onmac.net/archive/index.php/t-334.html
http://www.macx.cn/thread-2053938-1-1.html

阅读剩下更多

默认配图
学习笔记

JS遍历Table的所有单元格内容 (转载)

1
2
3
4
5
6
7
8
9
10
11
12
function GetInfoFromTable(tableid) {
var tableInfo = "";
var tableObj = document.getElementById(tableid);
for (var i = 0; i &lt; tableObj.rows.length; i++) {    //遍历Table的所有Row
for (var j = 0; j &lt; tableObj.rows[i].cells.length; j++) {   //遍历Row中的每一列
tableInfo += tableObj.rows[i].cells[j].innerText;   //获取Table中单元格的内容
tableInfo += "   ";
}
tableInfo += "\n";
}
return tableInfo;
}

阅读剩下更多

默认配图
学习笔记

js 获得多个同name 的input输入框的值 (转载)

js 获得多个同name 的input输入框的值

起初是使用document.all.id.length获得输入框的个数,但时而正常,时而显示undefined,效果不是很好,网上信息说document.all不是兼容所有浏览器的,现在似乎是只支持IE。

解决方法是如下:

1
2
3
var els =document.getElementsByName("search");
for (var i = 0, j = els.length; i &lt; j; i++){
alert(els[i].value);

阅读剩下更多

默认配图
学习笔记

mac系统如何显示和隐藏文件(转载)

苹果Mac OS X操作系统下,隐藏文件是否显示有很多种设置方法,最简单的要算在Mac终端输入命令。显示/隐藏Mac隐藏文件命令如下(注意其中的空格并且区分大小写):

显示Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles -bool true

隐藏Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles -bool false

或者

显示Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles YES

隐藏Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles NO

输完单击Enter键,退出终端,重新启动Finder就可以了

重启Finder:鼠标单击窗口左上角的苹果标志–>强制退出–>Finder–>重新启动

阅读剩下更多

默认配图
学习笔记

Javascript:向表格添加或删除一行(转载)

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
<html>
<head>
<title>title</title>
<script type="text/javascript">
function delIndex(obj) {
var rowIndex = obj.parentNode.parentNode.rowIndex;//获得行下标
alert(rowIndex);
var tb = document.getElementById("tb");
tb.deleteRow(rowIndex);//删除当前行
add(rowIndex);//在当前行插入一行
}
function add(rowIndex) {
var tb = document.getElementById("tb");
if (rowIndex == "-1") {
rowIndex = tb.rows.length;//默认在末尾插入一行
}
var row = tb.insertRow(rowIndex);//在表格的指定插入一行
var c1 = row.insertCell(0);
c1.innerHTML = "new" + rowIndex;
var c2 = row.insertCell(1);
c2.innerHTML = '<a href="javascript:void(0)" onclick="delIndex(this)">删除</a>';
}
</script>
</head>
<body>
<input type="button" value="添加一行" onclick="add('-1')" ><input type="button" value="删除选中项" onclick="del()" />
<table id="tb">
<tr><td>1</td><td><a href="javascript:void(0)" onclick="delIndex(this)">删除</a></td></tr>
<tr><td>2</td><td><a href="javascript:void(0)" onclick="delIndex(this)">删除</a></td></tr>
<tr><td>3</td><td><a href="javascript:void(0)" onclick="delIndex(this)">删除</a></td></tr>
<tr><td>4</td><td><a href="javascript:void(0)" onclick="delIndex(this)">删除</a></td></tr>
<tr><td>5</td><td><a href="javascript:void(0)" onclick="delIndex(this)">删除</a></td></tr>
<tr><td>6</td><td><a href="javascript:void(0)" onclick="delIndex(this)">删除</a></td></tr>
<tr><td>7</td><td><a href="javascript:void(0)" onclick="delIndex(this)">删除</a></td></tr>
</table>
</body>
</html>

阅读剩下更多

默认配图
学习笔记

js&javascript js单击&事件修改html&body元素内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
function myFunction()
{
document.getElementById("demo"); // 找到元素
document.getElementById("demo").innerHTML="Hello JavaScript!"; // 改变内容
}
function myFunction1()
{
x=document.getElementById("demo"); // 找到元素
x.innerHTML="JavaScript 能改变 HTML 元素的内容。"; // 改变内容
}

</script>

<button type="button" onclick="myFunction()">点击这里</button>
<button type="button" onclick="myFunction1()">点击这里</button>

阅读剩下更多

默认配图
学习笔记

网页特殊符号HTML代码大全

HTML特殊字符编码大全:往网页中输入特殊字符,需在html代码中加入以&开头的字母组合或以&#开头的数字。下面就是以字母或数字表示的特殊符号大全。

字符 实体 字符 实体 字符 实体 字符 实体
´ &acute; © &copy; > &gt; µ &micro;
& &amp; ° &deg; ¡ &iexcl; &nbsp;
¦ &brvbar; ÷ &divide; ¿ &iquest; ¬ &not;
§ &sect; • &bull; ½ &frac12; « &laquo;
&para; ¨ &uml; § &sect; • &bull;
½ &frac12; « &laquo; &para; ¨ &uml;
¸ &cedil; ¼ &frac14; < &lt; ± &plusmn;
× &times; ¢ &cent; ¾ &frac34; ¯ &macr;
“ &quot; ™ &trade; € &euro; £ &pound;
¥ &yen; „ &bdquo; … &hellip; · &middot;
› &rsaquo; ª &ordf; ˆ &circ; “ &ldquo;
— &mdash; ’ &rsquo; º &ordm; † &dagger;
‹ &lsaquo; – &ndash; ‚ &sbquo; ” &rdquo;
‡ &Dagger; ‘ &lsquo; ‰ &permil; ­ &shy;
˜ &tilde; ≈ &asymp; ⁄ &frasl; ← &larr;
∂ &part; ♠ &spades; ∩ &cap; ≥ &ge;
≤ &le; ″ &Prime; ∑ &sum; ♣ &clubs;
↔ &harr; ◊ &loz; ′ &prime; ↑ &uarr;
↓ &darr; ♥ &hearts; − &minus; ∏ &prod;
‍ &zwj; ♦ &diams; ∞ &infin; ≠ &ne;
√ &radic; ‌ &zwnj; ≡ &equiv; ∫ &int;
‾ &oline; → &rarr; α &alpha; η &eta;
μ &mu; π &pi; θ &theta; β &beta;
γ &gamma; ν &nu; ψ &psi; υ &upsilon;
χ &chi; ι &iota; ω &omega; ρ &rho;
ξ &xi; δ &delta; κ &kappa; ο &omicron;
σ &sigma; ζ &zeta; ε &epsilon; λ &lambda;
φ &phi; τ &tau; Α &Alpha; Η &Eta;
Μ &Mu; Π &Pi; Θ &Theta; Β &Beta;
Γ &Gamma; Ν &Nu; Ψ &Psi; Υ &Upsilon;
Χ &Chi; Ι &Iota; Ω &Omega; Ρ &Rho;
Ξ &Xi; Δ &Delta; Κ &Kappa; Ο &Omicron;
Σ &Sigma; Ζ &Zeta; Ε &Epsilon; Λ &Lambda;
Φ &Phi; Τ &Tau; ς &sigmaf;

HTML常用特殊字符:只要你认识了 HTML 标记,你便会知道特殊字符的用处。

HTML原代码 显示结果 描述
&lt; < 小于号或显示标记
&gt; > 大于号或显示标记
&amp; & 可用于显示其它特殊字符
&quot; “ 引号
&reg; ® 已注册
&copy; © 版权
&trade; ™ 商标
&ensp; 半个空白位
&emsp; 一个空白位
&nbsp; 不断行的空白

阅读剩下更多

默认配图
学习笔记

JS实现同时提交多个form基础方法

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
<script language="javascript">
//点击提交按钮触发下面的函数
function submitit(){

//第一个表单
var tform1= document.getElementById("formid1");

//第二个表单
var tform2= document.getElementById("formid2");

//提交第一个表单
tform1.submit();

//提交第二个表单
tform2.submit();
} </script>
表单1
<form id="formid1" action="#" method="post" name="formed">
<input name="tname" type="text" value="张三" />
</form>
表单2
<form id="formid2" action="#" method="post" name="formed">
<input name="tname" type="text" value="李四" />
<input type="button" value="提交" onClick="javascript:submitit();" />
</form>

阅读剩下更多

默认配图
学习笔记

弹出层-表单填写js实现

点击打开效果

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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312" />
<title>弹出提示</title>
<style>
* {margin:0;padding:0;font-size:12px;}
html,body {height:100%;width:100%;}
#content {background:#f8f8f8;padding:30px;height:100%;}
#content a {font-size:30px;color:#369;font-weight:700;}
#alert {border:1px solid #369;width:300px;height:150px;background:#e2ecf5;z-index:1000;position:absolute;display:none;}
#alert h4 {height:20px;background:#369;color:#fff;padding:5px 0 0 5px;}
#alert h4 span {float:left;}
#alert h4 span#close {margin-left:210px;font-weight:500;cursor:pointer;}
#alert p {padding:12px 0 0 30px;}
#alert p input {width:120px;margin-left:20px;}
#alert p input.myinp {border:1px solid #ccc;height:16px;}
#alert p input.sub {width:60px;margin-left:30px;}
</style>

</head>

<body>
<div id="content">
<a href="#">注册</a>
</div>
<div id="alert">
<h4><span>现在注册</span><span id="close">关闭</span></h4>
<p><label>用户名</label><input type="text" class="myinp" onmouseover="this.style.border='1px solid #f60'" onfoucs="this.style.border='1px solid #f60'" onblur="this.style.border='1px solid #ccc'" /></p>
<p><label>密 码</label><input type="password" class="myinp" onmouseover="this.style.border='1px solid #f60'" onfoucs="this.style.border='1px solid #f60'" onblur="this.style.border='1px solid #ccc'" /></p>
<p><input type="submit" value="注册" class="sub" /><input type="reset" value="重置" class="sub" /></p>
</div>
<script type="text/javascript">
var myAlert = document.getElementById("alert");
var reg = document.getElementById("content").getElementsByTagName("a")[0];
var mClose = document.getElementById("close");
reg.onclick = function()
{
myAlert.style.display = "block";
myAlert.style.position = "absolute";
myAlert.style.top = "50%";
myAlert.style.left = "50%";
myAlert.style.marginTop = "-75px";
myAlert.style.marginLeft = "-150px";

mybg = document.createElement("div");
mybg.setAttribute("id","mybg");
mybg.style.background = "#000";
mybg.style.width = "100%";
mybg.style.height = "100%";
mybg.style.position = "absolute";
mybg.style.top = "0";
mybg.style.left = "0";
mybg.style.zIndex = "500";
mybg.style.opacity = "0.3";
mybg.style.filter = "Alpha(opacity=30)";
document.body.appendChild(mybg);

document.body.style.overflow = "hidden";
}

mClose.onclick = function()
{
myAlert.style.display = "none";
mybg.style.display = "none";
}
</script>
</body>
</html>

阅读剩下更多

默认配图
学习笔记

mac(Linux)下批改mysql服务器的默认字符集为utf8

在终端操作数据库:
mysql> show variables like ‘%char%’;
+————————–+——————————————————–+
| Variable_name | Value |
+————————–+——————————————————–+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql-5.6.19-osx10.7-x86_64/share/charsets/ |
+————————–+——————————————————–+
这里可以看见 character_set_database 和 character_set_server 都是默认的latin1,这就表明创建的数据库和表都是以latin1为字符集的,在这种情况下,无法正常使用中文! 下面简单说一下怎么更改这个默认字符集:
1.将本地mysql目录中的支持文件my-xxxx.cnf copy到/etc/my.cnf 命令:( sudo cp /usr/local/mysql/support-files/my-default.cnf /etc/my.cnf )
2.编辑my.cnf 命令(sudo vi /etc/my.cnf )
3.在文件中添加两段代码:“括号中没有的自己补上”
在[client]部分加入:
default-character-set = utf8
在[mysqld]部分加入:
character-set-server = utf8
4.重启mysql;
(提示,如果重启失败,尝试将[client]下面的default-character-set=utf8改成[mysqld]中的一样)

然后就可以查看字符集是否修改成功了,登录mysql 命令查询:
mysql> show variables like ‘%char%’;
+————————–+——————————————————–+
| Variable_name | Value |
+————————–+——————————————————–+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql-5.6.19-osx10.7-x86_64/share/charsets/ |
+————————–+——————————————————–+
8 rows in set (0.00 sec)
这样就成功了。

阅读剩下更多

默认配图
编程语言

Javascript中write的注意事项

在网页的学习中,需要利用js向页面写入文字,此时需要用到document.write();

但是,使用这个方法的时候需要注意,如果在页面都加载完成后调用这个方法,将会导致之前的内容全被它给覆盖重写。

例如:

<html>

<head>

<script type=”text/javascript”>

function myfunction(txt)

{

document.write(txt);

}

</script>

</head>

<body>

<form>

<input type=”button” onclick=”myfunction(‘哎呀!我赢了!你们都不见啦,哈哈!’)” value=”点我试试!”>

</form>

<p>通过点击这个按钮,可以调用一个函数。该函数会输出传递给它的参数。 此时所有内容被重写!</p>

</body>

</html>

阅读剩下更多

默认配图
学习笔记

如何让局域网(内网)服务器被广域网用户访问

随着IPv4的资源逐渐耗尽而新的IPv6还未被广泛使用,多用户公用同一广域IP成为了一个很好的解决办法,这种方法就类似于我们每个家庭使用的路由器,连接到同一个路由器的计算机或者手机都是同用一个广域网IP,路由器给连接到的每台设备都分配一个内网IP,内网用户可以通过广域网IP访问互联网中的服务器,但是互联网中的其他用户无法通过这个广域IP直接访问到内网的设备,因为内网中的服务器和互联网全部都通过同一个广域IP进行通信。要想使内网的服务器被互联网访问,那么需要做的就是端口映射!

如何进行映射?用家用路由器来做个简单的教程吧。

首先,我们需要登录路由器管理页面,在左边选择“虚拟服务器”-“端口映射”,这时右边出来一个映射表,把你内网中服务器的ip地址和对应的端口填入表中,选择启用,然后保存,重启路由器,路由器的配置就完成了。配置如下图所示:

QQ20140706-1
现在我们来测试一下能否通过广域ip来进行访问这台服务器,注意:不可以在这台服务器上测试,因为不用映射也能访问!(不多解释)

访问的时候记得带上端口号,如果是80端口的话可以省略。

QQ20140706-2
上图是成功的案例,通过广域ip+对应端口号成功访问到我们内网中ip为192.168.0.6的服务器。

阅读剩下更多

默认配图
学习笔记

利用js在dedecms标签内插入图片

加入javascript函数代码:

<script>

function gettempletsurl(string){

document.write(“<img src="{dede:global.cfg_basehost/}{dede:global.cfg_templets_skin/}/“+string+”" />”);

}

</script>
在需要插入图片的地方插入一下代码:
<script>

gettempletsurl(“images/right-arrow.jpg”);    //引号内写默认模版下的图片路径

</script>
 
这种利用javascript插入图片的方法完美解决了在dedecms中标签嵌套无法插入图片的问题!

阅读剩下更多

默认配图
学习笔记

MySQL常用命令

连接MySQL数据库:

mysql -h host -u user -p

1.SHOW DATABASES;  查询已有的数据库

2.USE DatabaseName; 使用某个数据库

3.SHOW TABLES; 查询已有的表单;

4.SELECT * FROM TableName; 检索表单所有数据(*代表所有,可用字段名替代)

5.CREATE TABLE TableName (Id INT, Name VARCHAR(20)); 创建一个表

6.DESCRIBE TableName; 显示表的结构

7.INSERT INTO TableName VALUES (2014,”Jack”);向表中添加记录
INSERT INTO TableName (Id,Name) VALUES (2014,”Jack”);

8.DROP TABLE TableName; 删除表

9.DELATE FROM TableName; 清空表

10.UPDATE TableName set Name=”Tom” where Id=2014;更新表中数据

阅读剩下更多

默认配图
返回顶部