1. 程式人生 > >socket程式設計簡介

socket程式設計簡介

1.Socket的機制是什麼?

2.C/C++Socket需要什麼標頭檔案、庫檔案、DLL,它們可以由誰提供,安裝後一般處於系統的哪個資料夾內?

3.編寫Socket程式需要的程式設計基礎是什麼?

4.Socket庫內最重要的幾個函式和資料型別是什麼?

5.兩個最簡單的例子程式;

6.一個貼近應用的稍微複雜的Socket應用程式。

我將一一講述這些要點,並給出從簡到繁,從樸素到花哨的所有原始碼以及編譯連結的命令。

4Socket的機制是什麼?

我們可以簡單的把Socket理解為一個可以連通網路上不同計算機程式之間的管道,把一堆資料從管道的A端扔進去,則會從管道的B端(也許同時還可以從CD

EF……端冒出來)。管道的埠由兩個因素來唯一確認,即機器的IP地址和程式所使用的埠號。IP地址的含義所有人都知道,所謂埠號就是程式設計師指定的一個數字,許多著名的木馬程式成天在網路上掃描不同的埠號就是為了獲取一個可以連通的埠從而進行破壞。比較著名的埠號有http80埠和ftp21(我記錯了麼?)。當然,建議大家自己寫程式不要使用太小的埠號,它們一般被系統佔用了,也不要使用一些著名的埠,一般來說使用1000~5000之內的埠比較好。

Socket可以支援資料的傳送和接收,它會定義一種稱為套接字的變數,傳送資料時首先建立套接字,然後使用該套接字的sendto等方法對準某個IP/埠進行資料傳送;接收端也首先建立套接字,然後將該套接字繫結到一個

IP/埠上,所有發向此埠的資料會被該套接字的recv等函式讀出。如同讀出檔案中的資料一樣。

5所需的標頭檔案、庫檔案和DLL

對於目前使用最廣泛的Windows Socket2.0版本,所需的一些檔案如下(以安裝了VC6為例說明其物理位置):

l標頭檔案winsock2.h,通常處於C:"Program Files"Microsoft Visual Studio"VC98"INCLUDE;檢視該標頭檔案可知其中又包含了windows.hpshpack4.h標頭檔案,因此在windows中的一些常用API都可以使用;

l庫檔案Ws2_32.lib,通常處於C:"Program Files"Microsoft Visual Studio"VC98"Lib

lDLL檔案Ws2_32.dll,通常處於C:"WINDOWS"system32,這個是可以猜到的。

6編寫Socket程式需要的程式設計基礎

在開始編寫Socket程式之前,需要以下程式設計基礎:

lC++語法;

l一點點windows SDK的基礎,瞭解一些SDK的資料型別與API的呼叫方式;

l一點點編譯、連結和執行的技術;知道cllink的最常用用法即可。

7UDP

用最通俗的話講,所謂UDP,就是傳送出去就不管的一種網路協議。因此UDP程式設計的傳送端只管傳送就可以了,不用檢查網路連線狀態。下面用例子來說明怎樣編寫UDP,並會詳細解釋每個API和資料型別。

7.1 UDP廣播發送程式

下面是一個用UDP傳送廣播報文的例子。

#include <winsock2.h>

#include <iostream.h>

void main()

{

    SOCKET sock;   //socket套接字

    char szMsg[] = "this is a UDP test package";//被髮送的欄位

    //1.啟動SOCKET庫,版本為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庫的版本是否為2.0

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

    {

       WSACleanup( );

       return;

    }

    //2.建立socket

    sock = socket(

       AF_INET,           //internetwork: UDP, TCP, etc

       SOCK_DGRAM,        //SOCK_DGRAM說明是UDP型別

       0                  //protocol

       );

    if (INVALID_SOCKET == sock ) {

       cout<<"Socket 建立失敗,Exit!";

       return;

    }

    //3.設定該套接字為廣播型別,

    bool opt = true;

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

    //4.設定發往的地址

    sockaddr_in addrto;            //發往的地址 

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

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

    addrto.sin_addr.s_addr = INADDR_BROADCAST; //設定ip為廣播地址

    addrto.sin_port = htons(7861);             //埠號為7861

    int nlen=sizeof(addrto);

    unsigned int uIndex = 1;

    while(true)

    {

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

       //向廣播地址傳送訊息

       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)) //關閉套接字

    {

       WSAGetLastError();

       return;

    }

    if (!WSACleanup())       //關閉Socket

    {

       WSAGetLastError();

       return;

    }  

}

編譯命令:

CL /c UDP_Send_Broadcast.cpp

連結命令(注意如果找不到該庫,則要在後面的/LIBPATH引數後加上庫的路徑):

link UDP_Send_Broadcast.obj ws2_32.lib

執行命令:

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

下面一一解釋程式碼中出現的資料型別與API函式。有耐心的可以仔細看看,沒耐心的依葫蘆畫瓢也可以寫程式了。

7.2 SOCKET型別

SOCKETsocket套接字型別,在WINSOCK2.H中有如下定義:

typedef unsigned int    u_int;

typedef u_int           SOCKET;

可知套接字實際上就是一個無符號整型,它將被Socket環境管理和使用。套接字將被建立、設定、用來發送和接收資料,最後會被關閉。

7.3 WORD型別、MAKEWORDLOBYTEHIBYTE巨集

WORD型別是一個16位的無符號整型,在WTYPES.H中被定義為:

typedef unsigned short WORD;

其目的是提供兩個位元組的儲存,在Socket中這兩個位元組可以表示主版本號和副版本號。使用MAKEWORD巨集可以給一個WORD型別賦值。例如要表示主版本號2,副版本號0,可以使用以下程式碼:

WORD wVersionRequested;

wVersionRequested = MAKEWORD( 2, 0 ); 

注意低位記憶體儲存主版本號2,高位記憶體儲存副版本號0,其值為0x0002。使用巨集LOBYTE可以讀取WORD的低位位元組,HIBYTE可以讀取高位位元組。

7.4 WSADATA型別和LPWSADATA型別

WSADATA型別是一個結構,描述了Socket庫的一些相關資訊,其結構定義如下:

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欄位,儲存了Socket的版本型別。LPWSADATAWSADATA的指標型別。它們不用程式設計師手動填寫,而是通過Socket的初始化函式WSAStartup讀取出來。

7.5 WSAStartup函式

WSAStartup函式被用來初始化Socket環境,它的定義如下:

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

其返回值為整型,呼叫方式為PASCAL(即標準型別,PASCAL等於__stdcall),引數有兩個,第一個引數為WORD型別,指明瞭Socket的版本號,第二個引數為WSADATA型別的指標。

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

7.6 WSACleanup函式

這是Socket環境的退出函式。返回值為0表示成功,SOCKET_ERROR表示失敗。

7.7 socket函式

socket的建立函式,其定義為:

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

第一個引數為int af,代表網路地址族,目前只有一種取值是有效的,即AF_INET,代表internet地址族;

第二個引數為int type,代表網路協議型別,SOCK_DGRAM代表UDP協議,SOCK_STREAM代表TCP協議;

第三個引數為int protocol,指定網路地址族的特殊協議,目前無用,賦值0即可。

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

7.8 setsockopt函式

這個函式用來設定Socket的屬性,若不能正確設定socket屬性,則資料的傳送和接收會失敗。定義如下:

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

                           const char FAR * optval, int optlen);

其返回值為int型別,0代表成功,SOCKET_ERROR代表有錯誤發生。

第一個引數SOCKET s,代表要設定的套接字;

第二個引數int level,代表要設定的屬性所處的層次,層次包含以下取值:SOL_SOCKET代表套接字層次;IPPROTO_TCP代表TCP協議層次,IPPROTO_IP代表IP協議層次(後面兩個我都沒有用過);

第三個引數int optname,代表設定引數的名稱,SO_BROADCAST代表允許傳送廣播資料的屬性,其它屬性可參考MSDN

第四個引數const char FAR * optval,代表指向儲存引數數值的指標,注意這裡可能要使用reinterpret_cast型別轉換;

第五個引數int optlen,代表儲存引數數值變數的長度。

7.9 sockaddr_inin_addr型別,inet_addrinet_ntoa函式

sockaddr_in定義了socket傳送和接收資料包的地址,定義:

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的含義,很顯然它是一個儲存ip地址的聯合體(忘記union含義的請看c++書),有三種表達方式:

第一種用四個位元組來表示IP地址的四個數字;

第二種用兩個雙位元組來表示IP地址;

第三種用一個長整型來表示IP地址。

in_addr賦值的一種最簡單方法是使用inet_addr函式,它可以把一個代表IP地址的字串賦值轉換為in_addr型別,如

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

本例子中由於是廣播地址,所以沒有使用這個函式。其反函式是inet_ntoa,可以把一個in_addr型別轉換為一個字串。

sockaddr_in的含義比in_addr的含義要廣泛,其各個欄位的含義和取值如下:

第一個欄位short   sin_family,代表網路地址族,如前所述,只能取值AF_INET

第二個欄位u_short sin_port,代表IP地址埠,由程式設計師指定;

第三個欄位struct in_addr sin_addr,代表IP地址;

第四個欄位char    sin_zero[8],很搞笑,是為了保證sockaddr_inSOCKADDR型別的長度相等而填充進來的欄位。

以下代表指明瞭廣播地址,埠號為7861的一個地址:

    sockaddr_in addrto;            //發往的地址 

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

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

    addrto.sin_addr.s_addr = INADDR_BROADCAST; //設定ip為廣播地址

    addrto.sin_port = htons(7861);             //埠號為7861

7.10sockaddr型別

sockaddr型別是用來表示Socket地址的型別,同上面的sockaddr_in型別相比,sockaddr的適用範圍更廣,因為sockaddr_in只適用於TCP/IP地址。Sockaddr的定義如下:

struct sockaddr {

 u_short    sa_family;

 char       sa_data[14];

};  

可知sockaddr16個位元組,而sockaddr_in也有16個位元組,所以sockaddr_in是可以強制型別轉換為sockaddr的。事實上也往往使用這種方法。

7.11Sleep函式

執行緒掛起函式,表示執行緒掛起一段時間。Sleep(1000)表示掛起一秒。定義於WINBASE.H標頭檔案中。WINBASE.H又被包含於WINDOWS.H中,然後WINDOWS.HWINSOCK2.H包含。所以在本例中使用Sleep函式不需要包含其它標頭檔案。

7.12sendto函式

Socket中有兩套傳送和接收函式,一是sendtorecvfrom;二是sendrecv。前一套在函式引數中要指明地址;而後一套需要先將套接字和一個地址繫結,然後直接傳送和接收,不需繫結地址。sendto的定義如下:

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

第一個引數就是套接字;

第二個引數是要傳送的資料指標;

第三個引數是要傳送的資料長度(位元組數);

第四個引數是傳送方式的標識,如果不需要特殊要求則可以設定為0,其它值請參考MSDN

第五個引數是目標地址,注意這裡使用的是sockaddr的指標;

第六個引數是地址的長度;

返回值為整型,如果成功,則返回傳送的位元組數,失敗則返回SOCKET_ERROR

7.13WSAGetLastError函式

該函式用來在Socket相關API失敗後讀取錯誤碼,根據這些錯誤碼可以對照查出錯誤原因。

7.14closesocket

關閉套接字,其引數為SOCKET型別。成功返回0,失敗返回SOCKET_ERROR

7.15小結

總結以上內容,寫一個UDP傳送程式的步驟如下:

1.WSAStartup函式初始化Socket環境;

2.socket函式建立一個套接字;

3.setsockopt函式設定套接字的屬性,例如設定為廣播型別;很多時候該步驟可以省略;

4.建立一個sockaddr_in,並指定其IP地址和埠號;

5.sendto函式向指定地址傳送資料,這裡的目標地址就是廣播地址;注意這裡不需要繫結,即使綁定了,其地址也會被sendto中的引數覆蓋;若使用send函式則會出錯,因為send是面向連線的,而UDP是非連線的,只能使用sendto傳送資料;

6.closesocket函式關閉套接字;

7.WSACleanup函式關閉Socket環境。

那麼,與之類似,一個UDP接收程式的步驟如下,注意接收方一定要bind套接字:

1.WSAStartup函式初始化Socket環境;

2.socket函式建立一個套接字;

3.setsockopt函式設定套接字的屬性,例如設定為廣播型別;

4.建立一個sockaddr_in,並指定其IP地址和埠號;

5.bind函式將套接字與接收的地址繫結起來,然後呼叫recvfrom函式或者recv接收資料;注意這裡一定要繫結,因為接收報文的套接字必須在網路上有一個繫結的名稱才能保證正確接收資料;

6.closesocket函式關閉套接字;

7.WSACleanup函式關閉Socket環境。

廣播接收程式見源程式程式碼UDP_Recv_Broadcast.cpp。編譯、連結、執行與UDP_Send_Broadcast類似。

7.16UDP點對點發送接收程式

廣播發送和接收使用並不廣泛,一般來說指定傳送和接收的IP比較常用。點對點方式的UDP傳送和接收與上面的例子非常類似,不同的就是需要指定一個具體的IP地址。並且不需要呼叫setsockopt設定socket的廣播屬性。

其具體原始碼見UDP_Send_P2P.cppUDP_Recv_P2P.cpp

注意在使用這兩個程式時要設為自己所需的IP

8TCP

TCPUDP最大的不同之處在於TCP是一個面向連線的協議,在進行資料收發之前TCP必須進行連線,並且在收發的時候必須保持該連線。

傳送方的步驟如下(省略了Socket環境的初始化、關閉等內容):

1.socket函式建立一個套接字sock

2.bindsock繫結到本地地址;

3.listen偵聽sock套接字;

4.accept函式接收客戶方的連線,返回客戶方套接字clientSocket

5.在客戶方套接字clientSocket上使用send傳送資料;

6.closesocket函式關閉套接字sockclientSocket

而接收方的步驟如下:

1.socket函式建立一個套接字sock

2.建立一個指向服務方的遠端地址;

3.connectsock連線到服務方,使用遠端地址;

4.在套接字上使用recv接收資料;

5.closesocket函式關閉套接字sock

值得注意的是,在服務方有兩個地址,一個是本地地址myaddr,另一個是目標地址addrto。本地地址myaddr用來和本地套接字sock繫結,目標地址被sock用來accept客戶方套接字clientSocket。這樣sockclientSocket連線成功,這兩個地址也連線上了。在服務方使用clientSocket傳送資料,則會從本地地址傳送到目標地址。

在客戶方只有一個地址,即來源地址addrfrom。這個地址被用來connect遠端的服務方套接字,connect成功則本地套接字與遠端的來源地址連線了,因此可以使用該套接字接收遠端資料。其實這時客戶方套接字已經被隱性的綁定了本地地址,所以不需要顯式呼叫bind函式,即使呼叫也不會影像結果。

具體原始碼見TCP_Send.cppTCP_Recv.cpp注意將原始碼中的IP地址修改為符合自己需要的IP。為了減少程式碼複雜性,沒有使用讀取本機IP的程式碼,後續例子程式中含有此功能程式碼。

8.1 bind函式

bind函式用來將一個套接字繫結到一個IP地址。一般只在服務方(即資料傳送方)呼叫,很多函式會隱式的呼叫bind函式。

8.2 listen函式

從服務方監聽客戶方的連線。同一個套接字可以多次監聽。

8.3 connectaccept函式

connect是客戶方連線服務方的函式,而accept是服務方同意客戶方連線的函式。這兩個配套函式分別在各自的程式中被成功呼叫後就可以收發資料了。

8.4 sendrecv函式

sendrecv是用來發送和接收資料的兩個重要函式。send只能在已經連線的狀態下使用,而recv可以面向連線和非連線的狀態下使用。

send的定義如下:

int WSAAPI send(

    SOCKET s,

    const char FAR * buf,

    int len,

    int flags

    );

其引數的含義和sendto中的前四個引數一樣。而recv的定義如下:

int WSAAPI recv(

    SOCKET s,

    char FAR * buf,

    int len,

    int flags

    );

其引數含義與send中的引數含義一樣。

9一個區域網聊天工具的編寫

掌握了以上關於socket的基本用法,編寫一個區域網聊天程式也就變得非常簡單,如同設計一個普通的對話方塊程式一樣。

9.1 功能設計

功能設計如下:

1.要能夠指定聊天物件的IP和埠(埠可以內部確定);

2.要能夠傳送訊息給指定聊天物件;

3.要能夠接收聊天物件的訊息;

4.接收訊息時要播放聲音;

5.接收訊息時如果當前對話方塊不是最前端,要閃動圖示;

6.要有托盤圖示,可以將對話方塊收入托盤;

9.2 功能實現

將內部埠設為3456,提供一個IP地址控制元件來設定聊天物件的IP。該控制元件必須能夠讀取IP地址並賦值給內部變數。將地址轉換為in_addr型別。

傳送訊息需要使用一個套接字。

接收訊息也需要使用一個套接字,由於傳送訊息也使用了一個套接字,為了在同一個程序中同時傳送和接收訊息,需要使用多執行緒技術,將傳送訊息的執行緒設為主執行緒;而接收訊息的執行緒設為子執行緒,子執行緒只負責接收UDP訊息,在收到訊息後顯示到主介面中。

接收訊息時播放聲音這個功能在子執行緒中完成,使用sndPlaySound函式,並提供一個wav檔案即可。

閃動圖示這個最白痴的功能需要使用一個Timer,在主對話方塊類中新增一個OnTimer函式,定時檢查當前視窗狀態變數是否為假,若為假就每次設定另一個圖示。若當前視窗顯示到最頂端,則設定為預設圖示。

托盤圖示功能用網上下載的CtrayIcon類輕鬆搞定。需要提供一個自定義訊息,一個彈出選單資源。

9.3 所需資源

標頭檔案:winsock2.hMmsystem.h

庫檔案:ws2_32.libwinmm.lib

dllWs2_32.dllwinmm.dll

wav檔案:recv.wav

圖示:一個主程式圖示IDI_MAIN、四個變化圖示IDI_ICON1~4;

選單:一個給托盤用的彈出選單IDR_TRAYICON;

說明,Mmsystem.hwinmm.libwinmm.dll是為了那個播放聲音的功能。

9.4 托盤功能

托盤屬於介面功能,是變更很少的需求,因此首先完成。

1.引入TRAYICON.HTRAYICON.cpp兩個類;

2.CLANTalkDlg類中加入一個CTrayIconm_trayIcon;屬性;

3.CLANTalkDlg的建構函式中初始化m_trayIconm_trayIcon(IDR_TRAYICON)

4.新增一個自定義訊息WM_MY_TRAY_NOTIFICATION,即在三個地方新增訊息定義、訊息響應函式、訊息對映;

5.InitDialog方法中呼叫托盤初始化的兩個函式      m_trayIcon.SetNotificationWnd(this, WM_MY_TRAY_NOTIFICATION);    m_trayIcon.SetIcon(IDI_MAIN);

6.重寫OnClose方法,新增彈出選單的OnAppSuspendOnAppOpen以及OnAppAbout方法;

7.重寫對話方塊的OnCancel方法。

9.5 動態圖示

動態圖示也是介面相關功能,首先完成。

1.新增四個HICON變數m_hIcon1,m_hIcon2,m_hIcon3,m_hIcon4

2.在建構函式中初始化這四個變數m_hIcon1 = AfxGetApp()->LoadIcon(IDI_ICON1);

3.InitDialog中設定呼叫SetTimer(1,300,NULL);設定一個timerid1,間隔為300微秒;

4.新增一個布林屬性m_bDynamicIcon,指示目前是否需要動態圖示,並給出一個設定函式SetDynamicIcon

5.新增一個OnTimer函式,讓每次timer呼叫時根據m_bDynamicIcon的值修改圖示;

兩個地方是用來設定動態圖示的,一個是當程式收到訊息並且程式不在桌面頂端時,這時設定為動態圖示,在後面的訊息接收執行緒中處理;二是當程式顯示到桌面頂端時,設定為非動態;

過載OnActivate方法可以完成第二個時刻的要求。當視窗狀態為WA_ACTIVE或者WA_CLICKACTIVESetDynamicIcon(false),否則設定SetDynamicIcon(true);

9.6 傳送UDP報文功能

傳送UDP報文只需在主執行緒中完成,需要以下步驟:

1.初始化Socket環境,這可以在CLANTalkAppInitInstance中完成,同理關閉Socket環境在ExitInstance中完成;我們可以使用前面的方法,也可以直接呼叫MFC中的AfxSocketInit函式,這個函式可以確保在程式結束時自動關閉Socket環境;

2.建立socket,考慮到報錯資訊需要彈出對話方塊,因此不在CLANTalkDlg的建構函式中建立,而是在InitDialog中構建;傳送報文的socketm_sendSock

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

4.讀取文字框中的文字,用sendto傳送到物件地址;

5.清空文字框,在記錄框中新增聊天記錄。

這時可以使用前面的UDP簡單接收程式來輔助測試,因為此時還未完成報文接收功能。

9.7 接收UDP報文功能

接收UDP報文要考慮幾個問題,第一個是要建立一個子執行緒,在子執行緒中接收報文;第二是接收報文和傳送報文要有互斥機制,以免衝突;第三是接收到報文要播放聲音;第四是接收報文且當前視窗不在桌面頂端要呼叫動態圖示功能。

按照以上需求設計步驟如下:

1.建立接收套接字m_recvSock

2.利用gethostnamegethostbyname等函式獲取本機IP,並將套接字bind到該地址;

3.新增一個CwinThread* m_pRecvThread屬性,並在InitDialog中呼叫AfxBeginThread建立子執行緒;

4.編寫子執行緒執行函式void RecvProcess(LPVOID pParam),這時一個全域性函式,為了方便呼叫CLANTalkDlg類中的各種變數與方法,將CLANTalkDlg類的指標作為引數傳入子執行緒函式,並將RecvProcess設定為CLANTalkDlg類的友元。

5.子執行緒函式中完成以下功能:利用recv接收報文;儲存聊天記錄;判斷當前視窗是否在前臺,並修改動態圖示屬性;播放聲音。

6.用來記錄聊天資訊的ClistBoxSort屬性要去掉,否則記錄會按內容排序,很不好看。在RC編輯器中去掉這個屬性即可。

7.最後要注意,在主執行緒退出時要保證子執行緒退出,但此時子執行緒還阻塞在recv方法上,因此主執行緒向自己傳送一條訊息消除阻塞,同時改變子執行緒退出標誌保證子執行緒可以退出。

9.8 設定聊天物件IP

點選“確認物件”按鈕時,檢測IP地址控制元件,如果IP地址有效,則將IP地址讀入內部屬性。這個IP地址作為傳送資訊的目標地址。

這個設定只能設定傳送訊息的物件,所有人都可以向本機發送資訊,只要他的埠是正確的。

9.9 編譯連結和執行

下載壓縮包後可以開啟VC工程編譯連結,若直接執行則可以點選LANTalkExeFile目錄中的可執行檔案,這個目標包含了執行所需要的所有dll和資原始檔。

當然,如果需要可以用InstallShield做一個安裝程式,不過看來是沒有必要的。

9.10小結

這個聊天程式很簡單,但是基本上具有了一個框架,可以有最簡單的聊天功能。要在此基礎上進行擴充套件幾乎已經沒有什麼技術問題了。

10使用好的Socket包可以簡化開發過程

本文中所有的技術儘量採用最原始的方式來使用。例如多執行緒使用的是AfxBeginThread,套接字使用了最原始的套接字,並在很多地方直接使用了SDK函式,而儘量避免了MFC等程式碼框架,這是為了方便他人掌握技術的最基本內涵。

其實在具體的程式設計中,當然是怎麼方便怎麼來,Socket和多執行緒以及介面等功能都有大量方便可用的程式碼庫,複用這些程式碼庫會比自己動手寫方便很多。但是,掌握了基本原理再使用這些庫,事半功倍!