123

IOS、安卓IM语音聊天开发初探部分心得——本地音频处理篇(下)

前文书咱们说到IOS下如何录制一个wav格式的音频,然而现在的情况确实安卓不支持wav格式,于是有看官说了,你个二百五,就不能选个安卓支持的格式录制么,我很负责任的说,苹果和谷歌掐架,苦的就是我们这帮苦逼的技术人员。。。安卓的格式苹果全不支持,看好是全不,不是全部,反过来苹果的格式,安卓也不惯着。。。。

当然上有政策下有对策是万年不变的真理,Ios与安卓的音频互通是难不倒我们伟大的程序员的,而目前解决这个问题方案有很多种但大致以下3种方式,且听我细细道来。

第一种方案对于服务器负荷较大,不论是安卓端亦或是IOS端都将音频传输到服务器,通过服务器进行转换再进行转发。这种做法可以不受系统限制,但是信息量较大时对服务器负荷较大,对服务器端的要求很高。据传闻,微信就是采用这种方式进行的语音IM交互

第二种方案是不论IOS端还是安卓端都统一使用相同的第三方音频库进行编解码处理,然后再进行网络传输,优点是可供选择的音频库非常多,可以根据自己不同的需求选择各种各样的音频格式,但是因为不论是IOS端还是安卓端都需要对其进行i编码解码处理,而项目初期并没有设计这方面的需求所以如果双端都进行修改修改量实在太大。同样据传闻,同为语音IM的成熟案例微米就是依靠Speex的三方开源库来完成的,这种格式体积小,能降噪,是目前比较受推崇的方式。

我采用的是第三种方式,amr格式的音频文件是安卓系统中默认的录音文件,也算是安卓支持的很方便的音频文件,IOS系统曾经是支持这种格式的文件,自4.3以后才取消了对amr的支持(原因应该不需要我多说。。。),可见,amr格式的音频文件并非IOS处理不了的,因为有了这样的概念和潜意识的植入,我就开始一门心思在网络上找寻各种各样的实例以及demo,我要做的就是把问题尽量都解决在IOS端。终于功夫不负有心人,最终让我得以成功的在IOS端成功的转换出安卓端可以使用的amr文件。接下来,我们就说说如何在IOS端完成wav与amr文件的互转。

首先推荐给大伙提供一个demo在下面的连接下载。此demo转载自中国开源社区,本人发自内心的向发布者Jeans大人致以最崇高的敬意。

http://www.oschina.net/code/snippet_562429_12400

demo下载打开项目后将如下四个源码文件以及两个库文件拖入自己的项目,引用AudioToolbox.framework、CoreAudio.framework以及AVFouncation.framework即可完成类库的导入

打开我们导入的头文件就会发现有又大量的struct,而在开启ARC项目中是禁止使用struct和union的,而我们的项目确实可以开启ARC的,这里涉及到一个知识点,之前也在网络上看到有人提问在开启ARC后改如何使用struct,我也是接触这个项目之后开始涉及混编才了解该如何解决这个问题, 只要将Compile Sources As(设置编译源)的设置为Ojbective-C 或者将包含到声名struct和union头文件的实作文件的扩展名改为.mm就可以在项目中使用struct和union了,但是请注意此时你编写的代码不再是纯粹的Objective-C语言了,而是涉及到Objective-C ,此处涉及到混编的问题,我们再后面还会再讲解混编相关的内容,但并不会很多,感兴趣的看官可以自己查找资料。回到原题,如果在导入文件后遇到了编译错误,请点击项目的TARGETS下的Build Settings找到以下编译设置并按照图内容修改

注意,如果是新建空项目Compile Sources As的设置在According to File Type(依照文件类型选择编译源)的模式下应该也可以正常编译,尽量不要设置为Ojbective-C 进行编译,我是因为项目中含有其他的SDK需要用到所以才如此设置,一旦设置成Ojbective-C 会和我们之后讲的网络传输篇中所使用的SDK产生一定冲突很那解决。所以此最好保持According to File Type。

文件正常导入之后就可以直接使用转换方法了,和常规的SDK不同,这个库并非以累的形式封装的,而是数个功能函数,所以并不需要我们去构造对象,接下来我们说一下转换时具体用到的方法,虽然这些方法简单易用,但是我还是愿意为大家提供一点便利,所谓帮人到底送佛送到西,下面我每一个方法的名称、功能、参数说明以及使用示例贴出以供大家参考~。

哦~对了不要忘记,我们的第一步永远都是导入头文件

#import “amrFileCodec.h”;

接下来我们开始第一个函数EncodeWAVEFileToAMRFile从函数名称中就可看出,此方法是将WAV转换为AMR文件的,我们先来看一下示例

参数列表也并不是很复杂4个参数分别问:1.WAV的文件地址,2.AMR的文件地址,3.音频通道数,也就是我们上篇文章中所提到录制音频的最后一个参数,声道数量。4.编码位数,同样在上一篇文章中我们也已经介绍过不再赘述。

接下来第二个函数,DecodeAMRFileToWAVEFile这个参数与前一个功能正好相反,是从amr转换为WAV,下面是具体代码示例

这个参数可以说较上一个更加简单,第一个参数是需要一个AMR的文件地址也就是源,第二个参数则是目标地址也就是一个WAV文件的地址,简单的两个参数就可完成调用了。需要注意的是,此处所使用的地址和之前我们再使用AVFouncation的时候又不同了,它既不是要NSString的字符串,也不是NSURL对象而是一个const char的指针,但是这并不是问题,实例代码中所转换的方法并不是最简的只是急于演示所以拖拽出来的,希望有心的看官可以自行过滤,过眼不过心是编程大忌。

相对于导入可以说使用的方法简单的一塌糊涂,并不需要我们多少功夫,也没有那么高深莫测,但是测试还是要下一定功夫的,经过实机检测IOS下录制出的WAV转换为AMR之后放到安卓平台可以正常播放,而安卓录制的AMR文件拿到IOS下转换出WAV一样可以播放完全没有任何问题。但是这个方法也是有一定的弊端,音频转换的速度较慢,如果是时间较长的音频文件转换起来会有短时间顿卡,但是用来实现语音IM聊天是完全可以满足的

至此我们本地音频处理篇的内容全部完结,也算是告一段落,但是我们现在只是在本地机器上实现了正确的音频转换以及播放,想要完成语音IM聊天我们还差关键的环节就是与服务器的交互,详细的内容,我们将在下一篇文章中介绍,尽请关注IOS、安卓IM语音聊天开发初探部分心得——异步Socket传输篇

IOS、安卓IM语音聊天开发初探部分心得——本地音频处理篇(上)

最近的项目的内容开始涉及到一定的IM语音对讲的内容,而笔者从未接触过此类开发,也只是在摸索中一点点探索学习,几日下来略有了一点收获,以博客的形式跟诸位看官分享

先说明一下什么是IM语音聊天,IM全称Instant Messenger,即时通讯,简单的来说就是MSN ,QQ一类的聊天软件。而IM语音聊天即是使用语音音频来代替传统文字交流的方式进行沟通交流,目前市面上的语音IM根据聊天的方式又几种不同的方式,一种是即时发言犹如电话通信一样的语音通信,其中比较具有代表的软件有UCTalk,YY语音等,此类语音软件以PC平台为主,另外一种则是先进行录音之后发送形式与传统文字IM的形式略有相似之处的聊天方式,比较具有代表性的软件有微信,陌陌等,此类的语音软件大多出现在移动端,PC平台上同时也具有少量的使用。还有一种则可以视为介于两者中间的,模仿传统对讲机形式进行轮流发言的聊天方式,这种方式只在PC台上少量使用出现过,较前两者使用的范围比较小。

本次我们做的是移动端的项目,于是也难免于俗套使用先录制后发送的方式实现语音聊天,鄙人才疏学浅了解不深,所以可能是主观上的臆断,感觉以录制发送的形式实现的语音IM实现起来从技术将要简单很多,毕竟不会设计流媒体的问题。

既然是语音聊天难免涉及到一些音频相关的问题,笔者是负责IOS端开发的,所以大部分的内容以IOS角度为主,IOS提供的AVFoundation框架可以实现大部分系统声音服务不支持的超过30秒的音频播放功能,同时还提供了录音功能。而我们主要使用到的是AVAudioRecorder与AVAudioPlayer两个类,通过名字我们就可以判断出,前者是提供音频录制服务而后者则是提供播放服务。AVAudioRecorder以各种不同的格式将声音录制到内存或设备本地文件中。录音过程可再应用程序执行其他功能时持续进行。而AVAudioPlayer能够播放任意长度的音频。使用这个类可以实现游戏配乐和其他复杂的音频应用程序。可以全名控制播放过程,包括同时播放多个音频文件等。无疑IOS提供的音频服务是强大以及便利的。再使用AVFoundation框架之前必须要将AVFoundation.framework与CoreAudio.framework加入到项目中,再导入两个接口文件。

#import<AVFoundation/AVFoundation.h>

#import<CoreAudio/CoreAudioTypes.h>

具体的使用实例代码如下,首先是音频录制的使用方法:

然后是播放音频的部分:

现在我我们来详细解读一下者两段代码的含义,首先是音频录制的代码,我们先后声明并且定义了一下几样东西,创建音频的参数键值对MyRecordParam,一个路径数组pathArray,一个Docment路径字符串DocmentPath以及我们这一步的主角AVAudioRecorder对象MyRecorder。

我们先来解释一下路径的获取,至于音频参数,重头戏需要放在后面不是么~

NSSearchPathForDirectoriesInDomains是IOS中一个搜索路径的方法,它三个参数前两个为枚举,而最后一个参数为BOOL类型,第一个参数的枚举列表如下:

enum {

NSApplicationDirectory = 1,//Supported applications (/Applications)

NSDemoApplicationDirectory,//Unsupported applications and demonstration versions

NSDeveloperApplicationDirectory,//Developer applications (/Developer/Applications)

NSAdminApplicationDirectory,//System and network administration applications

NSLibraryDirectory,//Various user-visible documentation, support, and configuration files (/Library)

NSDeveloperDirectory,//Developer resources (/Developer)

NSUserDirectory,//User home directories (/Users)

NSDocumentationDirectory,//

NSDocumentDirectory,//

NSCoreServiceDirectory,//Location of core services (System/Library/CoreServices)

NSAutosavedInformationDirectory = 11,//Location of user’s autosaved documents Library/Autosave Information

NSDesktopDirectory = 12,//

NSCachesDirectory = 13,//Location of discardable cache files (Library/Caches)

NSApplicationSupportDirectory = 14,//Location of application support files (Library/Application Support)

NSDownloadsDirectory = 15,//

NSInputMethodsDirectory = 16,//

NSMoviesDirectory = 17,//

NSMusicDirectory = 18,//

NSPicturesDirectory = 19,//

NSPrinterDescriptionDirectory = 20,//

NSSharedPublicDirectory = 21,//

NSPreferencePanesDirectory = 22,//

NSItemReplacementDirectory = 99,//

NSAllApplicationsDirectory = 100,//

NSAllLibrariesDirectory = 101//

};

其每一项代表一种希望获取到的目录类型,这其中不只是IOS中的目录类型,也有MAC下的路径类型,没错,就跟你想的一样,这个函数并非IOS下专用。

第二个参数的枚举列表如下

enum {

NSUserDomainMask = 1,//用户主目录中

NSLocalDomainMask = 2,//当前机器中

NSNetworkDomainMask = 4,//网络中可见的主机

NSSystemDomainMask = 8,//系统目录,不可修改(/System)

NSAllDomainsMask = 0x0ffff,//全部

};

第二个参数代表要搜索路径的位置,本机?亦或是当前程序,还是局域网连接到的其他电脑。

第三个参数是一个BOOL值他代表是否将返回完整路径

而返回的路径中并不包含文件名,我们一定要记住再路径结尾处加上我们想要的文件名,别忘了我们是要创建一个音频文件。

当然此函数搜索的结果可能又很多条路径,因为根据你的参数不同他返回的路径甚至可能包含其他电脑上的(具体本人未测,有心人可进一步测试,也希望其讲结果与大家分享)所以他的结果是一个数组,而我们要取得的路径目的极为明确,就是程序的Docment路径,并且可以更加肯定是我们的程序只有一个Docment路径,所以我们直接取得了第一条返回记录。

不得不说的是AVAudioRecorder的设计者是个好人,没错,他没有将构造函数的参数设置成一大堆参数,那让人看起来头疼,但其实他用了一个更加让人头疼的方法,没错他让你去手动设置一个参数键值对,你甚至不知道建值对中该填什么参数,哪些参数…这对于习惯看参数列表直接调用方法的人无疑是个噩梦(尤其是当他们英文文档阅读能力低下时- -),目前我所掌握的参数键的相关资料如下:

AVSampleRateKey, //采样率

AVFormatIDKey,//音频编码格式

AVLinearPCMBitDepthKey,//采样位数 默认 16

AVNumberOfChannelsKey,//通道的数目

AVLinearPCMIsBigEndianKey,//大端还是小端 是内存的组织方式

AVLinearPCMIsFloatKey,//采样信号是整数还是浮点数

AVEncoderAudioQualityKey,//音频编码质量

鉴于考虑到可能各位看官对于我们所要给出的参数并不了解,所以在此我们来依次解释一下每一个参数的含义

首先是采样率,简单地说就是通过波形采样的方法记录1秒钟长度的声音,需要多少个数据。44KHz采样率的声音就是要花费44000个数据来描述1秒钟的声音波形。原则上采样率越高,声音的质量越好。

编码格式可以理解为每种音频格式不同的编解码方式,鄙人对于此了解的也不是非常多(能熟知所有编解码的人一定是偶像级的超人!)而IOS下这些编码方式被集中到一个枚举中,而我们本次代码中所使用的编码格式是WAV文件的格式,想要使用其他的编码格式就在成功导入AVFouncation框架之后即可通过Xcode的自动提示找到以kAudioFormat开头的各种枚举的名称。

采样位数即采样值或取样值,是用来衡量声音波动变化的参数,是指声卡在采集和播放声音文件时所使用数字声音信号的二进制位数。声卡的位客观地反映了数字声音信号对输入声音信号描述的准确程度。

通道数目应该很好理解了,1意味着单声道声音,2指立体声,4是指四个声道等等。

接下来的AVLinearPCMIsBigEndianKey是指再内存中音频的存储模式,在计算机中,通常采用的字节存储机制主要有两种:big-endian和little-endian,即大端模式和小端模式。这个参数为BOOL值,YES为大端,NO为小端。关于大端和小端相关到两个关键词,MSB以及LSB。MSB:Most Significant Bit( 最高有效位),LSB:Least Significant Bit (最低有效位)你可以理解为一段数据再内存中的起始位置以及终止位置,大端模式就是MSB存放在最低端的地址上。而小端口模式就是LSB存放在最低端的地址上。

在Big-Endian中,对于bit序列中的序号编排方式如下(以双字节数0x8B8A为例):
bit | 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15
——MSB———————————-LSB
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |
——————————————–

在Little-Endian中,对于bit序列中的序号编排和Big-Endian刚好相反,其方式如下(以双字节数0x8B8A为例):

bit | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0
——MSB———————————–LSB
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |
———————————————

总之可以理解为在内存中正反两种存储顺序。

对于采样信号是整数还是浮点数也是一个BOOL类型的参数,本人并未理解会影响到的含义,或许是设计到音频信号再解析时候的精准度但并不敢确定还有待设定,所以也没有设置这个参数。

最后一个参数音频编码质量比较好理解了,这个参数又是一个枚举,我们可以找到以AVAudioQuality开头的High、Low、Medium、Max、Min五种设置。介于参数键值对的解释暂时就是这些了,如果又看官得到了一些其他参数的资料或者对于我的解释有纠正补充的欢迎指教。

 

AVAudioRecorder构造函数中的最后一个参数为一个出参,用以保存音频录制的错误信息,如果不想保存错误的信息直接设置为空即可

接下来我们的AVAudioRecorder对象好像看起来成功创建完成了,慢着,不对,为什么会提示错误?仔细看一下,原来是路径的问题,别担心,其实只是AVAudioRecorder跟你开了个小玩笑,因为他实在太懒了,都懒得把字符串路径转换成URL了,所以我们得手动转换一下~没错使用NSURL的静态方法 urlWithString即可解决~接下来只要调用record方法即可开始录音。而以上代码只是实例,建议在编写程序的时候,我们的AVAudioRecorder对象要声名在类内属性中。否则需要结束录音时无法调用到AVAudioRecorder的stop方法。

AVAudioRecorder对象创建起来或许会比较麻烦,但是使用起来确很方便,只要再录音开始时调用record,暂停的时候调用pause方法,而结束的时候调用stop方法就可以了。

相对于录音,播放音频可以说简单的可以,我们在创建AVAudioPlayer对象的时候只需要将之前录音的文件路径给它并且给一个空的错误出参便可轻松的创建出一个AVAudioPlayer对象,使用起来也是那么的方便只要调用一下play函数即可~~~他和AVAudioRecorder一样也可以提供暂停和停止的功能,那它是否可以支持进度调节呢?想知道的话不如用自动提示打开他的方法列表看看呢~一切看起来好像都很简单,我们就要轻松愉快的大功告成了,可当你运行程序之后却惊奇的发现为什么播放不出声音!使用iTools一类的软件检查一下程序的Docment目录,没错啊!文件在啊~,莫非我录制错了?等等等等上万种可能就这样出现在你的脑中!好吧我真不忍心看你像我一样没头没脑的研究几个小时只是因为你没有把实例代码中AVAudioPlayer的声明放到类属性中。。。没错,如果你再某个方法中声明了它并且调用播放函数你就会发现怎么样也播放不出声音,如果你在调用play方法的位置设置下断点再仔细听的话可能会听到一小段声音,为什么呢?因为你刚调用了play的方法你的AVAudioPlayer对象就被释放了,当然什么也没有了也就播放不出声音了~使用AVAudioPlayer就把它的对象放入类内属性吧!

wav没错我们的成功的录制并播放了一个wav,就在你开心的时候你会发现一个问题,纳尼!?安卓不支持wav!!!这可怎么办?难道要让本来就少的可怜的用户还要永隔安卓与IOS的大洋两岸么!大丈夫!一切问题都会有解决方法的!但是,预知后事如何,请听下回分解~~~~~

MySQL 命令行数据库导出/导入

在mysql安装路径(windows:mysql/bin,linux:相应路径)下执行如下命令:

  • 导出

mysqldump -u 用户名 -p 数据库名 > backup.sql

回车后输入密码

  • 导入

mysql -u 用户名 -p

回车后输入密码

mysql > use 数据库名;

mysql > source backup.sql;

对于百度地图API搜索请求,消息请求,线程控制等最新应用心得

内容可能较为散乱,皆是最近学习的一部分心得,记录下以备不时之需

首先是对百度地图API请求机制的一点理解。

百度地图API并非开源请求,所以针对于其内部机制,只是从使用上面一些猜的而已,如有错误,忘情指教

第一,百度地图的消息推送,百度地图API的消息推送并非之前所使用的一些API包是通过HTTP请求发送消息,应该是TCP/IP的消息推送,因最近再使用搜索功能再优化期间发现的一些BUG推断,当搜索对象在数次发送相同请求之后出现了crash而crash的原因通过百度查出可能是因为消息列队或socket阻塞引起的,而之前在其他项目上并未碰到过此类情况,所以推断可能是因为传输机制的差别而产生的

第二,百度地图的响应机制也略有差异,虽然同其他的网络应用API(如新浪微博,腾讯微博等)同样拥有相似的回调方法来处理请求结果,但是新浪微博和腾讯微博等采用的方式是可以自设回调函数,通过设置的回调对象和函数指针来实现消息回传,而百度地图则是使用协议委托的机制,遵从协议后完成指定的方法从而处理返回的结果数据,后者更加符合IOS惯有的程序理念,而二者各有优缺点,前者的优点是足够灵活,灵活程度几乎可以媲美弱类型语言,但是缺点也是对编写者要求较高,使用起来较为繁琐,并且上手的难度略为高一些,错误也容易出现,如果是自己完成可能还略为好一些,如果是团队协作就很容易出现各种各样的错误,因为通过函数指针调用的方法传参只能是id类型,对于数据的结构等错误理解,很可能导致很多不必要的错误,而后者则没有这些缺点,完整的参数列表,多参数的设置,可以让其他程序编写者很容易理解函数的意图以及使用方法,而缺点就在于过多的代码冗余再一起,当然可以通过调用多个方法将代码分离开来,但是始终是不够方便,再一些情况下过多的判断使代码看来很空洞,难以编写易于理解的代码。

第三,说说关于线程的操作,IOS中的线程操作十分强大简便,但是正是由于简便,很多时候反倒感觉到无从下手,改如何关闭以及怎样关闭线程,对于现在的我来说依然有些模糊,尤其是混入ARC机制之后更是时而让人摸不着头脑,就目前而言,我对大家提出几点使用线程的建议,如有错误还忘情指教,首先,线程套线程的操作应当尽量避免,可能是由于个人习惯的原因,我很多时候再开启线城时并非是使用NSThread,而是使用

performSelector…一系列方法(这个习惯可能很不好。。。由于没有老师教导。。。我只能自行摸索所以不敢妄言。。。),这种方式开启的线程,你无法得以全局的控制,所以也就不能像使用普通的NSThread对象那样取判断isCancel属性来对线程进行exit操作,而根据个人经验,如果在这种情况下,再去无限制的开启线程,那么你的线程数只能突飞猛进,到数十,甚至造成很多线程无法关闭,那么碰到需要再线程之中在开启线程的需求怎么办呢?我也遇到了这个问题,比如再使用自行封装的http请求类的时候,异步请求的回调函数中需要使用其中数据来开启新的视图或再次发送一个异步请求等,这种情况下如果保存到类内属性,则不知该何时开启视图,简单的通过延时调用来解决只是唯心主义的自我欺骗,不符合正确的逻辑观念,所以并不可取,我推荐给大家一个好的办法,就是将需要开启视图的代码封装成函数,然后这是通过performSelectorOnMainThread:withObject:waitUntilDone:方法将封装的好的函数运行再主线程上,这样,不论如何开启线程,都是在主线程开启,就可以关闭当前的子线程避免冗余的情况发生。

最后再说一个刚解决过的小BUG,这个BUG虽然解决起来容易,不过找出问题确花费了我不少功夫,BUG是这样的,再我们当前制作的项目中,一个提交订单的请求发送之后,会跳转到下一个界面,而在下一个界面经过一定时间后则会返回到之前的界面,如此反复的操作之后会出现的creash的BUG,经过反复查证原来是百度地图BMKSearch引发的BUG,BUG的起因如下:在下订单的界面中ViewDidLoad会创建视图对象并且对其设置,经过一番操作,跳转到下一个界面之后再次返回之前的界面,而此时的BMKSearch执行搜索操作时就会出现crash,而crash的原因可能是以下几种,第一可能是再下单视图推入下一层视图时被销毁,而回调委托找不到之前的对象而crash,第二可能是之前的delegate委托对象依然在执行中,线程并未正常退出,导致的消息列队阻塞,第三还可能是之前的Search对象已经销毁而且当前的对象并未重新设置过委托对象从而返回的消息无法正确发送给视图(因为调试过程中最后发现crash虽然可以避免但是会一直无法接收到百度地图的搜索消息返回),恕本人愚钝。。。暂时还不能给出正确的解答,但是可以肯定的是,通过重新设置BMKSearch对象的委托即可解决当前的Crash问题,算是以解燃眉之急吧。。。

以上是最近几天的探索心得,记录下来,一来与众看官分享讨论,二来以备个人日后学习参考备忘

ecshop 后台左侧导航添加菜单

涉及改动的有两块:1是添加左侧菜单,2是添加权限菜单

1 添加左侧菜单

涉及文件:/admin/includes/inc_menu.php

若在“系统设置”内添加菜单,加入如下代码:

$modules[“11_system”][“05_warehouse_list”] = “warehouse_list.php?act=list”;

涉及文件:/languages/zh_cn/admin/common.php

$_LANG[“05_warehouse_list”] = “**列表”;

2 添加到权限菜单

涉及文件:/admin/includes/inc_priv.php

$purview[“05_warehouse_list”] = “warehouse_manage”;

涉及文件:/languages/zh_cn/admin/priv_action.php

$_LANG[“warehouse_manage”] = “**列表管理”;

之后在表ecs_admin_action中插入action_code=warehouse_manage的一条数据,

清缓存后即可看到菜单和权限列表中新加入的菜单项。

CentOS PureFTPd无法连接

使用lnmp安装完PureFTPd后,使用FileZilla却无法连接上。
原因是iptables中没有打开默认的被动端口范围。

# nano /usr/local/pureftpd/pure-ftpd.conf
PassivePortRange          10000 20000

按照上面的端口范围修改iptables配置,重启服务即可。

# nano /etc/sysconfig/iptables
-A INPUT -m state --state NEW -m tcp -p tcp --dport 10000:20000 -j ACCEPT
service iptables restart

新浪微博开发平台之网站接入微博群发

什么是新浪微博开放平台
         新浪微博开放平台(Weibo Open Platform)是基于新浪微博海量用户和强大的传播能力,接入第三方合作伙伴服务,向用户提供丰富应用和完善服务的开放平台。将你的服务接入微博平台,有助于推广产品,增加网站/应用的流量、拓展新用户,获得收益。
让你的应用在新浪微博开放平台上运营,只需要通过简单的六个步骤:

成为开发者
1.创建微博帐号
在开发者页面点击“登录”或者“创建应用”,通过帐号登录成为一名开发者。一个新浪微博帐号可以管理10个不同的应用,建议开发人员使用官方微博的帐号,以便统一管理。
2.选择应用类型
点击“创建应用”,即进入目标应用的类型选择环节。请根据应用类型的提示,选择相应的应用创建流程。
3.开发者信息设置
请在开发者信息设置页填写真实资料。成为新浪微博认证的开发者,你需要通过邮箱验证和手机验证。我们会给你填写的邮箱和手机号发送验证信息,请根据提示验证你的身份。
请注意:开发者类型一经填写,不可修改。个人开发者不可接入微博支付业务,请谨慎选择。
创建应用
         在“管理中心”可查看应用信息。App Key是应用唯一的识别标志,新浪微博开放平台通过App Key鉴别应用的身份。App Secret是给应用分配的密钥,你需要妥善保存这个密钥,从而保证应用来源的的可靠性,防止被伪造。你可通过这两个数据进行相关的技术开发工作。
以上完成后就可以开发测试了

config文件是配置文件
<?php
header(“Content-Type: text/html; charset=UTF-8”);
define( “WB_AKEY” , XXXXXXXX);
define( “WB_SKEY” , XXXXXXXXXXXXXXXXXXXXXX);
define( “WB_CALLBACK_URL” , “http://www.XXXXX.com/callback.php” );
conn.php文件是链接数据库
<?php
$conn=mysql_connect(“localhost”,”root”,””)or die(“连接失败:”.mysql_error());//连接MYSQL
mysql_select_db(“send”,$conn) or die(“数据库不存在”.mysql_error());//选择库
mysql_query(“SET NAMES “Utf8″”,$conn);//设置字符集
date_default_timezone_set(“PRC”);//设置时区

index.php文件就是登录入口
callback.php文件是授权文件
saetv2.ex.class.php文件是封装好类文件
群发时代码改成这样
function __construct( $akey, $skey, $access_token, $refresh_token = NULL)
 {
  $conn=mysql_connect(“localhost”,”root”,””)or die(“连接失败:”.mysql_error());//连接MYSQL
  mysql_select_db(“send”,$conn) or die(“数据库不存在”.mysql_error());//选择库
  mysql_query(“SET NAMES “Utf8″”,$conn);//设置字符集
  date_default_timezone_set(“PRC”);//设置时区
  $sql=”select AccessToken from token order by ID desc”;
  $res=mysql_query($sql,$conn);
  $i=1;
  while($row=mysql_fetch_array($res))
  {
  $this->$i = new SaeTOAuthV2( $akey, $skey, $row[“AccessToken”], $refresh_token );
  $i ;
  }
  $this->oauth = new SaeTOAuthV2( $akey, $skey, $access_token, $refresh_token );
 }
哪里需要改哪里!

weibolist.php文件就是我们要编写的了.

<?php
session_start();
include_once( “config.php” );
include_once( “saetv2.ex.class.php” );
include_once( “conn.php” );
$c = new SaeTClientV2( WB_AKEY , WB_SKEY , $_SESSION[“token”][“access_token”] );
$ms  = $c->home_timeline(); // done
$uid_get = $c->get_uid();
$uid = $uid_get[“uid”];
$user_message = $c->show_user_by_id( $uid);//根据ID获取用户等基本信息
 $t = $_SESSION[“token”][“access_token”];    access_token
 $m = $_SESSION[“token”][“expires_in”];         access_token存在时间(单位/秒)
 $name = $user_message[“screen_name”];    微博的名字
 $timer = date(“Y-m-d H:i:s”);
 $sql=”select * from token where `Name` = “$name””;
 $res=mysql_query($sql,$conn);
 if(!$row=mysql_fetch_row($res))
 {
        $sql=”insert into token (Name,AccessToken,expires,timer) values (“$name”,”$t”,”$m”,”$timer”)”;
        mysql_query($sql,$conn);
 }
 else
 {
       $t1 = time(date(“Y-m-d H-i-s”));
       $st= strtotime($row[“4”]);
       if($st $row[“3”] <= $t1)
        {
        $sql=”update token set `AccessToken` =”$t”,`expires` = “$m”,`timer` = “$timer” where `ID` = “$row[0]””;
        mysql_query($sql,$conn);
         }
 }
?>

本次发微博与access_token到期的提示代码

<?php
echo “<h2>本次发微博人</h2>”;
$t = time(date(“Y-m-d H-i-s”));
 $sql=”select * from token”;
 $res=mysql_query($sql,$conn);
 $ii=1;
 while($row=mysql_fetch_array($res))
 {
 $st= strtotime($row[“timer”]);
 if($st $row[“expires”] > $t)
 {
 echo $ii.”、 ”.$row[“Name”].”</br>”;
 $ii ;
 }
 } 
 $sql=”select * from token”;
 $res=mysql_query($sql,$conn);
 $i1=1;
 while($row=mysql_fetch_array($res))
 {
 $st= strtotime($row[“timer”]);
 if($st $row[“expires”] <= $t)
 {
 if($i1==1){
 echo “<h2>授权过期</h2>”;
 }
 echo $i1.”、 ”.$row[“Name”].” 授权时间已到,请重新授权!</br></br>”;
 $i1 ;
 }
 } 
?>

发送新微博
<h2 align=”left”>发送新微博</h2>
 <form action=”weibolist.php” enctype=”multipart/form-data” method=”post”>
  <p>微博内容:
  <textarea name=”text” id=”text” cols=”45″ rows=”5″></textarea></p></br>
  <p>微博图片:
  <input name=”thumbnail” type=”file” id=”thumbnail” style=”width:200px” onMouseOut=”file()”/></p></br>
  <input name=”picpath” id=”picpath” type=”hidden” value=”” />
  <input name=”submit” type=”submit” value=”发送到微博” />
 </form>
<?php
  ini_set(“max_execution_time”, “100”);
         if(!empty($_REQUEST[“picpath”])){
   define(“ROOT_PATH”,$_SERVER[“DOCUMENT_ROOT”].”/”);
   define(“ROOT_UPLOAD”,ROOT_PATH.”upload/”);
   $filename=$_FILES[“thumbnail”][“name”];
   $filetype=$_FILES[“thumbnail”][“type”];
   $filesize=$_FILES[“thumbnail”][“size”];
   $filetmpname=$_FILES[“thumbnail”][“tmp_name”];
   if(($filetype==”image/pjpeg” || $filetype==”image/jpeg” || $filetype==”image/gif”) )
   {
    
    move_uploaded_file($filetmpname,ROOT_UPLOAD.$filename);
    $str=explode(“.”,$filename);  
    $num=count($str)-1;
    $round=rand();
    rename(ROOT_UPLOAD.$filename,ROOT_UPLOAD.time().$round.”.”.$str[$num]);
    $filepath=ROOT_PATH.”upload/”.time().$round.”.”.$str[$num];
    $ww = strtr($filepath,”/”,”\\”);
   }
 }

if( !empty($_REQUEST[“text”])) {
 if(!empty($_REQUEST[“picpath”])) {
 $ret = $c->upload( $_REQUEST[“text”], $ww); //发送微博图片
 }else{
 $ret = $c->update( $_REQUEST[“text”]); //发送微博
 }
 if ( isset($ret[“error_code”]) && $ret[“error_code”] > 0 ) {
 
  echo “<p>发送失败,错误:{$ret[“error_code”]}:{$ret[“error”]}</p>”;
 } else {
  echo “<p>发送成功</p>”;
 }
}
?>

function file(){
  var msg=document.getElementById(“thumbnail”).value;
  //alert(msg);
  document.getElementById(“picpath”).value=msg;
 }

 

123