-------------------------------------------------------------------------- 优势:
1.它是系统级别的语言,静态编译,是C系列语言。 2.具有很多内置库,使用起来和Python很类似。 3.语法足够简单,入门学习成本很低,适合我这样从PHP和Python切换过来的人。 4.速度快,就拿简单的页面来说,我用PHP开发并发能够达到500很好了,但是用Go轻松就到上万,这是无法比拟的性能
提升,而且用Go开发的效率和PHP差不多。 5.出自Google之手,而且有一帮牛人在维护,基于BSD开源,社区活跃。 -------------------------------------------------------------------------- 缺点:
1.有些库不成熟,例如图像处理。 2.cgo在Window系统下面编译很麻烦,就拿SQLite的数据库驱动来说,在Window下面编译就会遇到很大的麻烦。 3.runtime还不够成熟,GC还不是很好,不过听说Go 1.1版本会有比较大的性能提升。 4.Go的开源项目还不够多。我觉得一个语言的发展不仅仅是语言本身出色,还要有大公司推动或者好的项目推动。 -------------------------------------------------------------------------- 开源项目
开源项目给我很多自信,举几个开源系统: vitess(YouTube的数据库proxy系统)、 nsq(bitly的一个实时信息处理系统)、 skynet(轻量级的分布式服务框架)、 七牛公司全部用Go开发、 360开发的类iMessage应用,支持上千万用户,同时单台服务器长连80w, 这些系统都是他们线上跑的,这给我更大的信心能够用Go来开发高性能,高稳定的应用。
-------------------------------------------------------------------------- 为什么Go被称为互联网时代的C呢
我认为是Go在语言级别上支持了并发,通过简单的关键字go就可以充分利用多核,这对于硬件不断发展的时代,这么简
单就可以充分利用硬件的多核,这是多么重要的一个特性啊!但是相比C而言,Go还缺少一些高质量的第三方包,例如
OpenGL等,所以Go内部也支持用cgo直接调用C语言编写的代码。
同时我还开发了两个开源的项目:
beego:一个模仿Python的tornado系统开发的Go开发框架,现在开发的几个系统都是基于该框架开发。 beedb: 一个Go语言的ORM库,可以像操作struct一样操作数据库数据。目前我们内部的API接口我就是采用了这个ORM
开发的。
1.书籍《go语言程序设计》 1.1 go语言是一门静态编译型的语言。编译速度非常快,明显快于c,c++。 go语言的官方编译器是gc。 查看官方文档: 运行指令 godoc -http=:8000 在浏览器中打开http://localhost:8000就可以查看go语言官方文
档。 go语言支持在程序中以cgo工具的形式调用外部的c语言代码。
1.2 编辑 go语言关键字和操作符都使用ASCII编码字符,但是GO语言中的标识符可以使任一Unicode编码字符串,所以go语言 开发者可以再代码中自由地使用它们的母语。 编译 go语言的编译速度超快,所以go语言可以作为类UNIX系统上的#!脚本使用。将#!/usr/bin/env gonow 或者 #!/usr/bin/env gorun加到main()所在的.go文件开始处即可 。 示例 www.qtrac.eu/gobook.html 得到本书所有的源码。 环境变量设置 在.bashrc文件中添加以下行: export GOROOT=$HOME/opt/go export PAHT=$PATH:$GOROOT/bin http://www.qtrac.eu/gobook-1.0.zip
gofmt -w src 可以格式化整个项目
如何查看相应的package文档? 如果是 builtin包,那么执行godoc builtin 如果是 http 包, 那么执行godoc net/http 如果查看某一个包里面的函数,则执行godoc fmt Printf,也可以查看相应的代码,执行 godoc -src fmt Printf
----------------------------------- UTF-8 天生支持utf-8字符串和标示符,因为utf-8的发明者也是go语言的发明者。
并发 goroutine 是go语言并行设计的核心,goroutine说到底就是线程,但是它比线程更小,十几个goroutine可能 体现在底层就是五六个线程,go语言内部帮你实现了这些goroutine之间的内存共享。 执行goroutine只需极小的栈内存(4--5k)。 默认情况下,调度器仅使用单线程。想要发挥多核处理器的并行,需要在我们的程序中显示调用 runtime.GOMAXPROCS(n) 告知调度器同时使用多个线程。
channel 1.无缓冲的 channel 默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得goroutine同步变得更加简
单,而不需要显式的lock。 所谓阻塞,也就说如果读取,它就会被阻塞,直到有数据接收;任何发送也会被阻塞,直到读数据被读出。 无缓冲的channel是在多个goroutine之间同步最棒的工具。
2.有缓冲的channel ch := make(chan bool ,4)创建了一个可以存储4个元素的bool型channel 。在这个channel中,前4个元素可以无
阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel中读取一些元素,腾出空间。
range 和close
func Test(){
c := make(chan int, 10) go fibonacci(cap(c) ,c) for i := range c { //使用range操作缓存类型的channel fmt.Println(i) }
}
func fibonacci(n int,c chan int){ x,y := 1,1 for i := 0 ;i < n ;i++ { c <- x x,y = y,x+y } close(c) //在生产者的地方关闭channel,而不是在消费的地方去关闭它,这样容易起panic。 }
select
func Test(){
c := make(chan int) quit := make(chan int) go func(){ for i :=0 ;i<10;i++{ fmt.Println(<-c) } quit <- 0 }() fibonacci(c,quit)
}
func fibonacci(c,quit chan int){ x,y := 1,1
for { select {//*select 默认是阻塞的,只要当监听的channel中发送或者接收可以进行时,才会运行。 case c <-x ://*当多个channel都准备好的时候,select是随机选择一个执行的。 x,y = y,x+y case <-quit: fmt.Println("quit") return default: fmt.Println("do some thing "); } } }
超时
func Test(){ c := make(chan int) o := make(chan bool) go func(){ for { select { case v := <-c : fmt.Println(v) case <- time.After(5 * time.Second) : fmt.Println("timeout") o <- true break } } }() <- o
}
摘要: Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--># -*- coding: utf-8 -*-#!/usr/bin/env pythonimport SocketServerfrom ... 阅读全文
:: 参考了 :: http://wenku.baidu.com/link?url=MjO8qNL3ttMb6gqjmprzXBxIWKvRwxSYjjDNGOSypuM-s5dKeuan1OOF7r3N6Fe6zXrqnrZmBuAB6xhn_Gxqk_ :: http://www.lixin.me/blog/2012/05/26/29536 :: zhangtao :: 将此bat脚本,保存为xxx.bat,将NAME与LANG两个变量设置为对应的值,然后存放在对应的目录,即可用。 @set NAME=heilongjiang.ft.exp0 @set LANG=heilongjiang @ if not exist %NAME%.box ( goto LABLE_MAKEBOX ) else ( goto LABLE_TRIAN ) :LABLE_MAKEBOX @echo -------begin,make box ----------- @pause ..\ocr.exe %NAME%.tif %NAME% -l eng batch.nochop makebox @ if not exist %NAME%.box goto END_FLAG @echo -------end,make box ,ok----------- @pause exit :LABLE_TRIAN @echo -------begin,train ----------- @pause ..\ocr.exe %NAME%.tif %NAME% box.train @pause ..\unicharset_extractor.exe %NAME%.box @pause echo ft 0 0 1 0 0 > %LANG%.font_properties ..\mftraining.exe -F %LANG%.font_properties -U unicharset %NAME%.tr @pause ..\cntraining.exe %NAME%.tr @pause @rename normproto %LANG%.normproto @rename unicharset %LANG%.unicharset @rename inttemp %LANG%.inttemp @rename pffmtable %LANG%.pffmtable @rename shapetable %LANG%.shapetable @pause ..\combine_tessdata.exe %LANG%. @echo %NAME%.traineddata @echo ------end,succ------------- @pause
::---------------------------------------------------------- ::请参考 :: http://www.kaiyuanba.cn/html/1/131/227/7891.htm :: http://blog.wudilabs.org/entry/f25efc5f/ :: author: zhangtao/20140823 /QQ:406878851 ::---------------------------------------------------------- @echo @set NAME=heilongjiang @ if not exist %NAME%.box ( goto LABLE_MAKEBOX ) else ( goto LABLE_TRIAN ) :LABLE_MAKEBOX @echo -------begin,make box ----------- @pause tesseract %NAME%.tif %NAME% -l heilongjiang batch.nochop makebox @ if not exist %NAME%.box goto END_FLAG @echo -------end,make box ,ok----------- @pause exit :LABLE_TRIAN @echo -------begin,train ----------- @pause @echo -1------------- tesseract %NAME%.tif %NAME% nobatch box.train @ if not exist %NAME%.tr goto END_FLAG @echo -2------------- unicharset_extractor %NAME%.box mftraining -U unicharset -O %NAME%.unicharset %NAME%.tr @ if not exist %NAME%.unicharset goto END_FLAG @echo -3------------- cntraining %NAME%.tr @rename normproto %NAME%.normproto @rename Microfeat %NAME%.Microfeat @rename inttemp %NAME%.inttemp @rename pffmtable %NAME%.pffmtable @echo -4------------- combine_tessdata %NAME%. @ if not exist %NAME%.traineddata goto END_FLAG @echo ----------------------------- @echo %NAME%.traineddata @echo ------end,succ--------------- @pause exit :END_FLAG @echo -------end,failed,----------- @pause exit
# -*- coding: utf-8 -*-
import os
from PIL import *
curdir="E:\\py\\WinPython-32bit-2.7.6.4\\study"
os.chdir(curdir)
def RGB2BlackWhite(filename):
im=Image.open(filename)
print "image info,",im.format,im.mode,im.size
(w,h)=im.size
R=0
G=0
B=0
for x in xrange(w):
for y in xrange(h):
pos=(x,y)
rgb=im.getpixel( pos )
(r,g,b)=rgb
R=R+r
G=G+g
B=B+b
rate1=R*1000/(R+G+B)
rate2=G*1000/(R+G+B)
rate3=B*1000/(R+G+B)
print "rate:",rate1,rate2,rate3
for x in xrange(w):
for y in xrange(h):
pos=(x,y)
rgb=im.getpixel( pos )
(r,g,b)=rgb
n= r*rate1/1000 + g*rate2/1000 + b*rate3/1000
#print "n:",n
if n>=90:
im.putpixel( pos,(255,255,255))
else:
im.putpixel( pos,(0,0,0))
im.save("blackwhite.bmp")
def saveAsBmp(fname):
pos1=fname.rfind('.')
fname1=fname[0:pos1]
fname1=fname1+'_2.bmp'
im = Image.open(fname)
new_im = Image.new("RGB", im.size)
new_im.paste(im)
new_im.save(fname1)
return fname1
if __name__=="__main__":
filename=saveAsBmp("4.bmp")
RGB2BlackWhite(filename)
这里用RGB明度值来做比较,效果更好,速度更快 #计算某一点的明度 def getLightness( (r,g,b)): return (r*30+g*59+b*11)
import Image import sys import os
curdir="E:\\py\\WinPython-32bit-2.7.6.4\\study"
os.chdir(curdir)
def processImage(infile): try: im = Image.open(infile) except IOError: print "Cant load", infile sys.exit(1) i = 0 mypalette = im.getpalette()
try: while 1: im.putpalette(mypalette) new_im = Image.new("RGB", im.size) new_im.paste(im) new_im.save('foo'+str(i)+'.bmp')
i += 1 im.seek(im.tell() + 1)
except EOFError: pass # end of sequence
if __name__ == '__main__': processImage("10.gif") im=Image.open("foo0.bmp") print "img info:",im.format,im.size
import os
def compare_linelist(filename1,filename2): print "compare_linelist (%s,%s)"%(filename1,filename2) f1=open(filename1,"r") f2=open(filename2,"r") line1=f1.readline().strip() line2=f2.readline().strip() nSucc=0 nCount=0 while True: if line1=='' or line2=='': break print "%s : %s"%(line1,line2) nCount=nCount+1 if line1==line2: nSucc=nSucc+1 line1=f1.readline().strip() line2=f2.readline().strip() rate=float(nSucc)/float(nCount)*100 f1.close() f2.close() return (nCount,nSucc,rate)
if __name__=='__main__': work_directory="E:\\py\\WinPython-32bit-2.7.6.4\\study" os.chdir(work_directory) print "current directory:", work_directory filename1="1.txt" filename2="2.txt" result=compare_linelist(filename1,filename2) print("nCount:%d nSucc:%d rate:%d%%")%result
参考链接: http://lijunwei1228ok.blog.163.com/blog/static/9738379720140231713138/
import sys from _winreg import *
# tweak as necessary version = sys.version[:3] installpath = sys.prefix regpath = "SOFTWARE\\Python\\Pythoncore\\%s\\" % (version) installkey = "InstallPath" pythonkey = "PythonPath" pythonpath = "%s;%s\\Lib\\;%s\\DLLs\\" % ( installpath, installpath, installpath )
def RegisterPy(): print "begin RegisterPy " try: print "open key : %s"%regpath reg = OpenKey(HKEY_CURRENT_USER, regpath) except EnvironmentError as e: try: reg = CreateKey(HKEY_CURRENT_USER, regpath) SetValue(reg, installkey, REG_SZ, installpath) SetValue(reg, pythonkey, REG_SZ, pythonpath) CloseKey(reg) except: print "*** EXCEPT: Unable to register!" return print "--- Python", version, "is now registered!" return
if (QueryValue(reg, installkey) == installpath and QueryValue(reg, pythonkey) == pythonpath): CloseKey(reg) print "=== Python", version, "is already registered!" return CloseKey(reg)
print "*** ERROR:Unable to register!" print "*** REASON:You probably have another Python installation!"
def UnRegisterPy(): #print "begin UnRegisterPy " try: print "open HKEY_CURRENT_USER key=%s"%(regpath) reg = OpenKey(HKEY_CURRENT_USER, regpath) #reg = OpenKey(HKEY_LOCAL_MACHINE, regpath) except EnvironmentError: print "*** Python not registered?!" return try: DeleteKey(reg, installkey) DeleteKey(reg, pythonkey) DeleteKey(HKEY_LOCAL_MACHINE, regpath) except: print "*** Unable to un-register!" else: print "--- Python", version, "is no longer registered!"
if __name__ == "__main__": RegisterPy() 其实手动在注册表里 操作就可以了。
#!/bin/sh
DBFILE=./migratedata2redis.sql
for((nDBIndex=0;nDBIndex<4;nDBIndex++)) do
if [ -f $DBFILE ] then rm -rf $DBFILE fi for((nTBIndex=0;nTBIndex<64;nTBIndex++)) do ( cat <<EOF SELECT concat( '*4\r\n\$4\r\nzadd\r\n', '\$', LENGTH(redis_key), '\r\n', redis_key, '\r\n', '\$', LENGTH(redis_score), '\r\n', redis_score, '\r\n', '\$', LENGTH(redis_value), '\r\n', redis_value, '\r' ) FROM( select FtoID ,FmsgID,FdbIndex,FtableIndex,FmsgTimestamp, concat("offlinemsg_",FtoID) as redis_key, FmsgTimestamp as redis_score, concat(FmsgID,"_", FdbIndex,"_", FtableIndex) as redis_value from db_im_user_msg_$nDBIndex.t_im_user_msgidlist_$nTBIndex ) AS T; EOF ) >> $DBFILE done echo "begin migrate data to redis,use $DBFILE .." mysql db_im_user_msg_$nDBIndex --skip-column-names --raw < $DBFILE | redis-cli --pipe echo "migrate data to redis,end" done 这个脚本是将以多库多表存储在MYSQL的数据迁移到REDIS的一个库里。 但是假如,需求是将这些数据安全UID的规则迁移到REDIS的不同的库里呢,怎么办?
#!/bin/sh
DBFILE=./migratedata2redis.sql
function select_data() { DBINDEX=$1 TBINDEX=$2 MOINDEX=$3 #echo "select_data $DBINDEX $TBINDEX $MOINDEX"
( cat <<EOF SELECT concat( '*4\r\n\$4\r\nzadd\r\n', '\$', LENGTH(redis_key), '\r\n', redis_key, '\r\n', '\$', LENGTH(redis_score), '\r\n', redis_score, '\r\n', '\$', LENGTH(redis_value), '\r\n', redis_value, '\r' ) FROM( select FtoID ,FmsgID,FdbIndex,FtableIndex,FmsgTimestamp, concat("offlinemsg_",FtoID) as redis_key, FmsgTimestamp as redis_score, concat(FmsgID) as redis_value from db_im_user_msg_$DBINDEX.t_im_user_msgidlist_$TBINDEX where FtoID%4=$MOINDEX ) AS T1; SELECT concat( '*3\r\n\$3\r\nset\r\n', '\$', LENGTH(redis_key), '\r\n', redis_key, '\r\n', '\$', LENGTH(redis_value), '\r\n', redis_value, '\r' ) FROM( select FtoID ,FmsgID,FdbIndex,FtableIndex,FmsgTimestamp, concat("msg_",FmsgID) as redis_key, FmsgTimestamp as redis_score, concat(FmsgID,"_", FdbIndex,"_", FtableIndex) as redis_value from db_im_user_msg_$DBINDEX.t_im_user_msgidlist_$TBINDEX where FtoID%4=$MOINDEX ) AS T2; EOF ) >> $DBFILE }
function traverse_tb() { DBINDEX=$1 MOINDEX=$2 #echo "traverse_tb $DBINDEX $MOINDEX"
for((nTBIndex=0;nTBIndex<64;nTBIndex++)) do select_data $DBINDEX $nTBIndex $MOINDEX done }
function traverse_md() { DBINDEX=$1 # echo "traverse_md $DBINDEX"
for((nModNum=0;nModNum<4;nModNum++)) do [ -f $DBFILE ] && rm -rf $DBFILE traverse_tb $DBINDEX $nModNum echo " mysql db_im_user_msg_$DBINDEX --skip-column-names --raw < $DBFILE | redis-cli --pipe -n $nModNum " mysql db_im_user_msg_$DBINDEX --skip-column-names --raw < $DBFILE | redis-cli --pipe -n $nModNum # echo "migrate data to redis,end" done }
function traverse_db() { for((nDBIndex=0;nDBIndex<4;nDBIndex++)) do echo -e "\n-------traverse_db $nDBIndex--------------" traverse_md $nDBIndex done }
function main() { traverse_db }
main
初步思路是:先把数据全量迁移到一个库,然后keys * 到指定的记录,再一个一个的move一遍。
对于“ 如果一个消息发送失败,比如因为 deviceToken 不合法,APNs 会在大约 500ms 后断掉链接,在断链前发送的消息也会发送失败;”的解决,JAVA写代码,真优雅! https://github.com/notnoop/java-apns
private void monitorSocket(final Socket socket) { logger.debug("Launching Monitoring Thread for socket {}", socket);
Thread t = threadFactory.newThread(new Runnable() { final static int EXPECTED_SIZE = 6;
@SuppressWarnings("InfiniteLoopStatement") @Override public void run() { logger.debug("Started monitoring thread"); try { InputStream in; try { in = socket.getInputStream(); } catch (IOException ioe) { in = null; }
byte[] bytes = new byte[EXPECTED_SIZE]; while (in != null && readPacket(in, bytes)) { logger.debug("Error-response packet {}", Utilities.encodeHex(bytes)); // Quickly close socket, so we won't ever try to send push notifications // using the defective socket. Utilities.close(socket);
int command = bytes[0] & 0xFF; if (command != 8) { throw new IOException("Unexpected command byte " + command); } int statusCode = bytes[1] & 0xFF; DeliveryError e = DeliveryError.ofCode(statusCode);
int id = Utilities.parseBytes(bytes[2], bytes[3], bytes[4], bytes[5]);
logger.debug("Closed connection cause={}; id={}", e, id); delegate.connectionClosed(e, id);
Queue<ApnsNotification> tempCache = new LinkedList<ApnsNotification>(); ApnsNotification notification = null; boolean foundNotification = false;
while (!cachedNotifications.isEmpty()) { notification = cachedNotifications.poll(); logger.debug("Candidate for removal, message id {}", notification.getIdentifier());
if (notification.getIdentifier() == id) { logger.debug("Bad message found {}", notification.getIdentifier()); foundNotification = true; break; } tempCache.add(notification); }
if (foundNotification) { logger.debug("delegate.messageSendFailed, message id {}", notification.getIdentifier()); delegate.messageSendFailed(notification, new ApnsDeliveryErrorException(e)); } else { cachedNotifications.addAll(tempCache); int resendSize = tempCache.size(); logger.warn("Received error for message that wasn't in the cache..."); if (autoAdjustCacheLength) { cacheLength = cacheLength + (resendSize / 2); delegate.cacheLengthExceeded(cacheLength); } logger.debug("delegate.messageSendFailed, unknown id"); delegate.messageSendFailed(null, new ApnsDeliveryErrorException(e)); }
int resendSize = 0;
while (!cachedNotifications.isEmpty()) {
resendSize++; final ApnsNotification resendNotification = cachedNotifications.poll(); logger.debug("Queuing for resend {}", resendNotification.getIdentifier()); notificationsBuffer.add(resendNotification); } logger.debug("resending {} notifications", resendSize); delegate.notificationsResent(resendSize);
drainBuffer(); } logger.debug("Monitoring input stream closed by EOF");
} catch (IOException e) { // An exception when reading the error code is non-critical, it will cause another retry // sending the message. Other than providing a more stable network connection to the APNS // server we can't do much about it - so let's not spam the application's error log. logger.info("Exception while waiting for error code", e); delegate.connectionClosed(DeliveryError.UNKNOWN, -1); } finally { close(); } }
编译Linux c++代码的几个问题
1.c++项目移植到不同版本的linux平台,需要重新编译一次。可能并不能顺利地编译通过, 问题可能出现:gcc编译器的版本,较高版的,对语法的检查更加的严格,遇到的问题是, include文件的依赖,template语法的声明,所依赖的系统库的缺失,在64位系统下编译 32位程序等等。 2./usr/bin/ld: cannot find -lstdc++ 解决 sudo yum search "static" |grep "\(libc\|stdc\+\+\)"
将搜索到到包,安装上就行了。
3.x86_64 Linux Error: gnu/stub-32.h
Fix for the RHEL/CentOS 5.x for GCC gnu/stub-32.h missing error Type the following yum command: # yum -y install glibc-devel.i386 Fix for the RHEL/CentOS 6.x for GCC gnu/stub-32.h missing error Type the following yum command: # yum -y install glibc-devel.i686 glibc-devel
请索引帮助文档: http://www.cyberciti.biz/faq/x86_64-linux-error-gnustub-32h-missing-error-and-solution/
4. uuid/uuid.h 找不到
yum -y install libuuid-devel
总结较好的文档:http://www.ityran.com/archives/240 /* 若1秒内对同一个用户产生大量的推送请求,会造成该用户接收正常速度的消息时前总是收到之前已经推送过的消息,这所谓重复消息。 经过测试发现,即便provider产生大量推送请求,apns推送给iPhone的时间间隔大约为3s。那么应该在provider端设计一种算法,使得 对同一个用户大量并发的请求以3秒间隔地推送。实践结果,果然。 */ #pragma pack(1) struct STRU_PUSHDATA { public: void setFieldCommand(uint8 auCommand) { muCommand = auCommand; } void setFieldIdentifier(uint32 auIdentifier) { muIdentifier = auIdentifier; } void setFieldExpiry(uint32 auExpiry) { muExpiry = htonl(auExpiry); } void setFieldTokenLen(uint16 auTokenLen) { muTokenLen = htons(auTokenLen); } void setFieldToken( const uint8* apToken) { memcpy(mszToken,apToken,32); } void setFieldPayloadLen(uint16 auPayloadLen) { muPayloadLen = htons(auPayloadLen); } void setFieldPayLoad( const char* apszPayload,uint16 auPayloadLen) { memcpy(mszPayload,apszPayload,auPayloadLen); } inline const uint8* getPushDataAddr() const { return (uint8*)&muCommand; } inline uint16 getPushDataLen() { return (1+4+4+2+32+2+getPayloadLen()); } inline uint16 getPayloadLen() { return ntohs(muPayloadLen); } std:: string getPayloadString() { int nLen = getPayloadLen(); if (nLen>=256) { return std:: string(""); } mszPayload[nLen]='\0'; return std:: string(mszPayload); } inline void setFieldFromId( const uint64& auFromId) { muFromId = auFromId; } inline void setFieldToId( const uint64& auToId) { muToId = auToId; } inline const uint64& getFieldFromId() const { return muFromId; } inline const uint64& getFieldToId() const { return muToId; } STRU_PUSHDATA() { init(); } void init() { memset( this,0, sizeof(* this)); } uint8 muCommand; uint32 muIdentifier; uint32 muExpiry; uint16 muTokenLen; uint8 mszToken[32]; uint16 muPayloadLen; char mszPayload[256+1]; uint16 iPayloaLlen; uint64 muFromId; uint64 muToId; STRU_PUSHDATA* next; //*指向下一个STRU_PUSHDATA
}; #pragma pack(0) // 类名:CApnsAdapter // 功能:与apns交互的适配器,适配器时一个包含各种推送策略的推送发射装置 // 设计者:zhangtao 20140419
typedef void (*PFCALLBACK_EVENT_ON_TRAVERSAL)(HANDLE ahCallBack,STRU_PUSHDATA* apData); typedef void (*PFCALLBACK_EVENT_ON_CLEARCACHE)(HANDLE ahCallBack,STRU_PUSHDATA* apData); class CApnsAdapter { public: struct HEAD_NODE { uint64 muToId; uint32 muLastPushTime; STRU_PUSHDATA* mpHead; STRU_PUSHDATA* mpTail; uint32 muCount; HEAD_NODE():muToId(0),muLastPushTime(0),mpHead(NULL),mpTail(NULL),muCount(0) { //*构造
} HEAD_NODE(uint64 auId,uint32 auTime):muToId(auId),muLastPushTime(auTime),mpHead(NULL),mpTail(NULL),muCount(0) { //*构造
} bool operator==( const HEAD_NODE& rhs) const { return (muToId == rhs.muToId); } void push_back(STRU_PUSHDATA* mpData) { if(NULL == mpHead) { mpHead = mpData; } else { mpTail->next = mpData; } mpData->next = NULL; mpTail = mpData; muCount++; } STRU_PUSHDATA* pop_front() { if (NULL == mpHead || NULL ==mpTail) { return NULL; } STRU_PUSHDATA* lpData = mpHead; mpHead = mpHead->next; muCount--; return lpData; } STRU_PUSHDATA* get_front() { if (NULL == mpHead || NULL ==mpTail) { return NULL; } return mpHead; } bool is_empty() { return ( NULL == mpHead) ; } uint16 get_count() { return muCount; } void set_lastpushtime( const uint32& auCurTime) { muLastPushTime = auCurTime; } bool is_canpush( const uint32& auCurTime) { return (auCurTime >= muLastPushTime+3); //3是实践出来的值
} }; CApnsAdapter():mhcbContext(NULL),mpcbEventOnTraversal(NULL),mpcbEventOnClearCache(NULL) { } ~CApnsAdapter() { clear_all_cache(); } void setCallbackEvent(HANDLE ahcbContext,PFCALLBACK_EVENT_ON_TRAVERSAL apcbEventOnTraversal,PFCALLBACK_EVENT_ON_CLEARCACHE apcbEventOnClearCache) { mhcbContext = ahcbContext; mpcbEventOnTraversal = apcbEventOnTraversal; mpcbEventOnClearCache = apcbEventOnClearCache; } bool is_empty() { return ( 0 == moNodeList.size()); } uint16 size() { return moNodeList.size(); } //*先缓冲待推送的数据请求,然后又策略地推送 void add_data_to_cache( STRU_PUSHDATA* mpData) { HEAD_NODE node(mpData->getFieldToId(),0); std::list<HEAD_NODE>::iterator it = std::find(moNodeList.begin(),moNodeList.end(),node); if (it != moNodeList.end()) { it->push_back(mpData); } else { node.push_back(mpData); moNodeList.push_back(node); } } void active_push_on_traversal() { uint32 uNowTime = (uint32)time(NULL); std::list<HEAD_NODE>::iterator it = moNodeList.begin(); for ( ; it != moNodeList.end() ;) { if ( it->is_canpush( uNowTime )) { if ( it->is_empty() ) { moNodeList.erase( it++ ); continue; } else { STRU_PUSHDATA * lpData = it->pop_front() if ( NULL != mpcbEventOnTraversal) { mpcbEventOnTraversal(mhcbContext,lpData); } it->set_lastpushtime( uNowTime ); } } else { } it++; } } void clear_all_cache() { std::list<HEAD_NODE>::iterator it = moNodeList.begin(); for ( ; it != moNodeList.end() ;) { if ( it->is_empty() ) { moNodeList.erase( it++ ); continue; } else { STRU_PUSHDATA* lpPushData = NULL; while( NULL != (lpPushData = it->pop_front()) ) { if ( NULL != mpcbEventOnClearCache) { mpcbEventOnClearCache(mhcbContext,lpPushData); } } it++; } } moNodeList.clear(); } void print_each_node(); private: std::list<HEAD_NODE> moNodeList; HANDLE mhcbContext; PFCALLBACK_EVENT_ON_TRAVERSAL mpcbEventOnTraversal; PFCALLBACK_EVENT_ON_CLEARCACHE mpcbEventOnClearCache; };
sudo yum search "static" |grep "\(libc\|stdc\+\+\)"
将搜索到到包,安装上就行了。
1.1 什么是木马程序 “木马”是一种经过伪装的欺骗性程序,它通过将自身伪装吸引用户下载执行,从而破坏或盗取使用者的重要文件和资料。 1.2 木马与病毒的区别 广义地讲,木马是病毒的一种,因为它们都具有潜伏性和触发性; 区别是:木马不会自我繁殖; 1.3 木马与远程控制软件的区别 木马的远程控制是隐蔽地进行的 1.4 一个完整的木马包含哪些部分 一个完整的木马程序包含两部分:服务器和控制器 服务器部分:是植入受害者电脑的部分; 控制器部分:是黑客控制使用的部分; 1.5 木马的发展 第一代木马:伪装性病毒 (1986年出现) 伪装成合法程序诱骗用户上当; 第二代木马:诱骗型木马 (1989年出现) 开始具备传播特性 第三代木马:网络传播性木马 1.添加后门功能;2.添加击键记录功能;3.变化多端的隐藏功能;4.多种技术融合 *1.写注册表的启动来启动木马 *2.加载到服务中作为合法服务启动 *3.将木马插入到合法的进程中 1.6 正向连接与反向连接 正向连接:木马作为服务端被植入用户电脑后,启动端口监听;黑客利用控制端连接该服务端; 反向连接:木马作为服务端被植入用户电脑后,同时控制端在黑客电脑上启动端口监听,服务端主动连接控制端; 可绕过防火墙;nat;典型例子:灰鸽子; 1.7 木马的进化论 1.简单的后台运行 2.内部间谍软件(反向连接技术) 3.无进程木马后门;DLL木马 4.网页木马 5.主流技术“rootkit” 1.8 常用木马编程技术 *1.修改注册表技术;比如“超级兔子” *2.调用win32 API编程 *3.多线程技术; *4.后台监控技术; *5.定时触发器技术;
vs2003安装步骤与可能问题的解决
2012-08-15 changshoumeng试验成功
1. 安装盘 sc_vs.net_2003_prereq.iso VS.Net_2003_Enar_CHS_CD1.ISO VS.Net_2003_Enar_CHS_CD2.ISO 2. 安装步骤 2.1 安装 sc_vs.net_2003_prereq.iso 可能出现问题:FrontPage2000 服务器扩展安装失败。 解决办法: a:下载关于iis的完整包 (说明:这一步解决的问题是,xp上可能并没有iis的安装文件)
控制面板/添加或删除文件/添加或删除windows组件/ b: 首先 勾选 [附件和工具] /详细信息D/取消勾选游戏/确定 (说明:这一步解决的问题是,安装IIS 时出现安装程序无法复制文件zClinettm.exe) c: 然后 勾选 [Inernet信息服务(IIS)] /详细信息D/勾选FrontPage2000 服务扩展 点击下一步 进行安装。 (说明:这一步解决的问题时,FrontPage2000 服务器扩展组件安装失败) 2.2 安装VS.Net_2003_Enar_CHS_CD1.ISO 可能出现的问题: 错误 1308。未找到源文件: d:\everbox\vs2003\vs.net_2003_enar_chs_cd1\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1
\Docs\2052\cpsamples.HxS。请验证文件是否存在以及是否可以访问它。 解决办法: 这些文件,都来自于VS.Net_2003_Enar_CHS_CD2.ISO里; 只要从光盘2里把对应的文件拷贝到光盘1里,就行了。 但是,如果这样的话,这样的操作就会出现很多次,颇为繁琐。 于是我想,假如能够光盘1与光盘2合并一个光盘就好了。解决办法,使用文件同步工具 GoodSyncPro, 将两个文件夹进行合并。 继续点击重试,进行安装,这样就安装ok了。
参考网文: http://hi.baidu.com/vc_net/item/086b31dff8ee4df792a97455
http://www.codesky.net/article/200911/125322.html
坚持每天看一章或者半章,一个月就可以看完一本书 changshoumeng 2012/06-03/ 多媒体通信的关键技术 1. 多媒体数据的压缩编码 多媒体系统需要对多媒体数据进行捕获、存储、传输和播放等处理工作,数据压缩技术是多媒体通信技术的核心技术之一。先进的数据压缩技术尤其是视频压缩技术可实现较低的时延和高的压缩比,达到较好的图像质量,这正是多媒体视听业务能被广泛接受的重要因素之一。 视频压缩编码标准,主要标准如下: (1) ISO 10918 (JPEG)是用于连续色调静止图像压缩编码的标准。 (2) H.261适用的速率范围是p × 64 kbit/s ( p = 1 ~ 30),即 64~ 1920 kbit/s,主要用于可视电话和会议电视系统。 (3) H.263是以H2.61为基础改进而来的,可以获得更高的压缩比和较高的图像质量。 (4) ISO 11172 (MPEG1)主要用于CIF(公共中间格式)格式的图像分辨率和大约1.5Mbit/s的数码率,适用于VCD。 (5) ISO/IEC 13818 (MPEG2)基于3~4M bit/s 或4Mbit/s以上速率的压缩存储视频,图像质量可达到高清晰度电视水平,主要适用于DVD、 数字电视、视频点播和数字视频广播(DVD)系统。 (6) MPEG4比MPEG2的应用范围更广,其压缩方法不再是限定的某种算法,而是可以根据不同的应用进行系统裁剪,选用不同的算法。MPEG4中引入的最重要,也最引人注目的新概念是视频对象平面VOP。这一概念直接导致了基于内容的压缩,为提供更高的压缩比打下了基础,同时也将传统的基于帧的时空可分级扩展到基于图像内容的时空可分级性。 (7)MPEG7 是有关多媒体内容描述接口标准,适用于基于视频和音频内容的多媒体检索业务。 音频压缩编码标准: G.711、G.721、G.722、G728、G723标准以及MPEG1、MPEG2、AC3音频编码系统。 2. 多媒体数据的同步 多媒体技术可以处理视觉、听觉甚至触觉信息,但支持的媒体越多,计算机系统的相应处理子系统也越多,处理这些媒体之间的同步问题也就越复杂。 同步要求:多媒体通信同步、多媒体表现同步、多媒体交互同步。多媒体通信同步是其他同步功能的基础。 解决多媒体通信同步的方法: (1) 时间戳法。原理:相同时间戳的媒体单元同时表现;优点:不需要改变数据流、不需要附加同步信息,适用范围广;缺点:选择相对标和确定时间戳的操作较为复杂,主媒体失步会引起其他媒体失步。 (2) 同步标记法。原理:发送时在媒体流中插入同步标记,接收时按收到的同步标记来对各个媒体流进行同步处理。 实现方法: 1.同步标记用另外一条辅助信道进行传输 缺点:增加了比特开销; 2.同步标记和媒体数据在同一个媒体流中传输。 缺点:改变了数据流,不支持复杂的同步表现。 (3)多路复用法。 3. 多媒体数据库 4. 多媒体通信网 多媒体通信的应用 1.个人间的通信 (1) 会话型: 1.传统的电话机;2. 计算机电话集成(CTI)pc+电话接口卡+软件支持 3.电话会议CC,需要一个audiobridge (2) 电邮型: (3) 多点会议: 2.因特网上的交互应用 (1) 信息检索 (2) 远程教育和远程医疗 3.娱乐应用 (1) 视频点播 VOD:由视频服务器、高速网络和客户端组成。 1. 有线电视系统 2.宾馆娱乐服务系统 3.数字图书馆系统 4.远程交易系统 5.各种公共咨询和服务系统
MFC应用程序中处理消息的顺序
1.AfxWndProc() 该函数负责接收消息,找到消息所属的CWnd对象,然后调用AfxCallWndProc。
2.AfxCallWndProc() 该函数负责保存消息(保存的内容主要是消息标识符和消息参数)供应用程序以后使用,然后调用WindowProc()函数。
3.WindowProc() 该函数负责发送消息到OnWndMsg()函数,如果未被处理,则调用DefWindowProc()函数。
4.OnWndMsg() 该函数的功能首先按字节对消息进行排序,对于WM_COMMAND消息,调用OnCommand()消息响应函数,对于WM_NOTIFY消息调用OnNotify()消息响应函数。任何被遗漏的消息将是一个窗口消息。OnWndMsg()函数搜索类的消息映像,以找到一个能处理任何窗口消息的处理函数。如果OnWndMsg()函数不能找到这样的处理函数的话,则把消息返回到WindowProc()函数,由它将消息发送给DefWindowProc()函数。
5.OnCommand() 该函数查看这是不是一个控件通知(lParam参数不为NULL,如果lParam参数为空的话,说明该消息不是控件通知),如果它是,OnCommand()函数会试图将消息映射到制造通知的控件;如果他不是一个控件通知(或者如果控件拒绝映射的消息)OnCommand()就会调用OnCmdMsg()函数。
6.OnCmdMsg() 根据接收消息的类,OnCmdMsg()函数将在一个称为命令传递(Command Routing)的过程中潜在的传递命令消息和控件通知。例如:如果拥有该窗口的类是一个框架类,则命令和通知消息也被传递到视图和文档类,并为该类寻找一个消息处理函数。
MFC应用程序创建窗口的过程
1.PreCreateWindow() 该函数是一个重载函数,在窗口被创建前,可以在该重载函数中改变创建参数(可以设置窗口风格等等)。
2.PreSubclassWindow() 这也是一个重载函数,允许首先子分类一个窗口。
3.OnGetMinMaxInfo() 该函数为消息响应函数,响应的是WM_GETMINMAXINFO消息,允许设置窗口的最大或者最小尺寸。
4.OnNcCreate() 该函数也是一个消息响应函数,响应WM_NCCREATE消息,发送消息以告诉窗口的客户区即将被创建。
5.OnNcCalcSize() 该函数也是消息响应函数,响应WM_NCCALCSIZE消息,作用是允许改变窗口客户区大小。
6.OnCreate() 该函数也是一个消息响应函数,响应WM_CREATE消息,发送消息告诉一个窗口已经被创建。
7.OnSize() 该函数也是一个消息响应函数,响应WM_SIZE消息,发送该消息以告诉该窗口大小已经发生变化。
8.OnMove() 消息响应函数,响应WM_MOVE消息,发送此消息说明窗口在移动。
9.OnChildNotify() 该函数为重载函数,作为部分消息映射被调用,告诉父窗口即将被告知一个窗口刚刚被创建。
MFC应用程序关闭窗口的顺序(非模态窗口)
1.OnClose() 消息响应函数,响应窗口的WM_CLOSE消息,当关闭按钮被单击的时候发送此消息。
2.OnDestroy() 消息响应函数,响应窗口的WM_DESTROY消息,当一个窗口将被销毁时,发送此消息。
3.OnNcDestroy() 消息响应函数,响应窗口的WM_NCDESTROY消息,当一个窗口被销毁后发送此消息。
4.PostNcDestroy() 重载函数,作为处理OnNcDestroy()函数的最后动作,被CWnd调用。
MFC应用程序中打开模式对话框的函数调用顺序
1.DoModal() 重载函数,重载DoModal()成员函数。
2.PreSubclassWindow() 重载函数,允许首先子分类一个窗口。
3.OnCreate() 消息响应函数,响应WM_CREATE消息,发送此消息以告诉一个窗口已经被创建。
4.OnSize() 消息响应函数,响应WM_SIZE消息,发送此消息以告诉窗口大小发生变化。
5.OnMove() 消息响应函数,响应WM_MOVE消息,发送此消息,以告诉窗口正在移动。
6.OnSetFont() 消息响应函数,响应WM_SETFONT消息,发送此消息,以允许改变对话框中控件的字体。
7.OnInitDialog() 消息响应函数,响应WM_INITDIALOG消息,发送此消息以允许初始化对话框中的控件,或者是创建新控件。
8.OnShowWindow() 消息响应函数,响应WM_SHOWWINDOW消息,该函数被ShowWindow()函数调用。
9.OnCtlColor() 消息响应函数,响应WM_CTLCOLOR消息,被父窗口发送已改变对话框或对话框上面控件的颜色。
10. OnChildNotify() 重载函数,作为WM_CTLCOLOR消息的结果发送。
MFC应用程序中关闭模式对话框的顺序
1.OnClose() 消息响应函数,响应WM_CLOSE消息,当"关闭"按钮被单击的时候,该函数被调用。
2.OnKillFocus() 消息响应函数,响应WM_KILLFOCUS消息,当一个窗口即将失去键盘输入焦点以前被发送。
3.OnDestroy() 消息响应函数,响应WM_DESTROY消息,当一个窗口即将被销毁时,被发送。
4.OnNcDestroy() 消息响应函数,响应WM_NCDESTROY消息,当一个窗口被销毁以后被发送。
5.PostNcDestroy() 重载函数,作为处理OnNcDestroy()函数的最后动作被CWnd调用。
打开无模式对话框的顺序
1.PreSubclassWindow() 重载函数,允许用户首先子分类一个窗口。
2.OnCreate() 消息响应函数,响应WM_CREATE消息,发送此消息以告诉一个窗口已经被创建。
3.OnSize() 消息响应函数,响应WM_SIZE消息,发送此消息以告诉窗口大小发生变化。
4.OnMove() 消息响应函数,响应WM_MOVE消息,发送此消息以告诉窗口正在移动。
5.OnSetFont() 消息响应函数,响应WM_SETFONT消息,发送此消息以允许改变对话框中控件的字体。
http://blog.sina.com.cn/s/blog_55cf5f230100p471.html
配置 Linux 服务器 SSH 安全访问的四个小技巧(转载)http://www.xiaohui.com/dev/server/linux-centos-ssh-security.htm越来越多的站长,开始使用独立主机(Dedicated Host)和 VPS。而为了节省成本或提高性能,不少人的独机和 VPS,都是基于 unmanaged 的裸机,一切都要自己 DIY。这时候,安全策略的实施,就犹为重要。下面这篇文章 ( http://www.xiaohui.com/dev/server/centos-security-for-ssh.htm),我以 CentOS 为例,简单地总结一下如何配置 SSH 安全访问。
Linux SSH 安全策略一:关闭无关端口
网络上被攻陷的大多数主机,是黑客用扫描工具大范围进行扫描而被瞄准上的。所以,为了避免被扫描到,除了必要的端口,例如 Web、FTP、SSH 等,其他的都应关闭。值得一提的是,我强烈建议关闭 icmp 端口,并设置规则,丢弃 icmp 包。这样别人 Ping 不到你的服务器,威胁就自然减小大半了。丢弃 icmp 包可在 iptables 中, 加入下面这样一条: -A INPUT -p icmp -j DROP
Linux SSH 安全策略二:更改 SSH 端口
默认的 SSH 端口是 22。强烈建议改成 10000 以上。这样别人扫描到端口的机率也大大下降。修改方法: # 编辑 /etc/ssh/ssh_config
vi /etc/ssh/ssh_config
# 在 Host * 下 ,加入新的 Port 值。以 18439 为例(下同):
Port 22
Port 18439
# 编辑 /etc/ssh/sshd_config
vi /etc/ssh/sshd_config
#加入新的 Port 值
Port 22
Port 18439
# 保存后,重启 SSH 服务:
service sshd restart
这里我设置了两个端口,主要是为了防止修改出错导致 SSH 再也登不上。更改你的 SSH 客户端(例如:Putty)的连接端口,测试连接,如果新端口能连接成功,则再编辑上面两个文件,删除 Port 22 的配置。如果连接失败,而用 Port 22 连接后再重新配置。
端口设置成功后,注意同时应该从 iptables 中, 删除22端口,添加新配置的 18439,并重启 iptables。
如果 SSH 登录密码是弱密码,应该设置一个复杂的密码。Google Blog 上有一篇强调密码安全的文章:Does your password pass the test?
Linux SSH 安全策略三:限制 IP 登录
如果你能以固定 IP 方式连接你的服务器,那么,你可以设置只允许某个特定的 IP 登录服务器。例如我是通过自己的 VPN 登录到服务器。设置如下: # 编辑 /etc/hosts.allow
vi /etc/hosts.allow
# 例如只允许 123.45.67.89 登录
sshd:123.45.67.89
Linux SSH 安全策略四: 使用证书登录 SSH
相对于使用密码登录来说,使用证书更为安全。自来水冲咖啡有写过一篇详细的教程,征得其同意,转载如下:
为CentOS配置SSH证书登录验证
来源:自来水冲咖啡
下午帮公司网管远程检测一下邮件服务器,一台CentOS 5.1,使用OpenSSH远程管理。
检查安全日志时,发现这几天几乎每天都有一堆IP过来猜密码。看来得修改一下登录验证方式,改为证书验证为好。
为防万一,临时启了个VNC,免得没配置完,一高兴顺手重启了sshd就麻烦了。(后来发现是多余的,只要事先开个putty别关闭就行了)
以下是简单的操作步骤: 1)先添加一个维护账号:msa
2)然后su - msa
3)ssh-keygen -t rsa
指定密钥路径和输入口令之后,即在/home/msa/.ssh/中生成公钥和私钥:id_rsa id_rsa.pub
4)cat id_rsa.pub >> authorized_keys
至于为什么要生成这个文件,因为sshd_config里面写的就是这个。
然后chmod 400 authorized_keys,稍微保护一下。
5)用psftp把把id_rsa拉回本地,然后把服务器上的id_rsa和id_rsa.pub干掉
6)配置/etc/ssh/sshd_config
Protocol 2
ServerKeyBits 1024
PermitRootLogin no #禁止root登录而已,与本文无关,加上安全些
#以下三行没什么要改的,把默认的#注释去掉就行了
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
PermitEmptyPasswords no
7)重启sshd
/sbin/service sshd restart
8)转换证书格式,迁就一下putty
运行puttygen,转换id_rsa为putty的ppk证书文件
9)配置putty登录
在connection--SSH--Auth中,点击Browse,选择刚刚转换好的证书。
然后在connection-Data填写一下auto login username,例如我的是msa
在session中填写服务器的IP地址,高兴的话可以save一下
10)解决一点小麻烦
做到这一步的时候,很可能会空欢喜一场,此时就兴冲冲的登录,没准登不进去:
No supported authentication methods available
这时可以修改一下sshd_config,把
PasswordAuthentication no临时改为:
PasswordAuthentication yes 并重启sshd
这样可以登录成功,退出登录后,再重新把PasswordAuthentication的值改为no,重启sshd。
以后登录就会正常的询问你密钥文件的密码了,答对了就能高高兴兴的登进去。
至于psftp命令,加上个-i参数,指定证书文件路径就行了。
如果你是远程操作服务器修改上述配置,切记每一步都应慎重,不可出错。如果配置错误,导致 SSH 连接不上,那就杯具了。
基本上,按上述四点配置好后,Linux 下的 SSH 访问,是比较安全的了。当然,安全与不安全都是相对的,你应该定期检查服务器的 log,及时发现隐患并排除。如果你有更好的安全建议,欢迎在评论区留言指正。:)
class MemoryLeakCheck { public: ~MemoryLeakCheck(void); static MemoryLeakCheck & GetInstance(); void AddCheck( void * _pObj ,std::string _strBindData); template<typename T> void RemoveCheck(T * _pObj ) { Thread::Lock::CReadWriteLock::CWriteLock _Lock( m_RWLock ); std::map<void* ,_ReleaseObjInfo>::iterator it = m_ReleaseObjMap.find((void*) _pObj ); if( it != m_ReleaseObjMap.end() ) {
m_ReleaseObjMap.erase( it ); } m_SizeAll +=sizeof(T); static int i = 0; if( i ++ >256 ) { i = 0; } } private: MemoryLeakCheck(void); //省略 };
CPU三种工作模式总结
CPU三种模式
处理器有3种工作模式:实模式、保护模式和虚拟86模式。实模式和虚拟86模式是为了和8086处理器兼容而设置的。在实模式下,80386处理器就相当于一个快速的8086处理器。保护模式是80386处理器的主要工作模式。在此方式下,80386可以寻址4GB的地址空间,同时,保护模式提供了80386先进的多任务、内存分页管理和优先级保护等机制。为了在保护模式下继续提供和8086处理器的兼容,80386又设计了一种虚拟86模式,以便可以在保护模式的多任务条件下,有的任务运行32位程序,有的任务运行MS-DOS程序。在虚拟86模式下,同样支持任务切换、内存分页管理和优先级,但内存的寻址方式和8086相同,也是可以寻址1 MB的空间。
【批注:处理器从8086升级到80386,工作模式也从实模式升级到保护模式,为了兼容8086产生虚拟86模式】
由此可见,80386处理器的3种工作模式各有特点且相互联系。实模式是80386处理器工作的基础,这时80386当做一个快速的8086处理器工作。在实模式下可以通过指令切换到保护模式,也可以从保护模式退回到实模式。虚拟86模式则以保护模式为基础,在保护模式和虚拟86模式之间可以互相切换,但不能从实模式直接进入虚拟86模式或从虚拟86模式直接退到实模式。
【批注:实模式→保护模式→虚拟86模式】
1. 实模式
80386处理器被复位或加电的时候以实模式启动。这时候处理器中的各寄存器以实模式的初始化值工作。80386处理器在实模式下的存储器寻址方式和8086是一样的,由段寄存器的内容乘以16当做基地址,加上段内的偏移地址形成最终的物理地址,这时候它的32位地址线只使用了低20位。在实模式下,80386处理器不能对内存进行分页管理,所以指令寻址的地址就是内存中实际的物理地址。在实模式下,所有的段都是可以读、写和执行的。
【批注:物理地址】
实模式下80386不支持优先级,所有的指令相当于工作在特权级(优先级0),所以它可以执行所有特权指令,包括读写控制寄存器CR0等。实际上,80386就是通过在实模式下初始化控制寄存器,GDTR,LDTR,IDTR与TR等管理寄存器以及页表,然后再通过加载CR0使其中的保护模式使能位置位而进入保护模式的。实模式下不支持硬件上的多任务切换。
【批注:多任务】
实模式下的中断处理方式和8086处理器相同,也用中断向量表来定位中断服务程序地址。中断向量表的结构也和8086处理器一样,每4个字节组成一个中断向量,其中包括两个字节的段地址和两个字节的偏移地址。
【批注:中断向量】
从编程的角度看,除了可以访问80386新增的一些寄存器外,实模式的80386处理器和8086有什么进步呢?其实最大的好处是可以使用80386的32位寄存器,用32位的寄存器进行编程可以使计算程序更加简捷,加快了执行速度。比如在8086时代用16位寄存器来完成32位的乘法和除法时,要进行的步骤实在是太多了,于是考试时出这一类的题目就成了老师们的最爱,所以那时候当学生做梦都想着让寄存器的位数快快长,现在梦想终于成真了,用32位寄存器一条指令就可以完成(问题是老师们也发现了这个投机取巧的办法,为了达到让学生们基础扎实的目的,也把题目换成了64位的乘法和除法,所以现在晚上做的梦换成了寄存器忽然长到了64位);其次,80386中增加的两个辅助段寄存器FS和GS在实模式下也可以使用,这样,同时可以访问的段达到了6个而不必考虑重新装入的问题;最后,很多80386的新增指令也使一些原来不很方便的操作得以简化,如80386中可以使用下述指令进行数组访问:
mov cx, [eax + ebx*2 + 数组基地址]
这相当于把数组中下标为eax和ebx的项目放入cx中;ebx * 2中的2可以是1,2,4或8,这样就可以支持8位到64位的数组。而在8086处理器中,实现相同的功能要进行一次乘法和两次加法。另外,pushad和popad指令可以一次把所有8个通用寄存器的值压入或从堆栈中弹出,比起用下面的指令分别将8个寄存器入栈要快了很多:
push eax
push ebx
...
pop ebx
pop eax
当然,使用了这些新指令的程序是无法拿回到8086处理器上去执行的,因为这些指令的编码在8086处理器上是未定义的。
【批注:新增针对32位寄存器的指令,简化操作】
2. 保护模式
当80386工作在保护模式下的时候,它的所有功能都是可用的。这时80386所有的32根地址线都可供寻址,物理寻址空间高达4 GB。在保护模式下,支持内存分页机制,提供了对虚拟内存的良好支持。虽然与8086可寻址的1 MB物理地址空间相比,80386可寻址的物理地址空间可谓很大,但实际的微机系统不可能安装如此大的物理内存。所以,为了运行大型程序和真正实现多任务,虚拟内存是一种必需的技术。
【批注:虚拟内存】
保护模式下80386支持多任务,可以依靠硬件仅在一条指令中实现任务切换。任务环境的保护工作是由处理器自动完成的。在保护模式下,80386处理器还支持优先级机制,不同的程序可以运行在不同的优先级上。优先级一共分0~3 4个级别,操作系统运行在最高的优先级0上,应用程序则运行在比较低的级别上;配合良好的检查机制后,既可以在任务间实现数据的安全共享也可以很好地隔离各个任务。从实模式切换到保护模式是通过修改控制寄存器CR0的控制位PE(位0)来实现的。在这之前还需要建立保护模式必需的一些数据表,如全局描述符表GDT和中断描述符表IDT等。
DOS操作系统运行于实模式下,而Windows操作系统运行于保护模式下。
【批注:多任务,优先级】
3. 虚拟86模式
虚拟86模式是为了在保护模式下执行8086程序而设置的。虽然80386处理器已经提供了实模式来兼容8086程序,但这时8086程序实际上只是运行得快了一点,对CPU的资源还是独占的。在保护模式的多任务环境下运行这些程序时,它们中的很多指令和保护模式环境格格不入,如段寻址方式、对中断的处理和I/O操作的特权问题等。为了在保护模式下工作而丢弃这些程序的代价是巨大的。设想一下,如果Windows或80386处理器推出的时候宣布不能运行以前的MS-DOS程序,那么就等于放弃了一个巨大的软件库,Windows以及80386处理器可能就会落得和苹果机一样的下场,这是Microsoft和Intel都不愿看到的。所以,80386处理器又设计了一个虚拟86模式。
【批注:在保护模式下,兼容8086】
虚拟86模式是以任务形式在保护模式上执行的,在80386上可以同时支持由多个真正的80386任务和虚拟86模式构成的任务。在虚拟86模式下,80386支持任务切换和内存分页。在Windows操作系统中,有一部分程序专门用来管理虚拟86模式的任务,称为虚拟86管理程序。
既然虚拟86模式以保护模式为基础,它的工作方式实际上是实模式和保护模式的混合。为了和8086程序的寻址方式兼容,虚拟86模式采用和8086一样的寻址方式,即用段寄存器乘以16当做基址再配合偏移地址形成线性地址,寻址空间为1 MB。但显然多个虚拟86任务不能同时使用同一位置的1 MB地址空间,否则会引起冲突。操作系统利用分页机制将不同虚拟86任务的地址空间映射到不同的物理地址上去,这样每个虚拟86任务看起来都认为自己在使用0~1MB的地址空间。
【批注:虚拟86模式 = 实模式 + 保护模式】
8086代码中有相当一部分指令在保护模式下属于特权指令,如屏蔽中断的cli和中断返回指令iret等。这些指令在8086程序中是合法的。如果不让这些指令执行,8086代码就无法工作。为了解决这个问题,虚拟86管理程序采用模拟的方法来完成这些指令。这些特权指令执行的时候引起了保护异常。虚拟86管理程序在异常处理程序中检查产生异常的指令,如果是中断指令,则从虚拟86任务的中断向量表中取出中断处理程序的入口地址,并将控制转移过去;如果是危及操作系统的指令,如cli等,则简单地忽略这些指令,在异常处理程序返回的时候直接返回到下一条指令。通过这些措施,8086程序既可以正常地运行下去,在执行这些指令的时候又觉察不到已经被虚拟86管理程序做了手脚。MS-DOS应用程序在Windows操作系统中就是这样工作的。
摘要: /**//* ========================================================================Copyright (c) 2010, All rights reserved.|文件名称|:CommSocket.h|文件标识|:通信模块|摘 &... 阅读全文
#ifndef C_Class #define C_Class struct #endif ////////////////////////////////////////////////////////////////////////// //数据定义 ////////////////////////////////////////////////////////////////////////// C_Class A { C_Class A *A_this; void (*Foo)(C_Class A *A_this); int a; int b; };
C_Class B { //B继承了A C_Class B *B_this; //顺序很重要 void (*Foo)(C_Class B *Bthis); //虚函数 int a; int b; int c; }; ////////////////////////////////////////////////////////////////////////// //成员函数 ////////////////////////////////////////////////////////////////////////// void A_Foo(C_Class A *Athis) { printf("It is A.a=%d\ ",Athis->a); }
void B_Foo(C_Class B *Bthis) { printf("It is B.c=%d\ ",Bthis->c); }
////////////////////////////////////////////////////////////////////////// //构造函数 ////////////////////////////////////////////////////////////////////////// void A_Creat(struct A* p) { p->Foo=A_Foo; p->a=1; p->b=2; p->A_this=p; }
void B_Creat(struct B* p) { p->Foo=B_Foo; p->a=11; p->b=12; p->c=13; p->B_this=p; } ////////////////////////////////////////////////////////////////////////// //函数入口 ////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { C_Class A *ma,a; C_Class B *mb,b; A_Creat(&a);//实例化 B_Creat(&b); mb=&b; ma=&a; ma=(C_Class A*)mb;//引入多态指针 printf("%d\ ",ma->a);//可惜的就是 函数变量没有private ma->Foo(ma);//多态 a.Foo(&a);//不是多态了 return 0; }
摘要: C/C++编程规范
|适用范围|
本标准适用于利用Visul C++ ,Borland C++,GNC进行软件程序开发的人员。
一.变量命名
变量风格:匈牙利命名法
变量尽量采用匈牙利命名法,一般情况下,变量的取名方式为:
[范围前缀_],[类型前缀_],[限定词]。特殊的类型命名,前缀表示类、接口前缀 类型 &nbs... 阅读全文
void * my_memccpy(void *dest,const void *src,int c,int count) { while ( count && (*((char *)(dest = (char *)dest + 1) - 1) = *((char *)(src = (char *)src + 1) - 1)) != (char)c ) count--; return(count ? dest : NULL);
}
//memset void *my_memset(void *buffer, int c, int count) { char* p = (char*)buffer; while(count--) *p++ = (char)c; return buffer; } //memcpy void * my_memcpy(void *dst,const void *src,int count) { void * ret = dst; while (count--) { *(char *)dst = *(char *)src; dst = (char *)dst + 1; src = (char *)src + 1; } return(ret); }
//memmove /**//* memmove()由src所指定的内存区域赋值count个字符到dst所指定的内存区域。 src和dst所指内存区域可以重叠,但复制后src的内容会被更改。函数返回指向dst的指针。 */
void * my_memmove(void * dst,const void * src,int count) { void * ret = dst; if (dst <= src || (char *)dst >= ((char *)src + count)) { while (count--) { *(char *)dst = *(char *)src; dst = (char *)dst + 1; src = (char *)src + 1; } } else { dst = (char *)dst + count - 1; src = (char *)src + count - 1; while (count--) { *(char *)dst = *(char *)src; dst = (char *)dst - 1; src = (char *)src - 1; }
} return(ret); }
char * __cdecl strcpy(char * dst, const char * src) { char * cp = dst; while( *cp++ = *src++ ) ; return( dst ); }
char * strcat (char * dst, char * src) { char * cp = dst; while( *cp ) ++cp; /**//* Find end of dst */ while( *cp++ = *src++ ) /**//* Copy src to end of dst */ return( dst ); }
int my_strlen(const char * str ) { const char *p = str; while( *p++ ) ; return( (int)(p - str - 1) );
} //strcmp int my_strcmp(const char *string1, const char *string2 ) { int ret; while( ( ret=*(unsigned char *)string1++ -*(unsigned char *)string2++)==0 && string1 ); return ret; }
借鉴: http://www.cublog.cn/u2/64540/article_87467.html
五种程序设计方法
|
转自:http://www.neu.edu.cn/cxsj/pointchart/c1/Page19.html
|
|
|
|
为了提高程序的可读性、可重用性等,逐渐出现了将程序开发中经常用到的相同的功能,比如数学函数运算、字符串操作等,独立出来编写成函数,然后按照相互关系或应用领域汇集在相同的文件里,这些文件构成了函数库。
函数库是一种对信息的封装,将常用的函数封装起来,人们不必知道如何实现它们。只需要了解如何调用它们即可。函数库可以被多个应用程序共享,在具体编程环境中,一般都有一个头文件相伴,在这个头文件中以标准的方式定义了库中每个函数的接口,根据这些接口形式可以在程序中的任何地方调用所需的函数。
由于函数、库、模块等一系列概念和技术的出现,程序设计逐渐变成如图所示的风格。程序被分解成一个个函数模块,其中既有系统函数,也有用户定义的函数。通过对函数的调用,程序的运行逐步被展开。阅读程序时,由于每一块的功能相对独立,因此对程序结构的理解相对容易,在一定程度上缓解了程序代码可读性和可重用件的矛盾,但并未彻底解决矛盾。随着计算机程序的规模越来越大,这个问题变得更加尖锐,于是出现了另一种编程风格——结构化程序设计。
在结构化程序设计中,任何程序段的编写都基于3种结构:分支结构、循环结构和顺序结构。程序具有明显的模块化特征,每个程序模块具有惟一的出口和入口语句。结构化程序的结构简单清晰,模块化强,描述方式贴近人们习惯的推理式思维方式。因此可读性强,在软件重用性、软件维护等方面都有所进步,在大型软件开发尤其是大型科学与工程运算软件的开发中发挥了重要作用。因此到目前为止,仍有许多应用程序的开发采用结构化程序设计技术和方法。即使在目前流行的面向对象软件开发中也不能完全脱离结构化程序设计。
|
|
面向对象的程序役计方法是程序设计的一种新方法。所有面向对象的程序设计语言一般都含有三个方面的语法机制,即对象和类、多态性、继承性。
1.对象和类
对象的概念、原理和方法是面向对象的理序设计语言晕重要的特征。对象是用户定义的类型(称为类)的变量。一个对象是既包含数据又包合操作该数据的代码(函数)的逻辑实体。对象中的这些数据和函数称为对象的成员,即成员数据和成员函数。对象中的成员分为公有的和私有的。公有成员是对象与外界的接口界面。外界只能通过调用访问一个对象的公有成员来实现该对象的功能。私有成员体现一个对象的组织形式和功能的实现细节。外界无法对私有成员进行操作。类对象按照规范进行操作,将描述客观事物的数据表达及对数据的操作处理封装在一起,成功地实现了面向对象的程序设计。当用户定义了一个类类型后,就可以在该类型的名下定义变量(即对象)了。类是结构体类型的扩充。结构体中引入成员函数并规定了其访问和继承原则后便成了类。
2.多态性
面向对象的程序设计语言支持“多态性”,把一个接口用于一类活动。即“一个接口多种算法”。具体实施时该选择哪一个算法是由特定的语法机制确定的。C++编译时和运行时都支持多态性。编译时的多态性体现在重载函数和重载运算符等方面。运行时的多态性体现在继承关系及虚函数等方面。
3.继承性
C++程序中,由一个类(称为基类)可以派生出新类(称为派生类)。这种派生的语法机制使得新类的出现轻松自然,使得一个复杂事物可以被顺理成章地归结为由逐层派生的对象描述。“派生”使得程序中定义的类呈层次结构。处于子层的对参既具有其父层对象的共性.又具有自身的特性。继承性是一个类对象获得其基类对象特性的过程。C++中严格地规定了派生类对其基类的继承原则和访问权限,使得程序中对数据和函数的访间,需在家族和朋友间严格区分。
|
|
事件驱动的程序设计实际上是面向对象程序设计的一个应用,但它目前仅适用于windows系列操作系统。windows环境中的应用程序与MS-DOS环境中的应用程序运行机制不同、设计程序的方式也不一样。windows程序采用事件驱动机制运行,这种事件驱动程序由事件的发生与否来控制,系统中每个对象状态副改变都是事件发生的原由或结果,设计程序时需以一种非顺序方式处理事件,与顺序的、过程驱动的传统程序设计方法迥异。
事件也称消息,含义比较广泛,常见的事件有鼠标事件(如民标移动、单击、掠过窗口边界)、键盘事件(如按键的压下与拾起)等多种。应用程序运行经过一系列必要的初始化后,将进入等待状态,等待有事件发生,一旦事件出现,程序就被激活并进行相应处理。
事件驱动程序设计是围绕着消息的产生与处理进行的.消息可来自程序中的某个对象,也可由用户、wlndow s或运行着的其他应用程序产生。每当事件发生时,Windows俘获有关事件,然后将消息分别转发到相关应用程序中的有关对象,需要对消息作出反应的对象应该提供消息处理函数,通过这个消息处理函数实现对象的一种功能或行为。所以编写事件驱动程序的大部分工作是为各个对象(类)添加各种消息的处理函数。由于一个对象可以是消息的接收者,同时也可能是消息的发送者,所发送的消息与接收到的消息也可以是相同的消息,而有些消息的发出时间是无法预知的(比如关于键盘的消息),因此应用程序的执行顺序是无法预知的。
|
|
逻辑式程序设计的概念来自逻辑式程序设计语言Prolog这一曾经在计算机领域引起震动的日本“第五代”计算机的基本系统语言,在这种“第五代”计算机中,Prolog的地位相当于当前计算机中的机器语言。
Prolog主要应用在人工智能领域,在自然语言处理、数据库查询、算法描述等方面都有应用,尤其适于作为专家系统的开发工具。
Prolog是一种陈述式语言,它不是一种严格的通用程序设计语言,使用Prolog编写程序不需要描述具体的解题过程、只需结出一些必要的事实和规则,这些规则是解决问题方法的规范说明,根据这些规则和事实.计算机利用渭词逻辑,通过演绎推理得到求解问题的执行序列。
|
|
一个有实际应用的并行算法,最终总要在并行机上实现,为此首先就要将并行算法转化为并行程序,此过程就是所谓的并行程序设计(Parallel Program)。它要求算法设计者、系统结构师和软件工作者广泛频繁的交互。因为设计并行程序涉及到的知识面较广,主要包括操作系统中的有关知识和优化编译方面的知识。操作系统内容非常丰富,并行程序中最基本的计算要素如任务、进程、线程等基本概念、同步机制和通信操作等。
目前并行程序设计的状况是:⑴并行软件的发展落后于并行硬件;⑵和串行系统与应用软件相比,现今的并行系统与应用软件甚少且不成熟;⑶并行软件的缺乏是发展并行计算的主要障碍;⑷不幸的是,这种状态似乎仍在继续着。究其原因是并行程序设计远比串行程序设计复杂:⑴并行程序设计不但包含了串行程序设计,面且还包含了更多的富有挑战性的问题;⑵串行程序设计仅有一个普遍被接受的冯·诺依曼计算模型,而并行计算模型虽有好多,但没有一个可被共同认可的像冯·诺依曼那样的优秀模型;⑶并行程序设计对环境工具(如编译、查错等)的要求远比串行程序设计先进得多;⑷串行程序设计比较适合于自然习惯,且人们在过去积累了大量的编程知识、经验和宝贵的软件财富。
|
|
1、 C++ 定义了哪些类型转换操作符?分别有什么作用?
① 定义了四个操作符:static_cast,const_cast,dynamic_cast和reinterpret_cast。
② static_cast:可以被用于强制类型转换(例如,non-const对象转换为const对象,int转换为double,等等),它还可以用于很多这样的转换的反向转换(例如,void*指针转换为有类型指针,基类指针转换为派生类指针)。但是它不能将一个const对象转换为一个non-const对象(只有const-cast能做到)。它最接近于C-style的转换。
③const_cast:一般用于强制消除对象的常量性。它是唯一能做到这一点的C++风格的强制类型。
④dynamic_cast:主要用于执行“安全的向下转型”。也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制类型转换,也是唯一可能有重大运行时代价的强制转换。
⑤reinterpret_cast:是特意用于底层的强制转换,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果。例如,将一个指针转型为一个整数。这样的强制转换在底层代码以外应该极为罕见。
2、 C++ 定义了哪些访问控制关键字?分别有什么作用?
简单可以归为以下两种描述:
1)一个类友元(包含友元函数或者友元类的成员函数或者友元类的所有成员函数)可以访问该类的任何成员(包括成员变量及成员方法)。
2)除去友元外,private成员只有该类自身的成员函数可以访问,protected成员只有该类及其派生类的成员函数可以访问,public成员则该类及其派生类的成员函数和对象都可以访问。
3、 类的继承方式和区别?
派生类继承方式的影响
类的继承方式有三种:private、protected、public。
1)private属性不能被继承。
2)
使用private继承,父类的protected和public属性在子类中变为private; 使用protected继承,父类的protected和public属性在子类中变为protected; 使用public继承,父类中的protected和public属性不发生改变;
4、 在什么情形下调用虚函数不会有多态性?为什么?
由虚函数实现的动态多态性的方法:
1. 在基类用virtual声明成员函数为虚函数。
2. 在派生类中重新定义此函数。
3. 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
4. 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
因此,总结如下:
1、基类函数未使用virtual声明而子类中使用virtual关键字声明时调用虚函数不会有多态性;
2、基类使用virtual而子类未重新实现该虚函数则不会有多态性;
3、 使用子类创建的对象转换成基类类型调用虚函数不会有多态性;
5. 构造函数调用顺序。
class Y {...} class X : public Y {...} X one;
构造函数的调用顺序是下面的顺序:
Y(); // 基类的构造函数 X(); // 继承类的构造函数
对于多基类的情况,下面是一个例子:
class X : public Y, public Z X one;
构造函数以声明的次序调用。
Y(); // 基类构造函数首先被调用 Z(); X();
虚基类的构造函数在任何非虚基类构造函数前调用。如果构造中包括多个虚基类,它们的调用顺序以声明顺序为准。..
如果虚类是由非虚类派生而来,那非虚类的构造函数要先被调用。下面是一个例子:
class X : public Y, virtual public Z X one;
调用顺序如下:
Z(); // 虚基类初始化 Y(); // 非虚基类 X(); // 继承类
下面是一个复杂的例子:
class base; class base2; class level1 : public base2, virtual public base; class level2 : public base2, virtual public base; class toplevel : public level1, virtual public level2; toplevel view;
构造函数调用顺序如下:
base(); // 虚基类仅被构造一次 base2(); level2(); // 虚基类 base2(); level1(); toplevel();
如果类继承中包括多个虚基类的实例,基类只被初始化一次。
1、如果类里面有成员类,成员类的构造函数优先被调用;
2、创建派生类的对象,基类的构造函数函数优先被调用(也优先于派生类里的成员类);
3、 基类构造函数如果有多个基类则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序; 4、成员类对象构造函数如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序; 5、派生类构造函数 作为一般规则派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递给适当的基类构造函数否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。(基类设计者的责任是提供一组适当的基类构造函数)
6. RTTI是什么,怎么实现的?举个例子。
RTTI即运行时类型识别,通过它程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。
RTTI提供了一下两个非常有用的操作符:
1、typeid操作符,返回指针和引用所指的实际类型
2、dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。
摘要: #include "stdafx.h"#include "malloc.h"#include "stdlib.h"#include "stdio.h"#define NULL 0#define LEN sizeof(struct student)struct student{ ... 阅读全文
|
|
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
常用链接
留言簿(3)
随笔分类(81)
随笔档案(86)
文章分类(34)
文章档案(37)
c++博客
技术论坛
网络安全和黑客技术
资源
搜索
积分与排名
最新评论
阅读排行榜
评论排行榜
|
|