ACG狂人

其实我更爱姐汁...

#

封装MySQL时解决的两个字符集问题

在我开始封装MySQL C API时,真的没有想过居然会在字符集问题上连续卡壳,折腾了一下午+一晚上,终于搞定这些问题。下面记录一下我遇见的问题以及解决办法,以作备忘。
首先遇见的当然是中文乱码问题。这个问题相对来说资料比较多,文档写得应该也是很清楚的,但是我没心思看,靠猜搞定。我首先设置MySQL服务器的默认字符集为UTF8,并保证我操作的表中的列字符集也是UTF8,然后重启MySQL服务,并尝试用mysql_query("INSERT INTO TestTable VALUES('你好')")来写入,结果失败在预料之中。我猜可能可以使用类似"INSERT INTO TestTable VALUES('\0x2734\0x3432')"这样的方式写入中文,但这样太丑了,也没有实用价值,我坚决不会去用。我又尝试使用MultiByteToWideChar去做,甚至使用了Qt的QTextCodec去转换“你好”这两个字,结果依然失败。这就有些蹊跷了,我决定调用mysql_character_set_name()看看到底有没有正确的设置成UTF8格式,让我意料之外的是:结果是latin1,这难道是拉丁字符集??我查看了一下API函数的相关说明,发现这里获取到的是客户端链接的字符集,我突然觉得,也许MySQL可以自动的在客户端与服务器之间的字符集进行转化!说做就做,我查到了可以用mysql_options()或者mysql_set_character_name()函数来设置客户端链接字符集,但这两个函数用的时机分别在调用mysql_real_connect()之前或之后。我将客户端链接设置成gb2312字符集,这是VC中支持中文的标准字符集之一(另一个标准是UTF16,需要用wchar_t  wstring和常量字符串前的L),结果,当然是非常正确的插入中文啦,呵呵!
正当我觉得下面的封装肯定会顺汤顺水之时,在封装基本完成的时候,我发现控制台不能输出我从MySQL取出的中文了!这太奇怪了,输出英文就没有问题,而中文虽然的确被准确无误的读入了std::string中(通过单步调试和ofstream得到确认),然而输出到cout中却会出错。为什么文件流没问题,控制台流会出问题呢?我跟踪了代码,发现在fputc()函数中出现了问题。另一个奇特的地方,就是在mysql_init()函数被调用之前,cout可以正确输出中文,但调用之后就不能输出,iostate被赋予4值,即badbit。我求了一下google,发现sputc()中的一个_nolock_write()函数是msvcrtd.lib中的函数,晕了,我在单步调试中也发现就是这里有问题,确切的说,mysql_init()函数被调用后,_nolock_write()函数就会出错。哈哈,总结出了这些特点之后,我已经隐约猜到了是哪里的问题了,还记得前几日才写的blog里的东西,我由于想要将libmysql.lib换成mysqlclient.lib静态库,所以便禁掉了msvcrtd.lib库,我肯定这里面有关联。
到底是什么原因呢?我觉得,由于这个MySQL的SDK不是我自己编译的,mysqlclient.lib里面调用crt函数时,对字符集的本地化做了设置,由于是欧美的字符集设置,所以一旦我调用了mysqlclient.lib里的函数,就无法再显示中文了。
猜出了问题的根源,那么如何解决这个问题呢?我可以肯定的是,使用std::cout.imbue(std::locale())是行不通的,那么下面就是列出的几个办法:
1、使用人家编译好的动态库,libmysql.lib,并将libmysql.dll拷贝过来,由于动态库和你的程序共享crt dll,因此字符集设置应该会是一致的。
2、如果你还是想用静态库,可以考虑自己下载源码并编译一遍,这是根本的解决办法。
3、如果你可以自己编译,且想用mysqlclient.lib,那么就重新编译mysqlclient吧,动态静态都可以。不过官网上下载的编译好的mysqlclient.lib是静态的,不能使用。

我最终的解决办法是第1种,因为比较懒,不想自己再去编译了,呵呵。

posted @ 2009-10-09 22:29 酿妹汁 阅读(1937) | 评论 (3)编辑 收藏

原来静态库是这样链接的

最近在win32平台下封装MySQL的客户端C API时,出现的问题着实让我头痛了个把小时,不过多亏这一来,我终于明白了静态库是怎样链接的,结合了以前我对动态库与静态库之间进行比较的一些测试结论,也明白了为什么静态库需要这样的设计。不由感叹一下古人的那句话:“挫折果真是成功的母亲。”(好大雾^_^!)
闲话不多说,记录一些关键点,首先是静态链接到底和动态链接有哪些不太容易发现的区别呢?我来假设libA依赖libB,那么我的执行文件在使用libA的时候也需要导入libB才能正确链接,但若dllA依赖libB的话,则只需导入dllA就可以。我在一开始用MySQL时使用的内嵌数据库模式,所以导入的是那个libmysql.dll动态库,因此没出现问题。而换上了mysqlclient.lib后,居然告诉我没有找到socket相关的实现,即“无法解析的外部符号”这类错误,于是乎我只有加上ws2_32.lib库才能通过。
为什么这样设计?原因其实跟静态库的连接方式有关,由于静态库直接将函数实现和全局静态变量导入到包含它的执行体中,所以在多层多重的库包含中就会有大量重定义的问题存在,想想你在一个静态库中用了单件模式,那么其他库又包含你这个库,最后exe又包含所有这些库,最终形成菱形依赖,如果静态库不这样设计的话,问题就会像C++的多重继承问题一样。之所以静态库这样设计,跟C++的虚拟继承思想简直如出一辙。(我知道这段话其实只有已经懂了的人才能看得懂,不过没办法,我表达能力挺笨的...)

还有个极度郁闷的地方是:mysqlclient.lib中的libcmtd.lib和msvcrtd.lib这对活宝居然也被链接进去了,我这边首先需要忽略这两个默认库,然后再包含msvcrtd.lib才可以。我不知道为啥sdk提供的静态库非得在c runtime link的方式上跟一般人过不去,又懒得去自己编译,哎!将就一下就这么用吧......

posted @ 2009-10-06 21:30 酿妹汁 阅读(1548) | 评论 (0)编辑 收藏

PhysX动工

PhysX物理引擎是众多可选的物理引擎之一,但为什么我要选PhysX而不是其他呢?也许下面的一些论述纯粹是自己的意淫,因为并没有真正的去测试过这些,不对之处请看官们指点一二,免让我这个糊涂虫误入歧途。

选择PhysX的原因是这样的,拿目前与PhysX对立的Havok引擎来做比较,Intel和AMD-ATI支持的Havok引擎主要致力于CPU运算物理效果,而nvidia独推的cuda架构和PhysX引擎则主要致力于GPU或GPU群集运算,且不谈在游戏客户端的物理效果到底是用GPU好还是CPU好(这跟你GPU是否忙得过来有关),由于目前正在攻关的游戏开发项目需求较为特殊,我在这里只关注服务器方面的高性能刚体物理运算问题。nvidia近两年推出的cuda架构群集不断走向成熟,这种群集是用GPU阵列作为运算核心的超级并行计算机,在物理运算方面与CPU所组成的群集不是一个档次,总体运算性能甚至超过同等价位的CPU群集近百倍!我考虑将物理运算完全交给独立的GPU群集,让CPU去做那些它所擅长的工作。也许这正是符合了nvidia推广cuda的思想吧,因此看着PhysX对我就有格外的吸引力,所以当然是奋力研究了。

于是去了nvidia官网的Download区,先搞下来三个东西:cuda sdk、cuda toolkit、cuda 那个啥??没啥,只是随便看看,顺带装了下cuda显卡驱动,呵呵。于是又下了PhysX的SDK,好了,等一切安装完毕,设置好包含目录和库目录后就开始了我的HelloWorld历程!写一个PhysX的HelloWorld较为麻烦,要有能看得见的效果还得累一阵子,于是直接拿sample里的教程进行修改,第一步当然是戳一戳刚体碰撞了,发现PhysX的抽象建模比我料想的好很多,基本上我所考虑到的它全有了,而且理解起来非常简单。

PhysX的建立过程只有很少量的代码,首先是建立一个SDK对象,然后建立碰撞场景NxScene。
在PhysX中,一个可以检测碰撞的对象叫做NxActor,这个NxActor的建立除了需要一个指定的NxScene以外,还需要一些描述信息,这些描述如下:
碰撞对象的外形描述: 统一用的是NxXXXShapeDesc的命名方式,譬如NxBoxShapeDesc,甚至NxTranglesMeshShapeDesc之类的,你可以将任意的3D形态做成NxActor的外形,物理引擎会根据这个外形的面来做碰撞检测和质点的根据。
碰撞对象的体型描述: 即NxBodyDesc,用于记录一个碰撞对象的质量、初始速度、初始冲量等信息,感觉似乎是将一个对象作为一个质点考虑时的一些数据,总之,没有这个描述的话,碰撞对象会被视为一个静态对象,即不能主动变动坐标的物体,亦不会存在质量和速度等信息。
材质ID:材质是在物理引擎的NxScene中建立的,每个对象所使用的材质ID都是指的是NxScene中的第几个材质。材质包括了摩擦力和弹力之类的信息。

恩,目前还没看到joint的使用,估计就是连接两个碰撞对象的一种力学关系吧,来回摆动的吊篮,上面又要能站人,肯定需要这个。这样看来,刚体基本上不存在什么问题了,而在我目前攻关的服务器部分,只需要刚体部分,因此PhysX的学习目前到此为止了,接下来就是对游戏服务器的框架设计上多下功夫了。

posted @ 2009-09-03 01:28 酿妹汁 阅读(1888) | 评论 (2)编辑 收藏

异常处理的可恶陷阱

也许这个错误是很幼稚的,但是他着实难住了我好几个小时,一度我还以为永远解不开这个谜题了(*o*),情况是这样的,异常在抛出之后,到达处理异常的catch域,但这时上下文必须依然有效才行,否则catch将接收不到抛出的异常。具体代码如下:
    try
    
{
        moeutil::simple_pool spool(
32);
        
void* p1 = spool.malloc(15);
        
void* p2 = spool.malloc(102);
        
void* p3 = spool.malloc(154);
        
void* p4 = spool.malloc(7);
        
void* p5 = spool.malloc(7);
        
void* p6 = spool.malloc(70);
        
//std::cout<<p1<<'\n'<<p2<<'\n'<<p3<<'\n'<<p4<<'\n'<<p5<<'\n'<<p6<<std::endl;
        spool.free(p1);
        spool.free(reinterpret_cast
<char*>(p2)+2);
        spool.free(p3);
        spool.free(p4);
        spool.free(p5);
        spool.free(p6);

    }
catch (moeutil::Exception& e){
        std::cout
<<e.what()<<std::endl;
    }
这是一个简易的内存池类,其中simple_pool的析构函数会抛出异常,然后free函数也会抛出异常。析构函数可以检测是否有内存泄漏,而free函数是检测到无效的传入指针时会抛出异常。析构抛出异常是没问题的,但是free抛出异常的时候问题就来了,当free抛出异常时,由于会脱离try块,所以spool也会被析构,这样就会陷入麻烦,由于没有全部free,所以析构本身也会抛出异常,导致了类似于异常迭代的情况。也就是说,在throw一个异常的过程中又一次触发了异常。于是windows系统的debug error对话框出现了,而且是出现在throw那一行。
此随笔用于提醒自己勿犯第二次!

posted @ 2009-08-02 17:12 酿妹汁 阅读(607) | 评论 (1)编辑 收藏

使用boost库需要一定的素质

        可能由于在几个博客上发布了一些boost库使用心得的关系,最近总是被一些相关的询问邮件骚扰,而提问者问的问题却又让我不知道如何回答才好,或者说根本没有办法回答。一些问题根本就不该问的,所以便发些感慨,还望看了本人一些心得又不禁想问一些偷懒问题的各位手下留情,不要再问我一些奇怪的问题了(*w*)!
        我们学习并使用boost库的目的是简化编程,这并不代表简化到你根本不需要去理解原理的地步。我想,如果您希望很好的驾驭boost库的话,那么请对您自己发发狠努力编写代码积累经验,直到您认为boost库中30%的功能您都能够亲自动手实现的时候,才尝试去使用它。类似“怎么让线程组和asio合作使用”之类的问题,我觉得很囧,而且问的人还非常多,给代码都还迷糊。我觉得您想用asio,请先尝试编写线程类和IOCP框架的应用程序,如果您连线程类都写不好,或者连一个多线程死锁和共享区都不能安全排除的话,请先打打基础吧。
        boost库是一个非常有深度的东西,也是智慧的结晶,有很多东西在一定条件下您可以不用深究,但是起码的30%则必须刨根问底,这30%实际上都是很基础的东西,切不可急于一时而至整个工程最终因为boost的一点小问题卡壳,倘若无法驾驭,则不如不用,再急我也没法帮您了!

posted @ 2009-07-30 18:19 酿妹汁 阅读(18909) | 评论 (26)编辑 收藏

VC编译器中的一些编译时命令备忘

 

// 加载静态库
#pragma comment(lib, "路径+LIB库名")

// 禁止1221号警告
#pragma warning(disable: 1221)

// 导入并加载DLL exe之类的二进制动态库
#import "路径+二进制库名"

// 将ClassName类中的s_Data静态成员变量定义在头文件中时防止重定义
__declspec(selectany) int ClassName::s_Data = 0;

// 当用户使用void func()函数时,编译器将作出警告,并显示“不推荐使用该函数”字样
__declspec(deprecated("不推荐使用该函数")) void func();

posted @ 2009-07-30 18:00 酿妹汁 阅读(895) | 评论 (0)编辑 收藏

记录一下C++标准流感悟

        今天终于完成了自己的日志库核心的几个类和算法,折腾了许久,感觉安全性很好,一些多线程的死锁问题已经逐个排除了。由于需要跟C++标准库的输入输出流共事,所以么总算自认为理解了C++标准流的一些特性,记下来以供备忘。
        首先是文件流,关于该流的问题,要扯就扯远了,主要说一说std::wofstream的问题。不要被这个w骗了,其实它输出到文件上的依然还是那么回事,没有丝毫的变化。如果你是想输出Unicode到文本文件中,那么你最好是用ios::binary模式,然后一股脑的倒入文件中,如果要是使用<<重载符号的话,输出到文件中的跟std::ofstream没区别,而且中文还无法输出。如果你遇见中文无法输出的问题,恩,可以调用std::wofstream::imbue()方法,具体也就是一行:ofs.imbue(std::locale("chs"));搞定。
我想,C++标准库并不支持Unicode编码,而是给字符地域化的编码方式,中文的可能也就是GBK吧,听说C++0x标准会纳入Unicode编码支持,如果是这样的话就省心多了。
        然后还有个不省心的地方就是流的streambuf中的缓冲大小了,我发现,fstream中的写缓冲居然一直是0啊,这个就很头疼了,需要给文件流指定缓冲的大小。

posted @ 2009-07-29 23:46 酿妹汁 阅读(980) | 评论 (2)编辑 收藏

DLL的共享测试

今天稍微实验了一下DLL的数据共享问题,做了两种情况下的测试,第一种是两个进程同时调用一个DLL的实验,另一种是一个进程调用一个两个DLL,然后这两个DLL有依赖关系,记录一下得到的测试结果。

当一个进程调用LoadLibrary、LoadLibraryEx以及FreeLibrary时,DllMain会被调用,从DllMain的入口函数的第二个参数可以得知是加载还是释放。LoadLibraryEx的flag参数传入DONT_RESOLVE_DLL_REFERENCES可以跳过DllMain的调用,这在很多时候是有用的。
当一个进程在调用LoadLibrary后又启动了线程,则每启动一个线程,就会调用一次DllMain,同样可以从DllMain的第二个参数的值来区分调用时由进程引起的还是由线程引起的。第二个参数的值一般有4种,如下:
 DLL_PROCESS_ATTACH
 DLL_PROCESS_DETACH
 DLL_THREAD_ATTACH
 DLL_THREAD_DETACH
不用解释也能看得出来分别是表示什么意义了,通过这个可以选择性的对DLL中的数据进行初始化。
如果是两个进程同时调用一个DLL的话,数据是不会共享的,除非你设置了seg项为共享,具体不同的编译器会有不同的设置方法。两个进程对同一个DLL的调用计数也是分开的,这让我很放心了,之前一直害怕的冲突问题释然。


当一个进程去调用一个DLL甲,而甲又在初始化的时候调用了DLL乙,进程通过甲的函数来访问了DLL乙,同时自己也亲自调用了DLL乙并取出和更改了数据。这样的情况下,DLL乙的引用计数是共享的,数据也是共享的,加载和释放都很安全,之前担心DLL乙中的单件会被重复调用的情况也不可能存在了。

posted @ 2009-06-27 23:04 酿妹汁 阅读(544) | 评论 (0)编辑 收藏

ASIO攻破!!!

花了足足3天时间,外加1天心情休整,终于在第5天编写出了一个能运行的基于asio和thread_group的框架,差点没气晕过去,把源码都看懂了才感觉会用了。
测试了一下,debug下一万次回应耗时800+毫秒,release下是200+毫秒,机器配置双核2.5G英特尔,4个线程并行工作,无错的感觉真好,再也不用担心iocp出一些奇怪的问题啦,因为是巨人们写的实现,呵呵。

进入正题,简要说一下asio的实现原理吧。在win32平台上,asio是基于IOCP技术实现的,我以前也用过IOCP,却没想到居然能扩展成这样,真是神奇!在其他平台下还会有别的方法去实现,具体见io_service类下面这部分的源码:
  // The type of the platform-specific implementation.
#if defined(BOOST_ASIO_HAS_IOCP)
  typedef detail::win_iocp_io_service impl_type;
  friend 
class detail::win_iocp_overlapped_ptr;
#elif defined(BOOST_ASIO_HAS_EPOLL)
  typedef detail::task_io_service
<detail::epoll_reactor<false> > impl_type;
#elif defined(BOOST_ASIO_HAS_KQUEUE)
  typedef detail::task_io_service
<detail::kqueue_reactor<false> > impl_type;
#elif defined(BOOST_ASIO_HAS_DEV_POLL)
  typedef detail::task_io_service
<detail::dev_poll_reactor<false> > impl_type;
#else
  typedef detail::task_io_service
<detail::select_reactor<false> > impl_type;
#endif
这部分代码其实就在boost::asio::io_service类声明中的最前面几行,可以看见在不同平台下,io_service类的实现将会不同。很显然,windows平台下当然是win_iocp_io_service类为实现了(不过我一开始还以为win_iocp_io_service是直接拿出来用的呢,还在疑惑这样怎么有移植性呢?官方文档也对该类只字不提,其实我卡壳就是卡在这里了,差点就直接用这个类了^_^!)。

那么就分析一下win_iocp_io_service的代码吧,这里完全是用IOCP来路由各种任务,大家使用post来委托任务,内部调用的其实是IOCP的PostQueuedCompletionStatus函数,然后线程们用run来接受任务,内部其实是阻塞在IOCP的GetQueuedCompletionStatus函数上,一旦有了任务就立即返回,执行完后再一个循环,继续阻塞在这里等待下一个任务的到来,这种设计思想堪称神奇,对线程、服务以及任务完全解耦,灵活度达到了如此高度,不愧为boost库的东西!我只能有拜的份了...

说一下总体的设计思想,其实io_service就像是劳工中介所,而一个线程就是一个劳工,而调用post的模块相当于富人们,他们去中介所委托任务,而劳工们就听候中介所的调遣去执行这些任务,任务的内容就写在富人们给你的handler上,也就是函数指针,指针指向具体实现就是任务的实质内容。其实在整个过程中,富人们都不知道是哪个劳工帮他们做的工作,只知道是中介所负责完成这些就可以了。这使得逻辑上的耦合降到了最低。不过这样的比喻也有个不恰当的地方,如果硬要这样比喻的话,我只能说:其实劳工里面也有很多富人的^o^! 。很多劳工在完成任务的过程中自己也托给中介所一些任务,然后这些任务很可能还是自己去完成。这也难怪,运行代码的总是这些线程,那么调用post的肯定也会有这些线程了,不过不管怎么说,如此循环往复可以解决问题就行,比喻不见得就得恰当,任何事物之间都不可能完全相同,只要能阐述思想就行。

最后还要说明的一点就是:委托的任务其实可以设定执行的时间的,很不错的设定,内部实现则是通过定时器原理,GetQueuedCompletionStatus有一个等待时间的参数似乎被用在这方面,还有源码中的定时器线程我并没有过多的去理解,总之大体原理已基本掌握,剩下的就是使劲的用它了!!!

另外为了方便人交流,在这里插入一些代码可能更容易让人理解吧,
下面这个是启动服务时的代码:
void ServerFramework::run()
{
    boost::thread_group workers;
    
for (uint32 i = 0; i < mWorkerCount; ++i)
        workers.create_thread(
            boost::bind(
&boost::asio::io_service::run, &mIoService));
    workers.join_all();
}

在打开前就得分配好任务,否则线程们运行起来就退出了,阻塞不住,任务的分配就交给open函数了,它是分配了监听端口的任务,一旦有了连接就会抛出一个任务,其中一个线程就会开始行动啦。
void ServerFramework::open(const String& address, const String& port, uint32 nWorkers /*= DEFAULT_WORKER_COUNT*/)
{
    
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
    boost::asio::ip::tcp::resolver resolver(mIoService);
    boost::asio::ip::tcp::resolver::query query(address, port);
    boost::asio::ip::tcp::endpoint endpoint 
= *resolver.resolve(query);

    mAcceptor.open(endpoint.protocol());
    mAcceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(
true));
    mAcceptor.bind(endpoint);
    mAcceptor.listen();

    mNextConnection 
= new Connection(this);
    mAcceptor.async_accept(mNextConnection
->getSocket(),
        boost::bind(
&ServerFramework::__onConnect, this,
        boost::asio::placeholders::error));

    mWorkerCount 
= nWorkers;
    
if (mWorkerCount == DEFAULT_WORKER_COUNT)
    
{
        mWorkerCount 
= 4;
    }

}

open函数中给io_service的一个任务就是在有链接访问服务器端口的情况下执行ServerFramework::__onConnect函数,有一点需要格外注意的,io_service必须时刻都有任务存在,否则线程io_service::run函数将返回,于是线程都会结束并销毁,程序将退出,所以,你必须保证无论何时都有任务存在,这样线程们即使空闲了也还是会继续等待,不会销毁。所以,我在ServerFramework::__onConnect函数中又一次给了io_service相同的任务,即:继续监听端口,有链接了还是调用ServerFramework::__onConnect函数。如果你在ServerFramework::__onConnect执行完了还没有给io_service任务的话,那么一切都晚了...... 代码如下:
void ServerFramework::__onConnect(const BoostSysErr& e)
{
    
if (e)
    
{
        MOELOG_DETAIL_WARN(e.message().c_str());
    }


    Connection
* p = mNextConnection;
    mNextConnection 
= new Connection(this);

    
// 再次进入监听状态
    mAcceptor.async_accept(mNextConnection->getSocket(),
        boost::bind(
&ServerFramework::__onConnect, this,
        boost::asio::placeholders::error));

    
// 处理当前链接
    __addConnection(p);
    p
->start();
}

最后,展示一下这个类的所有成员变量吧:
    // 用于线程池异步处理的核心对象
    boost::asio::io_service mIoService;

    
// 网络链接的接收器,用于接收请求进入的链接
    boost::asio::ip::tcp::acceptor mAcceptor;

    
// 指向下一个将要被使用的链接对象
    Connection* mNextConnection;

    
// 存储服务器链接对象的容器
    ConnectionSet mConnections;

    
//// 为链接对象容器准备的strand,防止并行调用mConnections
    //boost::asio::io_service::strand mStrand_mConnections;

    
// 为链接对象容器准备的同步锁,防止并行调用mConnections
    boost::mutex mMutex4ConnSet;

    
// 为控制台输出流准备的strand,防止并行调用std::cout
    AsioService::strand mStrand_ConsoleIostream;

    
// 工作线程的数量
    uint32 mWorkerCount;



但愿这篇随笔也能对正在研究asio的朋友们有所帮助吧。

posted @ 2009-06-26 22:14 酿妹汁 阅读(9829) | 评论 (15)编辑 收藏

MySQL的备忘录

首先安装完MySQL5.1.30后,可以进入控制台,输入 mysql -u root -p然后回车,如果没有密码的话,-p似乎就能省掉了。
进入后输入 show databases命令就可以看到所有的数据库了。
然后是新用户的问题,如果你想添加一个新的用户,有很下列三种方法:
1、直接在mysql数据库中的user表里直接insert,但是很麻烦
2、使用GRANT [权限] ON *.* TO [username]@[客户端地址] identified by [password]  这样的命令,具体可以看文档
3、使用CREATE USER [用户名] 这样的命令创建用户
这里要注意的是,第二种方法创建用户的话,必须带有密码,如果你想创建无密码用户的话,则必须用第3种方法。
最后就是给用户权限的问题,一般都使用GRANT语句,具体可参考文档,不过你也可以直接对db表直接进行修改和添加,只要你不怕麻烦。

posted @ 2009-06-26 21:30 酿妹汁 阅读(190) | 评论 (0)编辑 收藏

仅列出标题
共3页: 1 2 3