Android Activity的生命周期

activity类处于android.app包中,继承体系如下:

1.java.lang.Object

2.android.content.Context

3.android.app.ApplicationContext

4.android.app.Activity

activity是单独的,用于处理用户操作。几乎所有的activity都要和用户打交道,所以activity类创建了一个窗口,开发人员可以通过setContentView(View)接口把UI放到activity创建的窗口上,当 activity指向全屏窗口时,也可以用其他方式实现:作为漂浮窗口(通过windowIsFloating的主题集合),或者嵌入到其他的 activity(使用ActivityGroup)。大部分的Activity子类都需要实现以下两个接口:

  • onCreate(Bundle)接口是初始化activity的地方. 在这儿通常可以调用setContentView(int)设置在资源文件中定义的UI, 使用findViewById(int) 可以获得UI中定义的窗口.
  • onPause()接口是使用者准备离开activity的地方,在这儿,任何的修改都应该被提交(通常用于ContentProvider保存数据).

为了能够使用Context.startActivity(),所有的activity类都必须在AndroidManifest.xml文件中定义有相关的“activity”项。

activity类是Android 应用生命周期的重要部分。

Activity生命周期

在系统中的Activity被一个Activity栈所管理。当一个新的Activity启动时,将被放置到栈顶,成为运行中的Activity,前一个Activity保留在栈中,不再放到前台,直到新的Activity退出为止。

Activity有四种本质区别的状态:

  1. 在屏幕的前台(Activity栈顶),叫做活动状态或者运行状态(active or running)
  2. 如果一个Activity失去焦点,但是依然可见(一个新的非全屏的Activity 或者一个透明的Activity 被放置在栈顶),叫做暂停状态(Paused)。一个暂停状态的Activity依然保持活力(保持所有的状态,成员信息,和窗口管理器保持连接),但是在系统内存极端低下的时候将被杀掉。
  3. 如果一个Activity被另外的Activity完全覆盖掉,叫做停止状态(Stopped)。它依然保持所有状态和成员信息,但是它不再可见,所以它的窗口被隐藏,当系统内存需要被用在其他地方的时候,Stopped的Activity将被杀掉。
  4. 如果一个Activity是Paused或者Stopped状态,系统可以将该Activity从内存中删除,Android系统采用两种方式进行删除,要么要求该Activity结束,要么直接杀掉它的进程。当该Activity再次显示给用户时,它必须重新开始和重置前面的状态。

下面的图显示了Activity的重要状态转换,矩形框表明Activity在状态转换之间的回调接口,开发人员可以重载实现以便执行相关代码,带有颜色的椭圆形表明Activity所处的状态。

activity

在上图中,Activity有三个关键的循环:

  1. 整个的生命周期,从onCreate(Bundle)开始到onDestroy()结束。Activity在onCreate()设置所有的“全局”状态,在onDestory()释放所有的资源。例如:某个Activity有一个在后台运行的线程,用于从网络下载数据,则该Activity可以在onCreate()中创建线程,在onDestory()中停止线程。
  2. 可见的生命周期,从onStart()开始到onStop()结束。在这段时间,可以看到Activity在屏幕上,尽管有可能不在前台,不能和用户交互。在这两个接口之间,需要保持显示给用户的UI数据和资源等,例如:可以在onStart中注册一个IntentReceiver来监听数据变化导致UI的变动,当不再需要显示时候,可以在onStop()中注销它。onStart(),onStop()都可以被多次调用,因为Activity随时可以在可见和隐藏之间转换。
  3. 前台的生命周期,从onResume()开始到onPause()结束。在这段时间里,该Activity处于所有 Activity的最前面,和用户进行交互。Activity可以经常性地在resumed和paused状态之间切换,例如:当设备准备休眠时,当一个 Activity处理结果被分发时,当一个新的Intent被分发时。所以在这些接口方法中的代码应该属于非常轻量级的。

Activity的整个生命周期都定义在下面的接口方法中,所有方法都可以被重载。所有的Activity都需要实现 onCreate(Bundle)去初始化设置,大部分Activity需要实现onPause()去提交更改过的数据,当前大部分的Activity也需要实现onFreeze()接口,以便恢复在onCreate(Bundle)里面设置的状态。

public class Activity extends ApplicationContext {
	protected void onCreate(Bundle icicle);
	protected void onStart();
	protected void onRestart();
	protected void onResume();
	protected void onFreeze(Bundle outIcicle);
	protected void onPause();
	protected void onStop();
	protected void onDestroy();

}

nginx安装配置flv流媒体服务器

apache也可以配置flv流媒体服务器,之前有文章介绍,但是用一些播放器播放的时候还是不能拖动,比如说使用VLC播放器播放的时候,拖动就不起作用,因此尝试使用nginx来配置,最后发现flowplay,或者VLC都可以完美播放并且可以任意拖动。

看看nginx的安装步奏

1.安装pcre

获取pcre编译安装包,在http://www.pcre.org/上可以获取当前最新的版本
解压缩pcre-xx.tar.gz包。
进入解压缩目录,执行./configure。
make & make install

2.安装openssl
获取openssl编译安装包,在http://www.openssl.org/source/上可以获取当前最新的版本。
解压缩openssl-xx.tar.gz包。
进入解压缩目录,执行./config。
make & make install

3.安装zlib
获取zlib编译安装包,在http://www.zlib.net/上可以获取当前最新的版本。
解压缩openssl-xx.tar.gz包。
进入解压缩目录,执行./configure。
make & make install

4.安装nginx
获取nginx,在http://nginx.org/en/download.html上可以获取当前最新的版本。
解压缩nginx-xx.tar.gz包。
进入解压缩目录,执行

./configure –with-pcre=/usr/harddisk/pcre-8.36 –with-http_flv_module –with-http_gzip_static_module –with-http_stub_status_module

make & make install

注意,可以不需要安装openssl , zlib可能操作系统就已经安装所以也不需要安装,但是pcre必须的,注意–with-pcre是指向源代码的目录而不是安装目录,不然的话编译报错。

5.配置虚拟路径

配置文件路径 vi /usr/local/nginx/conf/nginx.conf

location ^~/vedio/
        {
                alias /usr/harddisk/;
                flv;
                limit_rate 250k;
        }

以上就是配置路径/vedio/下的访问能使用flv模块并且限速,alias是对应的物理路径。

6.启动和停止

/usr/local/nginx/sbin/nginx
/usr/local/nginx/sbin/nginx -s stop

android实现拨打电话

调用系统自带的打电话功能非常简单,在应用中只需要调用对应的接口,并传入电话号码就可以了,界面会自动跳转到打电话界面,并且电话已经开始拨打,等待接通就可以了,下面就是示例的代码:

String number = "110";  
//用intent启动拨打电话  
Intent intent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:"+number));  
startActivity(intent);

当然打电话是调用系统的activity,所以需要分配权限才能使用

AndroidManifest.xml中加入下面的权限即可

<!--添加可以向外拨打电话的权限  -->  
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>

这就是拨打电话的全部,超级简单。

android中播放视频的几种方法

播放视频的方法有很多种,使用andorid自带的也可以播放大部分的视频,有些格式的视频需要专门的解码才能播放,大部分的视频网站都有自己的视频格式,同时自己提供播放软件,这就是最简单的防止盗版,同时也是根据需要压缩自己的视频文件可以减少存储空间,当然最重要的还是节省带宽;

在Android中,我们有三种方式来实现视频的播放:
1、使用其自带的播放器。指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型。
2、使用VideoView来播放。在布局文件中使用VideoView结合MediaController来实现对其控制。
3、使用MediaPlayer类和SurfaceView来实现,这种方式很灵活。

1.调用其自带的播放器:

Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/Test_Movie.m4v");     
//调用系统自带的播放器    
Intent intent = new Intent(Intent.ACTION_VIEW);    
Log.v("URI:::::::::", uri.toString());    
intent.setDataAndType(uri, "video/mp4");    
startActivity(intent);

2.使用VideoView来实现:

Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/Test_Movie.m4v");    
VideoView videoView = (VideoView)this.findViewById(R.id.video_view);    
videoView.setMediaController(new MediaController(this));    
videoView.setVideoURI(uri);    
videoView.start();    
videoView.requestFocus();

3.使用MediaPlayer:

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:weightSum="1">
    <SurfaceView android:layout_height="220dip" android:layout_gravity="center" android:id="@+id/surface" android:layout_weight="0.25" android:layout_width="320dip"></SurfaceView>
    <LinearLayout android:id="@+id/linearLayout1" android:layout_height="wrap_content" android:layout_width="fill_parent">
        <Button android:text="播放" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
        <Button android:text="暂停" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
        <Button android:text="停止" android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
    </LinearLayout>
</LinearLayout>

主程序:

public class SurfaceActivity extends Activity implements SurfaceHolder.Callback {
    /** Called when the activity is first created. */
    MediaPlayer player;
    SurfaceView surface;
    SurfaceHolder surfaceHolder;
    Button play,pause,stop;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        play=(Button)findViewById(R.id.button1);
        pause=(Button)findViewById(R.id.button2);
        stop=(Button)findViewById(R.id.button3);
        surface=(SurfaceView)findViewById(R.id.surface);
 
        surfaceHolder=surface.getHolder();  //SurfaceHolder是SurfaceView的控制接口
        surfaceHolder.addCallback(this);   //因为这个类实现了SurfaceHolder.Callback接口,所以回调参数直接this
        surfaceHolder.setFixedSize(320, 220);  //显示的分辨率,不设置为视频默认
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);  //Surface类型
 
        play.setOnClickListener(new OnClickListener(){
             @Override
            public void onClick(View v) {
                player.start();
            }});
        pause.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                player.pause();
            }});
        stop.setOnClickListener(new OnClickListener(){
             @Override
            public void onClick(View v) {
                player.stop();
            }});
    }
 
    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    }
 
    @Override
    public void surfaceCreated(SurfaceHolder arg0) {
//必须在surface创建后才能初始化MediaPlayer,否则不会显示图像
        player=new MediaPlayer();
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        player.setDisplay(surfaceHolder);
        //设置显示视频显示在SurfaceView上
            try {
                player.setDataSource("/sdcard/3.mp4");
                player.prepare();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder arg0) {
        // TODO Auto-generated method stub
 
    }
 
    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        if(player.isPlaying()){
        player.stop();
        }
        player.release();
        //Activity销毁时停止播放,释放资源。不做这个操作,即使退出还是能听到视频播放的声音
    }
}

总结

第三种算是比较常用的播放方式,比较灵活可以对视频进行控制,当然这毕竟是自带的播放器,支持视频格式根据系统安装的情况而定,那么可以使用一些第三方播放器来实现,比如说vlc开源播放器http://www.videolan.org/,或者其他的开源播放器都可以做的很好。

android使用ACTION_SEND分享内容

几乎是所有的app现在都有分享的功能,分享一些文本,链接和图片之类的到各大社交平台,那么这么一个简单的功能是如何实现的能,android里面有一个默认的接口,我们来看看怎么使用。

android默认的intent ACTION_SEND可以实现这个分享的功能,看看下面的示例

Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("text/html");
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, Html.fromHtml("<p>This is the text that will be shared.</p>"));
startActivity(Intent.createChooser(sharingIntent,"Share using"));

以上是分享一段html,也是最常用的分享方式,也可以分享一些图片如下

Intent sharingIntent = new Intent(Intent.ACTION_SEND);
Uri screenshotUri = Uri.parse(path);

sharingIntent.setType("image/png");
sharingIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
startActivity(Intent.createChooser(sharingIntent, "Share image using"));

分享多个图片

ArrayList<Uri> imageUris = new ArrayList<Uri>();
imageUris.add(imageUri1); // Add your image URIs here
imageUris.add(imageUri2);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
shareIntent.setType("image/*");
startActivity(Intent.createChooser(shareIntent, "Share images to.."));

上面的分享当然是在手机上有可以分享的软件才能接受到分享的内容,以上的分享事件都会打开一个选择分享软件的列表,比如微信或者微博之类的,用户选择一个之后发送分享内容,那么下面这段xml可以让你的app可以出现在分享列表中,以实现分享的效果:

<activity android:name=".ui.MyActivity" >
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="image/*" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SEND_MULTIPLE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="image/*" />
    </intent-filter>
</activity>

下面这段代码是处理接收到消息之后的基本过程

void onCreate (Bundle savedInstanceState) {
    ...
    // Get intent, action and MIME type
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();

    if (Intent.ACTION_SEND.equals(action) && type != null) {
        if ("text/plain".equals(type)) {
            handleSendText(intent); // Handle text being sent
        } else if (type.startsWith("image/")) {
            handleSendImage(intent); // Handle single image being sent
        }
    } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
        if (type.startsWith("image/")) {
            handleSendMultipleImages(intent); // Handle multiple images being sent
        }
    } else {
        // Handle other intents, such as being started from the home screen
    }
    ...
}

void handleSendText(Intent intent) {
    String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    if (sharedText != null) {
        // Update UI to reflect text being shared
    }
}

void handleSendImage(Intent intent) {
    Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
    if (imageUri != null) {
        // Update UI to reflect image being shared
    }
}

void handleSendMultipleImages(Intent intent) {
    ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
    if (imageUris != null) {
        // Update UI to reflect multiple images being shared
    }
}

总结

android自带的分享只是列出接收分享内容的app列表,由用户选择启动对应的app进行分享,当系统中没有安装目标app的时候是没办法分享的,那么对一般的应用来说这就已经足够了,系统自带的功能一般都是比较基本的东西,另一个办法是使用第三方分享sdk可以实现更加复杂的分享功能。

java导出可执行jar包与启动脚本

java写桌面程序的时候如何启动有很多种办法,有些打包工具就可以直接生成一个快捷方式启动可执行的jar包,也可以直接写一个启动脚本启动,我们来看看启动脚本是怎么写的。

1.导出可执行jar包,首先需要创建一个运行配置

image

2.起个名字并选择需要导出的项目以及启动的时候的入口函数main所在的类

image

3.在项目上右键->导出,选择可执行jar包

image

4.选择上面配置好的运行配置,选择导出的路径

image

这样就导出成功了,依赖的jar包会导到一个文件夹里

5.写个脚本,并制定虚拟机路径

示例脚本如下

@cls
set JRE_HOME=%cd%\jre6
set JAVA_HOME=%cd%\jre6
PATH=%JRE_HOME%\bin;%PATH%

set CLASSPATH = %JRE_HOME%\lib\rt.jar;

@echo %JRE_HOME%
start javaw -jar myapp.jar

把依赖的jar包放到可执行jar包同级的lib目录里面就可以了。

maven依赖war包创建项目

对于bs结构的项目,一般公司都是会有一个适合自己的通用框架,项目在此基础上添加业务功能,那么基础框架可以发布成一个独立的war包,其他项目都依赖与这个包来创建,当版本更新的时候就可以很方便的升级基础框架的东西。

下面来看一下创建的过程

1.创建maven项目

image

2.选择简单项目

image

3.输入项目的信息,和依赖的war包的信息

image

这样项目就创建完成了

打开创建好之后的项目里的文件pom.xml

可以看到依赖项如下

  <parent>
    <groupId>fullstacks</groupId>
    <artifactId>fullstacks.parent</artifactId>
    <version>1.0.0</version>
  </parent>

这样如果war包没有问题的话,就可以运行起来了,当然先配好tomcat

4.编译

image

第一次编译的时候会下载war包的内容,并生成目标文件

在项目的根目录下生成一个目录叫target,里面有下载的war包以及和新项目加入的文件,如果新项目里面有相同路径的文件则会覆盖掉依赖的内容,这样就形成了新的项目。如果想覆盖掉基础框架的文件比如js,html和资源文件之类的,只需要在相同的路径建立相同的文件就可以了。

c++创建atl activex的简单步骤

创建activex可以基于mcf框架也可以基于atl创建,如果是创建没有界面的activex控件,atl更加小巧,当然atl也可以支持界面的操作,但是比较简陋可以安装wtl进行扩展。下面来看看一个简单的atl创建控件的简单示例

1.创建项目,勾选下面的选项,其他默认

image

2.打开类视图,添加一个类

image

3.添加一个简单对象,给个类名就可以了

image

4.勾选下面的选项,其他默认

image

5.在类图里面找到接口,在接口上添加方法

image

6.添加输入参数输出参数,注意输出参数必须是指针类型,要不然选不了out类型

image

7.找源文件Add.cpp,看到新添加的方法实现,写上方法的实现逻辑

STDMETHODIMP CAdd::add(LONG a, LONG b, LONG* c)
{

	*c = a + b;

	return S_OK;
}

这样整个过程就完成了,编译项目这个空间就会自动注册了,也可以通过命令行注册

regsvr32 demo.dll

8.注册之后的classid可以从扩展名为idl的文件中找到,如下

image

9.在页面上可以如下使用

<object classid="clsid:8FA96F06-568C-40E3-8F51-69E77D984D9E" id="myAddObj" name="myAddObj"/>
<script>
	var result = myAddObj.add(1,2);
	alert(result);
</script>

总结

atl创建的控件最后打包的是dll文件,而mfc貌似是扩展名为ocx的文件,其实本质都是dll,通过注册可以在页面上根据classid来引用,那么编译完成之后的dll可以打包成cab文件在页面上使用,前面文章有介绍如何打包并签名cab文件。

c++对图片进行base64编码

有时候我们需要把图片编码成base64编码的文本,然后保存起来,这时候就需要把图片的二进制数据读到内存,然后按照base64的算法进行编码得到一大串文本,这样就方便存放或者在网络上传输,当然也可以在网页上显示。

下面是一个完整的c++实现的base64编码解码程序代码

base64.h

#include "stdafx.h"
#include <string>

std::string base64_encode(unsigned char const* , unsigned int len);
std::string base64_decode(std::string const& s);

base64.cpp

#include "base64.h"
#include <iostream>
using namespace std;

static const std::string base64_chars = 
             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
             "abcdefghijklmnopqrstuvwxyz"
             "0123456789+/";

static inline bool is_base64(unsigned char c) {
  return (isalnum(c) || (c == '+') || (c == '/'));
}

/**
* 对二进制进行编码
*/
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {

  std::string ret;
  int i = 0;
  int j = 0;
  unsigned char char_array_3[3];
  unsigned char char_array_4[4];

  while (in_len--) {
    char_array_3[i++] = *(bytes_to_encode++);
    if (i == 3) {
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
      char_array_4[3] = char_array_3[2] & 0x3f;

      for(i = 0; (i <4) ; i++)
        ret += base64_chars[char_array_4[i]];
      i = 0;
    }
  }

  if (i)
  {
    for(j = i; j < 3; j++)
      char_array_3[j] = '\0';

    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
    char_array_4[3] = char_array_3[2] & 0x3f;

    for (j = 0; (j < i + 1); j++)
      ret += base64_chars[char_array_4[j]];

    while((i++ < 3))
      ret += '=';

  }

  return ret;

}

/**
* 对base64串进行解码
*/
std::string base64_decode(std::string const& encoded_string) {
  int in_len = encoded_string.size();
  int i = 0;
  int j = 0;
  int in_ = 0;
  unsigned char char_array_4[4], char_array_3[3];
  std::string ret;

  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
    char_array_4[i++] = encoded_string[in_]; in_++;
    if (i ==4) {
      for (i = 0; i <4; i++)
        char_array_4[i] = base64_chars.find(char_array_4[i]);

      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

      for (i = 0; (i < 3); i++)
        ret += char_array_3[i];
      i = 0;
    }
  }

  if (i) {
    for (j = i; j <4; j++)
      char_array_4[j] = 0;

    for (j = 0; j <4; j++)
      char_array_4[j] = base64_chars.find(char_array_4[j]);

    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
  }

  return ret;
}

base64编码后会得到类似这样的一串文本:“/9j/4AAQSkZJRgABAQAAAQABA…”

在网页上可以直接显示base64编码的图片,需要在前面加上前缀:“data:image/jpg;base64,”

下面是显示图片的例子

<img id="jpg" src="data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABA..."/>

总结

图片不一定非要保存成文件存放在服务器上,也不一定非得保持二进制格式存放,也可以变成base64编码的字符串存放在数据库里面,比如说读取的身份证头像数据完全可以转换成base64编码的字符串存在数据库里,显示的时候直接能够显示不管是在网页上或者在文档里面都是可以的。

windows下cab打包与签名

cab的打包有各式各样的工具,windows下一直有个默认的工具iexpress可以做到这一点,下面简单看一下一个简单的示例如何打包一个cab并在网页上引用

1、制作inf文件

default.INF
最开始一般是[Version]区:

[Version]
signature=” $CHICAGO$ ”
AdvancedINF=2.0
接下来就是最重要的[Add.Code]区:

[Add.Code]
getcertocx.ocx=getcertocx.ocx
前面是要下载的文件名,后面是对应这个文件的区域名,可以是任何名字,不过一般都是和文件的名字相同,这样方便维护。
再接下来是各个文件的区域了
[getcertocx.ocx]
file-win32-x86=thiscab
RegisterServer=yes
clsid={0A0488CF-F9AB-4AC4-AD8F-CD893553DD22}
DestDir=11
FileVersion=1,0,0,1

[getcertocx.ocx]区域中的第一个file值告诉ie到哪里去得到这个ocx,file一共包括三个部分,第一部分是file,这个永远都是这样的;第二部分告诉声明支持的OS,win32表示windows,mac就是苹果MAC OX了;第三部分是CPU类型,比如说x86、mips等

file的值可以取三个:一个URL、ignore和thiscab,如果是URL则说明到URL所在的位置去下;如果是ignore说明对于这种OS和CPU,不需要下载这个文件;如果是thiscab就在当前的cab文件中了。

第二部分是RegisterServer,可以取两个值yes和no,如果为yes则说明ie要注册该ocx,如果是no就不必了;

接下来是clsid,需要填写该ocx的class guid。

再下来是DestDir,它的值是ocx将要存到本地硬盘的位置,如果它的值是10,则将ocx放到\Windows或者\WinNT下;如果是11,则放到\Windows\System或者

\WinNT\System32下;如果是空(就是没有值)则会放到\Windows或者\WinNT下的Downloaded Program Files目录下;

最后是FileVersion,说明了ocx的版本号。

完整的示例default.INF文件如下

[Version] 
signature="$CHICAGO$" 
AdvancedINF=2.0 

[Add.Code] 
getcertocx.ocx=getcertocx.ocx 

[getcertocx.ocx] 
file-win32-x86=thiscab 
RegisterServer=yes 
clsid={0A0488CF-F9AB-4AC4-AD8F-CD893553DD22} 
DestDir=11 
FileVersion=1,0,0,1 

2、制作cab文件

利用iexpress.exe(windows提供的一个向导式cab制作工具) 在路径C:\Windows\System32\iexpress.exe

1

选“创建新的自解压缩指定文件”,点“下一步”。

2

选“仅创建压缩文件(ActiveX安装)”,点“下一步”。

3

点“添加”将上边制作好的default.inf和getcertocx.ocx文件添加进来,点“下一步”。

4

点“浏览”输入要生成的CAB文件名称,并选中“在软件包中使用长文件名保存文件”,之后点“下一步”。

5

选“不保存”,点“下一步”。

6

点“下一步”。

8

点“完成”退出向导,cab文件生成。

9

3.web上引用

<object width="0" HEIGHT="0"
 classid="CLSID:0A0488CF-F9AB-4AC4-AD8F-CD893553DD22"
 codebase="getcert.cab#Version=1,0,0,1">
</object>

注意classid和版本号要与inf文件中的一样,而且版本号是dll或者ocx文件本身的真实版本,这样的话后续版本修改之后网页可自动更新。

4.对cab文件进行签名

一般来说如果做一个自签名的证书其实意义不是很大,只有正版的证书才有意义

这里是制作一个自签名的证书并对cab文件进行签名,使用的是Visual Studio2010的命令行工具

image

执行下面四行命令可对文件进行签名

makecert -r -sv fullstacks_key.pvk -n "CN=fullstacks" fullstacks.cer
cert2spc fullstacks.cer fullstacks.spc
pvk2pfx -pvk fullstacks_key.pvk -pi fullstacks -spc fullstacks.spc -pfx fullstacks.pfx -po fullstacks
signtool  sign  /f fullstacks.pfx /p fullstacks  *.cab

5.在cab安装的时候执行exe

下面是一个示例每次都执行的

[Setup Hooks]
hook1=hook1

[hook1]
run=%EXTRACT_DIR%\example_setup.exe /q

[Version]
; This section is required for compatibility on both Windows 95 and Windows NT.
Signature="$CHICAGO$"
AdvancedInf=2.0

下面是一个有条件执行,当机器里没有注册指定的dll的时候运行,当然也可以加上文件版本,如果版本更新也可以执行

[Add.Code]
example.ocx=example.ocx

[example.ocx]
Clsid={...}
hook=hook1
FileVersion=1,0,0,1

[hook1]
run=%EXTRACT_DIR%\example_setup.exe /q

[Version]
; This section is required for compatibility on both Windows 95 and Windows NT.
Signature="$CHICAGO$"
AdvancedInf=2.0

总结

cab在网页使用的场景比较多,最大的好处就是能够自动下载安装,如果浏览器里面当前网页在新人区里的话可以自动下载安装没有任何提示,这个在用户体验上也比较好,可以默默无闻的进行更新,这种情况比较适合计算机水平比较低的最终用户,不需要操作就可以使用最新的功能,但是对一些高级用户就不一合适。不过这些只是比较合适IE的用户,其他浏览器或多或少的总是有一些不一样的地方。

windows下编译使用libjpeg

libjpeg是一个图片处理c语言实现,可以比较方便的将几种图片格式互相转换,是个开放源代码软件,需要下载源代码编译之后才能使用,下载地址http://www.infai.org/jpeg/,windows下下载jpegsr9a.zip文件,使用Visual Studio 2010编译工具进行编译。

1.使用Visual Studio2010编译

首先将文件解压到某个目录,将文件jconfig.vc改为jconfig.h

然后打开Visual Studio2010的命令行工具

image

在解压目录下输入编译命令

nmake /f makefile.vc nodebug=1

这样就编译完成了,目录下会生成libjpeg.lib文件,还有其他一些工具如cjpeg.exe

可以使用cjpeg.exe通过命令行直接转换文件如

cjpeg.exe a.bmp b.jpg

可以直接将a.bmp文件转换为b.jpg文件。

2.使用libjpeg.lib

将这四个文件libjpeg.lib,jconfig.h,jmorecfg.h,jpeglib.h复制得到自己的项目中

在项目属性里面引入libjpeg.ib

image

在项目中需要用的的地方引入头文件

extern "C" {
	#include "jpeglib.h"
	#include "jmorecfg.h"
	#include "jconfig.h"
}

这样就可以调用库函数了

3.c与c++混合使用

Visual Studio2010里面混合使用c与c++需要配置不要使用预编译头文件

image

4.bmp转换为jpg示例

libjpeg.lib里面并没与读取bmp的辅助方法,但是cjpeg.exe 工具有,使用到以下几个文件

rdbmp.c,cdjpeg.c,cdjpeg.h,jinclude.h,jerror.h

若果是自己实现读取bmp文件的话就不需要添加上面的5个文件,这些文件可以独立放在一个目录里面再在项目中引用即可;

下面是一个完整的示例,使用Visual Studio2010创建一个c++命令行程序,这里是c和c++混合使用,所以需要配置不使用预编译头才能编译通过,使用下面的代码即可编译运行

#include "stdafx.h"
#include <stdio.h>

extern "C" {  
	#include "jpeglib.h"  
	#include "jmorecfg.h"  
	#include "jconfig.h"  
	#include "cdjpeg.h"
}

/**
* bmp 文件转换为jpg文件
*/
int bmp2jpg(char * input_file_name,char * output_file_name){
	//输入输出文件
	FILE * input_file;
	FILE * output_file;

	//图片转换相关数据结构
	cjpeg_source_ptr src_mgr;
	struct jpeg_compress_struct cinfo;
	struct jpeg_error_mgr jerr;
	JDIMENSION num_scanlines;

	//初始化
	cinfo.err = jpeg_std_error(&jerr);
	jpeg_create_compress(&cinfo);

	cinfo.in_color_space = JCS_RGB; /* arbitrary guess */
	jpeg_set_defaults(&cinfo);

	//读取源文件
	input_file = fopen(input_file_name,"rb");  
	if(input_file==0) return 0; 
	src_mgr =jinit_read_bmp(&cinfo);

	src_mgr->input_file = input_file;
	(*src_mgr->start_input) (&cinfo, src_mgr);
	jpeg_default_colorspace(&cinfo);
	
	//创面目标文件
	output_file = fopen(output_file_name,"wb");
	if(output_file==0) return 0;
	jpeg_stdio_dest(&cinfo, output_file);

	/* Start compressor */
	jpeg_start_compress(&cinfo, TRUE);

	/* Process data */
	while (cinfo.next_scanline < cinfo.image_height) {
		num_scanlines = (*src_mgr->get_pixel_rows) (&cinfo, src_mgr);
		(void) jpeg_write_scanlines(&cinfo, src_mgr->buffer, num_scanlines);
	}

	/* Finish compression and release memory */
	(*src_mgr->finish_input) (&cinfo, src_mgr);
	jpeg_finish_compress(&cinfo);
	jpeg_destroy_compress(&cinfo);

	fclose(input_file);
	fclose(output_file);

	return 1;
}


int _tmain(int argc, _TCHAR* argv[])
{
	char input_file_name[128]  = "c:\\a.bmp";
	char output_file_name[128] = "c:\\b.jpg";

	bmp2jpg(input_file_name,output_file_name);
}

这里可以手工截屏使用画图工具保存为a.bmp文件,然后执行上面的程序就看到转换之后的文件了。

总结

c语言实现的转换效率还是非常之高的,可以写成公共库让其他平台的语言调用,只需要传入文件名即可,非常的方便,在linux下的话需要重新编译。

linux下apache ftpserver的安装配置

apache-ftpd安装过程非常简单,但是需要做一些配置,一个是用户的配置另外一个就是端口的配置,在防火墙iptables打开的情况需要配置两个端口一个用与ftp连接一个用于ftp数据的传输。

从官方网站下载http://mina.apache.org/ftpserver-project/downloads.html ,下载linux包ftpserver-1.0.6.tar.gz

1.安装文件

比如安装到路径/usr/local/apache-ftpd

tar zxvf ftpserver-1.0.6.tar.gz
cp ftpserver-1.0.6 /usr/local/apache-ftpd

2.配置用户

编辑文件res/conf/user.properties

vi res/conf/user.properties

配置用户admin并设置ftp保存的文件路径,注意对目录的可写权限是writepermission=true,要不然不能上传文件

# Password is "admin"
ftpserver.user.admin.userpassword=21232F297A57A5A743894A0E4A801FC3
ftpserver.user.admin.homedirectory=/usr/local/ftp/
ftpserver.user.admin.enableflag=true
ftpserver.user.admin.writepermission=true
ftpserver.user.admin.maxloginnumber=0
ftpserver.user.admin.maxloginperip=0
ftpserver.user.admin.idletime=0
ftpserver.user.admin.uploadrate=0
ftpserver.user.admin.downloadrate=0

注意这里的ftp文件路径是/usr/local/ftp/,需要创建这个目录

mkdir /usr/local/ftp

3.配置连接端口

编辑文件res/conf/ftpd-typical.xml

vi res/conf/ftpd-typical.xml

在listeners节点下加入数据监听接口

<listeners>
	<nio-listener name="default" port="2121">
		<ssl>
			<keystore file="./res/ftpserver.jks" password="password" />
		</ssl>
		<data-connection idle-timeout="30">
		<active local-port="2120"/>
		<passive ports="2120" />
		</data-connection>
	</nio-listener>
</listeners>

这里配置的端口是2121和2120,所以防火墙iptables需要开启这两个端口其他机器才能访问这个ftp的2121端口

4.创建启动脚本并配置位开机启动

在bin目录下创建start.sh脚本,内容如下

/usr/local/apache-ftpd/bin/ftpd res/conf/ftpd-typical.xml &

保存并设置脚本可执行的权限

chmod +x bin/start.sh

开机启动,将启动脚本加入到开机执行脚本中

 vi /etc/rc.local

在文件中加入下面一行

/usr/local/apache-ftpd/bin/start.sh

这样开机的时候就会自动启动ftp服务了

也可以执行start.sh启动ftp 如

bin/start.sh

到此安装过程全部结束,如果想停止ftp服务只能通过ps找到进程将进程杀死.

以上配置完成之后ftp访问路径ftp://admin:admin@ip:2121/ 可以在浏览器地址栏输入进行验证ftp服务是否已经启动好。

spring mvc下载文件简单实现

文件下载有好多种方式,通过返回值,通过servlet的返回输出流等,我们这里使用spring的controller简单实现如何下载文件,并从url传入参数。

这是controller的简单实现,使用aspose.cells解析excel模板,并保存到输出流就可以下载了。

/**
* 下载文件,参数可以通过url传入
*/
@RequestMapping(value="/download/{fileName}.xls")
public void download(@RequestParam(required=false,value="arg1") String arg1,HttpServletRequest request, HttpServletResponse response){
	
	//解析excel模板
	WorkbookDesigner designer = new WorkbookDesigner();  
	String template_file_path = "d:/aspose/cell_sample.xls";  
	Workbook wb = new Workbook(template_file_path); 
	designer.setWorkbook(wb);
	
	//解析数据
	//designer.setDataSource("list", new MapData(getHashMapList()));//map list作为数据源
	//designer.process();//全自动赋值
	
	//文件输出到 输出流
	wb.save(response.getOutputStream(), SaveFormat.EXCEL_97_TO_2003);
	
}

在前端的脚本中直接使用url下载代码如下

window.location = escape("/download/myfile.xls?arg1=val1");

以上简易实现文件下载,注意如果是url传入中文的话,tomcat必须配置url的编码模式为utf-8,要不然的话在后台取到的参数会可能是乱码的。

linux下vi命令大全

进入vi的命令
vi filename :打开或新建文件,并将光标置于第一行首
vi +n filename :打开文件,并将光标置于第n行首
vi + filename :打开文件,并将光标置于最后一行首
vi +/pattern filename:打开文件,并将光标置于第一个与pattern匹配的串处
vi -r filename :在上次正用vi编辑时发生系统崩溃,恢复filename
vi filename….filename :打开多个文件,依次进行编辑

移动光标类命令
h :光标左移一个字符
l :光标右移一个字符
space:光标右移一个字符
Backspace:光标左移一个字符
k或Ctrl+p:光标上移一行
j或Ctrl+n :光标下移一行
Enter :光标下移一行
w或W :光标右移一个字至字首
b或B :光标左移一个字至字首
e或E :光标右移一个字至字尾
) :光标移至句尾
( :光标移至句首
}:光标移至段落开头
{:光标移至段落结尾
nG:光标移至第n行首
n+:光标下移n行
n-:光标上移n行
n$:光标移至第n行尾
H :光标移至屏幕顶行
M :光标移至屏幕中间行
L :光标移至屏幕最后行
0:(注意是数字零)光标移至当前行首
$:光标移至当前行尾

屏幕翻滚类命令
Ctrl+u:向文件首翻半屏
Ctrl+d:向文件尾翻半屏
Ctrl+f:向文件尾翻一屏
Ctrl+b;向文件首翻一屏
nz:将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部。

插入文本类命令
i :在光标前
I :在当前行首
a:光标后
A:在当前行尾
o:在当前行之下新开一行
O:在当前行之上新开一行
r:替换当前字符
R:替换当前字符及其后的字符,直至按ESC键
s:从当前光标位置处开始,以输入的文本替代指定数目的字符
S:删除指定数目的行,并以所输入文本代替之
ncw或nCW:修改指定数目的字
nCC:修改指定数目的行

删除命令
ndw或ndW:删除光标处开始及其后的n-1个字
do:删至行首
d$:删至行尾
ndd:删除当前行及其后n-1行
x或X:删除一个字符,x删除光标后的,而X删除光标前的
Ctrl+u:删除输入方式下所输入的文本

搜索及替换命令
/pattern:从光标开始处向文件尾搜索pattern
?pattern:从光标开始处向文件首搜索pattern
n:在同一方向重复上一次搜索命令
N:在反方向上重复上一次搜索命令
:s/p1/p2/g:将当前行中所有p1均用p2替代
:n1,n2s/p1/p2/g:将第n1至n2行中所有p1均用p2替代
:g/p1/s//p2/g:将文件中所有p1均用p2替换

选项设置
all:列出所有选项设置情况
term:设置终端类型
ignorance:在搜索中忽略大小写
list:显示制表位(Ctrl+I)和行尾标志($)
number:显示行号
report:显示由面向行的命令修改过的数目
terse:显示简短的警告信息
warn:在转到别的文件时若没保存当前文件则显示NO write信息
nomagic:允许在搜索模式中,使用前面不带“\”的特殊字符
nowrapscan:禁止vi在搜索到达文件两端时,又从另一端开始
mesg:允许vi显示其他用户用write写到自己终端上的信息

最后行方式命令
:n1,n2 co n3:将n1行到n2行之间的内容拷贝到第n3行下
:n1,n2 m n3:将n1行到n2行之间的内容移至到第n3行下
:n1,n2 d :将n1行到n2行之间的内容删除
:w :保存当前文件
:e filename:打开文件filename进行编辑
:x:保存当前文件并退出
:q:退出vi
:q!:不保存文件并退出vi
:!command:执行shell命令command
:n1,n2 w!command:将文件中n1行至n2行的内容作为command的输入并执行之,若不指定n1,n2,则表示将整个文件内容作为command的输入
:r!command:将命令command的输出结果放到当前行

寄存器操作
“?nyy:将当前行及其下n行的内容保存到寄存器?中,其中?为一个字母,n为一个数字
“?nyw:将当前行及其下n个字保存到寄存器?中,其中?为一个字母,n为一个数字
“?nyl:将当前行及其下n个字符保存到寄存器?中,其中?为一个字母,n为一个数字
“?p:取出寄存器?中的内容并将其放到光标位置处。这里?可以是一个字母,也可以是一个数字
ndd:将当前行及其下共n行文本删除,并将所删内容放到1号删除寄存器中。

VI的使用
——————————————————————————–

一、插入文本
┌──┬────────────┐
│命令│描述          │
├──┼────────────┤
│i  │在当前字符前插入文本  │
├──┼────────────┤
│I  │在行首插入文本      │
├──┼────────────┤
│a  │在当前字符后添加文本  │
├──┼────────────┤
│A  │在行末添加文本     │
├──┼────────────┤
│o  │在当前行后面插入一空行 │
├──┼────────────┤
│O  │在当前行前面插入一空行 │
├──┼────────────┤
│R  │以改写方式输入文本   │
└──┴────────────┘
二、移动光标
┌─────┬───────────┐
│命令   │描述         │
├─────┼───────────┤
│j或下箭头 │向下移动一行     │
├─────┼───────────┤
│k或上箭头 │向上移动一行     │
├─────┼───────────┤
│h或左箭头 │左移一个字符     │
├─────┼───────────┤
│l或右箭头 │右移一个字符     │
├─────┼───────────┤
│w     │右移一个词      │
├─────┼───────────┤
│W     │右移一个以空格分隔的词│
├─────┼───────────┤
│b     │左移一个词      │
├─────┼───────────┤
│B     │左移一个以空格分隔的词│
├─────┼───────────┤
│0     │移到行首       │
│Ctrl-F  │向前翻页       │
├─────┼───────────┤
│Ctrl-B  │向后翻页       │
├─────┼───────────┤
│nG    │到第n行        │
├─────┼───────────┤
│G     │到最后一行      │
└─────┴───────────┘
三、替换文本
┌─────┬──────┐
│命令   │描述    │
├─────┼──────┤
│$     │到行尾   │
├─────┼──────┤
│(     │到句子的开头│
├─────┼──────┤
│)     │到句子的末尾│
├─────┼──────┤
│{     │到段落的开头│
├─────┼──────┤
│}     │到段落的末尾│
└─────┴──────┘

四、删除文本
┌───┬───────────┐
│命令 │描述          │
├───┼───────────┤
│r   │替换一个字符      │
├───┼───────────┤
│c   │修改文本直到按下Esc健 │
├───┼───────────┤
│cw  │修改下一个词      │
├───┼───────────┤
│cnw  │修改接下来的n个词   │
└───┴───────────┘
五、文本编辑
┌──┬──────────────────────┐
│命寺│描述                    │
├──┼──────────────────────┤
│yy │将一行文本移到缺省缓冲区中          │
├──┼──────────────────────┤
│yn │将下一个词移到缺省缓冲区中          │
├──┼──────────────────────┤
│ynw │将后面的n个词移到缺省缓冲区中        │
├──┼──────────────────────┤
│p  │如果缺省缓冲区中包含一行文本,则在当前   │
│  │行后面插入一个空行井将缺省缓冲区中的声   │
│  │容粘贴到这一行中;如果缺省缓冲区中包含   │
│  │多个词,把这些词粘贴到光标的右边.     │
├──┼──────────────────────┤
│P  │如果缺省缓冲区中包含一行文本,则正当前    │
│   │行前面插入一个空行井将缺省缓冲区中的内    │
│  │容粘贴到这一行中;如果缺省缓冲区中包含    │
│   │多个词,把这些词粘贴到光标的左边


└──┴──────────────────────┘
六、保存退出
┌───────────┬───────────────┐
│命令         │描述             │
├───────────┼───────────────┤
│zz          │保存并退出          │
├───────────┼───────────────┤
│:w filename      │写入文件            │
├───────────┼───────────────┤
│:W          │写入文件           │
├───────────┼───────────────┤
│:x          │保存(如果当前文件修改过)并退出│
├───────────┼───────────────┤
│:q!          │不保存文件,直接退出      │
├───────────┼───────────────┤
│:q          │退出vi            │

VI常用技巧

VI命令可以说是Unix/Linux世界里最常用的编辑文件的命令了,但是因为它的命令集众多,很多人都不习惯使用它,其实您只需要掌握基本命令,然后加以灵活运用,就会发现它的优势,并会逐渐喜欢使用这种方法。本文旨在介绍VI的一些最常用命令和高级应用技巧。

一、基本命令介绍

—- 1.光标命令

k、j、h、l——上、下、左、右光标移动命令。虽然您可以在Linux中使用键盘右边的4个光标键,但是记住这4个命令还是非常有用的。这4个键正是右手在键盘上放置的基本位置。
nG——跳转命令。n为行数,该命令立即使光标跳到指定行。
Ctrl+G——光标所在位置的行数和列数报告。
w、b——使光标向前或向后跳过一个单词。
—- 2.编辑命令
i、a、r——在光标的前、后以及所在处插入字符命令(i=insert、a=append、r=replace)。
cw、dw——改变(置换)/删除光标所在处的单词的命令 (c=change、d=delete)。
x、d$、dd——删除一个字符、删除光标所在处到行尾的所有字符以及删除整行的命令。
—- 3.查找命令
—- /string、?string——从光标所在处向后或向前查找相应的字符串的命令。
—- 4.拷贝复制命令
—- yy、p——拷贝一行到剪贴板或取出剪贴板中内容的命令。

二、常见问题及应用技巧

—- 1.在一个新文件中读/etc/passwd中的内容,取出用户名部分。
—- vi file
—- :r /etc/passwd 在打开的文件file中光标所在处读入/etc/passwd
—- :%s/:.*//g 删除/etc/passwd中用户名后面的从冒号开始直到行尾的所有部分。
—- 您也可以在指定的行号后读入文件内容,例如使用命令“:3r /etc/passwd”从新文件的第3行开始读入 /etc/passwd的所有内容。
—- 我们还可以使用以下方法删掉文件中所有的空行及以#开始的注释行。
—- #cat squid.conf.default | grep -v ^$ | grep -v ^#

—- 2.在打开一个文件编辑后才知道登录的用户对该文件没有写的权限,不能存盘,需要将所做修改存入临时文件。
—- vi file
—- :w /tmp/1 保存所做的所有修改,也可以将其中的某一部分修改保存到临时文件,例如仅仅把第20~59行之间的内容存盘成文件/tmp/1,我们可以键入如下命令。
—- vi file
—- :20,59w /tmp/1

—- 3.用VI编辑一个文件,但需要删除大段的内容。
—- 首先利用编辑命令“vi file”打开文件,然后将光标移到需要删除的行处按Ctrl+G显示行号,再到结尾处再按Ctrl+G,显示文件结尾的行号。
—- :23,1045d 假定2次得到的行号为23和1045,则把这期间的内容全删除,也可以在要删除的开始行和结束行中用ma、mb命令标记,然后利用“:a,bd”命令删除。

—- 4.在整个文件的各行或某几行的行首或行尾加一些字符串。
—- vi file
—- :3,$s/^/some string / 在文件的第一行至最后一行的行首插入“some string”。
—- :%s/$/some string/g 在整个文件每一行的行尾添加“some string”。
—- :%s/string1/string2/g 在整个文件中替换“string1”成“string2”。
—- :3,7s/string1/string2/ 仅替换文件中的第3行到第7行中的“string1”成“string2”。
—- 注意: 其中s为substitute,%表示所有行,g表示global。

—- 5.同时编辑2个文件,拷贝一个文件中的文本并粘贴到另一个文件中。
—- vi file1 file2
—- yy 在文件1的光标处拷贝所在行
—- :n 切换到文件2 (n=next)
—- p 在文件2的光标所在处粘贴所拷贝的行
—- :n 切换回文件1

—- 6.替换文件中的路径。
—- 使用命令“:%s#/usr/bin#/bin#g”可以把文件中所有路径/usr/bin换成/bin。也可以使用命令“:%s//usr/bin//bin/g”实现,其中“”是转义字符,表明其后的“/”字符是具有实际意义的字符,不是分隔符。

java调用linux命令

Java可以直接调用Linux命令,形式如下:
Runtime.getRuntime().exec(command)
举例:运行ls,top命令可以这样:
Runtime.getRuntime().exec(“ls”);
但是这样执行时没有任何输出,原因:
调用Runtime.exec方法将产生一个本地的进程,并返回一个Process子类的实例,
(注意:Runtime.getRuntime().exec(command)返回的是一个Process类的实例),
该实例可用于控制进程或取得进程的相关信息. 由于调用Runtime.exec方法所创建的子进程没有自己的终端或控制台,因此该子进程的标准IO(如stdin,stdou,stderr)都通过Process.getOutputStream(),Process.getInputStream(), Process.getErrorStream()方法重定向给它的父进程了.用户需要用这些stream来向子进程输入数据或获取子进程的输出. 可以采用如下方法:
try
{
Process process = Runtime.getRuntime().exec (“ls”);
InputStreamReader ir=new InputStreamReader(process.getInputStream());
LineNumberReader input = new LineNumberReader (ir);
String line;
while ((line = input.readLine ()) != null){
System.out.println(line)
}
catch (java.io.IOException e){
System.err.println (“IOException ” + e.getMessage());
}

apache+flowplayer支持flv流媒体拖动播放

使用flowplay播放flv文件,默认情况下apache提供的http服务是不支持拖动到还没有缓冲好的区域,只能在缓冲区范围内拖动,想要支持拖动到任意位置播放,必须需要服务器的支持,并且flowplay也需要插件来支持。

1.linux下apache服务器安装mod_flvx.so模块

从这个地址https://github.com/osantana/mod_flvx下载mod_flvx.c文件,编译并安装

apxs -c -i ./mod_flvx.c

apxs是apache自带的工具在apache安装目录的bin目录下

在conf/httpd.conf配置文件中加入两行配置

LoadModule flvx_module modules/mod_flvx.so
AddHandler flv-stream .flv

重启apache服务

bin/apachectl retart

这样服务器端的配置就完成了

2.下载flowplayer播放器和插件

下载flash版本的免费flowplayer:https://flowplayer.org/pricing/player.html

下载插件Pseudostreaming插件http://flash.flowplayer.org/plugins/streaming/pseudostreaming.html

把插件复制到flowplay目录下

image

打开example文件夹里面的示例index.html,脚本部分修改如下

<!-- this will install flowplayer inside previous A- tag. -->
<script>
	var fp = flowplayer("player", "../flowplayer-3.2.18.swf",			
	{
		// configure clip to use "apache" plugin for providing video data
		clip: {
			provider: 'apache',
			url:'http://192.168.1.25/video/1440749458644.flv',
			scaling: 'fit'
		},			 
		// streaming plugins are configured normally under the plugins node
		plugins: {
			apache: {
				url: "../flowplayer.pseudostreaming-3.2.13.swf"
			}
		}
	});
</script>

以上完成前端的插件加载并播放视频

3.准备视频文件

视频文件需要处理才能支持拖动播放,yamdi(http://yamdi.sourceforge.net/)和flvtool2(http://blog.inlet-media.de/flvtool2/)都是对flv文件 进行MetaData注入的工具,貌似yamdi效率更高一些,处理命令如下

yamdi -i sample.flv -o sample_with_metadata.flv
flvtool2 -U sample.flv

以上工具都有windows和linux版本,都可以从官方网站下载,linux下自然是要编译安装了。准备好视频放入到apache的htdocs/video目录中,当然根据实际情况放到正确的发布目录里面,然后运行上面的示例页面,拖动到后续还没加载的区域看看是否已经能够正常播放了。

总结

简单的一个视频拖动播放的后面有不少的事情要做好,apache提供的服务是正常的http协议,在没有处理之前播放flv文件和下载没有什么区别,数据从文件开始的地方开始读取到结束,拖动的时候就不一样了是在请求后面加上一次参数?start=从某个时间点开始传送数据,所以呢服务端必须支持这样的请求,因此就出现了上面的配置过程。

aspose-word for java使用map作为数据源

aspose对word的操作和对excel的操作是分为两个独立的jar包实现的,同样的word模板的使用也是非常之方便,依然很遗憾默认并没有实现使用map数据作为数据源,这里我们来实现数据源接口IMailMergeDataSource来提供map数据源。

1.word模板,使用MergeFeild绑定数据

在word模板中通过菜单”插入→文档部件→域”插入MergeField域

1 2

根据上面插入域的方法,我们来创建一个简单的模板,使用一个map对象和一个map的列表

 

12

注意一个区域是以TableStart:和TableEnd:结束,冒号后面是数据源的名字.

2.实现IMailMergeDataSource接口提供map数据源

/**
 * 实现对HashMap的支持
 */
public class MapData implements IMailMergeDataSource  {

	@SuppressWarnings("rawtypes")
	private List<Map> dataList;

	private int index;

	// word模板中的«TableStart:tableName»«TableEnd:tableName»对应
	private String tableName = null;

	/**
	 * @param dataList
	 *            数据集
	 * @param tableName
	 *            与模板中的Name对应
	 */
	@SuppressWarnings("rawtypes")
	public MapData(List<Map> dataList, String tableName) {
		this.dataList = dataList;
		this.tableName = tableName;
		index = -1;
	}

	/**
	 * @param data
	 *            单个数据集
	 * @param tableName
	 *            与模板中的Name对应
	 */
	@SuppressWarnings("rawtypes")
	public MapData(Map data, String tableName) {
		if (this.dataList == null) {
			this.dataList = new ArrayList<Map>();
			this.dataList.add(data);
		}
		this.tableName = tableName;
		index = -1;
	}

	/**
	 * 获取结果集总数
	 * 
	 * @return
	 */
	private int getCount() {
		return this.dataList.size();
	}

	@Override
	public IMailMergeDataSource getChildDataSource(String arg0)
			throws Exception {
		return null;
	}

	@Override
	public String getTableName() throws Exception {
		return this.tableName;
	}

	/**
	 * 实现接口 获取当前index指向数据行的数据 将数据存入args数组中即可
	 * 
	 * @return ***返回false则不绑定数据***
	 */
	@Override
	public boolean getValue(String key, Object[] args) throws Exception {
		if (index < 0 || index >= this.getCount()) {
			return false;
		}
		if (args != null && args.length > 0) {
			args[0] = this.dataList.get(index).get(key);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 实现接口 判断是否还有下一条记录
	 */
	@Override
	public boolean moveNext() throws Exception {
		index += 1;
		if (index >= this.getCount()) {
			return false;
		}
		return true;
	}


}

3.实现IMailMergeDataSourceRoot接口以提供多个数据源

/**
 * 提供多个数据源
 * @author Administrator
 *
 */
public class MapDataSet implements  IMailMergeDataSourceRoot {

	/**
	 * 多个数据源
	 */
	private Map<String,IMailMergeDataSource> ds = new HashMap<String,IMailMergeDataSource>();
	
	public MapDataSet(){}

	@Override
	public IMailMergeDataSource getDataSource(String arg0) throws Exception {
		if(this.ds.containsKey(arg0)){
			return this.ds.get(arg0);
		}
		return null;
	}
	/**
	 * 添加一个数据源
	 * @param tableName
	 * @param data
	 */
	public void add(IMailMergeDataSource data){
		try {
			this.ds.put(data.getTableName(), data);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

4.使用实现好的数据源解析word模板

	public static void main(String[] args) throws Exception{
		String template_file_path = "d:/aspose/word_sample.doc";  
		Document doc = new Document(template_file_path);

		//两个数据源
		MapDataSet ds = new MapDataSet();
		ds.add( new MapData(getHashMapData(),"map"));
		ds.add( new MapData(getHashMapList(),"list"));
		
		//主从数据源
		/*
		DataSet dataset = new DataSet();
		DataTable dt1 = mapListToDataTable(getHashMapList(),"dt1");
		DataTable dt2 = mapListToDataTable(getHashMapList(),"dt2");
		dataset.getTables().add(dt1);
		dataset.getTables().add(dt2);
		dataset.getRelations().add(new DataRelation("name_key",dt1.getColumns().get("name"), dt2.getColumns().get("name")));
		doc.getMailMerge().executeWithRegions(dataset);
		*/
		//没有数据的模板清楚掉
		doc.getMailMerge().setCleanupOptions(MailMergeCleanupOptions.REMOVE_UNUSED_REGIONS);
		//自动插入数据
		doc.getMailMerge().executeWithRegions(ds);
		
		doc.save("d:/aspose/word_sample_i.doc");
		
		System.out.println("ok!");
	}
	
	/**
	 * 单个数据
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static HashMap getHashMapData(){
		
		HashMap data = new HashMap();
		
		data.put("name", "fullstacks");
		data.put("age", "30");
		
		return data;
		
	}
	
	/**
	 * hashMap 列表
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	private static List<Map> getHashMapList(){
		 List<Map> datas= new ArrayList<Map>();
		 datas.add(getHashMapData());
		 datas.add(getHashMapData());
		 datas.add(getHashMapData());
		 datas.add(getHashMapData());
		 return datas; 
	}

以上代码执行之后得到的效果如下

13

5.使用主从关系的列表

创建一个word模板如下

14

使用DataSet创建主从关系的数据结构,这里提供一个将Map列表转换为一个DataTable

	/**
	 * map列表转换为datatable
	 * @param data
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static DataTable mapListToDataTable(List<Map> datas,String tableName){
		if(datas == null || datas.size() == 0)return null;
		DataTable dt = new DataTable(tableName);
		Map fistrow = datas.get(0);
		for(Object key: fistrow.keySet()){
			dt.getColumns().add(new DataColumn((String)key,fistrow.get(key).getClass()));			
		}
		for(Map item:datas){
			DataRow dr = dt.newRow();
			for(Object key: item.keySet()){
				dr.set((String)key, item.get(key));
			}
			try {
				dt.getRows().add(dr);
			} catch (ConstraintException e) {
				e.printStackTrace();
			} catch (InvalidConstraintException e) {
				e.printStackTrace();
			}
		}	
		return dt;
	}

构建DataSet数据源

		DataSet dataset = new DataSet();
		DataTable dt1 = mapListToDataTable(getHashMapList(),"dt1");
		DataTable dt2 = mapListToDataTable(getHashMapList(),"dt2");
		dataset.getTables().add(dt1);
		dataset.getTables().add(dt2);
		dataset.getRelations().add(new DataRelation("name_key",dt1.getColumns().get("name"), dt2.getColumns().get("name")));
		doc.getMailMerge().executeWithRegions(dataset);

得到的效果如下

15

总结

以上是实现了一个通用的数据结构map作为aspose word的数据源,如果是结合spring jdbc的话,从数据库中查询出来的结果数据就是一个hashmap的list,这样就可以使用了,不必要转换为datatable那么麻烦,然后如果是多个数据源的话,就可以使用上面实现的MapDataSet实现,已提供多个数据源一次性把word模板解析完毕,如果是主从表的结构数据只能通过自带DataSet来完成了,上面已提供了一个静态方法方便把map列表转换为datatable,如果数据量比较大的话这样遍历可能效率低下,可以直接使用dataredder作为数据源,这样效率更高。

aspose-cells for java使用map作为数据源

使用excel作为模板导出数据是很普遍的应用,而且非常方便,aspose-cells是这样一个商业的软件,使用非常之方便,几行代码就可以搞定将数据自动插入到excel模板中,但是默认的并没有实现以map作为数据源,只是提供了一个借口ICellsDataTable,下面我们来实现这个接口。

1.实现接口ICellsDataTable支持Map的数据

/**
 * 使用hashmap作为数据源
 * @author Administrator
 *
 */
public class MapData implements ICellsDataTable {
	//数据集合  
    @SuppressWarnings("rawtypes")
	private List<Map> dataList = null;  

    //索引  
    private int index;  
      
    //存放dataList当中Map<String, Object>的key  
    private String[] columns = null;  
  
    @SuppressWarnings("rawtypes")
	public MapData(Map data) {  
        if(this.dataList == null) {  
            this.dataList = new ArrayList<Map>();  
        }  
        dataList.add(data);
    }  
      
    @SuppressWarnings("rawtypes")
	public MapData(List<Map> data) {  
        this.dataList = data;  
    }  
      
    /** 
     * 初始化方法 
     */  
    public void beforeFirst() {  
        index = -1;  
        columns = this.getColumns();  
    }  
  
    /** 
     * WorkbookDesigner自动调用 
     * 会将this.getColumns()方法所返回的列 按照顺序调用改方法 
     */  
    @SuppressWarnings("rawtypes")
	public Object get(int columnIndex) {  
        if(index < 0 || index >= this.getCount()) {  
            return null;  
        }  
        Map record = this.dataList.get(index);  
        String columnName = this.columns[columnIndex];  
        return record.get(columnName);  
    }  
  
    /** 
     * 根据columnName返回数据 
     */  
    @SuppressWarnings("rawtypes")
	public Object get(String columnName) {  
        Map record = this.dataList.get(index);  
        return record.get(columnName);  
    }  
  
    /** 
     * 获得列集合 
     */  
    @SuppressWarnings({ "rawtypes", "unchecked" })
	public String[] getColumns() {  
        Map temp = this.dataList.get(0);  
        Set<Entry> entrys = temp.entrySet();  
        List<String> columns = new ArrayList<String>();  
        for (Entry e : entrys) {  
            columns.add((String)e.getKey());  
        }  
        String[] s = new String[entrys.size()];  
        columns.toArray(s);  
        return s;  
    }  
  
    public int getCount() {  
        return this.dataList.size();  
    }  
  
    public boolean next() {  
        index += 1;  
        if(index >= this.getCount())  
        {  
            return false;  
        }  
        return true;  
    }  
  
}

2.使用的示例代码

我们来创建一个简单的excel模板,如下图所示

image

下面的代码将数据插入到这个模板中,这里我们做了两个数据源,分别命名为map和list,在模板的单元格中以&=开始加上数据源的名字加个点后面跟map里面的key名。格式就是&=name.key

public class ExcelUtils {

	public static void main(String[] args) throws Exception{		
		WorkbookDesigner designer = new WorkbookDesigner();  
		String template_file_path = "d:/aspose/cell_sample.xls";  
		Workbook wb = new Workbook(template_file_path); 
		designer.setWorkbook(wb);
				
		//给模板对象设置数据源  
		designer.setDataSource("map", new MapData(getHashMapData()));//hashmap对象作为数据源		
		//设置列表数据
		designer.setDataSource("list", new MapData(getHashMapList()));//map list作为数据源
		
		designer.process();//全自动赋值
		
		wb.save("d:/aspose/cell_sample_i.xls");//可以保存到数据流里面,以便下载使用
		System.out.println("ok!");
	}
	
	/**
	 * 单个数据
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static HashMap getHashMapData(){
		
		HashMap data = new HashMap();
		
		data.put("name", "fullstacks");
		data.put("age", "30");
		
		return data;
		
	}
	
	/**
	 * hashMap 列表
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	private static List<Map> getHashMapList(){
		 List<Map> datas= new ArrayList<Map>();
		 datas.add(getHashMapData());
		 datas.add(getHashMapData());
		 datas.add(getHashMapData());
		 datas.add(getHashMapData());
		 return datas; 
	}
	
}

上面的模板插入数据之后的效果如下

image

以上就是完美使用map作为数据源的实现,当然也可以实现以其他数据作为数据源,java bean作为数据源是直接支持的了。

总结

商业的软件使用起来确实是非常方便,几行代码的事情,使用一些开源的东西可能稍微要写一些复杂的代码,那么使用excel模板一个最大的好处就是能够在模板里面吧excel格式和样式都配好,包括一些统计等等,只需要插入数据就可以了。

Linux下安装Tomcat7与优化

默认情况下tomcat的配置适合开发模式或者比较小的系统应用,当访问量稍微多的时候比如1000人同时在线做一些频繁的业务操作的时候,可能性能方面就会存在问题,所以有必要在生产环境下对tomcat做一些优化。

tomcat 常用运行模式有3种,分别为 bio,nio,apr.生产环境建议用apr,从操作系统级别来解决异步的IO问题,大幅度的提高性能.

1.准备材料

apache-tomcat-7.0.65.tar.gz , 下载地址https://tomcat.apache.org/download-70.cgi ,下载apr 地址http://apr.apache.org/ ,下载两个文件apr-1.5.2.tar.gz和apr-util-1.5.4.tar.gz

2.安装

apr的安装

tar zxvf apr-1.5.2.tar.gz
cd apr-1.5.2
./configure --prefix=/usr/local/apr
make
make install

安装在路径 /usr/local/apr

apr-util的安装

tar zxvf apr-util-1.5.4.tar.gz
cd apr-util-1.5.4
./configure --with-apr=/usr/local/apr/bin/apr-1-config 
make
make install

解压tomcat压缩包,并复制到/usr/local/tomcat7

tar zxvf apache-tomcat-7.0.65.tar.gz
cp -r apache-tomcat-7.0.65 /usr/local/tomcat7

安装tomcat-navtive

cd /usr/local/tomcat7/bin
tar zxvf tomcat-native.tar.gz //该文件在tomcat的bin目录下面
cd /usr/local/tomcat7/bin/tomcat-native-1.1.33-src/jni/native
./configure --with-apr=/usr/local/apr/bin/apr-1-config --with-java-home=/usr/java/jdk1.7.0_75/
make
make install

这里注意的地方就是系统一定要先安装好jdk,要不然会报错,确保环境变量echo $JAVA_HOME能够看到jdk的安装目录。

3.编辑文件bin/catalina.sh加载apr,在任意地方加入下面一行

CATALINA_OPTS="$CATALINA_OPTS -Djava.library.path=/usr/local/apr/lib"

4.编辑conf/server.xml使用apr运行模式

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" prestartminSpareThreads="true"
        maxThreads="1000" minSpareThreads="4" maxSpareThreads="500" acceptCount="700"/>
		
<Connector executor="tomcatThreadPool" URIEncoding="UTF-8" useBodyEncodingForURI="true" enableLookups="false"
               port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"
               connectionTimeout="20000"
               redirectPort="8443" />

5.配置jvm运行参数,在bin目录下创建文件bin/setenv.sh

JAVA_OPTS="-Xms1024m -Xmx1024m -Xmn512m -Xss1024K -XX:PermSize=64m -XX:MaxPermSize=2048m -XX:+UseParallelGC"

jre版本不一样一些参数也不一样,可以参考详细的参数说明进行配置

6.配置数据库连接池,编辑文件conf/context.xml

 <Resource name="jdbc/fullstacks"
	author="Container"
	type="javax.sql.DataSource"
	username="root"
	password="123"
	driverClassName="com.mysql.jdbc.Driver"
	validationQuery="SELECT 1"
	logAbandoned="true"
	removeAbandoned="true"
	removeAbandonedTimeout="10"
	testOnBorrow="true"
	testWhileIdle="true"
	minIdle="1"
	maxIdle="20"
	maxWait="5000"
	maxActive="2000"
	initialSize="1"
	timeBetweenEvictionRunsMillis="10000"
	numTestsPerEvictionRun="10"
	minEvictableIdleTimeMillis="10000"
	url="jdbc:mysql://192.168.1.140:3306/fullstacks?useUnicode=true&amp;characterEncoding=UTF-8" />

数据库驱动程序需要复制到tomcat的lib目录下面才能使用。

7.spring中配置数据源

<bean id="dataSourceId" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/fullstacks</value>
        </property>
</bean>

8.优化网络参数,编辑文件/etc/sysctl.cnf

net.core.netdev_max_backlog = 32768 
net.core.somaxconn = 32768 
net.core.wmem_default = 8388608 
net.core.rmem_default = 8388608 
net.core.rmem_max = 16777216 
net.core.wmem_max = 16777216 
net.ipv4.ip_local_port_range = 1024 65000 
net.ipv4.route.gc_timeout = 100 
net.ipv4.tcp_fin_timeout = 30 
net.ipv4.tcp_keepalive_time = 1200 
net.ipv4.tcp_timestamps = 0 
net.ipv4.tcp_synack_retries = 2 
net.ipv4.tcp_syn_retries = 2 
net.ipv4.tcp_tw_recycle = 1 
net.ipv4.tcp_tw_reuse = 1 
net.ipv4.tcp_mem = 94500000 915000000 927000000 
net.ipv4.tcp_max_orphans = 3276800 
net.ipv4.tcp_max_syn_backlog = 65536

命令sysctl -p生效

9.配置linux打开文件和进程

编辑文件/etc/security/limits.conf ,在后面加入

* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535

以上需要重启机器才能生效,也可以使用命令行暂时配置

ulimit -u 65535
ulimit -n 65535

10.启动和关闭tomcat

bin/start.sh
bin/shutdown.sh

以上基本上就配置完了,大概1000的在用户没啥问题,根据服务器硬件配置可适当调整参数。

总结

性能的影响因素是多方面的,互相影响,首先是系统本身没问题,数据库的响应没问题,web容器顺畅,硬件顺畅,网络带宽足够,再使用一些小工具进行检测,只有在大量用户在实际的生产环境中使用系统,才能发现问题,找到问题的根源到底是哪一块引发的性能瓶颈,调整一下自然一切都变得顺畅。

Linux中安装Apache 2.4并限制下载速度

apache官方并没有提供linux的安装包只提供windows下的安装包,那么linux下的安装只能通过源代码的编译来安装了,下面我们来看看如何安装。

1.准备材料

下载源代码http://httpd.apache.org/download.cgi下载httpd-….tar.gz包,然后解压缩安装,按顺序执行下面的命令

gzip -d httpd-NN.tar.gz
tar xvf httpd-NN.tar
cd httpd-NN
./configure --prefix=/usr/local/apache2.4
make
make install

以上如果没有错误的话就安装在/usr/local/apache2.4目录下了,一般情况下会需要apr和apr-util以及pcre的依赖包才能

2.准备依赖包

apr和apr-util包可以从http://apr.apache.org/下载,使用gzip和tar命令解压后分别放到apache源代码根目录的srclib/apr和srclib/apr-util目录中,注意一定不能带版本号的了。

pcre可以从http://www.pcre.org下载,注意是下载pcre而不是pcre2,下载之后解压然后安装到一个指定的目录

./configure --prefix=/usr/local/pcre
make
make install

3.编译安装apache加依赖包的参数

./configure --prefix=/usr/local/apache2.4 --with-included-apr --with-pcre=/usr/local/pcre/bin/pcre-config
make
make install

以上应该是不会发生错误了,这样就安装完成了

4.启动和关闭

/usr/local/apache2.4/bin/apachectl -k start
/usr/local/apache2.4/bin/apachectl -k stop

第一行是启动第二行是停止,默认情况系你的启动可能会报错,因为ServerName错误,编辑httpd.conf配置文件

vi /usr/local/apache2.4/conf/httpd.conf

找到 ServerName 192.168.1.25:80 把服务器的正确ip地址写上保存好,重启之后就成功了,欢迎页面http://localhost就可以访问了

5.配置限速模块

同样编辑httpd.conf文件,找到

LoadModule ratelimit_module modules/mod_ratelimit.so

这一样吧前面的#去掉,在文件末尾加上下面的配置

<Location /video>
 SetOutputFilter RATE_LIMIT
 SetEnv rate-limit 200
</Location>

上面配置的意思是在路径/video路径下的下载速度限制最大为200kb/s

重启之后,复制一个比较大的文件如test.zip放到htdocs/video目录下,然后使用浏览器下载http://localhost/video/test.zip,多下载几个,最后发现速度都是在200kb/s以内。

总结

apache的限速是限制每个连接的下载速度,在有限的带宽的情况下非常有用,避免部分连接把带宽都占满,可以与tc配置使用限制指定端口的带宽,可以达到不同的端口下载速度不一样。