## 用C++ Builder创建基于Internet的点对点Chat

### 北京市白石桥路
周新栋

---- 创建基于Internet的应用程序，你也许会想到复杂的WinSock编程。不过，C++ Builder3提供了新的WebBroker的Internet套件，其中的TClientSocket和TServerSocket组件封装了Windows的有关API，大大简化了WinSock编程。要通过Internet传输数据，至少需要一对Socket，一个Socket在客户端，另一个Socket在服务器端。其实TClientSocket、TServerSocket组件并不是Socket对象，其属性Socket将返回各自的Socket对象。TClientSocket用来处理客户端到服务器端之间的socket连接，TServerSocket用来处理由客户端发来的socket连接，一旦客户端和服务器端都接通了socket，客户端和服务器端就可以相互通信了。

---- 建立一新项目，创建应用程序的用户界面：

---- 1.将组件页切换到Internet页，放一个TServerSocket组件和一个TClientSocket组件到窗体上，这样应用程序既可以是TCP/IP服务器，也可以是TCP/IP客户。将Port属性都设为同一个值(如1000)，确定Socket之间的连接类型为NonBlocking(非阻塞方式)。

---- 2.放两个TMemo组件到窗体上，用来分别显示双方的谈话内容，将Memo2的ReadOnly属性设为True。

---- 3.在窗体的顶部放上一个Panel组件，在其上放三个按钮：监听(btnlisten)、连接(btnconnect)、断开(btndisconnect)，用来启动相应的操作。

---- 4.在窗体底部放一个StatusBar组件，将其SimplePanel属性设为True,在相应的事件处理程序中改变状态条信息，让用户随时了解连接状态。

---- 打开头文件，在窗体类的Private段添加两个私有成员: bool IsServer;String Server。双方通信时需同时运行Chat程序，IsServer用来确定哪个Chat程序处于服务器端，Server用来存放服务器的主机名。建立窗体类的构造器如下：

    __fastcall TForm1::TForm1(TComponent* Owner)

        : TForm(Owner)

    {

        IsServer=false;

        Server="localhost";

    }

---- 这里Server被缺省设为localhost，这样程序可以在没有连入Internet的单机上进行调试。在Windows子目录下你可以找到hosts.sam文件中，在该文件中已经将本机IP地址127.0.0.1定义了主机名：localhost。

    void __fastcall TForm1::FormCreate(TObject *Sender)

    {

        btndisconnect- >Enabled=false;

    }

---- 程序运行后，如果用户按下＂监听＂钮，则将该程序设为服务器端，这时应将TServerSocket的Active属性设为True，使服务器自动进入监听状态。

    void __fastcall TForm1::btnlistenClick(TObject *Sender)

    {

        ClientSocket1- >Active=false;

        ServerSocket1- >Active=true;

        StatusBar1- >SimpleText="正在监听...";

        btnlisten- >Enabled=false;

        btnconnect- >Enabled=false;

    }

---- 当用户按下＂连接＂钮后，程序会弹出一个询问框，要求用户输入要连接的服务器的主机名，然后建立连接。

    void __fastcall TForm1::btnconnectClick(TObject *Sender)

    {

        if(InputQuery("连接到服务器","输入服务器地址:",Server)){

            if(Server.Length() >0){

                ClientSocket1- >Host=Server;

                ClientSocket1- >Active=true;

                btnlisten- >Enabled=false;

                btnconnect- >Enabled=false;

                btndisconnect- >Enabled=true;

            }

        }

    }

---- 当用户提出连接请求后，客户端会触发OnCreate事件，程序先在状态条中显示连接信息，然后将显示对方谈话内容的Memo2清空，准备开始交谈。

    void __fastcall TForm1::ClientSocket1Connect(TObject *Sender,

          TCustomWinSocket *Socket)

    {

        StatusBar1- >SimpleText="连接到:"+Server;

        Memo2- >Lines- >Clear();

    }

---- 在服务器接受了客户的请求后会触发OnAccept事件，在这个事件处理程序中将标志服务器端的变量IsServer设为True，并准备开始交谈。

    void __fastcall TForm1::ServerSocket1Accept(

    TObject *Sender,

          TCustomWinSocket *Socket)

    {

        Memo2- >Lines- >Clear();

        IsServer=true;

        StatusBar1- >SimpleText="连接到:"

    +Socket- >RemoteAddress;

    }

---- 在建立连接后，双方就可以在Memo1中输入谈话内容开始进行交谈了，按下Enter键后，将所在行的文本发送出去。服务器端的Socket的Connections属性返回一个数组，该数组由服务器当前活动的连接组成。

    void __fastcall TForm1::Memo1KeyDown(

    TObject *Sender, WORD &Key,

          TShiftState Shift)

    {

        if(Key==VK_RETURN){

            if(IsServer)

                ServerSocket1- >Socket- >Connections[0]- >SendText(

    Memo1- >Lines- >Strings[Memo1- >Lines- >Count-1]);

            else

                ClientSocket1- >Socket- >SendText(

    Memo1- >Lines- >Strings[Memo1- >Lines- >Count-1]);

        }

    }

---- 在本例中我们采用非阻塞传输方式，当其中的一方进行写操作时，另一方会触发OnRead事件(客户端)或OnClientRead事件(服务器端)，这两个事件的处理程序只是将接收到的内容添加到Memo2的后面。

        Memo2- >Lines- >Add(Socket- >ReceiveText());

---- 如果在用户建立连接后单击＂断开＂钮，将断开客户端与服务器的连接，服务器端将触发OnClientDisconnect事件，而客户端则会触发OnDisconnect事件，这时服务器端应回到监听状态，等待用户的连接；而客户端将返回到连接前的状态，等待用户再次建立连接，如果有不止一个服务器的话，可以选择连接到其他的服务器上。

    void __fastcall TForm1::btndisconnectClick(

    TObject *Sender)

    {

        ClientSocket1- >Close();

    }

    void __fastcall TForm1::ServerSocket1ClientDisconnect(

    TObject *Sender,

          TCustomWinSocket *Socket)

    {

        StatusBar1- >SimpleText="正在监听...";

    }

    void __fastcall TForm1::ClientSocket1Disconnect(

    TObject *Sender, TCustomWinSocket *Socket)

    {

        btnlisten- >Enabled=true;

        btnconnect- >Enabled=true;

        btndisconnect- >Enabled=false;

        StatusBar1- >SimpleText="";

    }

---- 此外在客户端还应该增加错误捕获机制，当用户输入无效的服务器名或服务器端没有处于监听状态时能够及时给用户反馈信息。

    void __fastcall TForm1::ClientSocke

    t1Error(TObject *Sender,

          TCustomWinSocket *Socket,

    TErrorEvent ErrorEvent, int &ErrorCode)

    {

    StatusBar1- >SimpleText="无法连接到:

    "+Socket- >RemoteHost;

        ErrorCode=0;

    }

---- 以上代码在C++ Builder3 C/S版中编译、运行通过。