基于VC++开发linux服务器程序指南
在此仅把此文献给那些奋斗在一线的c/c++开发人员,希望可以帮到大家。这是作者多年跨平台编程经验的总结,以文档的形式供大家参考。
本人一直基于微软Microsoft Visual C++(以下简称VC)开发后台服务器程序,
由于项目需要,程序需要运行在linux/unix(以下的linux代表Linux和unix)
平台。但是本人已经非常熟悉和依赖VC,不打算再掌握linux下的开发工具进
行开发。 如果采用linux特有的API函数,windows源程序需要进行大量的修
改。在朋友的指点下,采用基于VC开发、调试、测试,然后移植到linux/g++
进行编译运行。随着移植文件的完善和编码兼容性的优化,现在基本上可以做到VC的工程迁移到linux,只需要做很少或不做修改即可编译通过。
采用本方案希望达成以下几点目的。
1、充分利用微软公司VC强大的可视化编辑、调试功能。同时可使用配套的版本管理工具
和Visual Assit X开发助手,有助于提高开发过程的自动化和开发效率。
2、实现开发服务器程序跨平台(windows、linux、unix等)、跨开发工具(VC、g++等)。
3、只需在VC下一次编写源码、一次调试、一次测试,然后到其他平台只需要编译、运行
即可。
4、加快代码的编写速度和质量。
5、加快代码调试速度和质量。
6、加快程序乃至整个项目的上线进度。
本方案主要是移植大部分常用的功能函数,无意移植所有的功能函数。首先是有些函数由于操作系统的差异无法移植,或者移植代价太大完全没有必要。
给开发人员提供多一种跨平台项目开发的选择,无意也不可能取代linux/g++开发工具和其他跨平台开发解决方案。
本方案最适合开发后台服务器程序,如果开发人员要应用于其他领域,需要慎重考虑可行性。
本文适合以下读者:
1、本文适用读者是面向VC开发人员,他们需要进行linux下进行开发服务器程
序。 采用本方案对VC开发人员技能要求:熟悉VC开发工具、熟悉C/C/++、熟
悉标准库(可选)、熟悉windows核心编程(多线程、线程同步等)、Socket编
程、了解linux和g++基本知识,不要求掌握MFC。
2、现有的windows平台下的VC工程需要移植到linux平台下运行。
3、针对linux/g++ 开发人员也具有参考意义。
采用本方案的领域:后台服务器程序。
1、采用VC开发工具进行服务器程序开发,源码中不使用任何MFC的类
库,可以采用跨平台的第三方类库(标准库、Boost库),或采用我方提供的相
关功能类库。开发过程中只采用标准的C函数或者Windows系统API函数。
2、在linux下利用gcc/g++编写移植文件,把windows的API用linux/g++提供的
API进行封装,还有就是把部分只有VC特有的c函数也用gcc/g++进
行封装。
3、利用VC/windows上进行开发、调试、测试。
4、把代码文件复制到linux下,然后在把移植文件也加入到工程中,进行编译。
一般情况下可以编译通过,即使编译过程中有错误,也可以通过调整达到代
码兼容的效果。
7、用gcc/g++进行编译,生成可执行文件,运行可执行文件进行测试。
说明:
1、如果是开发动态库/静态库和上述过程类似。
2、作者会提供linux的2个移植源码文件:PortLinux.cpp、PortLinux.h
1、建议采用/VC 2008及以上版本,同时安装Visual Assist X开发助手。
2、操作系统
1)桌面系统采用64位Win7及以上版本。
2)服务器系统采用Windows 2008x64及以上版本
3、其他
如果只是开发32位程序,建议安装VC6,同时安装Visual Assist X开发助
手。
作者开发选用RedHat和CentOS 的linux,如果开发人员希望在其他厂商的linux上开发,需要进行进一步的测试
作者没有在unix下开发的经历,如果开发人员在unix(HP_UX、AIX、Solaris、BSD)下采用本方案,需要进行进一步测试作者提供的移植文件,如果有必要可进行修改和完善。
- linux移植工作
需要在linux/g++下编写移植文件,作者提供2个移植文件PortLinux.cpp和
PortLinux.h,开发人员需要在自己的源码文件(c、cpp、h)中包含(#include)PortLinux.h文件。
编写移植文件需要熟悉linux/gcc/g++的开发人员协助VC开发人员一起编写,移植文件需要在linux 下编译、调试、测试,后续就可以把移植文件加入到g++工程里进行整体编译。移植工作只需要做一次即可,但是做到可以一劳永逸。
#define INFINITE 0xFFFFFFFF
#define WINAPI
#define FALSE 0
#define TRUE 1
typedef int BOOL;
typedef void* HANDLE;
typedef void* POSITION;
typedef void* LPVOID;
typedef void* HINSTANCE;
typedef char* LPCTSTR;
typedef unsigned int DWORD;
typedef unsigned short WORD;
typedef unsigned char BYTE;
typedef int INT;
typedef int INT32;
typedef unsigned int UINT32;
typedef long long int INT64;
typedef unsigned long long int UNIT64;
typedef wchar_t WCHAR;
typedef void* THREAD_RETURN_TYPE;
typedef struct _LARGE_INTEGER
{
long long int QuadPart;
}LARGE_INTEGER;
初始化全局变量:获取操作系统开机时间
InitSysPort();
1、在移植文件中进行数据类型定义LPSECURITY_ATTRIBUTES;
typedef char* LPSECURITY_ATTRIBUTES;
用途:用于封装CreateThread函数。
2、LPTHREAD_START_ROUTINE
extern "C"
{
typedef void* (*LPTHREAD_START_ROUTINE)(void *);
}
用途:用于封装CreateThread函数。
-
-
-
- CreateThread函数
-
-
功能:CreateThread是一种微软在Windows API中提供了建立一个新的线程的
函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对
象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。
函数定义如下:
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
unsigned int dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
unsigned int dwCreationFlags,
unsigned long* lpThreadId);
函数使用说明:
1、其中第1、5参数在linux/g++没有意义,填写默认参数。
1)第1个参数lpThreadAttributes,赋值为NULL。
2)第5个参数dwCreationFlags,赋值为0。
2、执行成功返回非NULL的句柄。
-
-
-
- TerminateThread函数
-
-
功能:TerminateThread在线程外终止一个线程,用于强制终止线程。
函数声明如下:
BOOLTerminateThread( HANDLE hThread, DWORDdwExitCode);
bool SetThreadPriority(HANDLE hThread, int nPriority);
-
-
-
- GetCurrentThreadId函数
-
-
获取当前线程一个唯一的线程标识符。
int GetCurrentThreadId ();
-
-
-
- SetThreadPriority函数
-
-
暂时没有实现。
-
-
-
- 其他说明
-
-
1、不要封装暂停线程(SuspendThread)和恢复线程(ResumeThread)函数,主要是没有必要使
用这2个函数,另外在linux下也没有类似的函数和功能。
#define WAIT_OBJECT_0 00
#define WAIT_TIMEOUT 01
用于同一个进程内多线程间的互斥。
-
-
-
- 数据类型定义
-
-
typedef pthread_mutex_t CRITICAL_SECTION;
typedef CRITICAL_SECTION* LPCRITICAL_SECTION;
-
-
-
- 相关函数移植说明
-
-
采用linux的互斥锁进行简单封装即可。
void InitializeCriticalSection(CRITICAL_SECTION* lpCriticalSection)
{
pthread_mutex_init(lpCriticalSection, NULL);
}
void EnterCriticalSection(CRITICAL_SECTION* lpCriticalSection)
{
pthread_mutex_lock(lpCriticalSection);
}
BOOL TryEnterCriticalSection(CRITICAL_SECTION* lpCriticalSection)
{
int nret;
nret = pthread_mutex_trylock(lpCriticalSection);
if(nret == 0)
{
return TRUE;
}
else
{
return FALSE;
}
}
void LeaveCriticalSection(CRITICAL_SECTION* lpCriticalSection)
{
pthread_mutex_unlock(lpCriticalSection);
}
void DeleteCriticalSection(CRITICAL_SECTION* lpCriticalSection)
{
pthread_mutex_destroy(lpCriticalSection);
}
用于同一个进程内多线程间的互斥锁的操作,暂时没有实现多进程间的互斥锁操作。
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);
1、第1个参数lpMutexAttributes调用时填写NULL。
2、第4个参数lpName调用时填写NULL。
bool ReleaseMutex(HANDLE hMutex);
用于同一个进程内多线程间的信号量的操作,暂时没有实现多进程间的信号量操作。
具体实现采用linux的互斥锁(pthread_mutex_t mutex)和条件变量
(pthread_cond_t cond)组合实现。
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
int lInitialCount,
int lMaximumCount,
LPCTSTR lpName );
参数说明:
1、第1个参数lpSemaphoreAttributes调用时填写NULL。
2、第4个参数lpName调用时填写NULL。
bool ReleaseSemaphore(HANDLE hSemaphore,
int lReleaseCount,
long* lpPreviousCount);
用于同一个进程内多线程间的事件的操作,暂时没有实现多进程间的事件操作。
具体实现采用linux的互斥锁(pthread_mutex_t mutex)和条件变量
(pthread_cond_t cond)组合实现。
-
-
-
- 相关函数移植说明
-
-
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
bool bManualReset,
bool bInitialState,
LPCTSTR lpName);
参数说明:
1、第1个参数lpEventAttributes调用时填写NULL。
2、第4个参数lpName调用时填写NULL。
bool SetEvent(HANDLE hEvent);
bool ResetEvent(HANDLE hEvent);
bool PulseEvent(HANDLE hEvent); 此函数无用,不需要实现。
-
-
-
-
- 数据类型定义
-
-
-
把linux下的读写锁简单封装为windows形式的读写锁。
typedef struct _SRWLOCK
{
pthread_rwlock_t rwLock;
}SRWLOCK;
typedef SRWLOCK* PSRWLOCK;
typedef SRWLOCK* LPSRWLOCK;
-
-
-
- 相关函数移植说明
-
-
采用linux的读写锁进行简单封装即可。
1、初始化读写锁
void InitializeSRWLock(LPSRWLOCK pLock)
{
pthread_rwlock_init(&(pLock->rwLock), NULL);
}
2、以独占写方式使用读写锁
void AcquireSRWLockExclusive(LPSRWLOCK pLock)
{
pthread_rwlock_wrlock(&(pLock->rwLock));
}
3、以共享读方式使用读写锁
void AcquireSRWLockShared(LPSRWLOCK pLock)
{
pthread_rwlock_rdlock(&(pLock->rwLock));
}
4、释放之前以独占写方式使用的读写锁
void ReleaseSRWLockExclusive(LPSRWLOCK pLock)
{
pthread_rwlock_unlock(&(pLock->rwLock));
}
5、释放之前以共享读方式使用的读写锁
void ReleaseSRWLockShared(LPSRWLOCK pLock)
{
pthread_rwlock_unlock(&(pLock->rwLock));
}
6、删除读写锁,使之不可再被访问使用。
void DeleteSRWLock(LPSRWLOCK pLock)
{
pthread_rwlock_destroy(&(pLock->rwLock));
}
注意:windows的API中没有DeleteSRWLock这个函数,为了保持代码兼容新增此函数,在linux和windows下的移植文件中都需要声明和定义此函数。
在Windows的移植文件中实现代码如下:
void DeleteSRWLock(LPSRWLOCK pLock)
{
return;
}
如上所示,只需简单的返回即可,纯粹是为了兼容linux代码。
WaitForSingleObject 函数用来检测 hHandle 事件的信号状态,当函数的执行时间超过 dwMilliseconds 就返回,但如果参数 dwMilliseconds 为 INFINITE 时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到 WaitForSingleObject 有返回直才执行后面的代码。
DWORD WaitForSingleObject(HANDLE hObject, //指明一个内核对象的句柄
DWORD dwMilliseconds); //等待时间
WAIT_OBJECT_0,WAIT_TIMEOUT,WAIT_FAILED,WAIT_IO_COMPLETION。
关闭句柄函数如下:
bool CloseHandle(HANDLE hObject);
函数用途:
1、用于关闭线程的句柄,线程的句柄由CreateThread函数返回。
2、关闭同步对象的句柄:互斥锁(Mutex)、事件(Event)、信号量(Semaphore)
3、其他核心对象句柄。
具体实现参考作者提供的移植文件。
暂时没有实现,读者如果需要可自行实现。
实现了对以下常用字符串相关函数的封装,这些函数是作者经常使用但是linux没有提供相应的库函数,需要在移植文件中实现封装。
int strcmpi(const char *string1, const char *string2);
int stricmp(const char *string1, const char *string2);
int strnicmp(const char *string1, const char *string2, int nCount);
void strlower(char* sValue);
void strupper(char* sValue);
char* _strrev(char* sString);
char* itoa(int value, char* string, int radix);
char* _itoa(int value, char* string, int radix);
char* _ultoa(unsigned long value, char* string, int radix);
char* ultoa(unsigned long value, char* string, int radix);
int64 _atoi64(const char* string);
char* _i64toa(int64 value, char* string, int radix);
char* _ui64toa(uint64 value, char* string, int radix);
uint64 _atoui64(char* sValue);
uint _atoui(char* sValue);
typedef struct _FILETIME
{
unsigned long dwLowDateTime;
unsigned long dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;
typedef struct _SYSTEMTIME
{
unsigned short wYear;
unsigned short wMonth;
unsigned short wDayOfWeek;
unsigned short wDay;
unsigned short wHour;
unsigned short wMinute;
unsigned short wSecond;
unsigned short wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
BOOL SystemTimeToFileTime(LPSYSTEMTIME pSysTime, LPFILETIME pFileTime)
{
int64 nRet;
nRet = SystemTimeToInt(pSysTime);
memcpy(pFileTime, &nRet, 8);
return TRUE;
}
BOOL FileTimeToSystemTime(LPFILETIME pFileTime, LPSYSTEMTIME pSysTime)
{
int64 nRet;
memcpy(&nRet, pFileTime, 8);
IntToSystemTime(nRet, pSysTime);
return TRUE;
}
void GetLocalTime(LPSYSTEMTIME pSystemTime)
{
struct timeval now;
gettimeofday(&now, NULL);
struct tm* LocalTime;
LocalTime = localtime(&(now.tv_sec));
pSystemTime->wYear = 1900 + LocalTime->tm_year;
pSystemTime->wMonth = LocalTime->tm_mon + 1;
pSystemTime->wDay = LocalTime->tm_mday;
pSystemTime->wHour = LocalTime->tm_hour;
pSystemTime->wMinute = LocalTime->tm_min;
pSystemTime->wSecond = LocalTime->tm_sec;
pSystemTime->wMilliseconds = (now.tv_usec)/1000;
pSystemTime->wDayOfWeek = LocalTime->tm_wday;
}
void GetSystemTime(LPSYSTEMTIME pSystemTime)
{
struct timeval now;
gettimeofday(&now, NULL);
struct tm* LocalTime;
LocalTime = gmtime(&(now.tv_sec));
pSystemTime->wYear = 1900 + LocalTime->tm_year;
pSystemTime->wMonth = LocalTime->tm_mon + 1;
pSystemTime->wDay = LocalTime->tm_mday;
pSystemTime->wHour = LocalTime->tm_hour;
pSystemTime->wMinute = LocalTime->tm_min;
pSystemTime->wSecond = LocalTime->tm_sec;
pSystemTime->wMilliseconds = (now.tv_usec)/1000;
pSystemTime->wDayOfWeek = LocalTime->tm_wday;
}
void Sleep(unsigned int dwMilliseconds)
{
timespec tt;
tt.tv_sec = dwMilliseconds/1000;
tt.tv_nsec = (dwMilliseconds%1000)*1000000;
nanosleep(&tt, NULL);
}
需要实现GetTickCount()、GetTickCount64()
DWORD GetTickCount();
int64 GetTickCount()
{
struct timeval tv;
int64 qwVal;
gettimeofday(&tv, NULL);
qwVal = tv.tv_sec*1000 + tv.tv_usec/1000;
return qwVal;
}
- socket编程
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define SD_BOTH SHUT_RDWR
#define SOCKET_SEND_DEFAULT_FLAG MSG_NOSIGNAL
#define MAKEWORD(x,y) (((x<<8)&0xFF00)|(y&0xFF))
typedef int SOCKET;
typedef struct WSAData
{
unsigned short wVersion;
unsigned short wHighVersion;
char szDescription[20];
char szSystemStatus[20];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char* lpVendorInfo;
}WSADATA;
typedef WSADATA* LPWSADATA;
-
-
-
- WSAStartup函数
-
-
本函数必须是应用程序或DLL调用的第一个Windows Sockets函数.它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节.应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数.
此函数对于linux没有意义,封装此函数只需简单的返回0即可,在移植文件中此函
数声明和定义如下:
int WSAStartup(unsigned short wVersionRequested, LPWSADATA lpWSAData)
{
return 0;
}
-
-
-
- WSACleanup函数
-
-
应用程序在完成对请求的Socket库使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。
此函数对于linux没有意义,封装此函数只需简单的返回0即可,在移植文件中此函
数声明和定义如下:
int WSACleanup(void)
{
return(0);
}
-
-
-
- closesocket函数
-
-
本函数用来关闭一个套接字。在移植文件中此函数声明和定义如下:
int closesocket(SOCKET s)
{
close(s);
return 0;
}
-
-
-
- ioctlsocket函数
-
-
本函数用于设置套接口相关的操作参数,在移植此函数声明和定义如下:
int ioctlsocket(SOCKET s, unsigned long dwCmd, unsigned long* pArg)
{
return(ioctl(s, nCmd, pArg));
}
-
-
-
- 其它函数使用说明
-
-
以下函数不需要移植,windows和 linux的函数定义和语义是相同或类似。
1、socket()
生成一个套接字。
2、bind()
绑定套接字的本地IP地址和端口号。
3、listen()
用于TCP监听套接字。
4、accept()
接收来自外部的TCP连接请求。
5、connect()
连接远程TCP服务器。
6、recv()
用于TCP连接接收数据。
7、send()
8、recvfrom()
用于UDP连接接收数据。
9、sendto()
用于UDP连接发送数据。
10、select()相关函数,实现多路IO复用。
FD_ZERO、FD_SET、FD_ISSET、FD_CLR
Linux或windows平台采用select函数的多路复用方案, 最大支持上千个并发TCP连接访问。如果想支持几万、几十万乃至上百万的TCP并发连接,在linux下需要采用epoll网络编程,但是在windows/VC平台上不支持epoll的开发接口,只是提供完成端口方式。作者通过使用VC的select函数封装成epoll的函数形式,这样就可以在VC上使用epoll函数进行开发、调试、测试,由于linux本来就支持 epoll,移植过去毫无障碍。关于在VC下用select相关函数封装成epoll函数作者在专门的开发手册中会详细介绍。
作者体会: Windows对支持大TCP并发很糟糕,即使使用完成端口。 强烈建议使用linux/epoll的方案实现支持TCP大规模访问。
windows提供以下2个API函数读取Windows的ini配置文件。
UINT GetPrivateProfileInt(const char* lpAppName,
const char* lpKeyName,
int nDefault,
const char* lpFileName);
unsigned int GetPrivateProfileString(const char* lpAppName,
const char* lpKeyName,
const char* lpDefault,
char* lpReturnedString,
unsigned int nSize,
const char* lpFileName);
暂时作者没有提供修改配置文件的函数WritePrivateProfileString()。
1、加载动态库函数
HINSTANCE LoadLibrary(char* sFileName)
{
return(dlopen(sFileName, RTLD_LAZY));
}
2、获取动态库函数的地址
void* GetProcAddress(HINSTANCE hInst, char* sFunctionName)
{
return(dlsym(hInst, sFunctionName));
}
3、卸载动态库函数
BOOL FreeLibrary(HINSTANCE hInst)
{
int nRet;
nRet = dlclose(hInst);
if(nRet == 0)
{
return TRUE;
}
else
{
return FALSE;
}
}
- 文件/目录兼容
由于windows目录采用符号:\ 分隔,而linuxmu.lu采用符号/分隔,源码中的目录必须采用以下的宏定义。
#define PATH_SP '/'
#define PATH_SPS "/"
实例代码:
char sRootDir[1024];
sprintf(sRootDir, "%s%cmain.ini", GetCurrentDir(), PATH_SP);
假设函数GetCurrentDir()返回当前路径。
作者提供了以下目录和文件操作的函数:
1、获取当前路径
int GetCurrentDirectory(int len, char* sBuf);
2、 删除指定文件
BOOL DeleteFile(char* sFile);
3、创建目录
BOOL CreateDirectory(char* sFilePath, void* pPara);
4、移动文件
BOOL MoveFile(char* sSourFile, char* sDestFile);
5、 文件重命名
rename(char* sSourFile, char* sDestFile);
常用错误码的宏定义如下:
#define WSAEWOULDBLOCK EWOULDBLOCK
#define PEINPROGRESS EINPROGRESS
#define ERROR_ALREADY_EXISTS EEXIST
以下函数返回线程当前的错误码,函数声明和定义如下:
int WSAGetLastError()
{
return errno;
}
int GetLastError()
{
return errno;
}
- VC工程源码兼容性调整
1、不试用任何MFC类库,推荐试用标准库跨平台的第三方功能类库。
2、文件操作尽可能试用FILE(fopen、fclose、fseek、fread、fwrite)以及标准的IO函数(open、
close、read、write等)
3、c/c++语法选取VC和GCC/G++共同支持的语法规则
4、socket编程不要实用VC特有支持异步事件的函数WSA,实用标准的socket函数。
作者提供了移植文件为: PortWindows.cpp、PortWindows.h,开发人员把这2个文件加入到VC的工程中进行编译。
在移植文件中进行以下宏定义
#define MSG_NOSIGNAL 0
-
- socket函数调整
1、send()函数
用于TCP连接发送数据,注意第4个参数在VC下默认填写0,建议在使用
MSG_NOSIGNAL宏定义。
示例代码如下:
int nRet;
nRet = send(s, "1234567890", 10, MSG_NOSIGNAL);
Windows的读写锁不需要释放,也没有对应的函数,为了兼容linux,新增一个释放读写锁的函数DeleteSRWLock,此函数不执行任何功能,直接返回。
void DeleteSRWLock(LPSRWLOCK pLock)
{
return;
}
由于作者一向倾向于采用单进程多线程的方式实现服务器程序,本方案的实现对支持多进程的编程开始模式很弱。例如对进程间通讯的支持基本没实现,例如:共享内存、信号量、消息队列、管道等,还有就是进程间的线程锁互斥和同步、文件映射等。如果读者需要,可根据需要自行实现或采用其他的方案解决。
1、基于本文的移植方案的linux程序相比原生的linux程序,运行效率会降低一点,但是相
对整体来说,可以忽略不记,毕竟只是增加了一层简单的移植外壳代码。
2、有些Windows下的代码无法通过linux的移植文件实现,建议windows和linux分别实现,
然后通过加入预编译指令,分别编译。
3、需要购买VC的正版授权。
4、需要面临被微软断供的可能。
作者长期从事C/C++进行后台服务器程序开发,目前所有的项目或工程都支持跨平台,项目包括:
1、功能类库SDK
1)内存分配管理
2)数据结构类
链表、avl二叉树、红黑二叉树、trie查找树、trie模糊树、多级目录模糊匹配树、多
级树、内存表。
3)解析类库
xml解析、Json解析等。
4)其他
消息队列等。
2、内存数据库系统
3、脚本引擎
4、WebServer产品
5、消息网关类
短信网关、MQTT服务器系统等消息转发处理产品。
以上所有的项目都是基于VC开发、调试测试,然后一次性移植到linux平台,效果明显。