开发一个基于DCOM的局域网聊天室(三)

(接上文)

完善和修补:

基于修正通过测试所发现的bug,和功能的完善,我们有对客户端进行了一定的改动,主要体现在:

·对客户端进行更好的异常处理,以防止由于服务器异常中断而导致客户端仍不端请求服务器所造成的死锁。

·增加了说话对象和悄悄话功能(在客户端实现)

·增加了登录窗体,可以登陆到指定的房间并对服务器进行配置(参看下面服务器的改进)

另外在服务器端我们也做了部分的改进,主要完成了上次没有实现的功能,主要体现在:

·完成了服务器端任意配置并开放多个话题房间的功能(一个TchatRoom的实例对应着一个话题房间)

·在服务器端的每个房间维护一份登录进房间的人员列表,供客户端调用

·完善了服务器端的UI,并在服务器端实现为每个用户的登录和登出进行向客户系统公告的功能,并在服务器端限制登录的人数和进行重名判断

我们来看看主要的改进部分的代码变化情况,首先是服务器端的接口:

 

  IChatManager = interface(IDispatch)

    ['{E7CD7F0D-447F-497A-8C7B-1D80E748B67F}']

    ……

    function GetRoomList: IStrings; safecall;//客户端获得服务器端的房间列表

    function RoomCanLogin(RoomID: Integer; const UserName: WideString): Integer; safecall;

    //客户端接收到一个返回值用以判断服务器是否允许客户登录

    //返回值的表示:1:可以登陆 2:用户重名 3:人数过多

    function RoomUserList(RoomID: Integer): IStrings; safecall;

    //供客户端获得在一个房间内的人员列表,由TchatRoom维护这个列表

    //每登录和离开一个user便更新列表

  end;

 

其中RoomCanLogin需要的实现比较重要,其余的两个接口只是返回有服务器维护的两个列表而已。

 

//RoomCanLogin方法对应于TchatRoom类内的实现

function TChatRoom.CanLogin(UserName:string): integer;

var

 i:integer;

begin

 result:=1;

 if FRoomUserList.Count>50 then //最多允许一个房间有50个人

 begin

  result:=3;

  exit;

 end;

 for i:=0 to FRoomUserList.Count-1 do

 //遍历由TchatRoom维护的人员列表以判断是否有重名用户

 begin

  if FRoomUserList[i]=UserName then

   result:=2;

  break;

 end;

end;

 

再来看看,上次没有实现的多话题房间维护:

 

//请对比上篇文章的同名实现

constructor TChatRoomManager.Create;

var

 i:integer;

begin

 FRoomList:=TStringList.Create;

 try

  FRoomList.LoadFromFile(ExtractFilePath(application.ExeName)+'ChatRoomList.ini');

 except

  on E:Exception do

  begin

   application.MessageBox(Pchar('配置文件错误,错误代码:'+E.Message),'DComChatPro',MB_ICONWARNING);

   application.Terminate;

  end;

 end;

 FRoomList.Delete(0);

 FRoomCount:=FRoomList.Count;

 //这里将从配置文件中读出有几个聊天室

 setlength(ChatRoom,FRoomCount);

 for i:=1 to FRoomCount do

  ChatRoom[i]:=TChatRoom.Create(FRoomList[i-1],i);

  //创建房间的每一个实例

end;

 

客户端的Timer.OnTimer的重要改进(悄悄话和说话对象的功能都在这里实现):

 

//请对比上篇文章的同名实现

procedure TClientMainForm.Timer1Timer(Sender: TObject);

var

 TempStrings:TStrings;

 i:integer;

 ToStartPos,ToEndPos:integer;

 FromWho,ToWho,TempName:string;

begin

 try

  if ChatServer.Server.ReadReady(RoomID)=1 then

  begin

   TempStrings:=TStringList.Create;

   SetOleStrings(TempStrings,ChatServer.Server.ReadFrom(RoomID));

   if FReadStartPos>19 then

    if (FClearBufferTag=0-ChatServer.Server.TestClearBufferTag(RoomID)) then

    begin

     FReadStartPos:=0;

     FClearBufferTag:=ChatServer.Server.TestClearBufferTag(RoomID);

    end;

   for i:=FReadStartPos to TempStrings.Count-1 do

   begin

    if RightStr(TempStrings[i],11)='SecretSpeak' then

    //可以看到实现悄悄话无非是在说话内容的最后加了一个特殊的标示SecretSpeak

    begin

     //这一段程序从字符串中解析出说话的对象

     ToStartPos:=pos(' 悄悄的对 ',TempStrings[i]);

     FromWho:=Copy(TempStrings[i],1,ToStartPos-1);//谁说的

     ToStartPos:=ToStartPos+10;

     ToEndPos:=pos(' 说:',TempStrings[i]);

     ToWho:=Copy(TempStrings[i],ToStartPos,ToEndPos-ToStartPos);//说给谁

    

     if (ToWho='所有人') or (ToWho=UserName) or (FromWho=UserName) then

     //是自己说的,或自己应该看到的,或是说给所有人的悄悄话都有权看到

     begin

      Memo1.Lines.Add(Copy(TempStrings[i],1,length(TempStrings[i])-11));

      Memo1.Lines.Add('');

     end;

    end

    else //不该看到的内容

    begin

     Memo1.Lines.Add(TempStrings[i]);

     Memo1.Lines.Add('');

    end;

   end;

   FReadStartPos:=TempStrings.Count;

  end;

  //刷新在线人员列表

  Listbox1.Clear;

  SetOleStrings(ListBox1.Items,ChatServer.Server.RoomUserList(RoomID));

  //刷新说话对象列表

  TempName:=SpeakToCBx.Text;

  SpeakToCBx.Clear;

  SpeakToCBx.Items.Assign(ListBox1.Items);

  SpeakToCBx.Items.Insert(0,'所有人');

  for i:=0 to SpeakToCBx.Items.Count-1 do

  begin

   if SpeakToCBx.Items[i]=TempName then Break;

  end;

  if i>SpeakToCBx.Items.Count-1 then i:=0;

  SpeakToCBx.ItemIndex:=i;

  //

 except //异常处理

  on E:Exception do

  begin

   Timer1.Enabled:=false;

   application.MessageBox

    (pchar('通信中断或服务器故障,点确定后将关闭程序,请稍后重启动。详细中断原因:'+E.Message),'DCOMChatClient',MB_ICONWARNING);

   application.Terminate;

  end;

 end;

end;

 

当然上面的程序所分析的字符串(说给谁,谁说的,是否是悄悄话)都是在speak时产生的,这相当的简单:

 

//客户端的speak

procedure TClientMainForm.Button1Click(Sender: TObject);

var

 content:string;

begin

 if Edit1.Text='' then

 begin

  application.MessageBox('不能发空消息。','DCOMChatClient',MB_ICONINFORMATION);

  exit;

 end;

 if length(edit1.Text)>100 then

 begin

  application.MessageBox('说话内容过长。','DCOMChatClient',MB_ICONINFORMATION);

  exit;

 end;

 if CheckBox1.Checked then

  Content:=UserName+' 悄悄的对 '+SpeakToCBx.Text+' 说:'+edit1.Text+'SecretSpeak'

  //可以看到悄悄话功能和说话对象的功能只是在字符串上的简单处理罢了

 else

  Content:=UserName+' '+SpeakToCBx.Text+' 说:'+edit1.Text;

 ChatServer.Server.SpeakTo(Content,RoomID);

 edit1.Clear;

end;

 

至此这个程序已经基本完善了,我们可以打包发布它,以免去最终用户配置DCOM的麻烦。

                                                             ( 全文完 )
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
chatRoom.zip 聊天室聊天室服务端 package chatroom; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import javax.swing.JFrame; import javax.swing.JOptionPane; /** * 聊天室服务器端 ChatRoomServer类 * * @version 1.01, 09/04/10 */ public class ChatRoomServer { private ServerSocket ss; /* 存放Socket的集合hs */ private HashSet<Socket> hs; public ChatRoomServer() { JFrame jf = new JFrame(); do { /* * 弹出输入对话框,提示输入服务器需要绑定的端口号 */ int port = Integer.parseInt(JOptionPane.showInputDialog(jf, "bind port:")); try { ss = new ServerSocket(port); System.out.println("server start success,now listening port:" + port); } catch (Exception e) { /* 弹出确认框进行确认 */ int op = JOptionPane.showConfirmDialog(jf, // 指定是在jf中弹出确认框 "bind fail,retry?", // 框体内容 "bind fail", // 对话框的标题 JOptionPane.YES_NO_OPTION); // 确认框按钮项 /* 如果选择'否',则退出程序 */ if (op == JOptionPane.NO_OPTION) { System.exit(1); } /* 打印异常栈信息 */ e.printStackTrace(); } } while (ss == null); /* 创建HashSet,用来存放Socket对象 */ hs = new HashSet<Socket>(); while (true) { try { /* 获得Socket,网络阻塞,等待客户端的连接 */ Socket s = ss.accept(); /* 一旦客户端连接上,则加入HashSet,便于维护 */ hs.add(s); /* 启动一个服务线程,为客户端服务 */ new ServerThread(s).start(); } catch (IOException e) { e.printStackTrace(); } } } /** * 成员内部类,服务线程类 */ class ServerThread extends Thread { private Socket s; public ServerThread(Socket s) { this.s = s; } @Override public void run() { BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(s .getInputStream())); while (true) { /* 从网络中读取客户端发出的消息 */ String str = br.readLine(); /* * 客户退出时的处理逻辑 规则:以"%EXIT_CHATROOM%"开头的消息为客户退出标记 */ if (str.charAt(0) == '%') { String com = str.split("%")[1]; if (com.equals("EXIT_CHATROOM")) { hs.remove(s); printMessage(str.split("%")[2] + ",leave!"); s.close(); break; } } else { /* 正常情况,直接向客户端群发消息 */ printMessage(str); } } } catch (IOException e) { e.printStackTrace(); } } /* * 负责为客户端群发消息 */ private void printMessage(String mes) { System.out.println(mes); try { /* 遍历所有Socket */ for (Socket s : hs) { PrintStream ps = new PrintStream(s.getOutputStream()); /* 产生发消息的时刻 */ Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); /* 向客户端发消息,消息结构为mes [yyyy-MM-dd HH:mm:ss] */ ps.println(mes + " [" + sdf.format(date) + "]"); /* 注意需要及时flush,清空缓冲区 */ ps.flush(); } } catch (IOException e) { e.printStackTrace(); } } } /* * 主方法,启动聊天室服务器端 */ public static void main(String[] args) { new ChatRoomServer(); } } 客户端窗体 package chatroom; import java.awt.BorderLayout; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; /** * 客户端窗口ChatRoomClientFrame类 负责客户端的视图表示、事件处理等逻辑 作为一个窗口,所以本类继承自JFrame * 为了实现事件处理,本类实现了ActionListener接口 * * @version 1.01, 09/04/10 */ public class ChatRoomClientFrame extends JFrame implements ActionListener { private static final long serialVersionUID = 3484437496861281646L; private JTextArea jta; // 多行文本域 private JLabel label; // 文本标签 private JTextField jtf; // 单行文本框 private JButton jb; // 按钮 private Socket socket; // socket 套接字 private String name; // 用户名 /* * ChatRoomClientFrame构造方法,负责初始化以及添加事件处理 */ public ChatRoomClientFrame(String name, Socket socket) { super("chatroom"); Font f = new Font("楷体", Font.BOLD, 20); /* 设置字体 */ jta = new JTextArea(10, 10); /* 创建10行10列的空多行文本域 */ jta.setEditable(false); /* 设置多行文本域为不可编辑 */ jta.setFont(f); /* 设置多行文本域字体 */ label = new JLabel(name + ":"); /* 创建带有用户名的文本标签 */ label.setFont(f); /* 设置文本标签字体 */ jtf = new JTextField(25); /* 创建单行文本框 */ jtf.setFont(f); /* 设置单行文本框字体 */ jb = new JButton("send"); /* 创建按钮 */ this.name = name; this.socket = socket; init(); /* 初始化,设置组件关系 */ addEventHandler(); /* 为组件添加事件监听 */ } /* * 初始化组件关系方法,供构造方法调用 */ private void init() { JScrollPane jsp = new JScrollPane(jta); this.add(jsp, BorderLayout.CENTER); JPanel jp = new JPanel(); jp.add(label); jp.add(jtf); jp.add(jb); this.add(jp, BorderLayout.SOUTH); } /* * 负责为各组件添加事件处理机制 */ private void addEventHandler() { jb.addActionListener(this); // 为jb添加事件监听,JButton点击触发事件 jtf.addActionListener(this); // 为jtf添加事件监听,JTextField敲回车触发事件 /* 设置JFrame默认关闭状态:DO_NOTHING_ON_CLOSE */ this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); /* 为JFrame添加窗口事件监听 */ this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent arg0) { /* 弹出确认框进行交互 */ int op = JOptionPane.showConfirmDialog( ChatRoomClientFrame.this, "exit?", "exit", JOptionPane.YES_NO_OPTION); if (op == JOptionPane.YES_OPTION) { try { PrintStream ps = new PrintStream(socket .getOutputStream()); /* 向服务器发送以%EXIT_CHATROOM%name为格式的串,代表退出信号 */ ps.println("%EXIT_CHATROOM%" + name); /* 注意需要及时flush */ ps.flush(); try { Thread.sleep(168); // 进行延时控制,防止提前关闭socket } catch (InterruptedException e) { e.printStackTrace(); } socket.close(); // 关闭客户端socket ps.close(); // 关闭流 } catch (IOException e) { e.printStackTrace(); } System.exit(1); // 退出程序 } } }); } /* * 实现事件处理方法 */ public void actionPerformed(ActionEvent ae) { try { PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println(name + ": " + jtf.getText()); ps.flush(); /* 清空jtf中内容 */ jtf.setText(""); } catch (IOException e) { e.printStackTrace(); } } /* * 显示并执行窗口逻辑 */ public void showMe() { this.pack(); // 自动调整此窗口的大小 this.setVisible(true); // 设置窗口可见 /* * 启动线程,负责接收服务器端发来的消息 */ new Thread() { @Override public void run() { BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(socket .getInputStream())); while (true) { /* 从网络中读取服务器发出的数据 */ String str = br.readLine(); /* 对JTextArea进行追加消息 */ jta.append(str + "\n"); } } catch (IOException e) { e.printStackTrace(); } } }.start(); } } 客户端 package chatroom; import java.io.PrintStream; import java.net.Socket; import javax.swing.JFrame; import javax.swing.JOptionPane; /** * 聊天室客户端 ChatRoomClient * * @version 1.01, 09/04/10 */ public class ChatRoomClient { private String name; // 用户名 private Socket socket; // Socket 套接字 private ChatRoomClientFrame frame; // 组合复用 ChatRoomClientFrame /* * ChatRoomClient构造方法,负责构造客户端表示逻辑 */ public ChatRoomClient() { JFrame jf = new JFrame(); /* * 弹出用户输入对话框,提示用户输入服务器的IP地址 返回相应字符串形式,存于变量serverIP,缺省值127.0.0.1 */ String serverIP = JOptionPane.showInputDialog(jf, "server IP:", "127.0.0.1"); /* * 弹出用户输入对话框,提示用户输入服务器端口号 转化为int形式返回,存于变量serverPort */ int serverPort = Integer.parseInt(JOptionPane.showInputDialog(jf, "port:")); /* * 弹出用户输入对话框,提示用户输入用户名,存于成员属性name */ name = JOptionPane.showInputDialog(jf, "your name:"); try { /* 通过IP和Port,与服务器端建立连接 */ socket = new Socket(serverIP, serverPort); /* 给服务器发消息 */ PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println(name + ",login !"); } catch (Exception e) { JOptionPane.showMessageDialog(jf, "fail,check the connection!"); e.printStackTrace(); System.exit(1); } /* * 创建ChatRoomClientFrame,进行客户端主窗口的显示 */ frame = new ChatRoomClientFrame(name, socket); frame.showMe(); } /* * 主方法,启动聊天室客户端 */ public static void main(String[] args) { new ChatRoomClient(); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值