在项目开发中,经常需要读取应用配置文件的初始化参数,用于应用在启动前进行一些初始化配置。比如:Eclipse,参数项包含主题、字体大小、颜色、Jdk安装位置、自动提示等。Eclispe配置的文件格式是以键值对的方式存储的,即:key=value的形式,下面是Eclipse部份设置参数:

/instance/org.eclipse.jdt.ui/useQuickDiffPrefPage=true
/instance/org.eclipse.jdt.ui/content_assist_proposals_foreground=0,0,0
/instance/org.eclipse.egit.core/GitRepositoriesView.GitDirectories=/Users/yangxin/Downloads/kakaolink-android/.git\:/Users/yangxin/Documents/workspace_web/tfyj/.git\:
/instance/org.eclipse.wst.jsdt.ui/fontPropagated=true
/instance/org.eclipse.debug.core/org.eclipse.debug.core.USE_STEP_FILTERS=true
/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
/instance/org.eclipse.ui.workbench/org.eclipse.jface.textfont=1|Monaco|14.0|0|COCOA|1|Monaco;
@org.eclipse.jdt.ui=3.8.2.v20130107-165834
/instance/org.eclipse.cdt.ui/spelling_locale_initialized=true

从Eclipse配置文件中可以看出,都是以xxxx=xxxxx如最后一个配置项,key为/instance/org.eclipse.cdt.ui/spelling_locale_initialized,值为:true。在项目开发当中的也经常采用这种方式,笔者参考了Java的java.util.Properties类,设计了一个C的配置文件读写接口,供大家学习和使用。

1、定义接口头文件(Properties.h)

//
// Properties.h
// 读写配置文件
//
// Created by 杨信 on 14-4-24.
// Copyright (c) 2014年 yangxin. All rights reserved.
// #ifndef _______Properties_h
#define _______Properties_h #ifdef _cplusplus
extern "C" {
#endif // 初始化环境,成功返回0,失败返回非0值
int init(const char *filepath,void **handle); // 根据KEY获取值,找到返回0,如果未找到返回非0值
int getValue(void *handle, const char *key, char *value); // 修改key对应的属性值,修改成功返回0,失败返回非0值
int setValue(void *handle, const char *key, const char *value); // 添加一个属性,添加成功返回0,失败返回非0值
int add(void *handle, const char *key, const char *value); // 删除一个属性,删除成功返回0,失败返回非0值
int del(void *handle, const char *key); // 获取属性文件中所有的key,获取成功返回0,失败返回非0值
int getKeys(void *handle, char ***keys, int *keyscount); // 释放所有key的内存空间,成功返回0,失败返回非0值
int free_keys(char ***keys,int *keyscount); // 获取属性文件中所有的值,成功返回0,失败返回非0值
int getValues(void *handle, char ***values, int *valuescount); // 释放所有value的内存空间,成功返回0,失败返回非0值
int free_values(char ***values, int *valuescount); // 获取属性数量,成功返回0,失败返回非0值
int getCount(void *handle, int *count); // 释放环境资源,成功返回0,失败返回非0值
int release(void **handle); #ifdef _cplusplus
}
#endif #endif

2、实现头文件的接口(Properteis.c)

//
// Properties.c
// 读写配置文件
//
// Created by 杨信 on 14-4-24.
// Copyright (c) 2014年 yangxin. All rights reserved.
// #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include "Properties.h" #define KEY_SIZE 128 // key缓冲区大小
#define VALUE_SIZE 128 // value缓冲区大小 #define LINE_BUF_SIZE 256 // 读取配置文件中每一行的缓冲区大小 typedef struct Properties {
char *key;
char *value;
struct Properties *pNext;
}Properties; typedef struct PROPS_HANDLE {
Properties *pHead; // 属性链表头节点
char *filepath; // 属性文件路径
}PROPS_HANDLE; static int createPropsNode(Properties **props); // 创建一个节点
static int trimeSpace(const char *src,char *dest); // 去空格
static int saveConfig(const char *filepath,Properties *head); // 将修改或保存后的配置项保存到文件 // 初始化环境,成功返回0,失败返回非0值
int init(const char *filepath,void **handle)
{
int ret = 0;
FILE *fp = NULL;
Properties *pHead = NULL,*pCurrent = NULL, *pMalloc = NULL;
PROPS_HANDLE *ph = NULL;
char line[LINE_BUF_SIZE]; // 存放读取每一行的缓冲区
char keybuff[KEY_SIZE] = { 0 }; // 存放key的缓冲区
char valuebuff[VALUE_SIZE] = { 0 }; // 存放value的缓冲区
char *pLine = NULL; // 每行缓冲区数据的指针 if(filepath == NULL || handle == NULL)
{
ret = -1;
printf("fun init error:%d from (filepath == NULL || handler == NULL)\n",ret);
return ret;
} ph = (PROPS_HANDLE *)malloc(sizeof(PROPS_HANDLE));
if (ph == NULL) {
ret = -2;
printf("fun init malloc handle error:%d",ret);
return ret;
}
memset(ph, 0, sizeof(PROPS_HANDLE)); // 打开文件
fp = fopen(filepath, "r");
if (!fp) {
ret = -3;
printf("fun init open file error:%d from %s\n",ret,filepath);
return ret;
} // 创建头节点
ret = createPropsNode(&pHead);
if (ret != 0) {
fclose(fp); // 关闭文件
printf("fun init create head node error:%d\n",ret);
return ret;
}
memset(pHead, 0, sizeof(Properties)); // 保存链表头节点和文件路径到handle中
ph->pHead = pHead;
ph->filepath = (char *)malloc(strlen(filepath) + 1);
strcpy(ph->filepath, filepath); pCurrent = pHead; // 读取配置文件中的所有数据
while (!feof(fp)) {
if(fgets(line, LINE_BUF_SIZE, fp) == NULL)
{
break;
} // 找等号
if ((pLine = strstr(line, "=")) == NULL) { // 没有等号,继续读取下一行
continue;
} // 循环创建节点
ret = createPropsNode(&pMalloc);
if (ret != 0) {
fclose(fp); // 关闭文件
release((void **)&ph); // 创建节点失败,释放所有资源
printf("create new node error:%d\n",ret);
return ret;
} // 设置Key
memcpy(keybuff, line, pLine-line);
trimeSpace(keybuff, pMalloc->key); // 将keybuff去空格后放到pMallock.key中 // 设置Value
pLine += 1;
trimeSpace(pLine, valuebuff);
strcpy(pMalloc->value, valuebuff); // 将新节点入链表
pMalloc->pNext = NULL;
pCurrent->pNext = pMalloc;
pCurrent = pMalloc; // 当前节点下移 // 重置key,value
memset(keybuff, 0, KEY_SIZE);
memset(valuebuff, 0, VALUE_SIZE);
} // 设置环境句柄给调用者
*handle = ph; // 关闭文件
fclose(fp); return ret;
} // 获取属性数量,成功返回0,失败返回非0值
int getCount(void *handle, int *count)
{
int ret = 0,cn = 0;
PROPS_HANDLE *ph = NULL;
Properties *pCurrent = NULL;
if (handle == NULL || count == NULL) {
ret = -1;
printf("fun getCount error:%d from (handle == NULL || count == NULL)\n",ret);
return ret;
}
ph = (PROPS_HANDLE *)handle;
pCurrent = ph->pHead->pNext;
while (pCurrent != NULL) {
cn++;
pCurrent = pCurrent->pNext;
} *count = cn; return ret;
} // 根据KEY获取值,找到返回0,如果未找到返回非0值
int getValue(void *handle, const char *key, char *value)
{
int ret = 0;
PROPS_HANDLE *ph = NULL;
Properties *pCurrent = NULL;
if (handle == NULL || key == NULL || value == NULL) {
ret = -1;
printf("getValue error:%d from (handle == NULL || key == NULL || value == NULL)\n",ret);
return ret;
} ph = (PROPS_HANDLE *)handle;
pCurrent = ph->pHead->pNext;
while (pCurrent != NULL) {
if (strcmp(pCurrent->key,key) == 0) {
break;
}
pCurrent = pCurrent->pNext;
} if (pCurrent == NULL) {
ret = -2;
printf("fun getValue warning: not found the key:%s\n",key);
return ret;
} strcpy(value, pCurrent->value); return ret;
} // 修改key对应的属性值,修改成功返回0,失败返回非0值
int setValue(void *handle, const char *key, const char *value)
{
int ret = 0;
PROPS_HANDLE *ph = NULL;
Properties *pCurrent = NULL;
if (handle == NULL || key == NULL || value == NULL) {
ret = -1;
printf("fun setValue error:%d from (handle == NULL || key == NULL || value == NULL)\n",ret);
return ret;
} // 获得环境句柄
ph = (PROPS_HANDLE *)handle; // 从环境句柄中获取头节点
pCurrent = ph->pHead->pNext;
while (pCurrent != NULL) {
if (strcmp(pCurrent->key, key) == 0) { // 找到
break;
}
pCurrent = pCurrent->pNext;
} if (pCurrent == NULL) { // 未找到key
ret = -2;
printf("fun setValue warning: not found the key:%s\n",key);
return ret;
} // 修改key的value
strcpy(pCurrent->value, value);
if (strchr(value, '\n') == NULL) { // 加一个换行符
strcat(pCurrent->value, "\n");
} // 将修改的配置项写入到文件
ret = saveConfig(ph->filepath, ph->pHead); return ret;
} // 添加一个属性,添加成功返回0,失败返回非0值
int add(void *handle, const char *key, const char *value)
{
int ret = 0;
PROPS_HANDLE *ph = NULL;
Properties *pCurrent = NULL;
if (handle == NULL || key == NULL || value == NULL) {
ret = -1;
printf("fun add error:%d from (handle == NULL || key == NULL || value == NULL)\n",ret);
return ret;
} ph = (PROPS_HANDLE *)handle; //-----------如果key存在链表中,则直接修改,否则添加到链表中-----------//
pCurrent = ph->pHead;
while (pCurrent->pNext != NULL) {
if (strcmp(pCurrent->pNext->key,key) == 0) {
break;
}
pCurrent = pCurrent->pNext;
} if (pCurrent->pNext != NULL) {
return setValue(handle, key, value);
} //-----------key不存在,创建一个新的配置项,添加到链表中-----------//
Properties *pMalloc;
ret = createPropsNode(&pMalloc);
if (ret != 0) {
printf("fun add error:%d from malloc new node.",ret);
return ret;
} strcpy(pMalloc->key, key);
if (strchr(pCurrent->value,'\n') == NULL) {
strcat(pCurrent->value, "\n");
}
strcpy(pMalloc->value, value);
if (strchr(value, '\n') == NULL) { // 加一个换行符
strcat(pMalloc->value, "\n");
}
pCurrent->pNext = pMalloc; // 新配置项入链表 // 将新配置项写入到文件
ret = saveConfig(ph->filepath, ph->pHead); return ret;
} // 删除一个属性,删除成功返回0,失败返回非0值
int del(void *handle, const char *key)
{
int ret = 0;
PROPS_HANDLE *ph = NULL;
Properties *pCurrent = NULL, *pPrev = NULL;
if (handle == NULL || key == NULL) {
ret = -1;
printf("fun del error:%d from (handle == NULL || key == NULL)\n",ret);
return ret;
} ph = (PROPS_HANDLE *)handle;
pPrev = ph->pHead;
pCurrent = ph->pHead->pNext; while (pCurrent != NULL) {
if (strcmp(pCurrent->key, key) == 0) {
break;
}
pPrev = pCurrent; // 上一个节点下移
pCurrent = pCurrent->pNext; // 当前节点下移
} if (pCurrent == NULL) { // 没有找到
ret = -2;
printf("fun del warning:not found the key:%s\n",key);
return ret;
} pPrev->pNext = pCurrent->pNext; // 从链表中删除
free(pCurrent); // 释放内存
pCurrent = NULL; // 保存到文件
ret = saveConfig(ph->filepath, ph->pHead); return ret;
} // 获取属性文件中所有的key,获取成功返回0,失败返回非0值
int getKeys(void *handle, char ***keys, int *keyscount)
{
int ret = 0, count = 0, index = 0;
PROPS_HANDLE *ph = NULL;
Properties *pCurrent = NULL;
char **pKeys = NULL;
if (handle == NULL || keys == NULL || keyscount == NULL) {
ret = -1;
printf("fun getKeys error:%d from (handle == NULL || keys == NULL || keyscount == NULL) \n",ret);
return ret;
} // 获取配置项数量
ret = getCount(handle, &count);
if (ret != 0) {
printf("fun getKeys error:%d from getCount \n",ret);
return ret;
} ph = (PROPS_HANDLE *)handle;
pCurrent = ph->pHead->pNext; // 根据链表长度,申请内存空间
pKeys = (char **)malloc(sizeof(char *) * count);
if (pKeys == NULL) {
ret = -2;
printf("fun getKeys error:%d from malloc keys\n",ret);
return ret;
} pCurrent = ph->pHead->pNext;
while (pCurrent != NULL) {
pKeys[index] = pCurrent->key;
pCurrent = pCurrent->pNext;
index++;
} *keys = pKeys;
*keyscount = count; return ret;
} // 释放所有key的内存空间,成功返回0,失败返回非0值
int free_keys(char ***keys,int *keyscount)
{
int ret = 0;
if (keys == NULL || keyscount == NULL) {
ret = -1;
printf("fun free_keys error:%d from (keys == NULL || keyscount == NULL) \n",ret);
return ret;
} free(*keys);
*keys = NULL;
*keyscount = 0; return ret;
} // 获取属性文件中所有的值,成功返回0,失败返回非0值
int getValues(void *handle, char ***values, int *valuescount)
{
int ret = 0, count = 0, index = 0;
PROPS_HANDLE *ph = NULL;
Properties *pCurrent = NULL;
char **pValues = NULL;
if (handle == NULL || values == NULL || valuescount == NULL) {
ret = -1;
printf("fun getValues error:%d from (handle == NULL || values == NULL || valuescount == NULL)\n",ret);
return ret;
} // 获取配置项数量
ret = getCount(handle, &count);
if (ret != 0) {
printf("fun getValues error:%d from getCount \n",ret);
return ret;
} // 申请内存空间,存放所有的value
pValues = (char **)malloc(sizeof(char *) * count);
if (pValues == NULL) {
ret = -2;
printf("fun getValues error:%d from malloc values\n",ret);
return ret;
} ph = (PROPS_HANDLE *)handle;
pCurrent = ph->pHead->pNext;
while (pCurrent != NULL) {
pValues[index] = pCurrent->value;
pCurrent = pCurrent->pNext;
index++;
} *values = pValues;
*valuescount = count; return ret;
} // 释放所有value的内存空间,成功返回0,失败返回非0值
int free_values(char ***values, int *valuescount)
{
int ret = 0;
if (values == NULL || valuescount == NULL) {
ret = -1;
printf("fun free_values error:%d from (values == NULL || valuescount == NULL) \n",ret);
return ret;
} free(*values);
*values = NULL;
*valuescount = 0; return ret;
} // 释放环境资源,成功返回0,失败返回非0值
int release(void **handle)
{
int ret = 0;
PROPS_HANDLE *ph = NULL;
if(handle == NULL)
{
ret = -1;
printf("release error:%d from (handler == NULL)\n",ret);
return ret;
} ph = (PROPS_HANDLE *)*handle; // 释放链表内存资源
Properties *pCurr = ph->pHead;
Properties *pTemp = NULL; while (pCurr != NULL) {
if (pCurr->key != NULL) {
free(pCurr->key);
pCurr->key = NULL;
} if (pCurr->value != NULL) {
free(pCurr->value);
pCurr->value = NULL;
} pTemp = pCurr->pNext; free(pCurr); pCurr = pTemp;
} // 释放存放配置文件路径分配的内存空间
if(ph->filepath != NULL)
{
free(ph->filepath);
ph->filepath = NULL;
} // 释放环境句柄本身
free(ph);
*handle = NULL; // 避免野指针 return ret;
} // 去空格
static int trimeSpace(const char *src,char *dest)
{
int ret = 0;
if (src == NULL || dest == NULL) {
ret = -1;
printf("trimeSpace error:%d from (src == NULL || dest == NULL)\n",ret);
return ret;
} const char *psrc = src;
unsigned long i = 0,j = strlen(psrc) - 1,len;
while (psrc[i] == ' ')
{
i++;
} while (psrc[j] == ' ') {
j--;
} len = j - i + 1; memcpy(dest,psrc+i,len);
*(dest+len) = '\0'; return ret;
} // 创建一个节点
static int createPropsNode(Properties **props)
{
int ret = 0;
Properties *p = NULL;
if (props == NULL) {
ret = -100;
printf("createProps error:%d from (props == NULL)\n",ret);
return ret;
} p = (Properties *)malloc(sizeof(Properties));
if (p == NULL) {
ret = -200;
printf("createProps malloc %ld bytes error:%d\n",sizeof(Properties),ret);
return ret;
}
p->key = (char *)malloc(KEY_SIZE);
p->value = (char *)malloc(VALUE_SIZE);
p->pNext = NULL; *props = p; return ret;
} // 保存到文件
static int saveConfig(const char *filepath,Properties *head)
{
int ret = 0,writeLen = 0;
FILE *fp = NULL;
Properties *pCurrent = NULL;
if (filepath == NULL || head == NULL) {
ret = -100;
printf("fun saveConfig error:%d from (filepath == NULL || head == NULL)\n",ret);
return ret;
} fp = fopen(filepath,"w");
if (fp == NULL) {
ret = -200;
printf("fun saveConfig:open file error:%d from %s\n",ret,filepath);
return ret;
} pCurrent = head->pNext;
while (pCurrent != NULL) {
writeLen = fprintf(fp, "%s=%s",pCurrent->key,pCurrent->value); // 返回写入的字节数,出现错误返回一个负值
if (writeLen < 0) { //TODO 如果写入失败,如何将写入的数据回退???
ret = -300;
printf("fun saveConfig err:%d from (%s=%s)\n",ret,pCurrent->key,pCurrent->value);
break;
}
pCurrent = pCurrent->pNext;
} fclose(fp); // 关闭文件 return ret;
}

3、测试代码(需要在项目根目录创建props.txt)

//
// main.c
// 读写配置文件
//
// Created by 杨信 on 14-4-24.
// Copyright (c) 2014年 yangxin. All rights reserved.
// #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Properties.h" int main(int argc, const char * argv[])
{
int ret;
void *handle;
const char *filepath = "/Users/yangxin/Desktop/props.txt";
// 初始化
ret = init(filepath, &handle);
if (ret != 0) {
printf("env init error:%d\n",ret);
return 0;
} char valuebuf[128];
// 测试获取配置项
ret = getValue(handle, "host", valuebuf);
if (ret == 0) {
printf("value=%s\n",valuebuf);
}
else {
printf("获取值host的值失败\n");
} // 测试修改配置项
ret = setValue(handle, "version", "1.2.3");
if (ret == 0) {
printf("修改成功!\n");
}
else{
printf("修改失败\n");
} // 测试添加配置项
ret = add(handle, "pool_connection_countxx", "2000");
if (ret == 0) {
printf("添加成功!\n");
}
else{
printf("添加失败\n");
} // 测试删除配置项
ret = del(handle, "connectionMax");
if (ret == 0) {
printf("删除成功!\n");
}
else{
printf("删除失败\n");
} // 测试获取所有配置项的key
char **keys = NULL;
int keyscount;
ret = getKeys(handle, &keys, &keyscount);
if (ret == 0) {
printf("一共有%d个Key\n",keyscount);
for (int i =0; i<keyscount; i++) {
printf("%s\n",keys[i]);
}
}
// 释放内存
ret = free_keys(&keys, &keyscount);
if (ret == 0) {
printf("keys释放内存成功!\n");
} // 测试获取所有配置项的value
char **values = NULL;
int valuescount;
ret = getValues(handle, &values, &valuescount);
if (ret == 0) {
printf("一共有%d个Value\n",valuescount);
for (int i = 0; i < valuescount; i++) {
printf("%s",values[i]);
}
}
// 释放内存
ret = free_values(&values, &valuescount);
if (ret == 0) {
printf("values释放内存成功!\n");
} // 释放资源
ret = release(&handle);
if (ret != 0) {
printf("env release error:%d\n",ret);
} return 0;
}

测试配置文件:

username=root
password=root123456
host=192.168.1.100
port=9090
connectionMax=200
version=1.0

测试结果:

源码下载地址:git@github.com:xyang0917/RWAppProperites.git

C读写配置文件的更多相关文章

  1. 用ConfigParser模块读写配置文件——Python

    对于功能较多.考虑用户体验的程序,配置功能是必不可少的,如何存储程序的各种配置? 1)可以用全局变量,不过全局变量具有易失性,程序崩溃或者关闭之后配置就没了,再者配置太多,将变量分配到哪里也是需要考虑 ...

  2. 使用ConfigurationManager类读写配置文件

    使用ConfigurationManager类 读写配置文件app.config,以下为代码: view plaincopy to clipboard print? using System; usi ...

  3. Python自动化测试 (二) ConfigParser模块读写配置文件

    ConfigParser 是Python自带的模块, 用来读写配置文件, 用法及其简单. 直接上代码,不解释,不多说. 配置文件的格式是: []包含的叫section,    section 下有op ...

  4. Python自动化测试 -ConfigParser模块读写配置文件

    C#之所以容易让人感兴趣,是因为安装完Visual Studio, 就可以很简单的直接写程序了,不需要做如何配置. 对新手来说,这是非常好的“初体验”, 会激发初学者的自信和兴趣. 而有些语言的开发环 ...

  5. python:实例化configparser模块读写配置文件

    之前的博客介绍过利用python的configparser模块读写配置文件的基础用法,这篇博客,介绍下如何实例化,方便作为公共类调用. 实例化的好处有很多,既方便调用,又降低了脚本的维护成本,而且提高 ...

  6. python:利用configparser模块读写配置文件

    在自动化测试过程中,为了提高脚本的可读性和降低维护成本,将一些通用信息写入配置文件,将重复使用的方法写成公共模块进行封装,使用时候直接调用即可. 这篇博客,介绍下python中利用configpars ...

  7. python-ConfigParser模块【读写配置文件】

    对python 读写配置文件的具体方案的介绍 1,函数介绍 import configParser 如果Configparser无效将导入的configParser 的C小写 1.1.读取配置文件 - ...

  8. python 读写配置文件

    使用python读写配置文件,写个demo测试一下. #!/usr/bin/env python import os import ConfigParser # 如果文件不存在,就穿件文件. if o ...

  9. Java读写配置文件——Properties类的简要使用笔记

    任何编程语言都有自己的读写配置文件的方法和格式,Java也不例外. 在Java编程语言中读写资源文件最重要的类是Properties,功能大致如下: 1. 读写Properties文件 2. 读写XM ...

随机推荐

  1. 使用json-lib进行Java和JSON之间的转换【转载】

    1. json-lib是一个java类库,提供将Java对象,包括beans, maps, collections, java arrays and XML等转换成JSON,或者反向转换的功能. 2. ...

  2. 关于Parse库的配置问题

    在使用Parse的一些相关服务时候,需要下载Parse的SDK,除了需要添加相应的系统框架和库的支持 AudioToolbox.framework CFNetwork.framework CoreGr ...

  3. [D3] 8. Margins

    If you want ot add margins, should append graphics container in svg var svg = d3.select('#chartArea' ...

  4. GitHub具体教程

    GitHub具体教程 Table of Contents 1 Git具体教程 1.1 Git简单介绍 1.1.1 Git是何方神圣? 1.1.2 重要的术语 1.1.3 索引 1.2 Git安装 1. ...

  5. mysql 插入语句

    mysql 插入语句 什么时候用单引号,什么时候不用? 1.先创建一个表 create table user(username varchar(255),age int,marry boolean,b ...

  6. at91sam9x5 linux 4.1.0下dts驱动编程模型

    测试环境:  CPU: AT91SAM9X35      Linux: Atmel提供的linux-at91-linux4sam_5.3 (Linux-4.1.0) 转载请注明: 凌云物网智科嵌入式实 ...

  7. android--HttpURLConnection(转载)

    android之HttpURLConnection 1.HttpURLConnection连接URL1)创建一个URL对象 URL url = new URL(http://www.baidu.com ...

  8. js给当前日期加一天

    <script type="text/javascript"> function addDay(datetime, days) { var old_time = new ...

  9. 'EntityValidationErrors' property for more details

    很多小猿遇到这个Exception 的时候,都会有点无厘头.这个时候最好try-- catch下,找到出错的地方.本人习惯在页面上加个lable标签,把exc msg(exception messag ...

  10. 使用Ubuntu 新建vpn过程

    1.更新软件源      sudo apt-get update   2.安装pip    sudo apt-get install python-pip   3.安装shadowsocks    s ...