关于作者

姓名:蹲墙头等红杏

性别:男

出生日期:1981-5-6

地区:山东-济南

联系电话:

QQ:363668091婚否:未婚
用户名:kuaileren003
笔名:蹲墙头等红杏
地区: 山东-济南
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



青苹果贴图论坛

访问统计:
文章个数:66
评论个数:8
留言条数:2




Powered by BlogDriver 2.1

快乐人的博客

 

如果这真是一种缘份,那这一定是人世间最纯净、最妙不可言、最值得珍惜的缘份了。因为,它就象春曰的一缕和风,来得那么的突然,来得那么的感动,而又去得那么的悄无声息,去得那么的让人留恋~~~~~~

文章

修理老婆的十大秘籍!  (作者置顶)
很多兄弟总是抱怨老婆难养,怕老婆拿自己和别人比较。那么,从现在开始将抱怨的精力多花在这些实际行为上吧,为老婆一生一世永不变心而努力!



  1、每天尽可能的多拥抱老婆,即使她嫌弃你一身臭汗也不怕,因为她心里是甜的。
  令她眩晕的拥抱方式——意思是指:“使用某一种藉口轻碰对方的身体”,这也算是一种拥抱方式,但是多是发生在"有意无意中的意外″。这种意外触碰的动作,不管是哪一方碰触哪一方,都是一种让人心跳的示爱动作。例如:最老套的方法是吃饭的时候,不小心碰到对方的手;或者不小心跌倒,不小心靠在对方的身上...这些虽然是老套的情节,但是蛮有效的喔!

  2、在适当时候或心情愉悦的时候总是真诚的对老婆说:“我爱你!”“爱你的一切!”“我们在一起真快乐!”。 看到这里,也许有人会嗤之以鼻:“老套!”但究竟有几个人做到了呢?

  该如何说出“我爱你”——影片《算死草》里面有莫文蔚哄骗周星驰说出“i love you”的情节。记得李连杰主演的黄飞鸿系列电影里面,好象也有类似情节,应该是关之琳扮演的十三姨也在逗黄飞鸿说那“i love you”的事情吧。好像许多男人总不喜欢将爱宣诸于口,莫说是封建时代那些保守思想的男人,其实如今的时代男性也是差不多一个样子。

  3、家务永远是令人头疼的,主动和老婆分着做,并抢着炒菜(保护老婆的皮肤就是保护自己的口袋)。不管你做得多难吃,只有老婆会面不改色吃下去,并且还会鼓励你:“多做几次就好吃了!”(嘻嘻)。有时间尽量陪老婆一起吃饭,有研究表明一个人吃饭容易得胃病。

  测试你能不能变成好丈夫——好男人不会让心爱的女人受伤,好女人也不会去伤害自己的男人。一个男人是不是一个好的男人,这可是有标准的哦!做个测试吧!看看你是不是能变成一个“人见人爱,花见花开”的好丈夫。


很多兄弟总是抱怨老婆难养,怕老婆拿自己和别人比较。那么,从现在开始将抱怨的精力多花在这些实际行为上吧,为老婆一生一世永不变心而努力!



4、老婆的生日,各种节日,纪念日,你不大张旗鼓庆贺也罢,但是要记得送礼物给她;你不送礼物也罢,但是要请她与你共度晚餐;你不请她吃饭也罢,但要记得带束花给她;你嫌送花浪费也罢,那就给她买点她喜欢的小零食;你不买零食也罢,但要记得电话问候一下,最不济也要发个有意义的短信给她。你忘了发短信也罢,那就等着她埋怨你吧,怨气积多了可不是什么好事啊,看过鬼片的人多知道的吧!


  5、有空,不累,就陪老婆逛逛街,千万不要拿男人都讨厌逛街当借口,总觉得那是有些男人的阴谋,你不愿陪是吧,呵呵,殊不知那些制造阴谋的男人正等候时机呢!所以老婆要逛街,老公还是“偶尔”陪陪吧,知道你们委屈了,可你们的衣裤鞋袜,多少也是老婆置办的啊。

  6、自己的女同事,女性朋友,若有机会就介绍给老婆认识,千万不要来个电话就躲起来接听,和别人暧昧的谈笑,装得自己多受人欢迎似的!这只是小男人不成熟的表现,别让聪明的老婆笑话了!大方地让她了解你的朋友圈,只会让她更加地信任你!信任度是一点一点用心建立起来的!

 7、勇敢地担负起养家的责任,不论老婆是独立型还是小鸟依人型的,即使老婆现在同你一样工作着,每月拿或多或少的薪水,你也要把自己当作家里的顶梁柱!看到老婆回家累了还要做饭的时候,你要说:“这么辛苦,我来养你吧!”明智的老婆也会想到你的辛苦,得到了关心和承认,老婆的工作干劲会更大!

  8、老婆想学习知识提升自己的时候,尽量地鼓励她,为她创造条件,并且自己也要变得积极,同她一起进步,老婆学得越多越自信越有内涵,你也会少了许多女人胡搅蛮缠的烦恼。你们一起进步,也可以平衡彼此之间的差距,也为创造美好生活添砖加瓦,是谓比翼双飞也!

  9、人的很大一部分时间都花在工作上了,自己的工作要经常和老婆聊聊,其中的快乐和痛苦要与她一起分担。也许很多男人都认为,分享成功就行了,至于艰辛嘛,自己承受一下!在这里表扬一下先!但女人也愿意为你分担一切,这就好比你得了不治之症(是比方请谅解),怕不能给人幸福还连累人,就拼命装坏为的是让女人离开你。其实一旦得知真相,女人的痛苦和遗憾会折磨她终生!所以,遇到挫折最可靠的方法是和爱人分担,她绝不会落井下石,只会鼓励你支持你,因为你把她当成可以共苦的人,她就真的能与你共患难!

  10、如果家里只有一台电脑,永远不独占着(除非你是it的),要么和老婆一起玩,要么指点老婆玩。你离开去喝水的时候,顺便帮老婆也倒一杯!

- 作者: 蹲墙头等红杏 2005年12月9日, 星期五 21:42  回复(1) |  引用(0) 加入博采

嫁个山东老公过一生!  (作者置顶)

 山东人忠厚直爽,外粗内秀,讲义气,受儒家文化的熏陶,特别讲究人际关系和尊卑等级,爱走上层路线。山东人乡土意识重,地狱自豪感强,不仅在现实中到处认老乡,在网上“老乡”也成为聚众的招牌。
    实实在在的山东人——与山东人有过接触或交往的外地人对山东人的描述总是:憨厚而宽容,朴实无华,真诚坦率,朴素爽直,善民宽厚,朴实的就像一棵红高粱。也许有的精明小气的人会把山东的这种“实在”看成是“四肢发达,头脑简单”,从头到脚是根直线,没有一点思想。要是这样,那就是大错而特错了!山东人是有思想的,只是缺少太多的机智罢了,说白了,就是有经商之能却不精于此道,有文化却不善于辞令。
    听过山东地方戏曲如吕剧,山东梆子,山东快书的人,相信一定会感到这些戏曲除唱腔优美外,还有一个共同的特性,即语言质朴,爽快,它们既没有京剧的贵族气,也没有越剧的书卷气,只是用爽快的语言道出普通百姓的生活,真要说的话它有的恐怕也只是这一股股的憨气吧。    
    论酒量,山东人算不上天下第一,他们绝对喝不了东北人和新疆人那么多,但如果他们与东北人和新疆人坐到一块,不知底细的东北人和新疆人一定会甘拜下风,原因是山东人喝酒,能喝八两,绝对不喝半斤,用酒场的话说就是喝酒不胶泥,酒风硬朗,想当年武松路过景仰岗,人家三碗不过岗,他却连干数碗,照过不误,真是豪迈无比,而这种爽快,豪迈的岁月抹不尽,洗不掉的。    
    山东人特别重视老乡关系,在大学校园中,每到星期日山东籍的同学你来我往,共同的生活地域,共同的生活背景和氛围,共同的乡音,使这些出门在外的齐鲁学生们在相互交流中既找到了亲切感,又驱逐心中的寂寞。山东的文化离不开孔夫子,山东人的性格与他老人家也密切相关,孔夫子教诲人们要“忠义信勇”,给人深刻的还是那一股子的豪气。
    人常说:“自古山东出好汉”,大概是因为山东人都具有忠诚不二的天性,忠诚一直被山东人当作美德来颂扬,忠诚是一种品格,是山东人的品格,给世人印象最深的,当属以山东的农民起义为背景的两部英雄传奇小说——《水许传》和《说唐》,起义的主要领导人物及著名英雄都是山东人,这些英雄好汉从根本上说在骨子里喜欢的还是这些人的重义气,而义是什么?孔子,孟子解释的最为清楚,义的表现,即为忠于朋友,同生死,共患难,为朋友两肋插刀。忠诚构筑了山东人生生不息的本质,而这忠诚的信念厚厚地参透沉淀在山东人的心灵深处,一旦他们认准了值得为之忠心耿耿的事或人,就会忠诚有加,不会有丝毫懈怠。
    吃苦一族——不必王婆卖瓜,外国人的话最有说服力,一个德国人说,没有比山东人更优秀的铁路工,矿工了,因为他们能承受任何艰苦,山东人,可谓是“吃苦一族”,是中国人中最能吃苦耐劳的群体。也许是艰苦卓绝的自然环境与社会环境培养了山东人强壮的身体和坚强的意志,从而使山东人有足够的智力,体力来承担艰辛和困苦。山东人这种吃苦的能力既有自然环境磨练的结果,也有天性遗传因素,更有文化,精神的教诲和榜样的力量。
    有人说山东人头脑死,他们除了苦干外,就是苦干,不知巧干,其实只是厚实纯朴使山东人在经济发展过程中显得不够灵活精明,山东人不会将政策变通使用,不会打擦边球,别说擦边球,就连偏一点的球也不会,政策边界对山东人来讲是明确的,谁也不敢越雷池一步,他们只知道借鉴别人的成功经验,并使之发挥到及至,缺少创新性,就连这鲁菜中也专门有一“四平八稳席”,就是要人们规规矩矩,不要冒险从事,山东人也就成为维护着忠,义,信,勇的信条和理想的“守旧”的山东人了。
     山东人可以吃苦,但绝不可以冒险,于是,山东人宁肯稳坐家中吃煎饼,也不肯出外找寻金米粒!没辙。    
    山东商人的掘金论——几千年来,山东出了无数经邦治世的大政治家,大思想家,大军事家和豪情冲天的大文化家,大艺术家,从春秋首霸齐恒公和孔子,孙武,吴起,墨子,孙膑,扁鹊到秦汉的蒙恬,田横,东方嗍,从三过两晋南北朝的孔融,诸葛亮,到隋唐的房玄龄,黄巢,从宋金元的宋江,辛弃疾到明清的戚继光,蒲松龄,上下两千年竟真没有一个是值得一提的大商人,这些人驰骋于思想政治军事领域中,成一代豪杰,竟没有一人能在经济领域中一显身手,成一代实业大家的,实在令人费解。山东人不出大商人并不是由于山东人天生不善于经营,要究其原由,这儒家文化长期禁止和近代山东经济受帝国主义列强控制可能要算是罪魁祸首了,可以想见,儒家人多势大,连被马克思称为不可战胜的新生事物,与之相抵触,也免不了有灭顶之灾,传统观念使山东人普遍认为经商致富是“背德”的可耻之事。
     然而,一切过去都将成为历史,山东人用自己的行动,近几年来常听人们议论山东经济发展快的原因,有人说,山东发展快,是因为山东人的屁股下面坐着宝;也有人说,广东靠开放,北京靠中央,山东靠老乡,既然有人这么说,看来是有点道理。
    全国十大驰名商标名牌产品中,山东的产品就占百几项,青岛啤酒,海尔电器,小鸭牌洗衣机,在全国百强县中,山东占了23个,近四分之一,诸城实行贸工农一体化,实行商品生产大合唱,寿光的全国蔬菜批发市场等,常常见诸报端……山东人搞宣传,吸引外资可谓是水银落地,无孔不入,潍坊本不太有名,既没什么富矿,又无显赫的地理位置,但硬是搞了一个国际风筝节,而且越办越红火,弄的全国和世界都知道山东有个潍坊,文化资源比较缺乏的鲁西南地区,并没有难倒当地的人民,他们“冒天下之大不违”,居然搞了个“蟋蟀节”,醉翁之意不在酒,主要的是为了吸引外资,举办商品贸易洽谈会,提高知名度。
     山东人还是十分有胆识和魄力的,这一切的发迹都源于观念的改变。
    说起山东的饮食,最有名的莫过于蘸酱类,在一些人眼里,大葱蘸酱几乎成了山东人的代名词,山东大葱的辛辣微甜蘸上盐分充足的面酱,入口便产生一种急于要吞咽的欲望,于是高粱面窝窝头,玉米面饼子之类的粗粮也能填饱肚皮,这恰恰是出大力的农夫们所急需的,而在鲁西南,煎饼卷大葱则是家家必备,人人都会的一种吃法,在山东民间,有“面酱加大葱,撑得肚皮紧绷绷”,“大葱蘸酱,越吃越壮”之说,大概就是对大葱蘸酱的由衷赞美吧,至于这一点,要求精致的南方人是无论如何也无法理解的。
     所以MM们一定要嫁给山东人噢,哈哈…………

- 作者: kuaileren003 2005年11月8日, 星期二 08:44  回复(1) |  引用(0) 加入博采

我解决TCP-socket -掉线问题的总结
 我解决TCP-socket -掉线问题的总结。 」
→gselec 发表于 2007-7-12 18:16:00

直接用socket做的服务器和客户端,程序做的差不多了,实际使用时,发现经常“掉线”,而程序不知道,找了许多解决办法,最后全都用上了,总结一下。

1:在连接时,设置他的avalie保活定时器.

Type
    TCP_KeepAlive = record
        OnOff: Cardinal;
        KeepAliveTime: Cardinal;
        KeepAliveInterval: Cardinal;
    end;

procedure TFrm_Client.Sck_MainConnect(Sender: TObject;
  Socket: TCustomWinSocket);
var val:TCP_KeepAlive;
    Ret:DWord;
begin
    inherited;
    try
        val.OnOff:=1;
        val.KeepAliveTime:=1000 * 60 * 60 * 24;   //24小时
        val.KeepAliveInterval:=100;
        Ret:=WSAIoctl(socket.SocketHandle, IOC_IN or IOC_VENDOR or 4, @val, SizeOf(Val), nil, 0, @Ret, nil, nil)
    finally
        showInfo('已经连接到服务器.TCP_KeepAlive定时器'+intToStr(val.KeepAliveTime)+'结果:'+intToStr(Ret));
    end;
end;

含义一目了然,不再多说。

2:在出错中检查10053之类错误,遇到就直接close,主动触发关闭事件,在关闭事件中触发自动重新连接的timer的自动重连:

procedure TFrm_Client.Sck_MainError(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
Var nCode:Cardinal;
begin
    inherited;
    showInfo('||[Error]出错:'+intToStr(ErrorCode)+';自动重连:'+boolToStr(lAskForReConnect, true));
    nCode:=ErrorCode;               //GetLastError;
    ErrorCode:=0;
    //如果出错,关闭,触发断开事件
    if (nCode=EConnAborted)         //10053   EConnAborted  WSAEConnAborted
      or (nCode=ENetDown)           //10050   ENetDown
      or (nCode=ETimedOut)          //10060
      or (nCode=EConnRefused)       //10061
      or (nCode=EHostUnReach)       //10065
    then begin
        if lAskForReConnect and (not lInReConnecting) then Timer_ReConnect.enabled:=true;  //ReConnectServer;
        if not lInReConnecting then begin
            sck_Main.active:=false;
            sck_Main.close; //ReConnectServer   and (not sck_Main.active)
        end;
        showInfo('  连接状态:'+boolToStr(sck_Main.active,true)+';重连设置:'+boolToStr(lAskForReConnect,true)+';重连状态:'+boolToStr(timer_ReConnect.enabled,true));
    end;
end;
//断开连接,断开时自动尝试再次连接,连接不上才断开
procedure TFrm_Client.Sck_MainDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
    inherited;
    if (not lAskForReConnect) or (lInReConnecting) then exit;
    Timer_ReConnect.enabled:=true;  //ReConnectServer;
end;
//重新连接
Function TFrm_Client.ReConnectServer:Boolean;
const nMax=6;
var Qry:TAdoQuery;    L,L2:Boolean;     i:Integer;        s:String;
begin
    Result:=False;    i:=1;             if not lAskForReConnect then exit;
    L:=frm_MDI.ASysConfig.lAutoLogon;
    qry:=nil;
    with sck_Main do
    try
        if not lInCreatting then    //or (not lLoginOK)
        try
            if not lAskForReConnect then exit;
            sck_Main.Socket.Disconnect(sck_Main.Socket.Handle);
            sck_Main.Close;
            if L then begin
                showInfo('断线,开始尝试自动重新连接服务器,最大重试次数['+intToStr(nMax)+']...');
                i:=1;           //尝试3次重新连接
                while (not sck_Main.active) and (i<=nMax) do
                try
                    if not lAskForReConnect then break;
                    showInfo('  *尝试自动重新连接['+intToStr(i)+'/'+intToStr(nMax)+']...');
                    sck_Main.Open;
                    delay(2000);
                    L2:= sck_Main.active;         //connectToServer(sck_Main, txt_svrIP.text, strToIntDef(txt_SvrPort.text,65180));
                    if not L2 then delay(1000);   //sck_Main.active
                    inc(i);
                except
                    on x:Exception do ;
                end;
            end;
            //
        except
            on x:exception do ;
        end;
    finally
        lLoginOK:=sck_Main.Active;
        if not lLoginOK then begin
            showInfo('已经断开服务器的连接,正在尝试重新连接');
            lbl_Move.caption:='已经断开连接,正在尝试重新连接';
        end;
        Result:=lLoginOK;
        s:='失败';
        if lLoginOK then
        Try
            s:='[第'+intToStr(i)+'次重试成功]';
            qry:=tAdoQuery.create(self);
            qry.connection:=dmain.conn_Main;
            clsAskForNewMessages(sck_Main.Socket, qry);
        finally
            if assigned(qry) then qry.free;
        End;
        if L then showInfo('断线重新连接结果:'+s);
    end;
End;

procedure TFrm_Client.Timer_ReconnectTimer(Sender: TObject);
var msg:_msgIdle;
begin
    inherited;
    Try
        //已连接了或用户不要求自动重连,则退出
        showInfo('[##Timer_ReconnectTimer]断线自动重连。连接状态:'+boolToStr(sck_Main.active,true)+';重连设置:'+boolToStr(lAskForReConnect,true)+';重连状态:'+boolToStr(timer_ReConnect.enabled,true));
        if (sck_Main.active) or (not lAskForReConnect) then exit;
        //
        lInReConnecting:=True;            //处于连接中
        timer_ReConnect.enabled:=False;
        timer_ReConnect.enabled:=(not ReConnectServer) and lAskForReConnect;   //连上就停止
        //
    finally
        lInReConnecting:=False;
    end;
end;

3:服务器上。信息发送都用一个sendMessage方法,在他里边给socket加上错误处理,也时遇到10053错误直接就直接认为已经断开了。

//发送聊天信息
function sendMessage(socket:TCustomWinSocket; nType:Integer; pMessage:Pointer):boolean;
const sFile='d:\aa.txt';
var tp:_msgHead;      buf, pMsg, p:pByte;         f,m,hBuf:THandle;
    pOldErrorEvent:TSocketErrorEvent;
    s:String;         n,i,nLen,nMsgLen:integer;
begin
    result:=false;    buf:=nil;       m:=0;
    if lGlobalTerminate then exit;
    //
    try
      pOldErrorEvent:=nil;
      if assigned(pSocketErrProcedure) then begin
          pOldErrorEvent:=socket.OnErrorEvent;
          socket.OnErrorEvent:=pSocketErrProcedure;
      end;
      try
        if not socket.Connected then raise exception.Create('连接未打开或不存在');
        //
        tp.msgType:=nType;
        nMsgLen:=getMessageTypeLen(nType);
        if nMsgLen<0 then exit;
        tp.nBagSize:=sizeof(tp)+nMsgLen;
        tp.Version.dwMajorVersion:=frm_MDI.Version.dwMajorVersion;  //1
        tp.Version.dwMinorVersion:=frm_MDI.Version.dwMinorVersion;  //0
        //
        nLen:=nMsgLen+sizeOf(tp)+0;
        //分配内存区、复制头  globalAlloc(gmem_MoveAble, n+sizeOf(tp));
        hBuf:=globalAlloc(GMEM_MOVEABLE + GMem_Share, nLen);
        buf:=globalLock(hBuf);                //getMem(buf, nLen);
        fillChar( buf^, nLen, #0);
        copyMemory(buf, @tp, sizeOf(tp));     //strCopy(buf, @tp);
        //复制内容到全部内存区
        if (nMsgLen<>0) and (pMessage<>nil) then begin
         n:=integer(buf)+sizeOf(tp);
         p:=ptr(n);
         copyMemory(p, pMessage, nMsgLen);   //strLCopy(p, pMessage, nMsgLen);          //copyMemory(p, pMessage, nMsgLen);
        end;
        //发送,重试n次,最多150ms
        n:=-1;        i:=1;
        while (n=-1) and (i<=5) do begin
            if lGlobalTerminate or (not socket.Connected) then break;
            n:=socket.SendBuf(buf^, nLen);
            if n=-1 then delay(10*i);     //socket没有空间,则等待后重发
            inc(i);
        end;
        //
        result:=lGlobalTerminate or (n>-1);
      except
        on x:exception do begin
            s:='[sendMessage]发送信息出错!Type='+intToStr(nType)+#13+#10+'  '+x.Message;
            //if assigned(buf) then freeMem(buf);
            insShowInfo(s);
            raise eSocketError.Create(s);
        end;
      end;
    finally
      if assigned(pOldErrorEvent) then socket.OnErrorEvent:=pOldErrorEvent;
      if assigned(buf) then begin
          globalUnlock(hBuf);
          globalFree(hBuf);
      end;
    end;
end;
//服务器端初始化时:

pSocketErrProcedure:=defaultSocketErrProcedure;

//socket出错的处理
procedure TFrm_Service.defaultSocketErrProcedure(Sender: TObject; Socket: TCustomWinSocket;
        ErrorEvent: TErrorEvent; var ErrorCode: Integer);
var nCode:Integer;
begin
    inherited;            nCode:=errorCode;
    //若出错,关闭
    if nCode=10053 then begin
        socket.Close;
        raise exception.create('客户端已经断开连接了!');
    end;
end;

4:心跳包,可能这个最有效,心跳包的意思,就是像心跳一样,每隔多长事件跳一次,跳自然会触发错误了。

//保持连接,心跳包
procedure TFrm_Client.Timer_KeepConnectTimer(Sender: TObject);
var msg:_msgIdle;     H:TSocket;      n:Integer;      L:Boolean;
begin
    inherited;
    if (not lLoginOK) or (lInReConnecting) then exit;
    L:=False;
    try
        if lGlobalTerminate then exit;
        Timer_KeepConnect.enabled:=false;
        //
        //fillChar(msg.sComment,length(msg.sComment), #0);
        msg.Version.dwMajorVersion:=frm_MDI.Version.dwMajorVersion;
        msg.Version.dwMinorVersion:=frm_MDI.Version.dwMinorVersion;
        msg.nTickCount:=0;  //GetTickCount;
        msg.nHandle:=0;     //Socket.Handle;
        msg.sComment:='你好!有空常联系!';   //sComment;
        if not sendMessage(sck_Main.Socket, msgTypeIdle, @msg) then ;
        //
        //sendIdleMsg(sck_Main.socket);
        {n:=timer_KeepConnect.interval;
        H:=sck_Main.Socket.SocketHandle;
        waitForData(n-300);   }
        L:=True;
    finally
        Timer_KeepConnect.enabled:=true;
        showInfo('发送联系服务器消息IDLE'+intToStr(Timer_KeepConnect.interval)+',结果:'+boolToStr(L,true)+'...');
    end;
end;

- 作者: 蹲墙头等红杏 2007年09月26日, 星期三 17:04  回复(0) |  引用(0) 加入博采

基于TCP/IP的局域网多用户通信

引言

由于因特网的迅速流行,越来越多的应用程序具备了在网上与其它程序通信的能力。从WIN95开始微软把网络功能融进了它的操作系统,使得应用程序网络通信能力更为普及。因此,微软的TCP/IP协议也就成为网络应用程序基于的首选协议。

一般采用TCP/IP协议的应用程序只实现了单用户与服务器间点对点的连接,而本文在VC6.0的环境下,运用了了多线程以及共享数据结构技术,不仅实现了多用户与服务器间的连接,而且解决了多用户间信息互发问题----依靠服务器的转发功能。通过本文的阐述,希望能对那些需要编写多用户网络通信程序的读者以启发。

一、技术概述

1.1 基于TCP/IP的通信技术

基于TCP/IP的通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。下面简要地讲一下设计思路(VC6.0下):

第一部分 服务器端
  一、创建服务器套接字(create)。
  二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。
  三、接受来自用户端的连接请求(accept)。
  四、开始数据传输(send/receive)。
  五、关闭套接字(closesocket)。

第二部分 用户端
  一、创建用户套接字(create)。
  二、与远程服务器进行连接(connect),如被接受则创建接收进程。
  三、开始数据传输(send/receive)。
  四、关闭套接字(closesocket)。

通过以上设计思路,我们可以创建一个简单的面向连接的单用户程序。下面,将介绍多线程技术,以使程序支持多用户。

1.2 多线程技术

我们可以把线程看成是一个进程(执行程序)中的一个执行点,每个进程在任何给定时刻可能有若干个线程在运行。一个进程中的所有线程共享该进程中同样的地址空间,同样的数据和代码,以及同样的资源。进程中每个线程都有自己独立的栈空间,和其它线程分离,并且不可互相访问。每个线程在本进程所占的CPU时间内,要么以时间片轮换方式,要么以优先级方式运行。如果以时间片轮换方式运行,则每个线程获得同样的时间量;如果以优先级方式运行,则优先级高的线程将得到较多的时间量,而优先级低的线程只能得到较少的时间量。方式的选择主要取决于系统时间调度器的机制以及程序的实时性要求。

现在,运用多线程技术就可以实现对多用户的支持。即在服务器端,使接收来自用户端的连接请求(accept)这步无限循环,每接收一个用户请求,产生两个线程(send和receive线程),用来管理服务器与该用户的通信任务。下面,运用共享数据结构技术,就可以实现本问所要解决的关键技术---服务器转发技术。

1.3 共享数据结构技术

同一进程中的多个线程共存于单一的线性地址空间,因此,在多线程间共享数据结构是非常容易且方便的。但必须注意的是,对数据结构的访问必须是多线程互斥的,否则数据任意更改将导致不可预料的结果。本文所阐述的服务器转发技术也就是通过共享数据结构实现线程间的互相通信。

二、实现方案

整体方案的构思图如下:

通过上图,我们可以看到整个系统分为三个相关的程序,即注册/登陆服务器、通信服务器以及用户程序。其中,注册/登陆服务器负责用户的注册、登陆以及数据库管理;通信服务器负责完成数据转发以及共享数据结构的管理;用户端则完成注册、登陆和通信功能。为什么要把服务器分为两部分呢?主要是考虑到服务器的用户容量问题,以及对通信服务器的保护,只有在通过验证后,用户在能与通信服务器连接。

由此可见,整个系统通信任务的实现还是很复杂的。用户端首先必须注册自己,等待注册成功;然后根据自己的注册信息进行服务器登陆,登陆成功后才能与通信服务器连接,进行用户间通信。

注册/登陆服务器接收到用户端的信息后,首先判断是注册信息还是登陆信息。如果是注册信息,则将该数据按预定的格式写入数据库,然后返回注册成功的消息,期间有任何异常产生,服务器都会返回注册失败消息,提示用户重新注册;如果是登陆信息,则从数据中提取用户名和ID与数据库中的内容进行比较,如果该用户存在,则返回登陆成功消息,反之,返回登陆失败消息。

通信服务器所完成的主要功能是数据转发,这是通过与图中的共享数据结构进行交互完成的。服务器接收到用户端发来的消息后,提取消息的一部分与共享数据结构存储的内容进行比较,确定所要转发的对象,最后通过多线程及其通信机制完成数据转发。 下面,我们将分三部分来讨论系统的具体实现过程。

三、具体实施

3.1 注册/登陆服务器

注册/登陆服务器程序是基于对话框的,该程序使用I/O端口56789与用户端连接。
首先,在对话框初始化的同时完成网络初始化,即执行Init_net()函数,代码(不完整)如下:

BOOL CServerDlg::Init_net()
{////////////////////////网络初始化///////////////////////////////
    addrLen=sizeof(SOCKADDR_IN);
    status=WSAStartup(MAKEWORD(1, 1), &Data);
    ………
    memset(&serverSockAddr, 0, sizeof(serverSockAddr));

/*以下指定一个与某个SOCKET连接本地或远程地址*/

    serverSockAddr.sin_port=htons(PORT);
    serverSockAddr.sin_family=AF_INET;
    serverSockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    serverSocket=socket(AF_INET, SOCK_STREAM, 0);//初始化SOCKET
    ………

    status=bind(serverSocket,(LPSOCKADDR)&serverSockAddr,sizeof(serverSockAddr)); //将SOCKET与地址绑定
    ………
    status=listen(serverSocket, 5); //开始监听
    ………
    return true;
}
接着按下RUN键开始服务器功能,执行Reg_Load()函数,使服务器始终处于等待连接状态,但这样也使该线程始终阻塞。当有用户连接时,该函数创建一个任务用于处理与用户及数据库的事务。具体任务函数略(详见原始代码文件)。
void CServerDlg::Reg_Load()
{   
	while(1)
	{
		CWinThread*  hHandle;
		clientSocket=accept(serverSocket,(LPSOCKADDR)&clientSockAddr,&addrLen); //等待连接,阻塞
		hHandle=AfxBeginThread(talkToClient,(LPVOID)clientSocket);//有连接时,创建任务
	        ………
	}
}
任务函数在接收到消息时,要对数据库进行操作,由于数据库较简单,采用ODBC连接ACCESS数据库(将netuser.mdb在ODBC数据管理器中安装成同名数据源)具体代码略。

3.2 通信服务器

通信服务器是本程序实现的关键,它运用共享数据结构技术及多线程技术,通过I/O端口56790与用户端连接,实现了数据转发功能。首先,程序初始化网络Init_net(),接着当用户连接到服务器时,创建接收线程和发送线程,这样就可以实现数据转发。最后,当用户断开连接时,服务器关闭与他的连接,并结束相应的线程。

下面我们来看一下本程序中的共享数据结构的具体内容与使用方法以及多线程的相关内容与实现。

● 共享数据结构

本程序的共享数据结构一共有两个,即socket_info和send_info。前者包含了所有登陆用户的一些基本资料,后者则包含了当前服务器接收到的用户端所发送的信息资料。详细内容及注释如下:

struct socket_info  
{
    SOCKET s_client;                    //用户的SOCKET值
    u_long client_addr;             //用户网络地址
    CString pet;                        //用户昵称
    CWinThread* thread;             //为该用户创建的发送线程对象的指针
};

struct send_info
{
    CString data;                   //用户端发送的数据内容(经过编辑)
    CWinThread* thread;             //需要发送数据的任务指针
};
在程序中,定义两个全局变量,用来在线程间共享:
send_info info_data; CLists_info;
每当有用户连接到服务器,服务器就将用户端的一些信息以socket_info结构体的形式存入s_info列表中;而当服务器接收到用户端发送过来的数据时,就将数据格式化后存入结构体info_data,通过与结构体列表比较,确定需要恢复的发送线程(所有发送线程在创建时都被挂起)。这样,服务器就准确地转了发数据。

●多线程

每当服务器上有用户连接成功,服务器都会为其创建两个线程:接收线程(RecvData)和发送线程(SendData),并且接收线程在创建后处于可执行状态,而发送线程则阻塞,等待服务器将其唤醒。这两个线程都执行一个无限循环的过程,只有当通信出现异常或用户端关闭连接时,线程才被自身所结束,并且,这两个线程一定是同时生成,同时结束的。很显然,每个连接产生两个线程,使得数据转发变的简单,但同时又使得服务器的任务加重。因此,用户端的连接数量有所限制,视服务器软、硬件能力而定。

同时,由于多线程对结构体info_data都需要操作,所以线程间必须同步。这儿,我定义了互斥量CMutex m_mutex,用它的方法Lock()和Unlock()来完成同步。

我们首先来看一下接收线程(RecvData):(不完整代码)

UINT RecvData(void* cs)
{   
	SOCKET clientSocket=(SOCKET)cs;
	while(1)
	{
		numrcv=recv(clientSocket, buffer, MAXBUFLEN, NO_FLAGS_SET);
		buffer[numrcv]=''\0'';
		if(strcmp(buffer,"Close!")!=0)  //不是接收的“Close”数据
		{
			…………
		        for(i=0;iResumeThread(); //恢复发送相应的线程
					break;
				}
			}
		}
		else
		{
			…………
			if(clientSocket==s1.s_client)
			{
				m_mutex.Lock(); //互锁
				info_data.data=buffer;
				m_mutex.Unlock();           //解锁
				s1.thread->ResumeThread();  //恢复发送相应的线程
				s_info.RemoveAt(po1);       //删除该用户信息
				break;
			}
			………
			goto aa;
		}
	}
	aa: closesocketlink((LPVOID)clientSocket);          //关闭连接
	AfxEndThread(0,true);                           //结束本线程
	return 1;
}
接下来看一下发送线程(SendData):(不完整代码)
UINT SendData(void* cs)
{
	SOCKET clientSocket=(SOCKET)cs;
	while(1)
	{
		if(info_data.data!="Close!")
		{
			m_mutex.Lock();               //互锁
			numsnd=send(clientSocket,info_data.data,
			info_data.data.GetLength(),NO_FLAGS_SET); //发送数据
			now=info_data.thread;
			m_mutex.Unlock();             //解锁
			now->SuspendThread();         //自身挂起
		}
		else
		{   goto bb; }

	}
	bb: closesocketlink((LPVOID)clientSocket);    //关闭连接
	AfxEndThread(0,true);                         //结束本线程
	return 1;
}
3.3 用户端

很显然,用户端不用考虑多线程,网络连接技术也比较成熟,因此在通信方面没有什么难题。但是,用户端是面向实际用户的,所以,不论是界面还是功能都必须友好。就像大多数软件的更新一样,界面友好度的提高以及功能的完善往往是放在首位的。由此可见,单从总体设计与技术实现角度来讲,用户端的工作量是十分大的,并且设计较服务器端复杂得 多。我粗略总结了以下几条:

●与服务器通信格式兼容;

●操作简单、易用,有美观的界面及快捷键;

●准确地接收和传输数据;

●所有的数据记录与提取功能;

●多种消息接收提示方式,比如托盘图标(发送者头像)闪烁、声音提示等;

根据以上内容,我设计了三个独立的对话框分别用来完成注册、登陆、通信功能,登陆和注册对话框与服务器的56789I/O端口连接,通信对话框与服务器的56790I/O端口连接,这样就很好地实现了注册登陆与通信的隔离,既能使服务器负载降低,同时又能保证一定的通信安全性。

由于本部分不是主要内容,详细代码见程序。

四、结束语

通过以上阐述可以知道,本系统分为服务器端和用户端,服务器端又分为注册/登陆服务器和通信服务器,通过通信服务器的转发功能实现了局域网内的多用户通信功能。本文运用了多线程技术和共享数据结构技术实现了通信服务器的转发功能,使一般基于TCP/IP的网络应用程序得到了发展。本系统已经在我实验室的局域网(一台服务器,二十台客户机)运行通过。

参考文献:

[1] Eugene Olafsen ,Kenn Scribner, K.David White等著. MFC Visual C++ 6.0编程技术内幕. 北京:机械工业出版社 2000.2
[2] Charles Wright. Visual C++程序员实用大全. 北京:中国水利水电出版社 2001.10

- 作者: 蹲墙头等红杏 2007年09月26日, 星期三 15:57  回复(0) |  引用(0) 加入博采

Tchart分析报告

1          Tchart分析报告

1.1      [概述]

   TChartdelphi里面一个标准的图形显示控件。它可以静态设计(at design time)也可以动态生成。

1.2      [继承关系]

   

TObject

TPersistent

TComponent

TControl

TCustomControl

TWedgetControl

TChart

TCustomPanel

1.3      [tips]

1.3.1            Pro Version支持Bezier , Contour , Radar   point3D 曲线

1.3.2            支持jpeg文件的导出

1.3.3            Chart中的Series 可以连接到Table , Query , RemoteDataset(其他数据集)

1.3.4            TChart里的seriesactive属性可以实现对已绘制图形的显示或者隐藏

1.3.5            TChart, tchartSeries是所有具体series的父类,没有画出什么来的,用一个具体的series类来创建就可以了,比如用TLineSeriesTPieSeries TPointSeries TPointSeries等等都行

1.3.6            TTeeFunction Component可以实现在同一个TChart里面,一个Serries对另一个Serries的统计

1.4      [问题极其使用技巧]

1.4.1            TChart中如何实现只有Y轴的放大与缩小功能?

设置BottomAxis或者LeftAxisAutomatic:=false并同时设置Minimum,Maximum属性

1.4.2            如何固定TChart中的坐标,不使TChart中的坐标跟随Series的变化而变化?

//设置底座标

  with myChart.BottomAxis do

  begin

    Automatic:=false;

    Minimum:=0;

    LabelStyle := talText;

  end;

  //设置左坐标

  with myChart.LeftAxis do

  begin

    Automatic:=false;

    Minimum:=0;

    Title.Angle:=270;

    Title.Font:=Self.Font;

    Title.Font.Charset:=ANSI_CHARSET;

    Title.Font.Name:='@宋体';

    Grid.Visible := False;

  end;

  //设置右坐标

  with myChart.RightAxis do

  begin

    Automatic:=false;

    Title.Font:=Self.Font;

    Title.Font.Charset:=ANSI_CHARSET;

    Title.Font.Name:='@宋体';

    Title.Caption:='累计百分比(%';

    Maximum:=100;

    Minimum:=0;

  end;

1.4.3            如何删除一个图形中的一个点?

使用Seriesdelete 方法

1.4.4            如何修改一个点的X或者Y 值?

LineSeries1.YValue[3] := 27.1 ;

{In Bubble Series}

BubbleSeries1.RadiusValues.Value[ 8 ] := 8.1 ;

{In Pie Series}

PieSeries1.PieValues.Value[ 3 ] := 111 ;

1.4.5            如果横坐标是时间(日期),如何进行设置?

{First, you need to set the DateTime property to True in the desired X and/or Y values list.}

LineSeries1.XValues.DateTime := True ;

{Second, use the same above described methods, but give the values as Date, Time or DateTime values}

LineSeries1.AddXY( EncodeDate( 1996 , 1 , 23 ) , 25.4 , 'Barcelona' , clGreen );

1.4.6            如何在chart中画出的曲线某个点上标记出该点的值?

Series.Marks.Visible:=true;

Series.Marks.Style:=smsValue;

1.4.7            如何设置横轴或者纵轴的增长率?

Chart.BottomAxis.Increment := DataTimeStep[ dtOneHour ] ;

Chart.RightAxis.Increment := 1000;

1.4.8            如何对图象进行缩放?

TChartZoomRect或者ZoomPercent方法 Pie图可能不支持缩放)

1.5      [TChart可以绘制的图形]

1.5.1            Line ( TLineSeries)

1.5.2            FastLine (TFastLineSeries) 相对Line来说,它损耗了某些属性从而来实现快速绘制

1.5.3            Bar (TBarSeries)

1.5.4            Horizontal bar (THorizBarSeries)

1.5.5            Area (TAreaSeries)

1.5.6            Point (TPointSeries)

1.5.7            Pie (TPieSeries)

1.5.8            Arrow (TArrowSeries)

1.5.9            Bubble (TBubbleSeries)

1.5.10         Gantt (TGanttSeries)

1.5.11         Sharp (TChartShape)

1.6      [TChart的实时绘制]

  实时绘制对机器性能要求比较高,因此我们在编程的时候要注意下面几个方面:

ü         使用2D图形

ü         Chart尽可能包含少的点

ü         如果需要,可以移除(removechartlegend(?????)Title

ü         使用默认的字体和字体大小

ü         使用FastLineSeries

ü         使用实体(solid)画笔和画刷格式

ü         尽量避免使用圆形和环行bar样式

ü         不要使用背景图片和渐变效果样式

ü         ChartBevelInnerBevelOUter属性设置为bcNone

ü         如果需要,把TChartAxisVisible属性设置为False

ü         BufferedDisplay设置为false可以加速chart的重绘

1.7      [Scrolling]

   TChart4scroll选择(AllowPanning属性),分别是 不允许Scroll ( pmNone) ; 水平Scroll (pmHorizontal) ; 垂直Scroll (pmVertical)  ;  水平和垂直Scroll (pmBoth)

Procedure Scroll(Const Offset:Double; CheckLimits:Boolean);

例子如下:

 Chart1.BottomAxis.Scroll(  1000, True );这段代码也等同于

With Chart1.BottomAxis do

Begin

 Automatic:=false;

 SetMinMax( Minimum+1000, Maximum+1000 );

    End;

1.8      [TChart中的全局变量]

ü         TeeScrollMouseButton := mbRight;设置鼠标右键为TChart滚动键(默认)

ü         TeeScrollKeyShift    := [ ssCtrl ]; 要按住Control键才可以使Scroll滚动

1.9      [TChartSerries使用技巧]

1.9.1            运行时候创建一个Serries, 三种方法:

1Var MySeries : TBarSeries ;

MySeries := TBarSeries.Create( Self );

MySeries.ParentChart := Chart1 ;

            2Chart1.AddSeries( TBarSeries.Create( Self ) );

                3Var MyClass : TChartSeriesClass;

MyClass := TBarSeries ;

Chart1.AddSeries( MyClass.Create( Self ) );

1.9.2            获得TChart中的Serries数组,也有三种方法

1MySeries := Chart1.SeriesList [ 0 ]

2MySeries := Chart1.Series [ 0 ]

3MySeries := Chart1 [ 0 ]

1.9.3            SerriesCount属性获得SeriesListSeries的个数

1.9.4            隐藏TChart中的Series有三种方法,但是效果不等价

1.  Series1.Active:=False; 仅仅隐藏,当设置为true的时候还可以显示出来

2.  Series1.ParentChart:=nil 隐藏,重新设置ParentChartTChart时候可以显示

3.  Series1.Free; 删除了Series. 不可以恢复

1.9.5            TChart中的数据排序

    With Series1 do

begin

    YValues.Order:=loAscending;

    YValues.Sort;

    Repaint;

end;

Ø         定位一个点(Loacate a point)

Series1.XValues.Locate(123);

Ø         XValueYValue都拥有的属性Total , TotalABS , MaxValue , MinValue