青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

興海北路

---男兒仗劍自橫行
<2025年11月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

統(tǒng)計(jì)

  • 隨筆 - 85
  • 文章 - 0
  • 評(píng)論 - 17
  • 引用 - 0

常用鏈接

留言簿(6)

隨筆分類

隨筆檔案

收藏夾

全是知識(shí)啊

搜索

  •  

最新評(píng)論

閱讀排行榜

評(píng)論排行榜

c++ 獲取當(dāng)前時(shí)間

#include <iostream>

#include <time.h>

using namespace std;

int main()

{

time_t ltime;
char tmpbuf[128];

//方法1:分別獲取當(dāng)前時(shí)間,日期
/* Display operating system-style date and time. */
    _strtime( tmpbuf );
    printf( "OS time:\\t\\t\\t\\t%s\\n", tmpbuf ); //打印當(dāng)前時(shí)間
    _strdate( tmpbuf );
    printf( "OS date:\\t\\t\\t\\t%s\\n", tmpbuf ); //打印當(dāng)前日期

//方法二:獲取當(dāng)前時(shí)間日期
time(&ltime); //獲取從1970至今經(jīng)過(guò)的秒數(shù)

cout << ctime(&ltime) << endl; //折算成當(dāng)前時(shí)間日期

return 0;

}

=========================================
VC中基于 Windows 的精確定時(shí)

http://www.vckbase.com/document/viewdoc/?id=1301

-------------------------------------------------------------------------------------------------------------

//獲取程序運(yùn)行時(shí)間   
          long   t1=GetTickCount();//程序段開(kāi)始前取得系統(tǒng)運(yùn)行時(shí)間(ms) ;
          Sleep(500);   
          long   t2=GetTickCount();();//程序段結(jié)束后取得系統(tǒng)運(yùn)行時(shí)間(ms)   
          str.Format("time:%dms",t2-t1);//前后之差即   程序運(yùn)行時(shí)間   
          AfxMessageBox(str);  


posted @ 2008-04-08 16:00 隨意門(mén) 閱讀(1391) | 評(píng)論 (0)編輯 收藏
VC使用C API 連接操作MySQL數(shù)據(jù)庫(kù)

一切盡在代碼中,代碼中也太多了if/else,可以對(duì)它進(jìn)行更好的函數(shù)及至類的封裝,規(guī)范的處理好異常。

  1. #include <windows.h>
  2. //需要在VC的Options設(shè)置一個(gè)include路徑指向'%mysql_home%/inlude'目錄   
  3. #include <mysql.h>
  4. //設(shè)置一個(gè)lib路徑指向'%mysql_home%/lib/opt'目錄 (mysql5.0是個(gè)目錄)   
  5. #pragma comment(lib,"libmysql.lib")    
  6. #define host_name "localhost"  //數(shù)據(jù)庫(kù)服務(wù)器   
  7. #define db_name "test"         //數(shù)據(jù)庫(kù)名   
  8. #define user_name "root"       //用戶名   
  9. #define password ""            //密碼   
  10. int  main( int  argc, char  * argv[]) {   
  11.     
  12.   char  szSqlText[500] ;   
  13.     
  14.  MYSQL *conn;   
  15.  MYSQL_RES *rs;   
  16.  MYSQL_ROW row;    //注意它的聲明 typedef char **MYSQL_ROW,字符串?dāng)?shù)組   
  17.   BOOL  bCreate = FALSE;   
  18.     
  19.  conn = mysql_init(NULL);   
  20.   if (conn == NULL)   
  21.  {   
  22.   fprintf(stderr, "mysql_init() failed (probably out of memory)\n" );   
  23.   exit(1);   
  24.  }   
  25.     
  26.   if  (mysql_real_connect(conn,host_name,user_name,password,   
  27.   db_name,MYSQL_PORT,NULL,0) == NULL)   
  28.  {   
  29.    //在MYSQL初始化之后的操作如果有錯(cuò)誤,可以用mysql_errno(MYSQL*)和   
  30.    //mysql_errer(MYSQL*) 分別獲得出錯(cuò)代號(hào)和描述   
  31.   fprintf(stderr, "mysql_real_connect() failed:\nError %u (%s)\n" ,   
  32.    mysql_errno(conn),mysql_error(conn));   
  33.   exit(1);   
  34.  }   
  35.     
  36.  printf( "connect to db successful.\n" );   
  37.   if  (bCreate) {   
  38.    //第一次運(yùn)行創(chuàng)建一個(gè)表mytable   
  39.   sprintf(szSqlText, "create table mytable(time datetime,s1 char(6),s2 char(11),s3 int,s4 int)" );   
  40.    if  (mysql_query(conn,szSqlText)) {   
  41.    printf( "Can't create table.\n" );   
  42.    mysql_close(conn);   
  43.     return  0;   
  44.   }   
  45.  }   
  46.  sprintf(szSqlText, "insert into mytable values('2000-3-10 21:01:30','Test','MySQLTest',2000,3)" );   
  47.   if  (mysql_query(conn,szSqlText)) {   
  48.   printf( "Insert values error:\nError %u (%s)\n" ,   
  49.    mysql_errno(conn),mysql_error(conn));   
  50.   mysql_close(conn);   
  51.    return  0;   
  52.  }   
  53.   else {   
  54.    //insert/delete/update 語(yǔ)句可用mysql-affected_rows()得到受作用的行   
  55.   printf( "INSERT statement succeeded: %lu rows affected\n" ,   
  56.    (unsigned  long )mysql_affected_rows(conn));   
  57.  }   
  58.     
  59.   //查詢數(shù)據(jù)   
  60.  sprintf(szSqlText, "select * from mytable" );   
  61.     
  62.   //執(zhí)行成功則返回零   
  63.   if  (mysql_query(conn,szSqlText) != 0) {   
  64.   mysql_close(conn);   
  65.    return  0;   
  66.  }   
  67.   else  {   
  68.    //立即從服務(wù)器返回所有行,存儲(chǔ)到本地,產(chǎn)生結(jié)果集,失敗則返回NULL   
  69.   rs = mysql_store_result(conn);   
  70.      
  71.    //結(jié)果集是保留在服務(wù)器上,fetch_row時(shí)才逐行從服務(wù)器上取   
  72.    //rs = mysql_use_result(conn);   
  73.    //Get query result.   
  74.    int  count = ( int )mysql_num_rows(rs);   
  75.   printf( "Query: %s.\n%ld records found.\n" ,szSqlText,count);   
  76.    //MYSQL_ROW是一個(gè)指向數(shù)值數(shù)組的指針,row[0],row[1]...row[列數(shù)-1]   
  77.    //所有的數(shù)據(jù)類型都以字符串返回,即使是數(shù)字型,要進(jìn)行串轉(zhuǎn)換   
  78.    //NULL指針代表數(shù)據(jù)庫(kù)字段的NULL,應(yīng)經(jīng)常檢查列是否為NULL   
  79.    while ((row = mysql_fetch_row(rs)) != NULL){   //返回NULL,則說(shuō)明不再有行   
  80.     for (unsigned  int  i=0; i<mysql_num_fields(rs);i++){   
  81.      if (i>0)   
  82.     fputc('\t',stdout);   
  83.     printf( "%s" ,row[i]!=NULL ? row[i]: "NULL" );   
  84.    }   
  85.    fputc('\n',stdout);   
  86.   }   
  87.    //使用完后,釋放結(jié)果集占用內(nèi)存   
  88.   mysql_free_result(rs);   
  89.  }   
  90.  mysql_close(conn);   
  91.   return  0;   
  92. }   
  93. //最后,注意查詢語(yǔ)句中字符串的轉(zhuǎn)義 select a from t where a=''1',是要出錯(cuò)的   
  94. //空字符轉(zhuǎn)換為'\0',這里的0 是可打印的ASCII 碼0,而不是空。   
  95. //反斜線、單引號(hào)和雙引號(hào)分別轉(zhuǎn)換為‘\\’、‘\'’ 和‘\"’   
  96. //你也可以用mysql_escape_string(to_str,from_str,from_len)轉(zhuǎn)換sql語(yǔ)句   
輸出結(jié)果是:
connect to db successful.
INSERT statement succeeded: 1 rows affected
Query: select * from mytable.
8 records found.
2000-03-10 21:01:30     Test    MySQLTest       2000    3
2000-03-10 21:01:30     Test    MySQLTest       2000    3
2000-03-10 21:01:30     Test    MySQLTest       2000    3
2000-03-10 21:01:30     Test    MySQLTest       2000    3
2000-03-10 21:01:30     Test    MySQLTest       2000    3
Press any key to continue

posted @ 2008-04-08 14:50 隨意門(mén) 閱讀(644) | 評(píng)論 (0)編輯 收藏
XSL:轉(zhuǎn)換從哪里開(kāi)始?

前言

愛(ài)好XML的人最終會(huì)試著將XML轉(zhuǎn)換為HTML,或者轉(zhuǎn)換為其他類型的文檔,DOM/SAX顯然不是專門(mén)為轉(zhuǎn)換設(shè)計(jì)的,CSS對(duì)于轉(zhuǎn)換也是力有不逮,所以XML的愛(ài)好者們幾乎無(wú)一例外的要遭遇XSL,但是XSL似乎有非常多的用法,對(duì)于XML僅僅只是表示格式化的數(shù)據(jù)而言,XSL顯得復(fù)雜且毫無(wú)頭緒。

例如《跟我學(xué)XSL》和《XSL基礎(chǔ)入門(mén)》這樣的教程會(huì)帶給你XSL的一些概念和例子,但是對(duì)于XSL的運(yùn)行環(huán)境、平臺(tái)特性和本質(zhì),似乎都語(yǔ)焉不詳,你最終學(xué)會(huì)的僅僅是在XMLSPY或者IE中打開(kāi)你的XML看看它轉(zhuǎn)換后的效果罷了。一有人提到腳本語(yǔ)言或者JAVA中調(diào)用XSL你就頭大了,甚至你不清楚XSL和XSLT究竟有什么區(qū)別。迷失在網(wǎng)絡(luò)中的人們喜歡不停的用google搜索你想要的中文資料,但是其實(shí)有那個(gè)時(shí)間,干脆去那種技術(shù)的官方網(wǎng)站上好好看看吧。http://www.w3.org/Style/XSL/是XSL技術(shù)的W3C的官方網(wǎng)站,在網(wǎng)頁(yè)正文的第一行它就解釋和XSL和XSLT的區(qū)別。原文如下:

XSL is a family of recommendations for defining XML document transformation and presentation. It consists of three parts:

XSL Transformations (XSLT)

a language for transforming XML

the XML Path Language (XPath)

an expression language used by XSLT to access or refer to parts of an XML document. (XPath is also used by the XML Linking specification)

XSL Formatting Objects (XSL-FO)

an XML vocabulary for specifying formatting semantics

XSL是一組定義XML文檔的轉(zhuǎn)換和顯示特征的推薦標(biāo)準(zhǔn),它包括三個(gè)部分:XSL轉(zhuǎn)換(XSLT)是一種為了轉(zhuǎn)換XML而定義的語(yǔ)言;XML路徑語(yǔ)言(XPath)是一種表達(dá)式語(yǔ)言,它被XSLT用來(lái)訪問(wèn)或者提交一個(gè)XML文檔的某些部分(XPath也同時(shí)被XML Linking標(biāo)準(zhǔn)使用);XSL格式化對(duì)象(XSL-FO)是一個(gè)XML詞匯表用來(lái)定義XML的格式化語(yǔ)義。

從何開(kāi)始

一般人學(xué)習(xí)XSL都是從XMLSPY等工具開(kāi)始運(yùn)行他的一個(gè)XSL例子,當(dāng)然用文本編輯器編輯XML何XSL文件,用IE去打開(kāi)XML也是一個(gè)好主意。因?yàn)閄MLSPY和IE都有嵌入式的XSL解析器,例如IE的XSL解析器是MSXML,這樣不用顯式的調(diào)用XSL進(jìn)行轉(zhuǎn)換過(guò)程,只需要在XML文檔的頭部加上一句<?xml:stylesheet type="text/xsl" href="xxx.xsl"?>就可以讓嵌入的XSL解析器自動(dòng)的進(jìn)行轉(zhuǎn)換了。例如下面這個(gè)著名的例子,它包括cd_catalog.xml和cd_catalog.xsl文件,內(nèi)容如下:

xml文件:

<?xml version="1.0" encoding="GB2312"?>

<?xml:stylesheet type="text/xsl" href="cd_catalog.xsl"?>

<CATALOG>

   <CD>

       <TITLE>Empire Burlesque</TITLE>

       <ARTIST>Bob Dylan</ARTIST>

       <COUNTRY>USA</COUNTRY>

       <COMPANY>Columbia</COMPANY>

       <PRICE>10.90</PRICE>

       <YEAR>1985</YEAR>

   </CD>

   <CD>

       <TITLE>喀什噶爾胡楊</TITLE>

       <ARTIST>刀郎</ARTIST>

       <COUNTRY>China</COUNTRY>

       <COMPANY>先之唱片</COMPANY>

       <PRICE>20.60</PRICE>

       <YEAR>2004</YEAR>

   </CD>

   <CD>

       <TITLE>敦煌(特別版)</TITLE>

       <ARTIST>女子十二樂(lè)坊</ARTIST>

       <COUNTRY>China</COUNTRY>

       <COMPANY>百代唱片</COMPANY>

       <PRICE>25.60</PRICE>

       <YEAR>2005</YEAR>

   </CD>

</CATALOG>

 

xsl文件:

<?xml version="1.0" encoding="GB2312"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">

   <xsl:template match="/">

       <html>

          <body>

             <table border="2" bgcolor="yellow">

                 <tr>

                    <th>Title</th>

                    <th>Artist</th>

                 </tr>

                 <xsl:for-each select="CATALOG/CD">

                    <tr>

                        <td>

                           <xsl:value-of select="TITLE"/>

                       </td>

                       <td>

                           <xsl:value-of select="ARTIST"/>

                       </td>

                    </tr>

                 </xsl:for-each>

             </table>

          </body>

       </html>

   </xsl:template>

</xsl:stylesheet>

將它們保存在同一目錄下然后用IE5以上版本的IE直接打開(kāi)xml文件,則會(huì)看到轉(zhuǎn)換后的效果。當(dāng)然用XMLSPY中自帶的瀏覽器也可。

JScript顯式調(diào)用XSL解析器

上面的運(yùn)行方法顯然是“貪天之功”,利用了IE和XMLSPY自帶的XSL解析器,是讓一只看不見(jiàn)的手運(yùn)行了轉(zhuǎn)換過(guò)程。那么,也可以用Jscript語(yǔ)言顯式的調(diào)用XSL解析器,讓沒(méi)有嵌入解析器的瀏覽器也可以運(yùn)行XSL,當(dāng)然,此瀏覽器必須支持Jscript腳本語(yǔ)言。我們還是使用上面的例子,不過(guò)將cd_catalog.xml中的<?xml:stylesheet type="text/xsl" href="cd_catalog.xsl"?>這一行去掉,同時(shí)新建一個(gè)cd_catalog.html文檔,內(nèi)容如下:

   <html>

    <body>

    <script language="javascript">

    // Load XML

    var xml = new ActiveXObject("Microsoft.XMLDOM")

    xml.async = false

    xml.load("cd_catalog.xml")

    // Load the XSL

    var xsl = new ActiveXObject("Microsoft.XMLDOM")

    xsl.async = false

    xsl.load("cd_catalog.xsl")

    // Transform

    document.write(xml.transformNode(xsl))

    </script>

    </body>

</html>

將此html文檔在支持Jscript的瀏覽器中打開(kāi),即可看到如前一段執(zhí)行的結(jié)果。當(dāng)然不僅僅是Jscript,其他的腳本語(yǔ)言如VBScript等等也可以,不過(guò)Jscript是XSL默認(rèn)的腳本語(yǔ)言。

腳本擴(kuò)充的XSL,令人疑惑的xsl:eval標(biāo)記

    xsl:eval標(biāo)記并不是一個(gè)標(biāo)準(zhǔn)的xsl標(biāo)記,它屬于http://www.w3.org/TR/WD-xsl這個(gè)名字空間,這個(gè)名字空間最終被微軟采用,于是xsl:eval也被微軟用來(lái)調(diào)用Jscript腳本,以此來(lái)擴(kuò)充XSL的功能。而標(biāo)準(zhǔn)的XSL1.0版本的名字空間是http://www.w3.org/1999/XSL/Transform,它并不包含xsl:eval標(biāo)記,這是很容易理解的,XSL應(yīng)該屬于一個(gè)平臺(tái)無(wú)關(guān)的技術(shù),如果它的某個(gè)標(biāo)記要依賴微軟公司的產(chǎn)品,那顯然是自掘墳?zāi)埂jP(guān)于平臺(tái)無(wú)關(guān)的討論,將在本文的最后展開(kāi)。

    xsl:eval標(biāo)記的含義是計(jì)算其中腳本語(yǔ)言的表達(dá)式,并作為文本輸出。下面的例子中計(jì)算了cd_catalog.xml中各種CD的總價(jià)格,修改上面的cd_catalog.xsl并另存為cd_catalog2.xsl文件如下:

<?xml version="1.0" encoding="GB2312"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl">

   <xsl:template match="/">

       <html>

          <body>

             <table border="2" bgcolor="yellow">

                 <tr>

                    <th>Title</th>

                    <th>Artist</th>

                 </tr>

                 <xsl:for-each select="CATALOG/CD">

                    <tr>

                       <td>

                           <xsl:value-of select="TITLE"/>

                       </td>

                       <td>

                           <xsl:value-of select="ARTIST"/>

                       </td>

                    </tr>

                 </xsl:for-each>

                 <tr>

                    <td>合計(jì)</td>

                    <td>

                       <xsl:eval>total("PRICE")</xsl:eval>

                    </td>

                    <xsl:script>

                       function total(q){

                            temp=0;

                            mark='/CATALOG/CD/'+q;

                            v=selectNodes(mark);

                            for(t=v.nextNode();t;t=v.nextNode()){

                                 temp+=Number(t.text);

                            }

                            return temp;

                      }

                    </xsl:script>

                 </tr>

             </table>

          </body>

       </html>

   </xsl:template>

</xsl:stylesheet>

在IE中打開(kāi)cd_catalog.xml文件(注意修改xsl為cd_catalog2.xsl)即可看到結(jié)果,注意這個(gè)xsl文件的這一行<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl">,寫(xiě)錯(cuò)了名字空間xsl:eval標(biāo)記就會(huì)報(bào)錯(cuò)。

瀏覽器無(wú)關(guān)的XSL解決方案,服務(wù)端的XSL

不管如何折騰,要將XML通過(guò)XSL轉(zhuǎn)換為HTML必須要求本地主機(jī)上有一個(gè)XSL解析器,不管是瀏覽器內(nèi)嵌的,還是可以通過(guò)腳本語(yǔ)言調(diào)用。那么,更好的解決方案當(dāng)然是從服務(wù)器端直接發(fā)送HTML回來(lái),這樣無(wú)論什么瀏覽器都可以看到轉(zhuǎn)換的結(jié)果了。ASP提供了這個(gè)功能,這是可想而知的,不過(guò)我對(duì)ASP不熟,這段略過(guò),有興趣的可以找本ASP的XML教材看看。

應(yīng)用程序中的XSL,語(yǔ)言相關(guān)的XSL

眾所周知,Java是對(duì)XML技術(shù)支持得最好的語(yǔ)言,Java上面的xml包非常多,其中支持XSL轉(zhuǎn)換的包最著名的有Saxon和xalan。Saxon包可以在http://saxon.sourceforge.net/上面下載。將Saxon包解壓縮到C:\saxon6_5_3,6.5.3版本提供了對(duì)XSL1.0最穩(wěn)定的支持。然后在Classpath中加入C:\saxon6_5_3\saxon.jar;C:\saxon6_5_3\saxon-jdom.jar。

Saxon提供命令行式的XSL轉(zhuǎn)換和API。其中命令行式的轉(zhuǎn)換如下,將目錄移動(dòng)到存放xml(去掉xml的指定xsl的那一行)和xsl的目錄,然后輸入下面的命令:

java com.icl.saxon.StyleSheet cd_catalog.xml cd_catalog.xsl

就可以看到輸出在屏幕上的結(jié)果,但是這樣看起來(lái)不方便,所以輸入如下命令:

java com.icl.saxon.StyleSheet cd_catalog.xml cd_catalog.xsl>a.html

然后將生成的a.html在瀏覽器中打開(kāi),可以清晰的看到結(jié)果。

下面是在Java程序中調(diào)用Saxon包,進(jìn)行XSL轉(zhuǎn)換的例子,文件名為XslExam.java:

import java.io.File;

import javax.xml.transform.Result;

import javax.xml.transform.Source;

import javax.xml.transform.Templates;

import javax.xml.transform.Transformer;

import javax.xml.transform.TransformerConfigurationException;

import javax.xml.transform.TransformerException;

import javax.xml.transform.sax.SAXSource;

import javax.xml.transform.stream.StreamResult;

import com.icl.saxon.ExtendedInputSource;

import com.icl.saxon.TransformerFactoryImpl;

 

public class XSLExam {

      public static void main(String[] args) {

        String sourceFileName = "cd_catalog.xml";

        String styleFileName = "cd_catalog.xsl";

        String outputFileName = "result.html";

        File sourceFile = null;

        File styleFile = null;

        File outputFile = null;

       

        TransformerFactoryImpl factory = new TransformerFactoryImpl();

       

        Source sourceInput = null;

        sourceFile = new File(sourceFileName);

        ExtendedInputSource eis = new ExtendedInputSource(sourceFile);

        sourceInput = new SAXSource(factory.getSourceParser(), eis);

        eis.setEstimatedLength((int)sourceFile.length());

       

        Source styleSource ;

        File sheetFile = new File(styleFileName);

        eis = new ExtendedInputSource(sheetFile);

        styleSource = new SAXSource(factory.getStyleParser(), eis);

       

        outputFile=new File(outputFileName);

       

             try {

                     Templates sheet = factory.newTemplates(styleSource);

                     Transformer instance = sheet.newTransformer();

               Result result = new StreamResult(outputFile);

               instance.transform(sourceInput, result);

              

              } catch (TransformerConfigurationException e) {

                     e.printStackTrace();

              }catch (TransformerException err) {

                     err.printStackTrace();

        }

       }

}

這個(gè)例子程序?qū)d_catalog.xml文件使用cd_catalog.xsl轉(zhuǎn)換為result.html。在Eclipse3.01中調(diào)試通過(guò)(Saxon沒(méi)有簡(jiǎn)單的xsl示例程序,我也是將com.icl.saxon.StyleSheet類拔光了才得到這個(gè)稍微簡(jiǎn)單的例子,如果需要更詳細(xì)的用法,參考com.icl.saxon.StyleSheet類)。

數(shù)據(jù)是獨(dú)立的,處理是平臺(tái)相關(guān)的

    總結(jié)前面的內(nèi)容,可以看出XSL轉(zhuǎn)換可以從這幾個(gè)地方開(kāi)始:

Ø         IE,XMLSPY:嵌入的解析器,例如MSXML3;

Ø         JScript,顯式調(diào)用XSL解析器;

Ø         用JScript擴(kuò)充XSL功能,半吊子的XSL;

Ø         瀏覽器無(wú)關(guān)的XSL解決方案,服務(wù)器端的XSL,ASP顯式調(diào)用XSL;

Ø         語(yǔ)言相關(guān)的XSL,Java的XSL包Saxon,xalan。

可以看出來(lái),XSL無(wú)論如何,都是要平臺(tái)相關(guān)的,第一種方法依賴嵌入瀏覽器的XSL解析器;第二、三種方法依賴操作系統(tǒng)安裝的XSL解析器;第四種方法依賴服務(wù)器端安裝的XSL解析器;最后的方法依賴JAVA語(yǔ)言提供的XSL API。其中微軟還不顧W3C的反對(duì),自定義了XSL的腳本擴(kuò)充功能,功能倒是強(qiáng)大了,可惜脫離了Windows就玩不轉(zhuǎn)了。JAVA號(hào)稱平臺(tái)無(wú)關(guān),可是JAVA本身就是一個(gè)平臺(tái),要是有人的機(jī)器沒(méi)有JRE又怎么辦呢?丟棄XSL?

不過(guò)事物總是有因果的,其實(shí)XML作為數(shù)據(jù)的存儲(chǔ)載體,可以做到完全的平臺(tái)無(wú)關(guān),但是XSL作為一個(gè)可執(zhí)行的語(yǔ)言,一定要依賴某種已存在的運(yùn)行環(huán)境的,就如同數(shù)據(jù)庫(kù)中的表格和SQL語(yǔ)言一樣。SQL號(hào)稱適用于任何關(guān)系數(shù)據(jù)庫(kù),但是實(shí)際上還是需要一個(gè)環(huán)境來(lái)run的。那么XSL是否破壞了XML的平臺(tái)無(wú)關(guān)性呢?我認(rèn)為沒(méi)有,因?yàn)閄SL本身是一個(gè)XML文檔,XML文檔可以平臺(tái)無(wú)關(guān)的保存和傳輸,至于使用何種方法來(lái)調(diào)用它則是另外考慮的問(wèn)題。再者,XSL的源和目標(biāo)都是平臺(tái)無(wú)關(guān)的文檔(例如XML和HTML),而它自己的調(diào)用方式則是可替換的,這點(diǎn)也減輕了XSL的負(fù)罪感吧。

以上的討論都是基于XSL1.0標(biāo)準(zhǔn)的,目前XSL2.0標(biāo)準(zhǔn)尚在討論中,不過(guò)初稿已經(jīng)發(fā)布了,而Saxon8.0以上的版本號(hào)稱已經(jīng)支持了XSL2.0。讓我們拭目以待XSL2.0帶給我們的驚喜。

參考文獻(xiàn)

W3C站點(diǎn):http://www.w3.org/Style/XSL/

XSL主題:http://www-900.ibm.com/developerWorks/cn/xml/theme/x-xsl.shtml

中文譯文站點(diǎn):http://www.opendl.com/

XSLT是什么類型的語(yǔ)言,SAXON的作者談XSLhttp://www-900.ibm.com/developerWorks/cn/xml/x-xslt/index.shtml

posted @ 2008-04-01 17:07 隨意門(mén) 閱讀(302) | 評(píng)論 (0)編輯 收藏
XML的本質(zhì)討論

(這里的XML不僅僅指XML腳本語(yǔ)言,還包括XML的一系列技術(shù),包括DTD,XSLT,XML SCHEMA,XPATH,DOM,SAX等等)

XML的本質(zhì)是什么?這個(gè)問(wèn)題對(duì)于很多XML的初學(xué)者來(lái)說(shuō)都不容易回答。因?yàn)閄ML涉及的方面太多,有人是為了寫(xiě)出更漂亮的網(wǎng)頁(yè)才從HTML進(jìn)一步學(xué)到XML;有人是為了學(xué)JAVA才來(lái)了解XML;有人是從數(shù)據(jù)庫(kù)到XML;有人是從UML到XML;當(dāng)然還有一些人是從SOAP或者其他網(wǎng)絡(luò)協(xié)議而了解到XML。那么到底如何解釋XML的本質(zhì)呢?

我認(rèn)為XML的本質(zhì)是數(shù)據(jù),XML文檔實(shí)際上是對(duì)數(shù)據(jù)的格式化存儲(chǔ),而XML的一系列技術(shù)都是圍繞著數(shù)據(jù)來(lái)發(fā)展的。例如DTD、Schema是對(duì)數(shù)據(jù)格式的定義和檢驗(yàn);XSLT是對(duì)數(shù)據(jù)的轉(zhuǎn)換;DOM、SAX是對(duì)數(shù)據(jù)的提取和操作。

既然XML只是數(shù)據(jù),而且是用文本形式存儲(chǔ)的數(shù)據(jù),那么為什么不更簡(jiǎn)單的用普通文本來(lái)存儲(chǔ)數(shù)據(jù)呢?早期的一些程序員確實(shí)是這么做的,但是這么做的缺點(diǎn)是對(duì)于每一組數(shù)據(jù),都需要專用的數(shù)據(jù)格式定義、檢驗(yàn)、轉(zhuǎn)換和操作的程序。如果使用XML來(lái)存儲(chǔ)數(shù)據(jù),由于XML的一系列技術(shù)已經(jīng)對(duì)以上的問(wèn)題提供了工具,我們只需要使用那些技術(shù)即可快捷的達(dá)到自己的目的。有人可能會(huì)說(shuō),使用數(shù)據(jù)庫(kù)不是更方便么?它也提供了以上的功能。確實(shí)如此,但是并不是每個(gè)地方都適用數(shù)據(jù)庫(kù)的,如果說(shuō)數(shù)據(jù)庫(kù)是大而全的數(shù)據(jù)解決方案的話,XML可以用“舉重若輕,大象無(wú)形”來(lái)形容,這一點(diǎn)后面再討論。

XML的本質(zhì)決定了它在網(wǎng)頁(yè)制作方面比HTML更具有優(yōu)越性。傳統(tǒng)的網(wǎng)頁(yè)包括HTML+CSS,在這種模式中,數(shù)據(jù)和數(shù)據(jù)的顯示特性都包含在HTML中,CSS只是對(duì)顯示特性的一種補(bǔ)充;而XML網(wǎng)頁(yè)包括XML+XML Schema+XSL,其中XML存儲(chǔ)數(shù)據(jù),XML Schema定義了數(shù)據(jù)的存儲(chǔ)格式,XSL定義了數(shù)據(jù)的顯示特性(其實(shí)它定義了如何將XML轉(zhuǎn)換為HTML,實(shí)際上就是定義了數(shù)據(jù)的顯示特性)。使用XML制作的網(wǎng)頁(yè)將數(shù)據(jù)、數(shù)據(jù)格式和顯示特性清晰的分為三個(gè)部分,在添加或者修改網(wǎng)頁(yè)的時(shí)候可以單獨(dú)的修改每個(gè)部分,從而得到更好的維護(hù)性和更高的制作效率。當(dāng)然動(dòng)態(tài)網(wǎng)頁(yè)可以由Database+腳本語(yǔ)言(JSP、ASP、PHP)+HTML+CSS組成;同樣基于XML的動(dòng)態(tài)網(wǎng)頁(yè)可以由Database+中間程序(提取數(shù)據(jù)庫(kù)內(nèi)容形成XML文檔)+XML+XML Schema+XSL組成。基于XML的解決方案同樣保持了層次清晰的優(yōu)點(diǎn)。

對(duì)于XML和數(shù)據(jù)庫(kù)的比較,我的上一篇文章中有過(guò)討論()。從本質(zhì)上來(lái)說(shuō),XML和數(shù)據(jù)的本質(zhì)差不多,都是圍繞著數(shù)據(jù)來(lái)提供一系列的解決方案,但是它們之間存在幾個(gè)顯著的不同:1.XML是輕量級(jí)的數(shù)據(jù)解決方案,容易學(xué)習(xí),可以用文本編輯器進(jìn)行編輯,一般的瀏覽器都支持XSLT,適用于數(shù)據(jù)量小的各種環(huán)境;2.XML是完全平臺(tái)無(wú)關(guān)的,不需要依賴于特定的操作系統(tǒng)、瀏覽器或者編程語(yǔ)言,而數(shù)據(jù)庫(kù)不是完全平臺(tái)無(wú)關(guān)的;3.XML是基于文本的,適合于網(wǎng)絡(luò)傳輸,你不能指望每個(gè)EJB的配置文檔都用數(shù)據(jù)庫(kù)來(lái)表示吧;4.XML和數(shù)據(jù)庫(kù)是可以互相結(jié)合和轉(zhuǎn)換的。

對(duì)數(shù)據(jù)的不同理解可以將XML應(yīng)用到不同的方面。你可以這樣理解:XML是數(shù)據(jù)庫(kù)中的數(shù)據(jù);Schema是數(shù)據(jù)庫(kù)的表;XSL是顯示數(shù)據(jù)的程序;也可以這么理解:XML是網(wǎng)頁(yè)素材;Schema是素材的數(shù)據(jù)結(jié)構(gòu);XSL是素材的顯示特性。同樣,在軟件建模方面,也可以用XML來(lái)替代UML。這是基于這么一種理解:Schema代表類圖,它如同UML一樣存儲(chǔ)了類的結(jié)構(gòu)特性;XML代表對(duì)象,它存儲(chǔ)了類的實(shí)例化對(duì)象的屬性數(shù)據(jù);而XSL是對(duì)類圖的轉(zhuǎn)換,即MDA(Model Driven Architecture,模型驅(qū)動(dòng)架構(gòu))中的提到的模型轉(zhuǎn)換。在UML中沒(méi)有模型轉(zhuǎn)換技術(shù),但是一些UML工具提供了代碼生成的功能(例如RationalRose),這中功能可以理解為模型轉(zhuǎn)換的一個(gè)特例。因此有人提出了用XSLT做代碼生成的建議,事實(shí)上這種代碼生成技術(shù)已經(jīng)比較成熟。由于XML Schema并不是天生就用來(lái)刻劃類圖的,所以它在類的繼承等方面存在一些不足之處,為了修正這些不足,OMG(Object Manage Group,對(duì)象管理組)組織提出了XMI(XML Metadata Interchange,XML元數(shù)據(jù)交換)標(biāo)準(zhǔn),用來(lái)補(bǔ)充XML Schema在軟件建模方面的不足。現(xiàn)在XMI已經(jīng)變成了各種軟件建模工具的通用存儲(chǔ)方式,可以將不同建模工具建立的模型互相轉(zhuǎn)換。

總的來(lái)說(shuō),XML是一種基于文本的、格式化的數(shù)據(jù)存儲(chǔ)技術(shù),它包括一系列的數(shù)據(jù)解決方案,它們是輕量級(jí)的、易于學(xué)習(xí)的、平臺(tái)無(wú)關(guān)的數(shù)據(jù)解決方案。弄清楚了這個(gè)概念再去學(xué)習(xí)XML,也許更有幫助。

posted @ 2008-04-01 17:06 隨意門(mén) 閱讀(153) | 評(píng)論 (0)編輯 收藏
Socket編程指南及示例程序

例子代碼就在我的博客中,包括六個(gè)UDP和TCP發(fā)送接受的cpp文件,一個(gè)基于MFC的局域網(wǎng)聊天小工具工程,和此小工具的所有運(yùn)行時(shí)庫(kù)、資源和執(zhí)行程序。代碼的壓縮包位置是http://www.blogjava.net/Files/wxb_nudt/socket_src.rar

1         前言

在一些常用的編程技術(shù)中,Socket網(wǎng)絡(luò)編程可以說(shuō)是最簡(jiǎn)單的一種。而且Socket編程需要的基礎(chǔ)知識(shí)很少,適合初學(xué)者學(xué)習(xí)網(wǎng)絡(luò)編程。目前支持網(wǎng)絡(luò)傳輸?shù)募夹g(shù)、語(yǔ)言和工具繁多,但是大部分都是基于Socket開(kāi)發(fā)的,雖說(shuō)這些“高級(jí)”的網(wǎng)絡(luò)技術(shù)屏蔽了大部分底層實(shí)現(xiàn),號(hào)稱能極大程度的簡(jiǎn)化開(kāi)發(fā),而事實(shí)上如果你沒(méi)有一點(diǎn)Socket基礎(chǔ),要理解和應(yīng)用這些技術(shù)還是很困難的,而且會(huì)讓你成為“半瓢水”。

深有感觸的是當(dāng)年我學(xué)習(xí)CORBA的時(shí)候,由于當(dāng)時(shí)各方面的基礎(chǔ)薄弱,整整啃了半年書(shū),最終還是一頭霧水。如果現(xiàn)在讓我?guī)б粋€(gè)人學(xué)CORBA,我一定會(huì)安排好順序:首先弄清C++語(yǔ)法;然后是VC編譯環(huán)境或者nmake的用法;接下來(lái)學(xué)習(xí)一些網(wǎng)絡(luò)基礎(chǔ)知識(shí);然后是Socket編程;這些大概要花費(fèi)3、4個(gè)月。有了這些基礎(chǔ)學(xué)習(xí)CORBA一周即可弄懂,兩個(gè)月就可以基于CORBA進(jìn)行開(kāi)發(fā)了。

好了,說(shuō)了半天其實(shí)中心思想就一個(gè),Socket很簡(jiǎn)單,很好學(xué)!如果你會(huì)C++或者JAVA,又懂一點(diǎn)點(diǎn)網(wǎng)絡(luò)基礎(chǔ)如TCP和UDP的機(jī)制,那么你看完本文就可以熟練進(jìn)行Socket開(kāi)發(fā)了。

2         Socket簡(jiǎn)介(全文摘抄)

(本節(jié)內(nèi)容全部抄自網(wǎng)絡(luò),不保證正確性,有興趣的可以看看!)

80年代初,美國(guó)政府的高級(jí)研究工程機(jī)構(gòu)(ARPA)給加利福尼亞大學(xué)Berkeley分校提供了資金,讓他們?cè)赨NIX操作系統(tǒng)下實(shí)現(xiàn)TCP/IP協(xié)議。在這個(gè)項(xiàng)目中,研究人員為T(mén)CP/IP網(wǎng)絡(luò)通信開(kāi)發(fā)了一個(gè)API(應(yīng)用程序接口)。這個(gè)API稱為Socket接口(套接字)。今天,SOCKET接口是TCP/IP網(wǎng)絡(luò)最為通用的API,也是在INTERNET上進(jìn)行應(yīng)用開(kāi)發(fā)最為通用的API。

90年代初,由Microsoft聯(lián)合了其他幾家公司共同制定了一套WINDOWS下的網(wǎng)絡(luò)編程接口,即WindowsSockets規(guī)范。它是BerkeleySockets的重要擴(kuò)充,主要是增加了一些異步函數(shù),并增加了符合Windows消息驅(qū)動(dòng)特性的網(wǎng)絡(luò)事件異步選擇機(jī)制。WINDOWSSOCKETS規(guī)范是一套開(kāi)放的、支持多種協(xié)議的Windows下的網(wǎng)絡(luò)編程接口。從1991年的1.0版到1995年的2.0.8版,經(jīng)過(guò)不斷完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成為Windows網(wǎng)絡(luò)編程的事實(shí)上的標(biāo)準(zhǔn)。目前,在實(shí)際應(yīng)用中的WINDOWSSOKCETS規(guī)范主要有1.1版和2.0版。兩者的最重要區(qū)別是1.1版只支持TCP/IP協(xié)議,而2.0版可以支持多協(xié)議。2.0版有良好的向后兼容性,任何使用1.1版的源代碼,二進(jìn)制文件,應(yīng)用程序都可以不加修改地在2.0規(guī)范下使用。

SOCKET實(shí)際在計(jì)算機(jī)中提供了一個(gè)通信端口,可以通過(guò)這個(gè)端口與任何一個(gè)具有SOCKET接口的計(jì)算機(jī)通信。應(yīng)用程序在網(wǎng)絡(luò)上傳輸,接收的信息都通過(guò)這個(gè)SOCKET接口來(lái)實(shí)現(xiàn)。在應(yīng)用開(kāi)發(fā)中就像使用文件句柄一樣,可以對(duì)SOCKET句柄進(jìn)行讀,寫(xiě)操作。

3         再說(shuō)兩句

網(wǎng)上很多文章對(duì)于Socket的來(lái)龍去脈有如教科書(shū)一般的精準(zhǔn)。但是涉及具體編程技術(shù)就往往被VC等集成開(kāi)發(fā)環(huán)境所毒害了,把Windows SDK、MFC、Socket、多線程、DLL以及編譯鏈接等等技術(shù)攪合在一起煮成一鍋夾生飯。

既然要學(xué)習(xí)Socket,就應(yīng)該用最簡(jiǎn)單直白的方式把Socket的幾個(gè)使用要點(diǎn)講出來(lái)。我認(rèn)為程序員最關(guān)心的有以下幾點(diǎn),按照優(yōu)先級(jí)排列如下:

1.         Socket的機(jī)制是什么?

2.         用C/C++寫(xiě)Socket需要什么頭文件、庫(kù)文件、DLL,它們可以由誰(shuí)提供,安裝后一般處于系統(tǒng)的哪個(gè)文件夾內(nèi)?

3.         編寫(xiě)Socket程序需要的編程基礎(chǔ)是什么?

4.         Socket庫(kù)內(nèi)最重要的幾個(gè)函數(shù)和數(shù)據(jù)類型是什么?

5.         兩個(gè)最簡(jiǎn)單的例子程序;

6.         一個(gè)貼近應(yīng)用的稍微復(fù)雜的Socket應(yīng)用程序。

我將一一講述這些要點(diǎn),并給出從簡(jiǎn)到繁,從樸素到花哨的所有源代碼以及編譯鏈接的命令。

4         Socket的機(jī)制是什么?

我們可以簡(jiǎn)單的把Socket理解為一個(gè)可以連通網(wǎng)絡(luò)上不同計(jì)算機(jī)程序之間的管道,把一堆數(shù)據(jù)從管道的A端扔進(jìn)去,則會(huì)從管道的B端(也許同時(shí)還可以從C、D、E、F……端冒出來(lái))。管道的端口由兩個(gè)因素來(lái)唯一確認(rèn),即機(jī)器的IP地址和程序所使用的端口號(hào)。IP地址的含義所有人都知道,所謂端口號(hào)就是程序員指定的一個(gè)數(shù)字,許多著名的木馬程序成天在網(wǎng)絡(luò)上掃描不同的端口號(hào)就是為了獲取一個(gè)可以連通的端口從而進(jìn)行破壞。比較著名的端口號(hào)有http的80端口和ftp的21端口(我記錯(cuò)了么?)。當(dāng)然,建議大家自己寫(xiě)程序不要使用太小的端口號(hào),它們一般被系統(tǒng)占用了,也不要使用一些著名的端口,一般來(lái)說(shuō)使用1000~5000之內(nèi)的端口比較好。

Socket可以支持?jǐn)?shù)據(jù)的發(fā)送和接收,它會(huì)定義一種稱為套接字的變量,發(fā)送數(shù)據(jù)時(shí)首先創(chuàng)建套接字,然后使用該套接字的sendto等方法對(duì)準(zhǔn)某個(gè)IP/端口進(jìn)行數(shù)據(jù)發(fā)送;接收端也首先創(chuàng)建套接字,然后將該套接字綁定到一個(gè)IP/端口上,所有發(fā)向此端口的數(shù)據(jù)會(huì)被該套接字的recv等函數(shù)讀出。如同讀出文件中的數(shù)據(jù)一樣。

5         所需的頭文件、庫(kù)文件和DLL

對(duì)于目前使用最廣泛的Windows Socket2.0版本,所需的一些文件如下(以安裝了VC6為例說(shuō)明其物理位置):

l         頭文件winsock2.h,通常處于C:"Program Files"Microsoft Visual Studio"VC98"INCLUDE;查看該頭文件可知其中又包含了windows.h和pshpack4.h頭文件,因此在windows中的一些常用API都可以使用;

l         庫(kù)文件Ws2_32.lib,通常處于C:"Program Files"Microsoft Visual Studio"VC98"Lib;

l         DLL文件Ws2_32.dll,通常處于C:"WINDOWS"system32,這個(gè)是可以猜到的。

6         編寫(xiě)Socket程序需要的編程基礎(chǔ)

在開(kāi)始編寫(xiě)Socket程序之前,需要以下編程基礎(chǔ):

l         C++語(yǔ)法;

l         一點(diǎn)點(diǎn)windows SDK的基礎(chǔ),了解一些SDK的數(shù)據(jù)類型與API的調(diào)用方式;

l         一點(diǎn)點(diǎn)編譯、鏈接和執(zhí)行的技術(shù);知道cl和link的最常用用法即可。

7         UDP

用最通俗的話講,所謂UDP,就是發(fā)送出去就不管的一種網(wǎng)絡(luò)協(xié)議。因此UDP編程的發(fā)送端只管發(fā)送就可以了,不用檢查網(wǎng)絡(luò)連接狀態(tài)。下面用例子來(lái)說(shuō)明怎樣編寫(xiě)UDP,并會(huì)詳細(xì)解釋每個(gè)API和數(shù)據(jù)類型。

7.1 UDP廣播發(fā)送程序

下面是一個(gè)用UDP發(fā)送廣播報(bào)文的例子。

#include <winsock2.h>

#include <iostream.h>

void main()

{

    SOCKET sock;   //socket套接字

    char szMsg[] = "this is a UDP test package";//被發(fā)送的字段

    //1.啟動(dòng)SOCKET庫(kù),版本為2.0

    WORD wVersionRequested;

    WSADATA wsaData;

    int err;  

    wVersionRequested = MAKEWORD( 2, 0 ); 

    err = WSAStartup( wVersionRequested, &wsaData );

    if ( 0 != err ) //檢查Socket初始化是否成功

    {

       cout<<"Socket2.0初始化失敗,Exit!";

       return;

    }

    //檢查Socket庫(kù)的版本是否為2.0

    if (LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 0 )

    {

       WSACleanup( );

       return;

    }

    //2.創(chuàng)建socket,

    sock = socket(

       AF_INET,           //internetwork: UDP, TCP, etc

       SOCK_DGRAM,        //SOCK_DGRAM說(shuō)明是UDP類型

       0                  //protocol

       );

    if (INVALID_SOCKET == sock ) {

       cout<<"Socket 創(chuàng)建失敗,Exit!";

       return;

    }

    //3.設(shè)置該套接字為廣播類型,

    bool opt = true;

    setsockopt(sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<char FAR *>(&opt), sizeof(opt));

    //4.設(shè)置發(fā)往的地址

    sockaddr_in addrto;            //發(fā)往的地址 

    memset(&addrto,0,sizeof(addrto));

    addrto.sin_family = AF_INET;               //地址類型為internetwork

    addrto.sin_addr.s_addr = INADDR_BROADCAST; //設(shè)置ip為廣播地址

    addrto.sin_port = htons(7861);             //端口號(hào)為7861

    int nlen=sizeof(addrto);

    unsigned int uIndex = 1;

    while(true)

    {

       Sleep(1000); //程序休眠一秒

       //向廣播地址發(fā)送消息

       if( sendto(sock, szMsg, strlen(szMsg), 0, (sockaddr*)&addrto,nlen)

           == SOCKET_ERROR )

           cout<<WSAGetLastError()<<endl;

       else

           cout<<uIndex++<<":an UDP package is sended."<<endl;

    }

    if (!closesocket(sock)) //關(guān)閉套接字

    {

       WSAGetLastError();

       return;

    }

    if (!WSACleanup())       //關(guān)閉Socket庫(kù)

    {

       WSAGetLastError();

       return;

    }  

}

編譯命令:

CL /c UDP_Send_Broadcast.cpp

鏈接命令(注意如果找不到該庫(kù),則要在后面的/LIBPATH參數(shù)后加上庫(kù)的路徑):

link UDP_Send_Broadcast.obj ws2_32.lib

執(zhí)行命令:

D:"Code"成品代碼"Socket"socket_src>UDP_Send_Broadcast.exe

1:an UDP package is sended.

2:an UDP package is sended.

3:an UDP package is sended.

4:an UDP package is sended.

^C

下面一一解釋代碼中出現(xiàn)的數(shù)據(jù)類型與API函數(shù)。有耐心的可以仔細(xì)看看,沒(méi)耐心的依葫蘆畫(huà)瓢也可以寫(xiě)程序了。

7.2 SOCKET類型

SOCKET是socket套接字類型,在WINSOCK2.H中有如下定義:

typedef unsigned int    u_int;

typedef u_int           SOCKET;

可知套接字實(shí)際上就是一個(gè)無(wú)符號(hào)整型,它將被Socket環(huán)境管理和使用。套接字將被創(chuàng)建、設(shè)置、用來(lái)發(fā)送和接收數(shù)據(jù),最后會(huì)被關(guān)閉。

7.3 WORD類型、MAKEWORD、LOBYTE和HIBYTE宏

WORD類型是一個(gè)16位的無(wú)符號(hào)整型,在WTYPES.H中被定義為:

typedef unsigned short WORD;

其目的是提供兩個(gè)字節(jié)的存儲(chǔ),在Socket中這兩個(gè)字節(jié)可以表示主版本號(hào)和副版本號(hào)。使用MAKEWORD宏可以給一個(gè)WORD類型賦值。例如要表示主版本號(hào)2,副版本號(hào)0,可以使用以下代碼:

WORD wVersionRequested;

wVersionRequested = MAKEWORD( 2, 0 ); 

注意低位內(nèi)存存儲(chǔ)主版本號(hào)2,高位內(nèi)存存儲(chǔ)副版本號(hào)0,其值為0x0002。使用宏LOBYTE可以讀取WORD的低位字節(jié),HIBYTE可以讀取高位字節(jié)。

7.4 WSADATA類型和LPWSADATA類型

WSADATA類型是一個(gè)結(jié)構(gòu),描述了Socket庫(kù)的一些相關(guān)信息,其結(jié)構(gòu)定義如下:

typedef struct WSAData {

        WORD                    wVersion;

        WORD                    wHighVersion;

        char                    szDescription[WSADESCRIPTION_LEN+1];

        char                    szSystemStatus[WSASYS_STATUS_LEN+1];

        unsigned short          iMaxSockets;

        unsigned short          iMaxUdpDg;

        char FAR *              lpVendorInfo;

} WSADATA;

typedef WSADATA FAR *LPWSADATA;

值得注意的就是wVersion字段,存儲(chǔ)了Socket的版本類型。LPWSADATA是WSADATA的指針類型。它們不用程序員手動(dòng)填寫(xiě),而是通過(guò)Socket的初始化函數(shù)WSAStartup讀取出來(lái)。

7.5 WSAStartup函數(shù)

WSAStartup函數(shù)被用來(lái)初始化Socket環(huán)境,它的定義如下:

int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);

其返回值為整型,調(diào)用方式為PASCAL(即標(biāo)準(zhǔn)類型,PASCAL等于__stdcall),參數(shù)有兩個(gè),第一個(gè)參數(shù)為WORD類型,指明了Socket的版本號(hào),第二個(gè)參數(shù)為WSADATA類型的指針。

若返回值為0,則初始化成功,若不為0則失敗。

7.6 WSACleanup函數(shù)

這是Socket環(huán)境的退出函數(shù)。返回值為0表示成功,SOCKET_ERROR表示失敗。

7.7 socket函數(shù)

socket的創(chuàng)建函數(shù),其定義為:

SOCKET PASCAL FAR socket (int af, int type, int protocol);

第一個(gè)參數(shù)為int af,代表網(wǎng)絡(luò)地址族,目前只有一種取值是有效的,即AF_INET,代表internet地址族;

第二個(gè)參數(shù)為int type,代表網(wǎng)絡(luò)協(xié)議類型,SOCK_DGRAM代表UDP協(xié)議,SOCK_STREAM代表TCP協(xié)議;

第三個(gè)參數(shù)為int protocol,指定網(wǎng)絡(luò)地址族的特殊協(xié)議,目前無(wú)用,賦值0即可。

返回值為SOCKET,若返回INVALID_SOCKET則失敗。

7.8 setsockopt函數(shù)

這個(gè)函數(shù)用來(lái)設(shè)置Socket的屬性,若不能正確設(shè)置socket屬性,則數(shù)據(jù)的發(fā)送和接收會(huì)失敗。定義如下:

int PASCAL FAR setsockopt (SOCKET s, int level, int optname,

                           const char FAR * optval, int optlen);

其返回值為int類型,0代表成功,SOCKET_ERROR代表有錯(cuò)誤發(fā)生。

第一個(gè)參數(shù)SOCKET s,代表要設(shè)置的套接字;

第二個(gè)參數(shù)int level,代表要設(shè)置的屬性所處的層次,層次包含以下取值:SOL_SOCKET代表套接字層次;IPPROTO_TCP代表TCP協(xié)議層次,IPPROTO_IP代表IP協(xié)議層次(后面兩個(gè)我都沒(méi)有用過(guò));

第三個(gè)參數(shù)int optname,代表設(shè)置參數(shù)的名稱,SO_BROADCAST代表允許發(fā)送廣播數(shù)據(jù)的屬性,其它屬性可參考MSDN;

第四個(gè)參數(shù)const char FAR * optval,代表指向存儲(chǔ)參數(shù)數(shù)值的指針,注意這里可能要使用reinterpret_cast類型轉(zhuǎn)換;

第五個(gè)參數(shù)int optlen,代表存儲(chǔ)參數(shù)數(shù)值變量的長(zhǎng)度。

7.9 sockaddr_in、in_addr類型,inet_addr、inet_ntoa函數(shù)

sockaddr_in定義了socket發(fā)送和接收數(shù)據(jù)包的地址,定義:

struct sockaddr_in {

        short   sin_family;

        u_short sin_port;

        struct in_addr sin_addr;

        char    sin_zero[8];

};

其中in_addr的定義如下:

struct in_addr {

        union {

                struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

                struct { u_short s_w1,s_w2; } S_un_w;

                u_long S_addr;

        } S_un;

首先闡述in_addr的含義,很顯然它是一個(gè)存儲(chǔ)ip地址的聯(lián)合體(忘記union含義的請(qǐng)看c++書(shū)),有三種表達(dá)方式:

第一種用四個(gè)字節(jié)來(lái)表示IP地址的四個(gè)數(shù)字;

第二種用兩個(gè)雙字節(jié)來(lái)表示IP地址;

第三種用一個(gè)長(zhǎng)整型來(lái)表示IP地址。

給in_addr賦值的一種最簡(jiǎn)單方法是使用inet_addr函數(shù),它可以把一個(gè)代表IP地址的字符串賦值轉(zhuǎn)換為in_addr類型,如

addrto.sin_addr.s_addr=inet_addr("192.168.0.2");

本例子中由于是廣播地址,所以沒(méi)有使用這個(gè)函數(shù)。其反函數(shù)是inet_ntoa,可以把一個(gè)in_addr類型轉(zhuǎn)換為一個(gè)字符串。

sockaddr_in的含義比in_addr的含義要廣泛,其各個(gè)字段的含義和取值如下:

第一個(gè)字段short   sin_family,代表網(wǎng)絡(luò)地址族,如前所述,只能取值A(chǔ)F_INET;

第二個(gè)字段u_short sin_port,代表IP地址端口,由程序員指定;

第三個(gè)字段struct in_addr sin_addr,代表IP地址;

第四個(gè)字段char    sin_zero[8],很搞笑,是為了保證sockaddr_in與SOCKADDR類型的長(zhǎng)度相等而填充進(jìn)來(lái)的字段。

以下代表指明了廣播地址,端口號(hào)為7861的一個(gè)地址:

    sockaddr_in addrto;            //發(fā)往的地址 

    memset(&addrto,0,sizeof(addrto));

    addrto.sin_family = AF_INET;               //地址類型為internetwork

    addrto.sin_addr.s_addr = INADDR_BROADCAST; //設(shè)置ip為廣播地址

    addrto.sin_port = htons(7861);             //端口號(hào)為7861

7.10           sockaddr類型

sockaddr類型是用來(lái)表示Socket地址的類型,同上面的sockaddr_in類型相比,sockaddr的適用范圍更廣,因?yàn)閟ockaddr_in只適用于TCP/IP地址。Sockaddr的定義如下:

struct sockaddr {

 u_short    sa_family;

 char       sa_data[14];

};  

可知sockaddr有16個(gè)字節(jié),而sockaddr_in也有16個(gè)字節(jié),所以sockaddr_in是可以強(qiáng)制類型轉(zhuǎn)換為sockaddr的。事實(shí)上也往往使用這種方法。

7.11           Sleep函數(shù)

線程掛起函數(shù),表示線程掛起一段時(shí)間。Sleep(1000)表示掛起一秒。定義于WINBASE.H頭文件中。WINBASE.H又被包含于WINDOWS.H中,然后WINDOWS.H被WINSOCK2.H包含。所以在本例中使用Sleep函數(shù)不需要包含其它頭文件。

7.12           sendto函數(shù)

在Socket中有兩套發(fā)送和接收函數(shù),一是sendto和recvfrom;二是send和recv。前一套在函數(shù)參數(shù)中要指明地址;而后一套需要先將套接字和一個(gè)地址綁定,然后直接發(fā)送和接收,不需綁定地址。sendto的定義如下:

int PASCAL FAR sendto (SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR *to, int tolen);

第一個(gè)參數(shù)就是套接字;

第二個(gè)參數(shù)是要傳送的數(shù)據(jù)指針;

第三個(gè)參數(shù)是要傳送的數(shù)據(jù)長(zhǎng)度(字節(jié)數(shù));

第四個(gè)參數(shù)是傳送方式的標(biāo)識(shí),如果不需要特殊要求則可以設(shè)置為0,其它值請(qǐng)參考MSDN;

第五個(gè)參數(shù)是目標(biāo)地址,注意這里使用的是sockaddr的指針;

第六個(gè)參數(shù)是地址的長(zhǎng)度;

返回值為整型,如果成功,則返回發(fā)送的字節(jié)數(shù),失敗則返回SOCKET_ERROR。

7.13           WSAGetLastError函數(shù)

該函數(shù)用來(lái)在Socket相關(guān)API失敗后讀取錯(cuò)誤碼,根據(jù)這些錯(cuò)誤碼可以對(duì)照查出錯(cuò)誤原因。

7.14           closesocket

關(guān)閉套接字,其參數(shù)為SOCKET類型。成功返回0,失敗返回SOCKET_ERROR。

7.15           小結(jié)

總結(jié)以上內(nèi)容,寫(xiě)一個(gè)UDP發(fā)送程序的步驟如下:

1.         用WSAStartup函數(shù)初始化Socket環(huán)境;

2.         用socket函數(shù)創(chuàng)建一個(gè)套接字;

3.         用setsockopt函數(shù)設(shè)置套接字的屬性,例如設(shè)置為廣播類型;很多時(shí)候該步驟可以省略;

4.         創(chuàng)建一個(gè)sockaddr_in,并指定其IP地址和端口號(hào);

5.         用sendto函數(shù)向指定地址發(fā)送數(shù)據(jù),這里的目標(biāo)地址就是廣播地址;注意這里不需要綁定,即使綁定了,其地址也會(huì)被sendto中的參數(shù)覆蓋;若使用send函數(shù)則會(huì)出錯(cuò),因?yàn)閟end是面向連接的,而UDP是非連接的,只能使用sendto發(fā)送數(shù)據(jù);

6.         用closesocket函數(shù)關(guān)閉套接字;

7.         用WSACleanup函數(shù)關(guān)閉Socket環(huán)境。

那么,與之類似,一個(gè)UDP接收程序的步驟如下,注意接收方一定要bind套接字:

1.         用WSAStartup函數(shù)初始化Socket環(huán)境;

2.         用socket函數(shù)創(chuàng)建一個(gè)套接字;

3.         用setsockopt函數(shù)設(shè)置套接字的屬性,例如設(shè)置為廣播類型;

4.         創(chuàng)建一個(gè)sockaddr_in,并指定其IP地址和端口號(hào);

5.         用bind函數(shù)將套接字與接收的地址綁定起來(lái),然后調(diào)用recvfrom函數(shù)或者recv接收數(shù)據(jù); 注意這里一定要綁定,因?yàn)榻邮請(qǐng)?bào)文的套接字必須在網(wǎng)絡(luò)上有一個(gè)綁定的名稱才能保證正確接收數(shù)據(jù);

6.         用closesocket函數(shù)關(guān)閉套接字;

7.         用WSACleanup函數(shù)關(guān)閉Socket環(huán)境。

廣播接收程序見(jiàn)源程序代碼UDP_Recv_Broadcast.cpp。編譯、鏈接、執(zhí)行與UDP_Send_Broadcast類似。

7.16           UDP點(diǎn)對(duì)點(diǎn)發(fā)送接收程序

廣播發(fā)送和接收使用并不廣泛,一般來(lái)說(shuō)指定發(fā)送和接收的IP比較常用。點(diǎn)對(duì)點(diǎn)方式的UDP發(fā)送和接收與上面的例子非常類似,不同的就是需要指定一個(gè)具體的IP地址。并且不需要調(diào)用setsockopt設(shè)置socket的廣播屬性。

其具體源代碼見(jiàn)UDP_Send_P2P.cpp和UDP_Recv_P2P.cpp。

注意在使用這兩個(gè)程序時(shí)要設(shè)為自己所需的IP。

8         TCP

TCP與UDP最大的不同之處在于TCP是一個(gè)面向連接的協(xié)議,在進(jìn)行數(shù)據(jù)收發(fā)之前TCP必須進(jìn)行連接,并且在收發(fā)的時(shí)候必須保持該連接。

發(fā)送方的步驟如下(省略了Socket環(huán)境的初始化、關(guān)閉等內(nèi)容):

1.         用socket函數(shù)創(chuàng)建一個(gè)套接字sock;

2.         用bind將sock綁定到本地地址;

3.         用listen偵聽(tīng)sock套接字;

4.         用accept函數(shù)接收客戶方的連接,返回客戶方套接字clientSocket;

5.         在客戶方套接字clientSocket上使用send發(fā)送數(shù)據(jù);

6.         用closesocket函數(shù)關(guān)閉套接字sock和clientSocket;

而接收方的步驟如下:

1.         用socket函數(shù)創(chuàng)建一個(gè)套接字sock;

2.         創(chuàng)建一個(gè)指向服務(wù)方的遠(yuǎn)程地址;

3.         用connect將sock連接到服務(wù)方,使用遠(yuǎn)程地址;

4.         在套接字上使用recv接收數(shù)據(jù);

5.         用closesocket函數(shù)關(guān)閉套接字sock;

值得注意的是,在服務(wù)方有兩個(gè)地址,一個(gè)是本地地址myaddr,另一個(gè)是目標(biāo)地址addrto。本地地址myaddr用來(lái)和本地套接字sock綁定,目標(biāo)地址被sock用來(lái)accept客戶方套接字clientSocket。這樣sock和clientSocket連接成功,這兩個(gè)地址也連接上了。在服務(wù)方使用clientSocket發(fā)送數(shù)據(jù),則會(huì)從本地地址傳送到目標(biāo)地址。

在客戶方只有一個(gè)地址,即來(lái)源地址addrfrom。這個(gè)地址被用來(lái)connect遠(yuǎn)程的服務(wù)方套接字,connect成功則本地套接字與遠(yuǎn)程的來(lái)源地址連接了,因此可以使用該套接字接收遠(yuǎn)程數(shù)據(jù)。其實(shí)這時(shí)客戶方套接字已經(jīng)被隱性的綁定了本地地址,所以不需要顯式調(diào)用bind函數(shù),即使調(diào)用也不會(huì)影像結(jié)果。

具體源代碼見(jiàn)TCP_Send.cpp和TCP_Recv.cpp。注意將源代碼中的IP地址修改為符合自己需要的IP。為了減少代碼復(fù)雜性,沒(méi)有使用讀取本機(jī)IP的代碼,后續(xù)例子程序中含有此功能代碼。

8.1 bind函數(shù)

bind函數(shù)用來(lái)將一個(gè)套接字綁定到一個(gè)IP地址。一般只在服務(wù)方(即數(shù)據(jù)發(fā)送方)調(diào)用,很多函數(shù)會(huì)隱式的調(diào)用bind函數(shù)。

8.2 listen函數(shù)

從服務(wù)方監(jiān)聽(tīng)客戶方的連接。同一個(gè)套接字可以多次監(jiān)聽(tīng)。

8.3 connect和accept函數(shù)

connect是客戶方連接服務(wù)方的函數(shù),而accept是服務(wù)方同意客戶方連接的函數(shù)。這兩個(gè)配套函數(shù)分別在各自的程序中被成功調(diào)用后就可以收發(fā)數(shù)據(jù)了。

8.4 send和recv函數(shù)

send和recv是用來(lái)發(fā)送和接收數(shù)據(jù)的兩個(gè)重要函數(shù)。send只能在已經(jīng)連接的狀態(tài)下使用,而recv可以面向連接和非連接的狀態(tài)下使用。

send的定義如下:

int WSAAPI send(

    SOCKET s,

    const char FAR * buf,

    int len,

    int flags

    );

其參數(shù)的含義和sendto中的前四個(gè)參數(shù)一樣。而recv的定義如下:

int WSAAPI recv(

    SOCKET s,

    char FAR * buf,

    int len,

    int flags

    );

其參數(shù)含義與send中的參數(shù)含義一樣。

9         一個(gè)局域網(wǎng)聊天工具的編寫(xiě)

掌握了以上關(guān)于socket的基本用法,編寫(xiě)一個(gè)局域網(wǎng)聊天程序也就變得非常簡(jiǎn)單,如同設(shè)計(jì)一個(gè)普通的對(duì)話框程序一樣。

9.1 功能設(shè)計(jì)

功能設(shè)計(jì)如下:

1.         要能夠指定聊天對(duì)象的IP和端口(端口可以內(nèi)部確定);

2.         要能夠發(fā)送消息給指定聊天對(duì)象;

3.         要能夠接收聊天對(duì)象的消息;

4.         接收消息時(shí)要播放聲音;

5.         接收消息時(shí)如果當(dāng)前對(duì)話框不是最前端,要閃動(dòng)圖標(biāo);

6.         要有托盤(pán)圖標(biāo),可以將對(duì)話框收入托盤(pán);

9.2 功能實(shí)現(xiàn)

將內(nèi)部端口設(shè)為3456,提供一個(gè)IP地址控件來(lái)設(shè)置聊天對(duì)象的IP。該控件必須能夠讀取IP地址并賦值給內(nèi)部變量。將地址轉(zhuǎn)換為in_addr類型。

發(fā)送消息需要使用一個(gè)套接字。

接收消息也需要使用一個(gè)套接字,由于發(fā)送消息也使用了一個(gè)套接字,為了在同一個(gè)進(jìn)程中同時(shí)發(fā)送和接收消息,需要使用多線程技術(shù),將發(fā)送消息的線程設(shè)為主線程;而接收消息的線程設(shè)為子線程,子線程只負(fù)責(zé)接收UDP消息,在收到消息后顯示到主界面中。

接收消息時(shí)播放聲音這個(gè)功能在子線程中完成,使用sndPlaySound函數(shù),并提供一個(gè)wav文件即可。

閃動(dòng)圖標(biāo)這個(gè)最白癡的功能需要使用一個(gè)Timer,在主對(duì)話框類中添加一個(gè)OnTimer函數(shù),定時(shí)檢查當(dāng)前窗口狀態(tài)變量是否為假,若為假就每次設(shè)置另一個(gè)圖標(biāo)。若當(dāng)前窗口顯示到最頂端,則設(shè)置為默認(rèn)圖標(biāo)。

托盤(pán)圖標(biāo)功能用網(wǎng)上下載的CtrayIcon類輕松搞定。需要提供一個(gè)自定義消息,一個(gè)彈出菜單資源。

9.3 所需資源

頭文件:winsock2.h,Mmsystem.h

庫(kù)文件:ws2_32.lib,winmm.lib

dll:Ws2_32.dll,winmm.dll

wav文件:recv.wav

圖標(biāo):一個(gè)主程序圖標(biāo)IDI_MAIN、四個(gè)變化圖標(biāo)IDI_ICON1~4;

菜單:一個(gè)給托盤(pán)用的彈出菜單IDR_TRAYICON;

說(shuō)明,Mmsystem.h和winmm.lib、winmm.dll是為了那個(gè)播放聲音的功能。

9.4 托盤(pán)功能

托盤(pán)屬于界面功能,是變更很少的需求,因此首先完成。

1.         引入TRAYICON.H和TRAYICON.cpp兩個(gè)類;

2.         在CLANTalkDlg類中加入一個(gè)CTrayIconm_trayIcon;屬性;

3.         在CLANTalkDlg的構(gòu)造函數(shù)中初始化m_trayIcon,m_trayIcon(IDR_TRAYICON);

4.         添加一個(gè)自定義消息WM_MY_TRAY_NOTIFICATION,即在三個(gè)地方添加消息定義、消息響應(yīng)函數(shù)、消息映射;

5.         在InitDialog方法中調(diào)用托盤(pán)初始化的兩個(gè)函數(shù)      m_trayIcon.SetNotificationWnd(this, WM_MY_TRAY_NOTIFICATION);    m_trayIcon.SetIcon(IDI_MAIN);

6.         重寫(xiě)OnClose方法,添加彈出菜單的OnAppSuspend和OnAppOpen以及OnAppAbout方法;

7.         重寫(xiě)對(duì)話框的OnCancel方法。

9.5 動(dòng)態(tài)圖標(biāo)

動(dòng)態(tài)圖標(biāo)也是界面相關(guān)功能,首先完成。

1.         添加四個(gè)HICON變量m_hIcon1,m_hIcon2,m_hIcon3,m_hIcon4;

2.         在構(gòu)造函數(shù)中初始化這四個(gè)變量m_hIcon1 = AfxGetApp()->LoadIcon(IDI_ICON1);

3.         在InitDialog中設(shè)置調(diào)用SetTimer(1,300,NULL);設(shè)置一個(gè)timer,id為1,間隔為300微秒;

4.         添加一個(gè)布爾屬性m_bDynamicIcon,指示目前是否需要?jiǎng)討B(tài)圖標(biāo),并給出一個(gè)設(shè)置函數(shù)SetDynamicIcon;

5.         添加一個(gè)OnTimer函數(shù),讓每次timer調(diào)用時(shí)根據(jù)m_bDynamicIcon的值修改圖標(biāo);

兩個(gè)地方是用來(lái)設(shè)置動(dòng)態(tài)圖標(biāo)的,一個(gè)是當(dāng)程序收到消息并且程序不在桌面頂端時(shí),這時(shí)設(shè)置為動(dòng)態(tài)圖標(biāo),在后面的消息接收線程中處理;二是當(dāng)程序顯示到桌面頂端時(shí),設(shè)置為非動(dòng)態(tài);

重載OnActivate方法可以完成第二個(gè)時(shí)刻的要求。當(dāng)窗口狀態(tài)為WA_ACTIVE或者WA_CLICKACTIVE時(shí)SetDynamicIcon(false),否則設(shè)置SetDynamicIcon(true);

9.6 發(fā)送UDP報(bào)文功能

發(fā)送UDP報(bào)文只需在主線程中完成,需要以下步驟:

1.         初始化Socket環(huán)境,這可以在CLANTalkApp的InitInstance中完成,同理關(guān)閉Socket環(huán)境在ExitInstance中完成;我們可以使用前面的方法,也可以直接調(diào)用MFC中的AfxSocketInit函數(shù),這個(gè)函數(shù)可以確保在程序結(jié)束時(shí)自動(dòng)關(guān)閉Socket環(huán)境;

2.         創(chuàng)建socket,考慮到報(bào)錯(cuò)信息需要彈出對(duì)話框,因此不在CLANTalkDlg的構(gòu)造函數(shù)中創(chuàng)建,而是在InitDialog中構(gòu)建;發(fā)送報(bào)文的socket為m_sendSock;

3.         設(shè)置目的地址功能,需要一個(gè)地址賦值函數(shù)setAddress(char* szAddr);可以將一個(gè)字符串地址賦值給sockaddr_in形式的地址;在CLANTalkDlg中增加一個(gè)sockaddr_in m_addrto;屬性;

4.         讀取文本框中的文字,用sendto發(fā)送到對(duì)象地址;

5.         清空文本框,在記錄框中添加聊天記錄。

這時(shí)可以使用前面的UDP簡(jiǎn)單接收程序來(lái)輔助測(cè)試,因?yàn)榇藭r(shí)還未完成報(bào)文接收功能。

9.7 接收UDP報(bào)文功能

接收UDP報(bào)文要考慮幾個(gè)問(wèn)題,第一個(gè)是要?jiǎng)?chuàng)建一個(gè)子線程,在子線程中接收?qǐng)?bào)文;第二是接收?qǐng)?bào)文和發(fā)送報(bào)文要有互斥機(jī)制,以免沖突;第三是接收到報(bào)文要播放聲音;第四是接收?qǐng)?bào)文且當(dāng)前窗口不在桌面頂端要調(diào)用動(dòng)態(tài)圖標(biāo)功能。

按照以上需求設(shè)計(jì)步驟如下:

1.         創(chuàng)建接收套接字m_recvSock,

2.         利用gethostname和gethostbyname等函數(shù)獲取本機(jī)IP,并將套接字bind到該地址;

3.         添加一個(gè)CwinThread* m_pRecvThread屬性,并在InitDialog中調(diào)用AfxBeginThread創(chuàng)建子線程;

4.         編寫(xiě)子線程運(yùn)行函數(shù)void RecvProcess(LPVOID pParam),這時(shí)一個(gè)全局函數(shù),為了方便調(diào)用CLANTalkDlg類中的各種變量與方法,將CLANTalkDlg類的指針作為參數(shù)傳入子線程函數(shù),并將RecvProcess設(shè)置為CLANTalkDlg類的友元。

5.         子線程函數(shù)中完成以下功能:利用recv接收?qǐng)?bào)文;保存聊天記錄;判斷當(dāng)前窗口是否在前臺(tái),并修改動(dòng)態(tài)圖標(biāo)屬性;播放聲音。

6.         用來(lái)記錄聊天信息的ClistBox的Sort屬性要去掉,否則記錄會(huì)按內(nèi)容排序,很不好看。在RC編輯器中去掉這個(gè)屬性即可。

7.         最后要注意,在主線程退出時(shí)要保證子線程退出,但此時(shí)子線程還阻塞在recv方法上,因此主線程向自己發(fā)送一條消息消除阻塞,同時(shí)改變子線程退出標(biāo)志保證子線程可以退出。

9.8 設(shè)置聊天對(duì)象IP

點(diǎn)擊“確認(rèn)對(duì)象”按鈕時(shí),檢測(cè)IP地址控件,如果IP地址有效,則將IP地址讀入內(nèi)部屬性。這個(gè)IP地址作為發(fā)送信息的目標(biāo)地址。

這個(gè)設(shè)置只能設(shè)置發(fā)送消息的對(duì)象,所有人都可以向本機(jī)發(fā)送信息,只要他的端口是正確的。

9.9 編譯鏈接和運(yùn)行

下載壓縮包后可以打開(kāi)VC工程編譯鏈接,若直接運(yùn)行則可以點(diǎn)擊LANTalkExeFile目錄中的可執(zhí)行文件,這個(gè)目標(biāo)包含了運(yùn)行所需要的所有dll和資源文件。

當(dāng)然,如果需要可以用InstallShield做一個(gè)安裝程序,不過(guò)看來(lái)是沒(méi)有必要的。

9.10           小結(jié)

這個(gè)聊天程序很簡(jiǎn)單,但是基本上具有了一個(gè)框架,可以有最簡(jiǎn)單的聊天功能。要在此基礎(chǔ)上進(jìn)行擴(kuò)展幾乎已經(jīng)沒(méi)有什么技術(shù)問(wèn)題了。

10    使用好的Socket包可以簡(jiǎn)化開(kāi)發(fā)過(guò)程

本文中所有的技術(shù)盡量采用最原始的方式來(lái)使用。例如多線程使用的是AfxBeginThread,套接字使用了最原始的套接字,并在很多地方直接使用了SDK函數(shù),而盡量避免了MFC等代碼框架,這是為了方便他人掌握技術(shù)的最基本內(nèi)涵。

其實(shí)在具體的編程中,當(dāng)然是怎么方便怎么來(lái),Socket和多線程以及界面等功能都有大量方便可用的代碼庫(kù),復(fù)用這些代碼庫(kù)會(huì)比自己動(dòng)手寫(xiě)方便很多。但是,掌握了基本原理再使用這些庫(kù),事半功倍

posted @ 2008-04-01 17:05 隨意門(mén) 閱讀(1136) | 評(píng)論 (0)編輯 收藏
DLL編寫(xiě)教程

引自http://www.blogjava.net/wxb_nudt/archive/2007/09/11/144371.html

DLL編寫(xiě)教程

半年不能上網(wǎng),最近網(wǎng)絡(luò)終于通了,終于可以更新博客了,寫(xiě)點(diǎn)什么呢?決定最近寫(xiě)一個(gè)編程技術(shù)系列,其內(nèi)容是一些通用的編程技術(shù)。例如DLL,COM,Socket,多線程等等。這些技術(shù)的特點(diǎn)就是使用廣泛,但是誤解很多;網(wǎng)上教程很多,但是幾乎沒(méi)有什么優(yōu)質(zhì)良品。我以近幾個(gè)月來(lái)的編程經(jīng)驗(yàn)發(fā)現(xiàn),很有必要好好的總結(jié)一下這些編程技術(shù)了。一來(lái)對(duì)自己是總結(jié)提高,二來(lái)可以方便光顧我博客的朋友。

好了,廢話少說(shuō),言歸正傳。第一篇就是《DLL編寫(xiě)教程》,為什么起這么土的名字呢?為什么不叫《輕輕松松寫(xiě)DLL》或者《DLL一日通》呢?或者更nb的《深入簡(jiǎn)出DLL》呢?呵呵,常常上網(wǎng)搜索資料的弟兄自然知道。

本文對(duì)通用的DLL技術(shù)做了一個(gè)總結(jié),并提供了源代碼打包下載,下載地址為:

http://www.blogjava.net/Files/wxb_nudt/DLL_SRC.rar

DLL的優(yōu)點(diǎn)

簡(jiǎn)單的說(shuō),dll有以下幾個(gè)優(yōu)點(diǎn):

1)      節(jié)省內(nèi)存。同一個(gè)軟件模塊,若是以源代碼的形式重用,則會(huì)被編譯到不同的可執(zhí)行程序中,同時(shí)運(yùn)行這些exe時(shí)這些模塊的二進(jìn)制碼會(huì)被重復(fù)加載到內(nèi)存中。如果使用dll,則只在內(nèi)存中加載一次,所有使用該dll的進(jìn)程會(huì)共享此塊內(nèi)存(當(dāng)然,像dll中的全局變量這種東西是會(huì)被每個(gè)進(jìn)程復(fù)制一份的)。

2)      不需編譯的軟件系統(tǒng)升級(jí),若一個(gè)軟件系統(tǒng)使用了dll,則該dll被改變(函數(shù)名不變)時(shí),系統(tǒng)升級(jí)只需要更換此dll即可,不需要重新編譯整個(gè)系統(tǒng)。事實(shí)上,很多軟件都是以這種方式升級(jí)的。例如我們經(jīng)常玩的星際、魔獸等游戲也是這樣進(jìn)行版本升級(jí)的。

3)      Dll庫(kù)可以供多種編程語(yǔ)言使用,例如用c編寫(xiě)的dll可以在vb中調(diào)用。這一點(diǎn)上DLL還做得很不夠,因此在dll的基礎(chǔ)上發(fā)明了COM技術(shù),更好的解決了一系列問(wèn)題。

最簡(jiǎn)單的dll

開(kāi)始寫(xiě)dll之前,你需要一個(gè)c/c++編譯器和鏈接器,并關(guān)閉你的IDE。是的,把你的VC和C++ BUILDER之類的東東都關(guān)掉,并打開(kāi)你以往只用來(lái)記電話的記事本程序。不這樣做的話,你可能一輩子也不明白dll的真諦。我使用了VC自帶的cl編譯器和link鏈接器,它們一般都在vc的bin目錄下。(若你沒(méi)有在安裝vc的時(shí)候選擇注冊(cè)環(huán)境變量,那么就立刻將它們的路徑加入path吧)如果你還是因?yàn)殡x開(kāi)了IDE而害怕到哭泣的話,你可以關(guān)閉這個(gè)頁(yè)面并繼續(xù)去看《VC++技術(shù)內(nèi)幕》之類無(wú)聊的書(shū)了。

最簡(jiǎn)單的dll并不比c的helloworld難,只要一個(gè)DllMain函數(shù)即可,包含objbase.h頭文件(支持COM技術(shù)的一個(gè)頭文件)。若你覺(jué)得這個(gè)頭文件名字難記,那么用windows.H也可以。源代碼如下:dll_nolib.cpp

#include <objbase.h>

#include <iostream.h>

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{

    HANDLE g_hModule;

    switch(dwReason)

    {

    case DLL_PROCESS_ATTACH:

       cout<<"Dll is attached!"<<endl;

       g_hModule = (HINSTANCE)hModule;

       break;

    case DLL_PROCESS_DETACH:

       cout<<"Dll is detached!"<<endl;

       g_hModule=NULL;

       break;

    }

    return true;

}

其中DllMain是每個(gè)dll的入口函數(shù),如同c的main函數(shù)一樣。DllMain帶有三個(gè)參數(shù),hModule表示本dll的實(shí)例句柄(聽(tīng)不懂就不理它,寫(xiě)過(guò)windows程序的自然懂),dwReason表示dll當(dāng)前所處的狀態(tài),例如DLL_PROCESS_ATTACH表示dll剛剛被加載到一個(gè)進(jìn)程中,DLL_PROCESS_DETACH表示dll剛剛從一個(gè)進(jìn)程中卸載。當(dāng)然還有表示加載到線程中和從線程中卸載的狀態(tài),這里省略。最后一個(gè)參數(shù)是一個(gè)保留參數(shù)(目前和dll的一些狀態(tài)相關(guān),但是很少使用)。

從上面的程序可以看出,當(dāng)dll被加載到一個(gè)進(jìn)程中時(shí),dll打印"Dll is attached!"語(yǔ)句;當(dāng)dll從進(jìn)程中卸載時(shí),打印"Dll is detached!"語(yǔ)句。

編譯dll需要以下兩條命令:

cl /c dll_nolib.cpp

這條命令會(huì)將cpp編譯為obj文件,若不使用/c參數(shù)則cl還會(huì)試圖繼續(xù)將obj鏈接為exe,但是這里是一個(gè)dll,沒(méi)有main函數(shù),因此會(huì)報(bào)錯(cuò)。不要緊,繼續(xù)使用鏈接命令。

Link /dll dll_nolib.obj

這條命令會(huì)生成dll_nolib.dll。

注意,因?yàn)榫幾g命令比較簡(jiǎn)單,所以本文不討論nmake,有興趣的可以使用nmake,或者寫(xiě)個(gè)bat批處理來(lái)編譯鏈接dll。

加載DLL(顯式調(diào)用)

使用dll大體上有兩種方式,顯式調(diào)用和隱式調(diào)用。這里首先介紹顯式調(diào)用。編寫(xiě)一個(gè)客戶端程序:dll_nolib_client.cpp

#include <windows.h>

#include <iostream.h>

int main(void)

{

    //加載我們的dll

    HINSTANCE hinst=::LoadLibrary("dll_nolib.dll"); 

    if (NULL != hinst)

    {

       cout<<"dll loaded!"<<endl;

    }

    return 0;

}

注意,調(diào)用dll使用LoadLibrary函數(shù),它的參數(shù)就是dll的路徑和名稱,返回值是dll的句柄。 使用如下命令編譯鏈接客戶端:

Cl dll_nolib_client.cpp

并執(zhí)行dll_nolib_client.exe,得到如下結(jié)果:

Dll is attached!

dll loaded!

Dll is detached!

以上結(jié)果表明dll已經(jīng)被客戶端加載過(guò)。但是這樣僅僅能夠?qū)ll加載到內(nèi)存,不能找到dll中的函數(shù)。

使用dumpbin命令查看DLL中的函數(shù)

Dumpbin命令可以查看一個(gè)dll中的輸出函數(shù)符號(hào)名,鍵入如下命令:

Dumpbin –exports dll_nolib.dll

通過(guò)查看,發(fā)現(xiàn)dll_nolib.dll并沒(méi)有輸出任何函數(shù)。

如何在dll中定義輸出函數(shù)

總體來(lái)說(shuō)有兩種方法,一種是添加一個(gè)def定義文件,在此文件中定義dll中要輸出的函數(shù);第二種是在源代碼中待輸出的函數(shù)前加上__declspec(dllexport)關(guān)鍵字。

Def文件

首先寫(xiě)一個(gè)帶有輸出函數(shù)的dll,源代碼如下:dll_def.cpp

#include <objbase.h>

#include <iostream.h>

void FuncInDll (void)

{

    cout<<"FuncInDll is called!"<<endl;

}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{

    HANDLE g_hModule;

    switch(dwReason)

    {

    case DLL_PROCESS_ATTACH:

       g_hModule = (HINSTANCE)hModule;

       break;

    case DLL_PROCESS_DETACH:

        g_hModule=NULL;

        break;

    }

    return TRUE;

}

這個(gè)dll的def文件如下:dll_def.def

;

; dll_def module-definition file

;

LIBRARY         dll_def.dll

DESCRIPTION     '(c)2007-2009 Wang Xuebin'

EXPORTS

                FuncInDll @1 PRIVATE

你會(huì)發(fā)現(xiàn)def的語(yǔ)法很簡(jiǎn)單,首先是LIBRARY關(guān)鍵字,指定dll的名字;然后一個(gè)可選的關(guān)鍵字DESCRIPTION,后面寫(xiě)上版權(quán)等信息(不寫(xiě)也可以);最后是EXPORTS關(guān)鍵字,后面寫(xiě)上dll中所有要輸出的函數(shù)名或變量名,然后接上@以及依次編號(hào)的數(shù)字(從1到N),最后接上修飾符。

用如下命令編譯鏈接帶有def文件的dll:

Cl /c dll_def.cpp

Link /dll dll_def.obj /def:dll_def.def

再調(diào)用dumpbin查看生成的dll_def.dll:

Dumpbin –exports dll_def.dll

得到如下結(jié)果:

Dump of file dll_def.dll

File Type: DLL

 Section contains the following exports for dll_def.dll

           0 characteristics

    46E4EE98 time date stamp Mon Sep 10 15:13:28 2007

        0.00 version

           1 ordinal base

           1 number of functions

           1 number of names

    ordinal hint RVA      name

          1    0 00001000 FuncInDll

 Summary

        2000 .data

        1000 .rdata

        1000 .reloc

        6000 .text

觀察這一行

          1    0 00001000 FuncInDll

會(huì)發(fā)現(xiàn)該dll輸出了函數(shù)FuncInDll。

顯式調(diào)用DLL中的函數(shù)

寫(xiě)一個(gè)dll_def.dll的客戶端程序:dll_def_client.cpp

#include <windows.h>

#include <iostream.h>

int main(void)

{

    //定義一個(gè)函數(shù)指針

    typedef void (* DLLWITHLIB )(void); 

    //定義一個(gè)函數(shù)指針變量

    DLLWITHLIB pfFuncInDll = NULL; 

    //加載我們的dll

    HINSTANCE hinst=::LoadLibrary("dll_def.dll"); 

    if (NULL != hinst)

    {

       cout<<"dll loaded!"<<endl;

    }

    //找到dll的FuncInDll函數(shù)

    pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll"); 

    //調(diào)用dll里的函數(shù)

    if (NULL != pfFuncInDll)

    {

       (*pfFuncInDll)();  

    }

    return 0;

}

有兩個(gè)地方值得注意,第一是函數(shù)指針的定義和使用,不懂的隨便找本c++書(shū)看看;第二是GetProcAddress的使用,這個(gè)API是用來(lái)查找dll中的函數(shù)地址的,第一個(gè)參數(shù)是DLL的句柄,即LoadLibrary返回的句柄,第二個(gè)參數(shù)是dll中的函數(shù)名稱,即dumpbin中輸出的函數(shù)名(注意,這里的函數(shù)名稱指的是編譯后的函數(shù)名,不一定等于dll源代碼中的函數(shù)名)。

編譯鏈接這個(gè)客戶端程序,并執(zhí)行會(huì)得到:

dll loaded!

FuncInDll is called!

這表明客戶端成功調(diào)用了dll中的函數(shù)FuncInDll。

__declspec(dllexport)

為每個(gè)dll寫(xiě)def顯得很繁雜,目前def使用已經(jīng)比較少了,更多的是使用__declspec(dllexport)在源代碼中定義dll的輸出函數(shù)。

Dll寫(xiě)法同上,去掉def文件,并在每個(gè)要輸出的函數(shù)前面加上聲明__declspec(dllexport),例如:

__declspec(dllexport) void FuncInDll (void)

這里提供一個(gè)dll源程序dll_withlib.cpp,然后編譯鏈接。鏈接時(shí)不需要指定/DEF:參數(shù),直接加/DLL參數(shù)即可,

Cl /c dll_withlib.cpp

Link /dll dll_withlib.obj

然后使用dumpbin命令查看,得到:

1    0 00001000 ?FuncInDll@@YAXXZ

可知編譯后的函數(shù)名為?FuncInDll@@YAXXZ,而并不是FuncInDll,這是因?yàn)閏++編譯器基于函數(shù)重載的考慮,會(huì)更改函數(shù)名,這樣使用顯式調(diào)用的時(shí)候,也必須使用這個(gè)更改后的函數(shù)名,這顯然給客戶帶來(lái)麻煩。為了避免這種現(xiàn)象,可以使用extern “C”指令來(lái)命令c++編譯器以c編譯器的方式來(lái)命名該函數(shù)。修改后的函數(shù)聲明為:

extern "C" __declspec(dllexport) void FuncInDll (void)

dumpbin命令結(jié)果:

1    0 00001000 FuncInDll

這樣,顯式調(diào)用時(shí)只需查找函數(shù)名為FuncInDll的函數(shù)即可成功。

extern “C”

使用extern “C”關(guān)鍵字實(shí)際上相當(dāng)于一個(gè)編譯器的開(kāi)關(guān),它可以將c++語(yǔ)言的函數(shù)編譯為c語(yǔ)言的函數(shù)名稱。即保持編譯后的函數(shù)符號(hào)名等于源代碼中的函數(shù)名稱。

隱式調(diào)用DLL

顯式調(diào)用顯得非常復(fù)雜,每次都要LoadLibrary,并且每個(gè)函數(shù)都必須使用GetProcAddress來(lái)得到函數(shù)指針,這對(duì)于大量使用dll函數(shù)的客戶是一種困擾。而隱式調(diào)用能夠像使用c函數(shù)庫(kù)一樣使用dll中的函數(shù),非常方便快捷。

下面是一個(gè)隱式調(diào)用的例子:dll包含兩個(gè)文件dll_withlibAndH.cpp和dll_withlibAndH.h。

代碼如下:dll_withlibAndH.h

extern "C" __declspec(dllexport) void FuncInDll (void);

dll_withlibAndH.cpp

#include <objbase.h>

#include <iostream.h>

#include "dll_withLibAndH.h"http://看到?jīng)]有,這就是我們?cè)黾拥念^文件

extern "C" __declspec(dllexport) void FuncInDll (void)

{

    cout<<"FuncInDll is called!"<<endl;

}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{

    HANDLE g_hModule;

    switch(dwReason)

    {

    case DLL_PROCESS_ATTACH:

       g_hModule = (HINSTANCE)hModule;

       break;

    case DLL_PROCESS_DETACH:

        g_hModule=NULL;

        break;

    }

    return TRUE;

}

編譯鏈接命令:

Cl /c dll_withlibAndH.cpp

Link /dll dll_withlibAndH.obj

在進(jìn)行隱式調(diào)用的時(shí)候需要在客戶端引入頭文件,并在鏈接時(shí)指明dll對(duì)應(yīng)的lib文件(dll只要有函數(shù)輸出,則鏈接的時(shí)候會(huì)產(chǎn)生一個(gè)與dll同名的lib文件)位置和名稱。然后如同調(diào)用api函數(shù)庫(kù)中的函數(shù)一樣調(diào)用dll中的函數(shù),不需要顯式的LoadLibrary和GetProcAddress。使用最為方便。客戶端代碼如下:dll_withlibAndH_client.cpp

#include "dll_withLibAndH.h"

//注意路徑,加載 dll的另一種方法是 Project | setting | link 設(shè)置里

#pragma comment(lib,"dll_withLibAndH.lib")

int main(void)

{

    FuncInDll();//只要這樣我們就可以調(diào)用dll里的函數(shù)了

    return 0;

}

__declspec(dllexport)和__declspec(dllimport)配對(duì)使用

上面一種隱式調(diào)用的方法很不錯(cuò),但是在調(diào)用DLL中的對(duì)象和重載函數(shù)時(shí)會(huì)出現(xiàn)問(wèn)題。因?yàn)槭褂胑xtern “C”修飾了輸出函數(shù),因此重載函數(shù)肯定是會(huì)出問(wèn)題的,因?yàn)樗鼈兌紝⒈痪幾g為同一個(gè)輸出符號(hào)串(c語(yǔ)言是不支持重載的)。

事實(shí)上不使用extern “C”是可行的,這時(shí)函數(shù)會(huì)被編譯為c++符號(hào)串,例如(?FuncInDll@@YAXH@Z、 ?FuncInDll@@YAXXZ),當(dāng)客戶端也是c++時(shí),也能正確的隱式調(diào)用。

這時(shí)要考慮一個(gè)情況:若DLL1.CPP是源,DLL2.CPP使用了DLL1中的函數(shù),但同時(shí)DLL2也是一個(gè)DLL,也要輸出一些函數(shù)供Client.CPP使用。那么在DLL2中如何聲明所有的函數(shù),其中包含了從DLL1中引入的函數(shù),還包括自己要輸出的函數(shù)。這個(gè)時(shí)候就需要同時(shí)使用__declspec(dllexport)和__declspec(dllimport)了。前者用來(lái)修飾本dll中的輸出函數(shù),后者用來(lái)修飾從其它dll中引入的函數(shù)。

所有的源代碼包括DLL1.H,DLL1.CPP,DLL2.H,DLL2.CPP,Client.cpp。源代碼可以在下載的包中找到。你可以編譯鏈接并運(yùn)行試試。

值得關(guān)注的是DLL1和DLL2中都使用的一個(gè)編碼方法,見(jiàn)DLL2.H

#ifdef DLL_DLL2_EXPORTS

#define DLL_DLL2_API __declspec(dllexport)

#else

#define DLL_DLL2_API __declspec(dllimport)

#endif

DLL_DLL2_API void FuncInDll2(void);

DLL_DLL2_API void FuncInDll2(int);

在頭文件中以這種方式定義宏DLL_DLL2_EXPORTS和DLL_DLL2_API,可以確保DLL端的函數(shù)用__declspec(dllexport)修飾,而客戶端的函數(shù)用__declspec(dllimport)修飾。當(dāng)然,記得在編譯dll時(shí)加上參數(shù)/D “DLL_DLL2_EXPORTS”,或者干脆就在dll的cpp文件第一行加上#define DLL_DLL2_EXPORTS。

VC生成的代碼也是這樣的!事實(shí)證明,我是抄襲它的,hoho!

DLL中的全局變量和對(duì)象

解決了重載函數(shù)的問(wèn)題,那么dll中的全局變量和對(duì)象都不是問(wèn)題了,只是有一點(diǎn)語(yǔ)法需要注意。如源代碼所示:dll_object.h

#ifdef DLL_OBJECT_EXPORTS

#define DLL_OBJECT_API __declspec(dllexport)

#else

#define DLL_OBJECT_API __declspec(dllimport)

#endif

DLL_OBJECT_API void FuncInDll(void);

extern DLL_OBJECT_API int g_nDll;

class DLL_OBJECT_API CDll_Object {

public:

    CDll_Object(void);

    show(void);

    // TODO: add your methods here.

};

Cpp文件dll_object.cpp如下:

#define DLL_OBJECT_EXPORTS

#include <objbase.h>

#include <iostream.h>

#include "dll_object.h"

DLL_OBJECT_API void FuncInDll(void)

{

    cout<<"FuncInDll is called!"<<endl;

}

DLL_OBJECT_API int g_nDll = 9;

CDll_Object::CDll_Object()

{

    cout<<"ctor of CDll_Object"<<endl;

}

CDll_Object::show()

{

    cout<<"function show in class CDll_Object"<<endl;

}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{

    HANDLE g_hModule;

    switch(dwReason)

    {

    case DLL_PROCESS_ATTACH:

       g_hModule = (HINSTANCE)hModule;

       break;

    case DLL_PROCESS_DETACH:

        g_hModule=NULL;

        break;

    }

    return TRUE;

}

編譯鏈接完后Dumpbin一下,可以看到輸出了5個(gè)符號(hào):

1    0 00001040 ??0CDll_Object@@QAE@XZ

 2    1 00001000 ??4CDll_Object@@QAEAAV0@ABV0@@Z

 3    2 00001020 ?FuncInDll@@YAXXZ

 4    3 00008040 ?g_nDll@@3HA

 5    4 00001069 ?show@CDll_Object@@QAEHXZ

它們分別代表類CDll_Object,類的構(gòu)造函數(shù),F(xiàn)uncInDll函數(shù),全局變量g_nDll和類的成員函數(shù)show。下面是客戶端代碼:dll_object_client.cpp

#include "dll_object.h"

#include <iostream.h>

//注意路徑,加載 dll的另一種方法是 Project | setting | link 設(shè)置里

#pragma comment(lib,"dll_object.lib")

int main(void)

{

    cout<<"call dll"<<endl;

    cout<<"call function in dll"<<endl;

    FuncInDll();//只要這樣我們就可以調(diào)用dll里的函數(shù)了

    cout<<"global var in dll g_nDll ="<<g_nDll<<endl;

    cout<<"call member function of class CDll_Object in dll"<<endl;

    CDll_Object obj;

    obj.show();

    return 0;

}

運(yùn)行這個(gè)客戶端可以看到:

call dll

call function in dll

FuncInDll is called!

global var in dll g_nDll =9

call member function of class CDll_Object in dll

ctor of CDll_Object

function show in class CDll_Object

可知,在客戶端成功的訪問(wèn)了dll中的全局變量,并創(chuàng)建了dll中定義的C++對(duì)象,還調(diào)用了該對(duì)象的成員函數(shù)。

中間的小結(jié)

牢記一點(diǎn),說(shuō)到底,DLL是對(duì)應(yīng)C語(yǔ)言的動(dòng)態(tài)鏈接技術(shù),在輸出C函數(shù)和變量時(shí)顯得方便快捷;而在輸出C++類、函數(shù)時(shí)需要通過(guò)各種手段,而且也并沒(méi)有完美的解決方案,除非客戶端也是c++。

記住,只有COM是對(duì)應(yīng)C++語(yǔ)言的技術(shù)。

下面開(kāi)始對(duì)各各問(wèn)題一一小結(jié)。

顯式調(diào)用和隱式調(diào)用

何時(shí)使用顯式調(diào)用?何時(shí)使用隱式調(diào)用?我認(rèn)為,只有一個(gè)時(shí)候使用顯式調(diào)用是合理的,就是當(dāng)客戶端不是C/C++的時(shí)候。這時(shí)是無(wú)法隱式調(diào)用的。例如用VB調(diào)用C++寫(xiě)的dll。(VB我不會(huì),所以沒(méi)有例子)

Def和__declspec(dllexport)

其實(shí)def的功能相當(dāng)于extern “C” __declspec(dllexport),所以它也僅能處理C函數(shù),而不能處理重載函數(shù)。而__declspec(dllexport)和__declspec(dllimport)配合使用能夠適應(yīng)任何情況,因此__declspec(dllexport)是更為先進(jìn)的方法。所以,目前普遍的看法是不使用def文件,我也同意這個(gè)看法。

從其它語(yǔ)言調(diào)用DLL

從其它編程語(yǔ)言中調(diào)用DLL,有兩個(gè)最大的問(wèn)題,第一個(gè)就是函數(shù)符號(hào)的問(wèn)題,前面已經(jīng)多次提過(guò)了。這里有個(gè)兩難選擇,若使用extern “C”,則函數(shù)名稱保持不變,調(diào)用較方便,但是不支持函數(shù)重載等一系列c++功能;若不使用extern “C”,則調(diào)用前要查看編譯后的符號(hào),非常不方便。

第二個(gè)問(wèn)題就是函數(shù)調(diào)用壓棧順序的問(wèn)題,即__cdecl和__stdcall的問(wèn)題。__cdecl是常規(guī)的C/C++調(diào)用約定,這種調(diào)用約定下,函數(shù)調(diào)用后棧的清理工作是由調(diào)用者完成的。__stdcall是標(biāo)準(zhǔn)的調(diào)用約定,即這些函數(shù)將在返回到調(diào)用者之前將參數(shù)從棧中刪除。

這兩個(gè)問(wèn)題DLL都不能很好的解決,只能說(shuō)湊合著用。但是在COM中,都得到了完美的解決。所以,要在Windows平臺(tái)實(shí)現(xiàn)語(yǔ)言無(wú)關(guān)性,還是只有使用COM中間件。

總而言之,除非客戶端也使用C++,否則dll是不便于支持函數(shù)重載、類等c++特性的。DLL對(duì)c函數(shù)的支持很好,我想這也是為什么windows的函數(shù)庫(kù)使用C加dll實(shí)現(xiàn)的理由之一。

在VC中編寫(xiě)DLL

在VC中創(chuàng)建、編譯、鏈接dll是非常方便的,點(diǎn)擊fileàNewàProjectàWin32 Dynamic-Link Library,輸入dll名稱dll_InVC然后點(diǎn)擊確定。然后選擇A DLL that export some symbols,點(diǎn)擊Finish。即可得到一個(gè)完整的DLL。

仔細(xì)觀察其源代碼,是不是有很多地方似曾相識(shí)啊,哈哈!



posted @ 2008-04-01 17:03 隨意門(mén) 閱讀(251) | 評(píng)論 (0)編輯 收藏
C++的XML編程經(jīng)驗(yàn)――LIBXML2庫(kù)使用指南

     摘要: 寫(xiě)這篇文章的原因有如下幾點(diǎn):1)C++標(biāo)準(zhǔn)庫(kù)中沒(méi)有操作XML的方法,用C++操作XML文件必須熟悉一種函數(shù)庫(kù),LIBXML2是其中一種很優(yōu)秀的XML庫(kù),而且它同時(shí)支持多種編程語(yǔ)言;2)LIBXML2庫(kù)的Tutorial寫(xiě)得不太好,尤其是編碼轉(zhuǎn)換的部分,不適用于中文編碼的轉(zhuǎn)換;3)網(wǎng)上的大多數(shù)關(guān)于Libxml2的介紹僅僅是翻譯了自帶的資料,沒(méi)有詳細(xì)介紹如何在windows平臺(tái)下進(jìn)行編程,更很少提到如...  閱讀全文

posted @ 2008-04-01 17:01 隨意門(mén)| 編輯 收藏
Linux下的多線程編程

Linux下的多線程編程


作者:姚繼鋒 2001-08-11 09:05:00 來(lái)自:http://www.china-pub.com

1 引言
  線程(thread)技術(shù)早在60年代就被提出,但真正應(yīng)用多線程到操作系統(tǒng)中去,是在80年代中期,solaris是這方面的佼佼者。傳統(tǒng)的Unix也支持線程的概念,但是在一個(gè)進(jìn)程(process)中只允許有一個(gè)線程,這樣多線程就意味著多進(jìn)程。現(xiàn)在,多線程技術(shù)已經(jīng)被許多操作系統(tǒng)所支持,包括Windows/NT,當(dāng)然,也包括Linux。
  為什么有了進(jìn)程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統(tǒng)應(yīng)該選用多線程?我們首先必須回答這些問(wèn)題。
  使用多線程的理由之一是和進(jìn)程相比,它是一種非常"節(jié)儉"的多任務(wù)操作方式。我們知道,在Linux系統(tǒng)下,啟動(dòng)一個(gè)新的進(jìn)程必須分配給它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來(lái)維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運(yùn)行于一個(gè)進(jìn)程中的多個(gè)線程,它們彼此之間使用相同的地址空間,共享大部分?jǐn)?shù)據(jù),啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間,而且,線程間彼此切換所需的時(shí)間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時(shí)間。據(jù)統(tǒng)計(jì),總的說(shuō)來(lái)桓黿痰目笤際且桓魷叱炭?0倍左右,當(dāng)然,在具體的系統(tǒng)上,這個(gè)數(shù)據(jù)可能會(huì)有較大的區(qū)別。
  使用多線程的理由之二是線程間方便的通信機(jī)制。對(duì)不同進(jìn)程來(lái)說(shuō),它們具有獨(dú)立的數(shù)據(jù)空間,要進(jìn)行數(shù)據(jù)的傳遞只能通過(guò)通信的方式進(jìn)行,這種方式不僅費(fèi)時(shí),而且很不方便。線程則不然,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個(gè)線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。當(dāng)然,數(shù)據(jù)的共享也帶來(lái)其他一些問(wèn)題,有的變量不能同時(shí)被兩個(gè)線程所修改,有的子程序中聲明為static的數(shù)據(jù)更有可能給多線程程序帶來(lái)災(zāi)難性的打擊,這些正是編寫(xiě)多線程程序時(shí)最需要注意的地方。
  除了以上所說(shuō)的優(yōu)點(diǎn)外,不和進(jìn)程比較,多線程程序作為一種多任務(wù)、并發(fā)的工作方式,當(dāng)然有以下的優(yōu)點(diǎn):
  1) 提高應(yīng)用程序響應(yīng)。這對(duì)圖形界面的程序尤其有意義,當(dāng)一個(gè)操作耗時(shí)很長(zhǎng)時(shí),整個(gè)系統(tǒng)都會(huì)等待這個(gè)操作,此時(shí)程序不會(huì)響應(yīng)鍵盤(pán)、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時(shí)長(zhǎng)的操作(time consuming)置于一個(gè)新的線程,可以避免這種尷尬的情況。
  2) 使多CPU系統(tǒng)更加有效。操作系統(tǒng)會(huì)保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí),不同的線程運(yùn)行于不同的CPU上。
  3) 改善程序結(jié)構(gòu)。一個(gè)既長(zhǎng)又復(fù)雜的進(jìn)程可以考慮分為多個(gè)線程,成為幾個(gè)獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會(huì)利于理解和修改。
  下面我們先來(lái)嘗試編寫(xiě)一個(gè)簡(jiǎn)單的多線程程序。

2 簡(jiǎn)單的多線程編程
  Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫(xiě)Linux下的多線程程序,需要使用頭文件pthread.h,連接時(shí)需要使用庫(kù)libpthread.a。順便說(shuō)一下,Linux下pthread的實(shí)現(xiàn)是通過(guò)系統(tǒng)調(diào)用clone()來(lái)實(shí)現(xiàn)的。clone()是Linux所特有的系統(tǒng)調(diào)用,它的使用方式類似fork,關(guān)于clone()的詳細(xì)情況,有興趣的讀者可以去查看有關(guān)文檔說(shuō)明。下面我們展示一個(gè)最簡(jiǎn)單的多線程程序example1.c。

/* example.c*/
#include <stdio.h>
#include <pthread.h>
void thread(void)
{
int i;
for(i=0;i<3;i++)
printf("This is a pthread.\n");
}

int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if(ret!=0){
printf ("Create pthread error!\n");
exit (1);
}
for(i=0;i<3;i++)
printf("This is the main process.\n");
pthread_join(id,NULL);
return (0);
}

我們編譯此程序:
gcc example1.c -lpthread -o example1
運(yùn)行example1,我們得到如下結(jié)果:
This is the main process.
This is a pthread.
This is the main process.
This is the main process.
This is a pthread.
This is a pthread.
再次運(yùn)行,我們可能得到如下結(jié)果:
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.

  前后兩次結(jié)果不一樣,這是兩個(gè)線程爭(zhēng)奪CPU資源的結(jié)果。上面的示例中,我們使用到了兩個(gè)函數(shù),  pthread_create和pthread_join,并聲明了一個(gè)pthread_t型的變量。
  pthread_t在頭文件/usr/include/bits/pthreadtypes.h中定義:
  typedef unsigned long int pthread_t;
  它是一個(gè)線程的標(biāo)識(shí)符。函數(shù)pthread_create用來(lái)創(chuàng)建一個(gè)線程,它的原型為:
  extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
  void *(*__start_routine) (void *), void *__arg));
  第一個(gè)參數(shù)為指向線程標(biāo)識(shí)符的指針,第二個(gè)參數(shù)用來(lái)設(shè)置線程屬性,第三個(gè)參數(shù)是線程運(yùn)行函數(shù)的起始地址,最后一個(gè)參數(shù)是運(yùn)行函數(shù)的參數(shù)。這里,我們的函數(shù)thread不需要參數(shù),所以最后一個(gè)參數(shù)設(shè)為空指針。第二個(gè)參數(shù)我們也設(shè)為空指針,這樣將生成默認(rèn)屬性的線程。對(duì)線程屬性的設(shè)定和修改我們將在下一節(jié)闡述。當(dāng)創(chuàng)建線程成功時(shí),函數(shù)返回0,若不為0則說(shuō)明創(chuàng)建線程失敗,常見(jiàn)的錯(cuò)誤返回代碼為EAGAIN和EINVAL。前者表示系統(tǒng)限制創(chuàng)建新的線程,例如線程數(shù)目過(guò)多了;后者表示第二個(gè)參數(shù)代表的線程屬性值非法。創(chuàng)建線程成功后,新創(chuàng)建的線程則運(yùn)行參數(shù)三和參數(shù)四確定的函數(shù),原來(lái)的線程則繼續(xù)運(yùn)行下一行代碼。
  函數(shù)pthread_join用來(lái)等待一個(gè)線程的結(jié)束。函數(shù)原型為:
  extern int pthread_join __P ((pthread_t __th, void **__thread_return));
  第一個(gè)參數(shù)為被等待的線程標(biāo)識(shí)符,第二個(gè)參數(shù)為一個(gè)用戶定義的指針,它可以用來(lái)存儲(chǔ)被等待線程的返回值。這個(gè)函數(shù)是一個(gè)線程阻塞的函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當(dāng)函數(shù)返回時(shí),被等待線程的資源被收回。一個(gè)線程的結(jié)束有兩種途徑,一種是象我們上面的例子一樣,函數(shù)結(jié)束了,調(diào)用它的線程也就結(jié)束了;另一種方式是通過(guò)函數(shù)pthread_exit來(lái)實(shí)現(xiàn)。它的函數(shù)原型為:
  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
  唯一的參數(shù)是函數(shù)的返回代碼,只要pthread_join中的第二個(gè)參數(shù)thread_return不是NULL,這個(gè)值將被傳遞給thread_return。最后要說(shuō)明的是,一個(gè)線程不能被多個(gè)線程等待,否則第一個(gè)接收到信號(hào)的線程成功返回,其余調(diào)用pthread_join的線程則返回錯(cuò)誤代碼ESRCH。
  在這一節(jié)里,我們編寫(xiě)了一個(gè)最簡(jiǎn)單的線程,并掌握了最常用的三個(gè)函數(shù)pthread_create,pthread_join和pthread_exit。下面,我們來(lái)了解線程的一些常用屬性以及如何設(shè)置這些屬性。

3 修改線程的屬性
  在上一節(jié)的例子里,我們用pthread_create函數(shù)創(chuàng)建了一個(gè)線程,在這個(gè)線程中,我們使用了默認(rèn)參數(shù),即將該函數(shù)的第二個(gè)參數(shù)設(shè)為NULL。的確,對(duì)大多數(shù)程序來(lái)說(shuō),使用默認(rèn)屬性就夠了,但我們還是有必要來(lái)了解一下線程的有關(guān)屬性。
  屬性結(jié)構(gòu)為pthread_attr_t,它同樣在頭文件/usr/include/pthread.h中定義,喜歡追根問(wèn)底的人可以自己去查看。屬性值不能直接設(shè)置,須使用相關(guān)函數(shù)進(jìn)行操作,初始化的函數(shù)為pthread_attr_init,這個(gè)函數(shù)必須在pthread_create函數(shù)之前調(diào)用。屬性對(duì)象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優(yōu)先級(jí)。默認(rèn)的屬性為非綁定、非分離、缺省1M的堆棧、與父進(jìn)程同樣級(jí)別的優(yōu)先級(jí)。
  關(guān)于線程的綁定,牽涉到另外一個(gè)概念:輕進(jìn)程(LWP:Light Weight Process)。輕進(jìn)程可以理解為內(nèi)核線程,它位于用戶層和系統(tǒng)層之間。系統(tǒng)對(duì)線程資源的分配、對(duì)線程的控制是通過(guò)輕進(jìn)程來(lái)實(shí)現(xiàn)的,一個(gè)輕進(jìn)程可以控制一個(gè)或多個(gè)線程。默認(rèn)狀況下,啟動(dòng)多少輕進(jìn)程、哪些輕進(jìn)程來(lái)控制哪些線程是由系統(tǒng)來(lái)控制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個(gè)線程固定的"綁"在一個(gè)輕進(jìn)程之上。被綁定的線程具有較高的響應(yīng)速度,這是因?yàn)镃PU時(shí)間片的調(diào)度是面向輕進(jìn)程的,綁定的線程可以保證在需要的時(shí)候它總有一個(gè)輕進(jìn)程可用。通過(guò)設(shè)置被綁定的輕進(jìn)程的優(yōu)先級(jí)和調(diào)度級(jí)可以使得綁定的線程滿足諸如實(shí)時(shí)反應(yīng)之類的要求。
  設(shè)置線程綁定狀態(tài)的函數(shù)為pthread_attr_setscope,它有兩個(gè)參數(shù),第一個(gè)是指向?qū)傩越Y(jié)構(gòu)的指針,第二個(gè)是綁定類型,它有兩個(gè)取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。下面的代碼即創(chuàng)建了一個(gè)綁定的線程。
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化屬性值,均設(shè)為默認(rèn)值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

  線程的分離狀態(tài)決定一個(gè)線程以什么樣的方式來(lái)終止自己。在上面的例子中,我們采用了線程的默認(rèn)屬性,即為非分離狀態(tài),這種情況下,原有的線程等待創(chuàng)建的線程結(jié)束。只有當(dāng)pthread_join()函數(shù)返回時(shí),創(chuàng)建的線程才算終止,才能釋放自己占用的系統(tǒng)資源。而分離線程不是這樣子的,它沒(méi)有被其他的線程所等待,自己運(yùn)行結(jié)束了,線程也就終止了,馬上釋放系統(tǒng)資源。程序員應(yīng)該根據(jù)自己的需要,選擇適當(dāng)?shù)姆蛛x狀態(tài)。設(shè)置線程分離狀態(tài)的函數(shù)為pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個(gè)參數(shù)可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。這里要注意的一點(diǎn)是,如果設(shè)置一個(gè)線程為分離線程,而這個(gè)線程運(yùn)行又非常快,它很可能在pthread_create函數(shù)返回之前就終止了,它終止以后就可能將線程號(hào)和系統(tǒng)資源移交給其他的線程使用,這樣調(diào)用pthread_create的線程就得到了錯(cuò)誤的線程號(hào)。要避免這種情況可以采取一定的同步措施,最簡(jiǎn)單的方法之一是可以在被創(chuàng)建的線程里調(diào)用pthread_cond_timewait函數(shù),讓這個(gè)線程等待一會(huì)兒,留出足夠的時(shí)間讓函數(shù)pthread_create返回。設(shè)置一段等待時(shí)間,是在多線程編程里常用的方法。但是注意不要使用諸如wait()之類的函數(shù),它們是使整個(gè)進(jìn)程睡眠,并不能解決線程同步的問(wèn)題。
  另外一個(gè)可能常用的屬性是線程的優(yōu)先級(jí),它存放在結(jié)構(gòu)sched_param中。用函數(shù)pthread_attr_getschedparam和函數(shù)pthread_attr_setschedparam進(jìn)行存放,一般說(shuō)來(lái),我們總是先取優(yōu)先級(jí),對(duì)取得的值修改后再存放回去。下面即是一段簡(jiǎn)單的例子。
#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;

pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, &param);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, &param);
pthread_create(&tid, &attr, (void *)myfunction, myarg);
  
4 線程的數(shù)據(jù)處理
  和進(jìn)程相比,線程的最大優(yōu)點(diǎn)之一是數(shù)據(jù)的共享性,各個(gè)進(jìn)程共享父進(jìn)程處沿襲的數(shù)據(jù)段,可以方便的獲得、修改數(shù)據(jù)。但這也給多線程編程帶來(lái)了許多問(wèn)題。我們必須當(dāng)心有多個(gè)不同的進(jìn)程訪問(wèn)相同的變量。許多函數(shù)是不可重入的,即同時(shí)不能運(yùn)行一個(gè)函數(shù)的多個(gè)拷貝(除非使用不同的數(shù)據(jù)段)。在函數(shù)中聲明的靜態(tài)變量常常帶來(lái)問(wèn)題,函數(shù)的返回值也會(huì)有問(wèn)題。因?yàn)槿绻祷氐氖呛瘮?shù)內(nèi)部靜態(tài)聲明的空間的地址,則在一個(gè)線程調(diào)用該函數(shù)得到地址后使用該地址指向的數(shù)據(jù)時(shí),別的線程可能調(diào)用此函數(shù)并修改了這一段數(shù)據(jù)。在進(jìn)程中共享的變量必須用關(guān)鍵字volatile來(lái)定義,這是為了防止編譯器在優(yōu)化時(shí)(如gcc中使用-OX參數(shù))改變它們的使用方式。為了保護(hù)變量,我們必須使用信號(hào)量、互斥等方法來(lái)保證我們對(duì)變量的正確使用。下面,我們就逐步介紹處理線程數(shù)據(jù)時(shí)的有關(guān)知識(shí)。

4.1 線程數(shù)據(jù)
  在單線程的程序里,有兩種基本的數(shù)據(jù):全局變量和局部變量。但在多線程程序里,還有第三種數(shù)據(jù)類型:線程數(shù)據(jù)(TSD: Thread-Specific Data)。它和全局變量很象,在線程內(nèi)部,各個(gè)函數(shù)可以象使用全局變量一樣調(diào)用它,但它對(duì)線程外部的其它線程是不可見(jiàn)的。這種數(shù)據(jù)的必要性是顯而易見(jiàn)的。例如我們常見(jiàn)的變量errno,它返回標(biāo)準(zhǔn)的出錯(cuò)信息。它顯然不能是一個(gè)局部變量,幾乎每個(gè)函數(shù)都應(yīng)該可以調(diào)用它;但它又不能是一個(gè)全局變量,否則在A線程里輸出的很可能是B線程的出錯(cuò)信息。要實(shí)現(xiàn)諸如此類的變量,我們就必須使用線程數(shù)據(jù)。我們?yōu)槊總€(gè)線程數(shù)據(jù)創(chuàng)建一個(gè)鍵,它和這個(gè)鍵相關(guān)聯(lián),在各個(gè)線程里,都使用這個(gè)鍵來(lái)指代線程數(shù)據(jù),但在不同的線程里,這個(gè)鍵代表的數(shù)據(jù)是不同的,在同一個(gè)線程里,它代表同樣的數(shù)據(jù)內(nèi)容。
  和線程數(shù)據(jù)相關(guān)的函數(shù)主要有4個(gè):創(chuàng)建一個(gè)鍵;為一個(gè)鍵指定線程數(shù)據(jù);從一個(gè)鍵讀取線程數(shù)據(jù);刪除鍵。
  創(chuàng)建鍵的函數(shù)原型為:
  extern int pthread_key_create __P ((pthread_key_t *__key,
  void (*__destr_function) (void *)));
  第一個(gè)參數(shù)為指向一個(gè)鍵值的指針,第二個(gè)參數(shù)指明了一個(gè)destructor函數(shù),如果這個(gè)參數(shù)不為空,那么當(dāng)每個(gè)線程結(jié)束時(shí),系統(tǒng)將調(diào)用這個(gè)函數(shù)來(lái)釋放綁定在這個(gè)鍵上的內(nèi)存塊。這個(gè)函數(shù)常和函數(shù)pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,為了讓這個(gè)鍵只被創(chuàng)建一次。函數(shù)pthread_once聲明一個(gè)初始化函數(shù),第一次調(diào)用pthread_once時(shí)它執(zhí)行這個(gè)函數(shù),以后的調(diào)用將被它忽略。

  在下面的例子中,我們創(chuàng)建一個(gè)鍵,并將它和某個(gè)數(shù)據(jù)相關(guān)聯(lián)。我們要定義一個(gè)函數(shù)createWindow,這個(gè)函數(shù)定義一個(gè)圖形窗口(數(shù)據(jù)類型為Fl_Window *,這是圖形界面開(kāi)發(fā)工具FLTK中的數(shù)據(jù)類型)。由于各個(gè)線程都會(huì)調(diào)用這個(gè)函數(shù),所以我們使用線程數(shù)據(jù)。
/* 聲明一個(gè)鍵*/
pthread_key_t myWinKey;
/* 函數(shù) createWindow */
void createWindow ( void ) {
Fl_Window * win;
static pthread_once_t once= PTHREAD_ONCE_INIT;
/* 調(diào)用函數(shù)createMyKey,創(chuàng)建鍵*/
pthread_once ( & once, createMyKey) ;
/*win指向一個(gè)新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 對(duì)此窗口作一些可能的設(shè)置工作,如大小、位置、名稱等*/
setWindow(win);
/* 將窗口指針值綁定在鍵myWinKey上*/
pthread_setpecific ( myWinKey, win);
}

/* 函數(shù) createMyKey,創(chuàng)建一個(gè)鍵,并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&myWinKey, freeWinKey);
}

/* 函數(shù) freeWinKey,釋放空間*/
void freeWinKey ( Fl_Window * win){
delete win;
}

  這樣,在不同的線程中調(diào)用函數(shù)createMyWin,都可以得到在線程內(nèi)部均可見(jiàn)的窗口變量,這個(gè)變量通過(guò)函數(shù)pthread_getspecific得到。在上面的例子中,我們已經(jīng)使用了函數(shù)pthread_setspecific來(lái)將線程數(shù)據(jù)和一個(gè)鍵綁定在一起。這兩個(gè)函數(shù)的原型如下:
  extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
  extern void *pthread_getspecific __P ((pthread_key_t __key));
  這兩個(gè)函數(shù)的參數(shù)意義和使用方法是顯而易見(jiàn)的。要注意的是,用pthread_setspecific為一個(gè)鍵指定新的線程數(shù)據(jù)時(shí),必須自己釋放原有的線程數(shù)據(jù)以回收空間。這個(gè)過(guò)程函數(shù)pthread_key_delete用來(lái)刪除一個(gè)鍵,這個(gè)鍵占用的內(nèi)存將被釋放,但同樣要注意的是,它只釋放鍵占用的內(nèi)存,并不釋放該鍵關(guān)聯(lián)的線程數(shù)據(jù)所占用的內(nèi)存資源,而且它也不會(huì)觸發(fā)函數(shù)pthread_key_create中定義的destructor函數(shù)。線程數(shù)據(jù)的釋放必須在釋放鍵之前完成。

4.2 互斥鎖
  互斥鎖用來(lái)保證一段時(shí)間內(nèi)只有一個(gè)線程在執(zhí)行一段代碼。必要性顯而易見(jiàn):假設(shè)各個(gè)線程向同一個(gè)文件順序?qū)懭霐?shù)據(jù),最后得到的結(jié)果一定是災(zāi)難性的。
  我們先看下面一段代碼。這是一個(gè)讀/寫(xiě)程序,它們公用一個(gè)緩沖區(qū),并且我們假定一個(gè)緩沖區(qū)只能保存一條信息。即緩沖區(qū)只有兩個(gè)狀態(tài):有信息或沒(méi)有信息。

void reader_function ( void );
void writer_function ( void );

char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定義延遲時(shí)間*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默認(rèn)屬性初始化一個(gè)互斥鎖對(duì)象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
writer_function( );
}

void writer_function (void){
while(1){
/* 鎖定互斥鎖*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打開(kāi)互斥鎖*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}

void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
  這里聲明了互斥鎖變量mutex,結(jié)構(gòu)pthread_mutex_t為不公開(kāi)的數(shù)據(jù)類型,其中包含一個(gè)系統(tǒng)分配的屬性對(duì)象。函數(shù)pthread_mutex_init用來(lái)生成一個(gè)互斥鎖。NULL參數(shù)表明使用默認(rèn)屬性。如果需要聲明特定屬性的互斥鎖,須調(diào)用函數(shù)pthread_mutexattr_init。函數(shù)pthread_mutexattr_setpshared和函數(shù)pthread_mutexattr_settype用來(lái)設(shè)置互斥鎖屬性。前一個(gè)函數(shù)設(shè)置屬性pshared,它有兩個(gè)取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來(lái)不同進(jìn)程中的線程同步,后者用于同步本進(jìn)程的不同線程。在上面的例子中,我們使用的是默認(rèn)屬性PTHREAD_PROCESS_ PRIVATE。后者用來(lái)設(shè)置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機(jī)制,一般情況下,選用最后一個(gè)默認(rèn)屬性。
  pthread_mutex_lock聲明開(kāi)始用互斥鎖上鎖,此后的代碼直至調(diào)用pthread_mutex_unlock為止,均被上鎖,即同一時(shí)間只能被一個(gè)線程調(diào)用執(zhí)行。當(dāng)一個(gè)線程執(zhí)行到pthread_mutex_lock處時(shí),如果該鎖此時(shí)被另一個(gè)線程使用,那此線程被阻塞,即程序?qū)⒌却搅硪粋€(gè)線程釋放此互斥鎖。在上面的例子中,我們使用了pthread_delay_np函數(shù),讓線程睡眠一段時(shí)間,就是為了防止一個(gè)線程始終占據(jù)此函數(shù)。
  上面的例子非常簡(jiǎn)單,就不再介紹了,需要提出的是在使用互斥鎖的過(guò)程中很有可能會(huì)出現(xiàn)死鎖:兩個(gè)線程試圖同時(shí)占用兩個(gè)資源,并按不同的次序鎖定相應(yīng)的互斥鎖,例如兩個(gè)線程都需要鎖定互斥鎖1和互斥鎖2,a線程先鎖定互斥鎖1,b線程先鎖定互斥鎖2,這時(shí)就出現(xiàn)了死鎖。此時(shí)我們可以使用函數(shù)pthread_mutex_trylock,它是函數(shù)pthread_mutex_lock的非阻塞版本,當(dāng)它發(fā)現(xiàn)死鎖不可避免時(shí),它會(huì)返回相應(yīng)的信息,程序員可以針對(duì)死鎖做出相應(yīng)的處理。另外不同的互斥鎖類型對(duì)死鎖的處理不一樣,但最主要的還是要程序員自己在程序設(shè)計(jì)注意這一點(diǎn)。

4.3 條件變量
  前一節(jié)中我們講述了如何使用互斥鎖來(lái)實(shí)現(xiàn)線程間數(shù)據(jù)的共享和通信,互斥鎖一個(gè)明顯的缺點(diǎn)是它只有兩種狀態(tài):鎖定和非鎖定。而條件變量通過(guò)允許線程阻塞和等待另一個(gè)線程發(fā)送信號(hào)的方法彌補(bǔ)了互斥鎖的不足,它常和互斥鎖一起使用。使用時(shí),條件變量被用來(lái)阻塞一個(gè)線程,當(dāng)條件不滿足時(shí),線程往往解開(kāi)相應(yīng)的互斥鎖并等待條件發(fā)生變化。一旦其它的某個(gè)線程改變了條件變量,它將通知相應(yīng)的條件變量喚醒一個(gè)或多個(gè)正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測(cè)試條件是否滿足。一般說(shuō)來(lái),條件變量被用來(lái)進(jìn)行線承間的同步。
  條件變量的結(jié)構(gòu)為pthread_cond_t,函數(shù)pthread_cond_init()被用來(lái)初始化一個(gè)條件變量。它的原型為:
  extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
  其中cond是一個(gè)指向結(jié)構(gòu)pthread_cond_t的指針,cond_attr是一個(gè)指向結(jié)構(gòu)pthread_condattr_t的指針。結(jié)構(gòu)pthread_condattr_t是條件變量的屬性結(jié)構(gòu),和互斥鎖一樣我們可以用它來(lái)設(shè)置條件變量是進(jìn)程內(nèi)可用還是進(jìn)程間可用,默認(rèn)值是PTHREAD_ PROCESS_PRIVATE,即此條件變量被同一進(jìn)程內(nèi)的各個(gè)線程使用。注意初始化條件變量只有未被使用時(shí)才能重新初始化或被釋放。釋放一個(gè)條件變量的函數(shù)為pthread_cond_ destroy(pthread_cond_t cond)。 
  函數(shù)pthread_cond_wait()使線程阻塞在一個(gè)條件變量上。它的函數(shù)原型為:
  extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex));
  線程解開(kāi)mutex指向的鎖并被條件變量cond阻塞。線程可以被函數(shù)pthread_cond_signal和函數(shù)pthread_cond_broadcast喚醒,但是要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,例如一個(gè)變量是否為0等等,這一點(diǎn)我們從后面的例子中可以看到。線程被喚醒后,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說(shuō)來(lái)線程應(yīng)該仍阻塞在這里,被等待被下一次喚醒。這個(gè)過(guò)程一般用while語(yǔ)句實(shí)現(xiàn)。
  另一個(gè)用來(lái)阻塞線程的函數(shù)是pthread_cond_timedwait(),它的原型為:
  extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex, __const struct timespec *__abstime));
  它比函數(shù)pthread_cond_wait()多了一個(gè)時(shí)間參數(shù),經(jīng)歷abstime段時(shí)間后,即使條件變量不滿足,阻塞也被解除。
  函數(shù)pthread_cond_signal()的原型為:
  extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
  它用來(lái)釋放被阻塞在條件變量cond上的一個(gè)線程。多個(gè)線程阻塞在此條件變量上時(shí),哪一個(gè)線程被喚醒是由線程的調(diào)度策略所決定的。要注意的是,必須用保護(hù)條件變量的互斥鎖來(lái)保護(hù)這個(gè)函數(shù),否則條件滿足信號(hào)又可能在測(cè)試條件和調(diào)用pthread_cond_wait函數(shù)之間被發(fā)出,從而造成無(wú)限制的等待。下面是使用函數(shù)pthread_cond_wait()和函數(shù)pthread_cond_signal()的一個(gè)簡(jiǎn)單的例子。

pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count () {
pthread_mutex_lock (&count_lock);
while(count==0)
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -1;
pthread_mutex_unlock (&count_lock);
}

increment_count(){
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}
  count值為0
時(shí),decrement函數(shù)在pthread_cond_wait處被阻塞,并打開(kāi)互斥鎖count_lock。此時(shí),當(dāng)調(diào)用到函數(shù)increment_count時(shí),pthread_cond_signal()函數(shù)改變條件變量,告知decrement_count()停止阻塞。讀者可以試著讓兩個(gè)線程分別運(yùn)行這兩個(gè)函數(shù),看看會(huì)出現(xiàn)什么樣的結(jié)果。
  函數(shù)pthread_cond_broadcast(pthread_cond_t *cond)用來(lái)喚醒所有被阻塞在條件變量cond上的線程。這些線程被喚醒后將再次競(jìng)爭(zhēng)相應(yīng)的互斥鎖,所以必須小心使用這個(gè)函數(shù)。

4.4 信號(hào)量
  信號(hào)量本質(zhì)上是一個(gè)非負(fù)的整數(shù)計(jì)數(shù)器,它被用來(lái)控制對(duì)公共資源的訪問(wèn)。當(dāng)公共資源增加時(shí),調(diào)用函數(shù)sem_post()增加信號(hào)量。只有當(dāng)信號(hào)量值大于0時(shí),才能使用公共資源,使用后,函數(shù)sem_wait()減少信號(hào)量。函數(shù)sem_trywait()和函數(shù)pthread_ mutex_trylock()起同樣的作用,它是函數(shù)sem_wait()的非阻塞版本。下面我們逐個(gè)介紹和信號(hào)量有關(guān)的一些函數(shù),它們都在頭文件/usr/include/semaphore.h中定義。
  信號(hào)量的數(shù)據(jù)類型為結(jié)構(gòu)sem_t,它本質(zhì)上是一個(gè)長(zhǎng)整型的數(shù)。函數(shù)sem_init()用來(lái)初始化一個(gè)信號(hào)量。它的原型為:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
  sem為指向信號(hào)量結(jié)構(gòu)的一個(gè)指針;pshared不為0時(shí)此信號(hào)量在進(jìn)程間共享,否則只能為當(dāng)前進(jìn)程的所有線程共享;value給出了信號(hào)量的初始值。
  函數(shù)sem_post( sem_t *sem )用來(lái)增加信號(hào)量的值。當(dāng)有線程阻塞在這個(gè)信號(hào)量上時(shí),調(diào)用這個(gè)函數(shù)會(huì)使其中的一個(gè)線程不在阻塞,選擇機(jī)制同樣是由線程的調(diào)度策略決定的。
  函數(shù)sem_wait( sem_t *sem )被用來(lái)阻塞當(dāng)前線程直到信號(hào)量sem的值大于0,解除阻塞后將sem的值減一,表明公共資源經(jīng)使用后減少。函數(shù)sem_trywait ( sem_t *sem )是函數(shù)sem_wait()的非阻塞版本,它直接將信號(hào)量sem的值減一。
  函數(shù)sem_destroy(sem_t *sem)用來(lái)釋放信號(hào)量sem。
  下面我們來(lái)看一個(gè)使用信號(hào)量的例子。在這個(gè)例子中,一共有4個(gè)線程,其中兩個(gè)線程負(fù)責(zé)從文件讀取數(shù)據(jù)到公共的緩沖區(qū),另兩個(gè)線程從緩沖區(qū)讀取數(shù)據(jù)作不同的處理(加和乘運(yùn)算)。
/* File sem.c */
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;
/* 從文件1.dat讀取數(shù)據(jù),每讀一次,信號(hào)量加一*/
void ReadData1(void){
FILE *fp=fopen("1.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*從文件2.dat讀取數(shù)據(jù)*/
void ReadData2(void){
FILE *fp=fopen("2.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*阻塞等待緩沖區(qū)有數(shù)據(jù),讀取數(shù)據(jù)后,釋放空間,繼續(xù)等待*/
void HandleData1(void){
while(1){
sem_wait(&sem);
printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],
stack[size][0]+stack[size][1]);
--size;
}
}

void HandleData2(void){
while(1){
sem_wait(&sem);
printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1],
stack[size][0]*stack[size][1]);
--size;
}
}
int main(void){
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,(void *)HandleData1,NULL);
pthread_create(&t2,NULL,(void *)HandleData2,NULL);
pthread_create(&t3,NULL,(void *)ReadData1,NULL);
pthread_create(&t4,NULL,(void *)ReadData2,NULL);
/* 防止程序過(guò)早退出,讓它在此無(wú)限期等待*/
pthread_join(t1,NULL);
}

  在Linux下,我們用命令gcc -lpthread sem.c -o sem生成可執(zhí)行文件sem。 我們事先編輯好數(shù)據(jù)文件1.dat和2.dat,假設(shè)它們的內(nèi)容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我們運(yùn)行sem,得到如下的結(jié)果:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11

  從中我們可以看出各個(gè)線程間的競(jìng)爭(zhēng)關(guān)系。而數(shù)值并未按我們?cè)鹊捻樞蝻@示出來(lái)這是由于size這個(gè)數(shù)值被各個(gè)線程任意修改的緣故。這也往往是多線程編程要注意的問(wèn)題。

5 小結(jié)
  多線程編程是一個(gè)很有意思也很有用的技術(shù),使用多線程技術(shù)的網(wǎng)絡(luò)螞蟻是目前最常用的下載工具之一,使用多線程技術(shù)的grep比單線程的grep要快上幾倍,類似的例子還有很多。希望大家能用多線程技術(shù)寫(xiě)出高效實(shí)用的好程序來(lái)。

posted @ 2008-03-26 15:11 隨意門(mén) 閱讀(244) | 評(píng)論 (0)編輯 收藏
Linux下非常命令學(xué)習(xí)

轉(zhuǎn)自http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_62.html

剛學(xué)linux的時(shí)候,有些東西不大熟悉,非常惱火
為了脫離這話總困境,把自己遇到并解決的一些常用命令行操作集中寫(xiě)到這里

1,如何刪除非空目錄?

用rmdir嗎?不是,而是

#rm [your directory] -rf

意思是強(qiáng)制刪除該目錄,以及該目錄下所有文件,試試,肯定奏效,呵呵
不過(guò)不要隨便用,毫無(wú)提示就會(huì)刪除掉的

而rmdir只能刪除空目錄哦
另外,如果不強(qiáng)制刪除,只用
#rm [your directory] -r

2,壓縮-解壓縮命令大全

tar.gz這個(gè)比較常見(jiàn)
解壓:tar zxvf FileName.tar.gz
壓縮:tar zcvf FileName.tar.gz DirName

還在為面對(duì)一大堆的壓縮文件無(wú)法解壓縮而煩惱嗎?
這里有比較全面的信息哦
http://www.chinaitlab.com/www/techspecial/tar/

3,如何用命令行創(chuàng)建和刪除文件名開(kāi)頭為"-"的文件?

讓我們來(lái)創(chuàng)建一個(gè)這樣的文件“-test”
#touch -test
touch:日期格式 "est" 無(wú)效
#touch -- -test
#rm -test
rm:無(wú)效選項(xiàng) --t
請(qǐng)嘗試執(zhí)行"rm --help"來(lái)獲取更多幫助
#rm -- test
呵呵,是不是發(fā)現(xiàn),只有加了"--"才可以正常操作阿

4,如果,我在鍵入ls命令以后只想顯示文件的部分信息,我該怎么辦呢?
也許你會(huì)查幫助ls --help
可是那么多的組合確實(shí)是讓人煩惱
不過(guò)先在不用煩惱拉
因?yàn)槲覀冇術(shù)awk

看看這個(gè):ls -l | gawk '{printf $9}'
看看輸出什么出來(lái)拉
是不是只有文件名拉
要是我還要?jiǎng)e的呢,那就在printf后面再加一個(gè)$x(x為1到9之間的字符哦)

呵呵,其實(shí)gawk是一個(gè)腳本語(yǔ)言哦,功能非常強(qiáng)大,有興趣看看相關(guān)的參考書(shū)去拉

5,有個(gè)好東西,可以對(duì)linux服務(wù)進(jìn)行相關(guān)的操作

chkconf

6,用rpm命令安裝和卸載軟件

RPM共有10種基本的模式:它們是安裝、查詢、驗(yàn)證、刪除等。

安裝模式:     rpm –i [安裝選項(xiàng)] <軟件包>
查詢模式:     rpm –q [查詢選項(xiàng)]
驗(yàn)證模式:     rpm –V 或 –verify [驗(yàn)證選項(xiàng)]
刪除模式:     rpm –e <軟件包>

7,tee命令

這個(gè)命令的強(qiáng)大指處在于它會(huì)從標(biāo)準(zhǔn)輸入設(shè)備讀取數(shù)據(jù),將其內(nèi)容輸出到標(biāo)準(zhǔn)輸出設(shè)備,同時(shí)保存成文件。
例如,我們想把一個(gè)文件inputfile的內(nèi)容即輸出到終端上也保存成outputfile1,outputfile2,那么我們就可以這么來(lái)弄:

Quote:

cat inputfile | tee outputfile1 outputfile2



參考資料:
http://jkwx007.blogchina.com/2514993.html
http://jordi.blogbus.com/logs/2004/10/452282.html
http://bbs.3671041.com/dispbbs.asp?boardid=9&id=747&star=1&page=1
http://www.knowsky.com/print.asp?id=18403

posted @ 2008-03-18 14:27 隨意門(mén) 閱讀(260) | 評(píng)論 (0)編輯 收藏
《windows網(wǎng)絡(luò)編程技術(shù)》之 Winsock基礎(chǔ) - [技術(shù)補(bǔ)鈣]

轉(zhuǎn)自http://xiekeli.blogbus.com/logs/4019775.html

前段時(shí)間根據(jù)客服的反映,老翁的前置機(jī)程序存在不工作的情況,初步表現(xiàn)為GPRS登錄失敗,我查看了報(bào)文(強(qiáng)烈要求老板發(fā)獎(jiǎng)金,有什么問(wèn)題我總 是沖鋒在前)發(fā)現(xiàn)基本出現(xiàn)在網(wǎng)絡(luò)頻繁斷開(kāi)的情況后(網(wǎng)絡(luò)每隔10分鐘被斷開(kāi)一次,socket錯(cuò)誤10053,什么原因還不得而知)。忘了說(shuō)了,前置機(jī)是 通過(guò)TCP連接到省局的GPRS代理服務(wù)器(是由小賴開(kāi)發(fā)的)然后和現(xiàn)場(chǎng)的終端進(jìn)行通信。前置機(jī)程序中是通過(guò)delphi的clientsocket進(jìn)行 連接的。一下子還真不知道是什么原因。對(duì)于socket這塊我絕對(duì)不是專家,知其然,不知其所以然。于是我決定先從清理基本概念開(kāi)始:
鳥(niǎo)瞰TCP/IP體系結(jié)構(gòu) 
首先從TCP/IP體系結(jié)構(gòu)開(kāi)始(這也是不少公司面試時(shí)的必備良題啊),相信下圖已經(jīng)表達(dá)得非常清除了
其次是winsocket與tcp/ip(其實(shí),不止TCP/IP協(xié)議族,這里只討論TCP/IP) 
TCP/IP協(xié)議核心與應(yīng)用程序關(guān)系圖。

最后是常用協(xié)議特性:
 關(guān)于定址
Winsock中,通過(guò)SOCKADDR_IN結(jié)構(gòu)來(lái)描述IP地址和服務(wù)端口:
struct sockaddr_in
{
      short                             sin_family;
      u_short                         sin_port;
      struct in_addr               sin_addr;
      char                              sin_zero[8];
};
哦,我只關(guān)心IP協(xié)議,所以sin_family = AF_INET;
關(guān)于端口要注意哦,0-1023為固定服務(wù)保留的(別打他們的注意了);1024-49151供普通用戶的普通用戶進(jìn)程使用;49152-65535是動(dòng)態(tài)和私有端口。
幾個(gè)特殊地址:
  INADDR_ANY:允許服務(wù)器應(yīng)用監(jiān)聽(tīng)主機(jī)上每個(gè)網(wǎng)絡(luò)接口上的客戶機(jī)活動(dòng);
  INADDR_BROADCAST用于在一個(gè)IP網(wǎng)絡(luò)中發(fā)送廣播UDP數(shù)據(jù)報(bào)。
字節(jié)排序:
從主機(jī)字節(jié)順序---> 網(wǎng)絡(luò)字節(jié)順序
返回四字節(jié),用于IP地址
u_long htonl(u_long hostlong)
int WSAHtonl(
        SOCKET s,
        u_long hostlong,
        u_long FAR * lpnetlong
);
返回兩字節(jié),用于端口號(hào)
u_short htons(u_short hostshort);
int WSAHtons(
      SOCKET s,
      u_short hostshort,
      u_short FAR * lpnetshort
); 
對(duì)應(yīng)的反向函數(shù):
u_long ntohl(u_long netong)
int WSANtohl(
        SOCKETs,
        u_long netong,
        u_long FAR * lphostlong
);
u_short htons(u_short netshort);
int WSANtons(
      SOCKET s,
      u_short netshort,
      u_short FAR * lphostshort
); 
進(jìn)入winsocket
 下面開(kāi)始整理winsocket 的一些細(xì)節(jié):
 所有的winsocket應(yīng)用其實(shí)都是調(diào)用winsock dll 中的方法,所以通過(guò)WSAstartup加載是第一步。否則就會(huì)出錯(cuò):WSANOTINITIALISED(10093)。
下面先來(lái)看看面向連接的協(xié)議:
從服務(wù)器端來(lái)看:
1.bind,將套接字和一個(gè)已知的地址進(jìn)行綁定。
 
這樣就創(chuàng)建了一個(gè)流套接字,這個(gè)步驟最常見(jiàn)的錯(cuò)誤是WSAEADDRINUSE (10048) ,表示另外一個(gè)進(jìn)程已經(jīng)和本地IP和端口進(jìn)行了綁定,或者那個(gè)IP地址和端口號(hào)處于TIME_WAIT狀態(tài)。
2.Listen,將套接字置于監(jiān)聽(tīng)狀態(tài)。
  
  int listen(
        SOCKET s,
       int backlog
    )
backlog參數(shù)指定了正在等待連接的最大隊(duì)列長(zhǎng)度,如果實(shí)際訪問(wèn)的客戶端大于該最大長(zhǎng)度就會(huì)出錯(cuò):WSAECONNREFUSED (10061)。事實(shí)上該backlog本身也是由基層協(xié)議提供者決定的。在這個(gè)階段還有一種常見(jiàn)的錯(cuò)誤就是WSAEINVAL (10022),即沒(méi)有綁定就進(jìn)行監(jiān)聽(tīng)了。
3.accept和WSAAccept
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR* addrlen,
調(diào)用accept可為待決連接隊(duì)列中的第一個(gè)連接請(qǐng)求提供服務(wù)。(在服務(wù)器端接收連接前,所有的客戶端連接請(qǐng)求是放在一個(gè)“待決”隊(duì)列中的。)
accept會(huì)返回一個(gè)新的套接字描述符,它對(duì)應(yīng)于已經(jīng)接受的那個(gè)客戶機(jī)連接。對(duì)于
該客戶機(jī)后續(xù)的所有操作,都應(yīng)使用這個(gè)新套接字。至于原來(lái)那個(gè)監(jiān)聽(tīng)套接字,它仍然用于
接受其他客戶機(jī)連接,而且仍處于監(jiān)聽(tīng)模式。
SOCKET WSAAccept(
SOCKET s,
struct sockaddr FAR *addr,
LPINT addrlen,
LPCONDITIONPROC lpfncondition,
DWORD dwCallBackData
對(duì)于客戶端相對(duì)要簡(jiǎn)單得多,主要由以下幾步:
1) 用socket或WSASocket創(chuàng)建一個(gè)套接字。
2) 解析服務(wù)器名(以基層協(xié)議為準(zhǔn))。
3) 用connect或WSAConnect初始化一個(gè)連接。
在connect過(guò)程常發(fā)生的錯(cuò)誤有:WSAECONNREFUSED (10061)連接的計(jì)算機(jī)沒(méi)有監(jiān)聽(tīng)指定端口的進(jìn)程;WSAETIMEDOUT (10060)這種情況一般發(fā)生在試圖連接的計(jì)算機(jī)不能用時(shí)(亦可能因?yàn)榈街鳈C(jī)之間的路由上出現(xiàn)硬件故障或主機(jī)目前不在網(wǎng)上)。
連接之后就是數(shù)據(jù)傳輸了,就是發(fā)送和接收了:
int send(
    SOCKET s,
    const char FAR * buf,
    int len,
    int flags)
返回發(fā)送的字節(jié)數(shù),如果出錯(cuò)常見(jiàn)的錯(cuò)誤是:WSAECONNABORTED (10053) 這一錯(cuò)誤一般發(fā)生在虛擬回路由于超時(shí)或協(xié)議有錯(cuò)而中斷的時(shí)候。遠(yuǎn)程主機(jī)上的應(yīng)用通過(guò)執(zhí)行強(qiáng)行關(guān)閉或意外中斷操作重新設(shè)置虛擬虛路時(shí),或遠(yuǎn)程主機(jī)重新啟動(dòng)時(shí),發(fā)生的則是WSAECONNRESET(10054)錯(cuò)誤。。最后一個(gè)常見(jiàn)錯(cuò)誤是WSAETIMEOUT(10060),它發(fā)生在連接由于網(wǎng)絡(luò)故障或遠(yuǎn)程連接系統(tǒng)異常死機(jī)而引起的連接中斷時(shí)。
int recv(
    SOCKET s,
    const char FAR * buf,
    int len,
    int flags)
無(wú)連接協(xié)議
    首先從接收端(類似于有連接方式中的服務(wù)端,但不是服務(wù)端)看,首先也是通過(guò)socket或WSAsocket創(chuàng)建套接字。再通過(guò)bind進(jìn)行綁定。下面跳過(guò)Listen和Accept步驟,直接等待接收就可以了。
接收函數(shù):
int recvfrom(
    SOCKET s,
    char FAR * buf,
    int len,
    int flags,
    struct SockAddr FAR *from,
    int FAR * fromlen
)
發(fā)送:建立SCOKET后調(diào)用sendto或WSASendTo
int sendto(
    SOCKET s,
    char FAR * buf,
    int len,
    int flags,
    struct SockAddr FAR * to,
    int FAR * tolen
)

posted @ 2008-03-18 11:16 隨意門(mén) 閱讀(614) | 評(píng)論 (0)編輯 收藏
僅列出標(biāo)題
共9頁(yè): 1 2 3 4 5 6 7 8 9 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产精品久久久久久久电影 | 伊人蜜桃色噜噜激情综合| 在线免费观看一区二区三区| 亚洲免费在线视频一区 二区| 欧美国产精品人人做人人爱| 欧美一区综合| 在线看不卡av| 亚洲人成网站777色婷婷| 老司机免费视频一区二区| 亚洲第一福利在线观看| 亚洲第一毛片| 欧美大片免费久久精品三p| 免播放器亚洲一区| 亚洲另类自拍| 亚洲网在线观看| 激情另类综合| 亚洲免费视频网站| 亚洲欧洲一区二区三区久久| 欧美电影在线观看| 欧美影院在线| 国产精品国产三级国产a| 亚洲欧美日韩国产中文在线| 久久久www免费人成黑人精品 | 国产精品国产福利国产秒拍| 欧美一二三视频| 欧美国产第一页| 久久久久高清| 国产精品女人网站| 9久re热视频在线精品| 在线电影院国产精品| 午夜一区在线| 欧美一区二区视频观看视频| 欧美午夜精品久久久久久人妖| 欧美+亚洲+精品+三区| 狠狠色狠狠色综合日日tαg| 亚洲欧美视频| 久久久国产91| 激情五月综合色婷婷一区二区| 午夜精品久久久99热福利| 在线综合欧美| 国产精品视频久久久| 亚洲午夜精品国产| 久久精品99国产精品酒店日本| 国产精品视频一| 欧美一区免费| 狠狠色狠狠色综合系列| 韩国av一区二区三区四区| 国产精品久久久久久超碰 | 国产综合视频| 亚洲神马久久| 久久精品麻豆| 亚洲精品乱码久久久久久| 欧美激情乱人伦| 一本一本久久| 久久影视三级福利片| 日韩亚洲国产欧美| 国产精品99一区二区| 欧美在线影院| 亚洲人成小说网站色在线| 亚洲视频一区二区| 伊人色综合久久天天| 欧美午夜激情视频| 老牛国产精品一区的观看方式| 久久久久亚洲综合| 亚洲一区三区电影在线观看| 红桃视频亚洲| 国产精品高清免费在线观看| 老司机一区二区| 欧美亚洲专区| 99精品免费视频| 亚洲第一区在线观看| 中日韩美女免费视频网址在线观看 | 美女诱惑一区| 一本一本久久a久久精品综合麻豆 一本一本久久a久久精品牛牛影视 | 国产欧美一区二区精品仙草咪| 亚洲一区免费在线观看| 久久久久综合网| 美女主播一区| 免费成人黄色| 久久亚洲春色中文字幕| 久久精品久久综合| 香蕉视频成人在线观看| 亚洲婷婷综合色高清在线| 亚洲精品亚洲人成人网| 伊甸园精品99久久久久久| 国产欧美日韩综合| 国产精品劲爆视频| 国产欧美日本一区视频| 国产亚洲精久久久久久| 国产一区二区福利| 亚洲靠逼com| 欧美 日韩 国产在线 | 亚洲一区不卡| 亚洲综合久久久久| 久久精品国产第一区二区三区| 久久精品国产精品亚洲| 免费观看亚洲视频大全| 欧美激情精品久久久久久蜜臀| 欧美精品亚洲| 国产欧美一级| 99re6热只有精品免费观看| 亚洲欧美成人网| 欧美国产日韩一区二区在线观看 | 欧美高清免费| 国产欧美日韩综合精品二区| 91久久精品视频| 久久av资源网站| 最新亚洲视频| 久久久久久一区二区| 欧美va亚洲va日韩∨a综合色| 亚洲麻豆视频| 欧美国产一区二区| 国产一在线精品一区在线观看| 亚洲精品久久久久久久久| 久久国产精品久久国产精品| 亚洲另类视频| 欧美激情视频一区二区三区在线播放| 国产精品―色哟哟| 香蕉成人久久| 亚洲综合另类| 国模吧视频一区| 欧美亚洲综合另类| 亚洲视频在线观看免费| 亚洲激情校园春色| 亚洲女同精品视频| 中国成人亚色综合网站| 欧美视频三区在线播放| 亚洲欧美福利一区二区| 亚洲一区二区在线免费观看视频 | 欧美一区二区三区免费视频| 国产精品99久久久久久久vr| 欧美午夜欧美| 午夜亚洲福利| 欧美专区在线播放| 一区二区三区在线视频免费观看 | 亚洲电影有码| 欧美日韩精品中文字幕| 亚洲欧美美女| 久久综合五月天婷婷伊人| 亚洲国产婷婷香蕉久久久久久| 免费在线观看精品| 欧美—级a级欧美特级ar全黄| 亚洲精品日韩激情在线电影| 亚洲精品国产精品国自产在线 | 米奇777在线欧美播放| 亚洲午夜av| 亚洲国产黄色片| 夜夜精品视频| 亚洲黄色尤物视频| 一二三区精品| 亚洲精华国产欧美| 亚洲欧美日韩精品| 亚洲人体偷拍| 亚洲欧美日韩直播| 在线一区二区三区四区| 久久中文欧美| 久久男人资源视频| 国产欧美 在线欧美| 亚洲欧洲日本专区| 在线播放日韩| 夜夜嗨av色一区二区不卡| 国产在线精品自拍| 亚洲欧美另类中文字幕| 日韩午夜免费| 欧美精品在线视频| 亚洲第一在线综合在线| 国产日本亚洲高清| 午夜视频精品| 久久在线视频在线| 亚洲国产日韩一区二区| 久久久久久久一区二区| 老司机精品久久| 亚洲片在线观看| 欧美丝袜一区二区| 亚洲欧洲av一区二区| 午夜精品久久久久久| 亚洲视频精品在线| 国产精品一区二区三区免费观看| 一本到12不卡视频在线dvd| 亚洲欧美精品在线| 黄色在线一区| 欧美四级在线| 亚洲视频视频在线| 欧美电影免费| 国产精品久久网站| 久久精品视频网| 在线视频欧美日韩精品| 久久九九精品99国产精品| 亚洲精品日韩在线观看| 国产日韩精品在线观看| 欧美xart系列在线观看| 亚洲欧美日韩第一区 | 亚洲精品中文字幕女同| 亚洲一区二区三区影院| 黄色国产精品| 国产日产欧美一区| 欧美日韩无遮挡| 欧美1区2区3区| 欧美在线综合| 久久国产免费看|