TServerSocket和TClientSocket的使用 抱雪 在网络编程中，WinSocket API编程是最基本，也是最麻烦的地方(说句不怕影响形象的话，我对此就是一知半解)。但是，如果你是使用C++Builder作为编程平台，你就偷着乐吧，有了BCB，菜鸟变高手！:-) 在BCB中，TServerSocket和TClientSocket涵盖了基本的WinSocket编程，其中TServerSocket作为服务器方使用，TClientSocket作为客户端使用，这两个组件本身并不提供Socket连接，但是他们都有一个Socket属性，这个属性才提供了Socket连接。下面就先向大家介绍一下这两个组件常用的方法属性，然后在通过一个例子来看看这两个组件的使用。 1)TServerSocket
名称 | 类型 | 说明
Socket | TServerWinSocket | 最重要的属性，提供Socket连接，事实上发送/接收数据都要靠这个属性，下面还有详细的介绍。
Port | int | 要监听的端口，如果在Service属性中指定了服务类型，此属性将被忽略。
Service | AnsiString | 提供的服务，如HTTP、FTP等，如果在这里指定了服务类型，Port将被忽略，因为各种服务都有特定的端口，如FTP:21、HTTP:80
ServerType | TServerType | 设置与客户连接的方式，取值为两个枚举常量stNonBlocking和 stThreadBlocking，stNonBlocking表示用非阻塞方式连接每一个客户，每个连接都在一个单独的线程中处理。并用OnClientRead()和OnClientWrite()通知服务器端的Socker进行读写。 stThreadBlocking表示以阻塞方式连接客户，即以主动查询的方式可客户连接。
Active | bool | 激活服务，相当于调用Open()方法。
OnAccept | 事件 | 当有客户请求连接时触发
OnClientRead | 事件 | 通知服务器去读取有关信息。OnClientWrite与此类似。
2)TClientSocket
名称 | 类型 | 说明
Socket | TClientWinSocket | 同TServerSocket
Active | bool | 同TServerSocket
Address | AnsiString | 服务器的IP地址，如202.98.35.14
ClientType | TClientType | 与服务器连接方式，取值为两个枚举常量ctNonBlocking，tBlocking。ctNonBlocking表示非阻塞方式，ctBlocking表示阻塞方式，详见上例。
Host | AnsiString | 要连接的主机名，如www.cpcw.com
Port | int | 同TServerSocket
Service | AnsiString | 同TServerSocket
OnConnect | 事件 | 当连接时发生，OnConnecting、OnDisConnect与此类似
OnRead | 事件 | 通知客户机去读取有关信息。OnWrite与此类似。
TServerSocket和TClientSocket只提供基本的服务器/客户机的连接，真正提供数据传输的是它们都有的属性Socket，它的类型分别是TServerWinSocket和TClientWinSocket，而TServerWinSocket和TClientWinSocket的父类都是TCustomWinSocket，下面我们就来看看TServerWinSocket和TClientWinSocket常用的属性和方法。 共同的属性方法（来源于TCustomWinSocket）
名称 | 类型 | 说明
Connected | bool | 检查是否连接成功
LocalAddress | AnsiString | 本地IP地址，与此类似LocalHost：本机域名，LocalPort：本机端口
RemoteAddress | AnsiString | 另一端的IP地址，与此类似RemoteHost：另一端域名，RemotePort：另一端端口
SocketHandle | int | 只读，返回Socket对象的Windows句柄，如果你要调用WinSocket API函数，将用到此句柄。
Handle | HWND | Socket能够接受到的异步事件都是以Windows消息的形式发送给此句柄的。
Close() | 方法 | 作为服务器，关闭所有连接；作为客户机，关闭自己与服务器的连接
SendText(AnsiString) | 方法 | 发送一个字符串，
SendBuf(void* buff,int count) | 发送缓冲区buff中的count个字节，返回实际发送的字节数
SendStream(TStream* AStream) | 发送一个流到Socket中。
ReceiveText() | 从Socket中读取并返回一个字符串。
ReceiveLength() | 从Socket读取数据需多少字节的缓冲区。
ReceiveBuf(void* buff,int count) | 从Socket中读取count字节的数据到buff。
TClientWinSocket TClientWinSocket只增加了一个ClientType属性，用于决定与服务器的连接类型(参见 TClientSocket->ClientType)。 TServerWinSocket
名称 | 类型 | 说明
ServerType |  | 服务类型，参见TServerSocket->ServerType。
ActiveConnection | int | 只读，返回当前活动的连接数。
Connection | TCustomWinSocket | 数组，索引n表示第n+1个连接，如Connection[0]表示第一个连接。
有了这些知识，我们就可以完成一些基本的WinSocket编程了，下面就结合一个简单的闲聊程序来看看具体的应用。 首先在窗体上放置以下VCL组件，并修改相应属性：
类型 | Name属性 | Caption/Text | 说明
TCheckBox | ckListen | 监听 | 当选取时，本程序作为服务器
TCheckBox | ckConnect | 连接 | 当选取时，本程序作为客户机
TEdit | edName | 无名氏 | 闲聊时所用的名字。
TBitBtn | bbtSave | &S保存 | 单击时保存谈话内容
TBitBtn | bbtClose | &C关闭 | 单击时关闭此窗口(设置Kind=bkClose)
TEdit | edTalk |  | 在此输入谈话内容
TMemo | mmTalk |  | 在此显示谈话内容
TServerSocket | ServerSocket1 |  | 作服务器时使用(设置Port=2222)
TClientSocket | ClientSocket1 |  | 作为客户时使用(设置Port=2222)
TSaveDialog | sdTalk |  | 保存文件时的选项(设置DefaultExt="*.txt",Filter=文本文件(*.TXT)|*.txt|所有文件(*.*)|*.*)。
TStatusBar | StatusBar1 |  | 用于显示一些提示信息，只要在属性"Pannels"中加一栏即可
程序作为服务器的设置： 当单击"监听"时，如果没有监听则开始监听，在提示栏中显示"监听"，并把"连接"这个复选框无效。如果已经监听，则取消监听，并使"连接"这个复选框有效。所以，在ckListen的OnClick事件中加入以下代码： if(ServerSocket1->Active) { ServerSocket1->Active=false; ckListen->Checked=false; StatusBar1->Panels->Items[0]->Text=""; } else { ServerSocket1->Active=true; ckListen->Checked=true; ClientSocket1->Active=false; StatusBar1->Panels->Items[0]->Text="监听..." ; } ckConnect->Enabled=!(ckListen->Checked); 当有客户加入时，向所有的客户发出通知：并在自已的mmTalk加入此消息：所以在ServerSocket1的OnAccept事件中加入如下代码： int i; AnsiString str1="服务器消息："+Socket->RemoteHost+"加入"; for(i=0;i<ServerSocket1->Socket->ActiveConnections;i++) ServerSocket1->Socket->Connections[i]->SendText("服务器消息："+Socket->RemoteHost+"加入"); StatusBar1->Panels->Items[0]->Text=str1; mmTalk->Lines->Add(str1); 当客户机通知服务器读信息时，首先读出字符串，然后把读到的字符串发送到每一台连接的客户，并在自已的mmTalk中加入客户发送来的字符串。所以，在TServerSocket的OnClientRead事件中加入以下代码： AnsiString str1=Socket->ReceiveText(); mmTalk->Lines->Add(str1); int i; for(i=0;i<ServerSocket1->Socket->ActiveConnections;i++) ServerSocket1->Socket->Connections[i]->SendText(str1); 程序作为客户机的设置： 当单击"连接"时，如果还未连接，则询问要连接的主机，然后连接之，屏蔽"监听"；如果已经连接，则断开连接。"监听"使能。所以，在ckConnect的OnClick事件中加入以下代码： if(ClientSocket1->Active) { ClientSocket1->Active=false; ckConnect->Checked =false; } else { AnsiString Server="localhost"; if(InputQuery("连接","请输入要连接的主机地址：",Server)) { ClientSocket1->Host=Server; ClientSocket1->Active=true; ckConnect->Checked =true; } } ckListen->Enabled= !(ckConnect->Checked); 当连接服务器成功时，在状态栏中显示此信息，所以，在ClientSocket1的ClientSocket1Connect中加入： StatusBar1->Panels->Items[0]->Text ="连接到主机："+Socket->RemoteHost; 当服务器发送字符串来时，把它加入mmTalk中，但如果本字符串就是自已发送的(因为服务器会把收到的消息发给每一客户)，为条信息就是重复的，所以，要比较mmTalk中最后两条信息是否相同，如果相同，则删除重复信息。代码如下： mmTalk->Lines->Add(Socket->ReceiveText()); int i=mmTalk->Lines->Count-1; if(mmTalk->Lines->Strings[i]==mmTalk->Lines->Strings[i-1]) mmTalk->Lines->Delete(i); 公用部分 当在edTalk输入交谈内容，按回车键表示输入完成，此时把交谈内容发送出去并清除edTalk的内容。在发送信息时，要看本程序是作为服务器还是客户机，如果是服务器则把信息发送到每一个客户；如果是作为客户则把信息发送到服务器。代码如下： if(Key==13) { mmTalk->Lines->Add(edName->Text+"："+edTalk->Text); if(ckListen->Enabled&&ckConnect->Enabled==false) //"监听"有效，"连接"无效。表示是服务器 { int i; for(i=0;i<ServerSocket1->Socket->ActiveConnections;i++) ServerSocket1->Socket->Connections[i]->SendText(edName->Text+"："+edTalk->Text); } else { ClientSocket1->Socket->SendText(edName->Text+"："+edTalk->Text); } edTalk->Text=""; } mmTalk的内容不可能永远增加，所以当它有100行时就清空它，在mmTalk的OnChange事件中检查： if(mmTalk->Lines->Count>=100)mmTalk->Lines->Clear(); 当然你也可以双击mmTalk来清空它，在mmTalk的OnDblClick事件中加入： mmTalk->Lines->Clear(); 当你觉得谈话的内容很有意思，你可以单击bbtSave打开保存对话框设置保存特性，所以在bbtSave的onClick中加入代码： if(sdTalk->Execute()) mmTalk->Lines->SaveToFile(sdTalk->FileName); OK，我们的闲聊程序就完成了，在局域网中试试吧？如果你只有一台机器，客户程序在连接时服务器时输入localhost或你机器的名字就可以了。 熟悉了这两个组件我们就可以写如联众游戏一样的网络游戏了（至少理论上如此），我也正想写一个网上赌博程序去诈骗我的同事，千万不要告诉他们哦！:=)