- 浏览: 11108 次
- 性别:
- 来自: 上海
文章分类
最新评论
在我的上一篇总结中,我初步完成了一个多线程的服务器,可是还有很多问题有待解决。比如,如何使两台客户机之间可以实现通信?这就需要我们增加一个通信类和通信方法来实现服务器接收到一条消息以后将它转发给另一个线程代表的客户机。当然要实现一个功能相对健全的群聊通信工具远没有那么简单。那么这篇日记中我将一一总结。
首先让我来谈谈群聊服务器的实现。我希望实现的功能大体如下:
1.当一个客户机连结上来时,服务器要求客户机输入用户名,密码;
2.如果输入的用户名密码和服务器端所保存的帐号一致,则登陆成功;否则断开。
3.登陆成功后,即给其它客户机提示:###用户进入聊天室,当前在线N人
4.登陆成功的客户机可以向服务器发送消息,其它在线的客户端也会收到这条消息;
5.当这个客户端发送bye退出时,或意外断掉时;向其它客户机通知:##离开聊天室;
在分析完需要的功能以后,我就在之前的简陋的服务器基础上,增加了4个类,第一个类为userInfo类,目的是为了模拟真是的用户信息,该类的每一个对象相当于一个用户。第二个类为DaoTools类,这个类主要是为了验证用户名和密码,判断是否可以登录成功!第三个类是ChatTool类,这个类主要是用一个队列来保存每一个客户机的线程,并提供一些处理转发消息,踢人等方法来辅助客户机之间的通信,这个类中的方法将全部被调用,用来实现群聊的功能。最后一个是界面类。当然其它的每一个类中的方法我也做了一定的修改,好了,话不多说,下面我上代码分6步具体分析!
1.我首先写一个userInfo类来模拟用户的信息。这个类中有几个属性,比如用户名,用户密码,用户IP地址等等,然后为这些属性构造一些定义和获取它的方法,一些模拟的用户就产生啦~这个类很简单,这个类的具体代码如下:
4.ServerThread线程类:调用一些ChatTools类的方法,接收消息并转发出去,用来处理与客户机之间的通信。这个类在上一篇总结中已经详细说明了,这里的改动不是很大。重点就是要理解一个线程对象就好比打开了一个与客户机联通的话筒,就好比移动公司的客服,肯定会有多个接线员允许同时接听多个电话。这里的思想也差不多,每登录上一台客户机,我们就为它建立一个服务器线程来建立与它通信的管道!这样每一个客户机就对应一个服务器线程了~这个类的代码如下: 5.ChatServer类:由于要实现服务器的启停,我把这个类放到一个独立的线程中去。当服务器启动以后就,这个线程就启动。当停止服务器时则关闭这个线程就好!其余大体的代码和之前的还是很类似的。就不具体解释了: 6.MainServerUI类:界面类。这里用到了swing中的很多方法。我这方面也不是特别熟悉,所以做得非常的简陋,这个类我主要是继承JTable来实验一个用户表。然后再把之前的各个类中的方法在这个类中进行一个汇总,并启动服务器线程。这样一个简单的有界面的服务器就做好啦!这个类的具体代码如下:
其中的那个UserInfoTableMode类我是继承了TableModel类的接口,制作了一个表格。不过还有一些问题需要改进,比如每次登录上一个客户信息,就把上一个客户信息给覆盖了,由于我swing不是很熟悉,所以这只是一个简单的表格模型。该类代码如下:
//处理与客户机通信的线程,验证信息,群发消息
public class ServerThread extends Thread{
private java.net.Socket client;// 线程中处理的客户对象
private java.io.OutputStream ous; // 输出流
private UserInfo user; // 这个线程处理对象对应的用户信息
public ServerThread(java.net.Socket client) {
this.client = client;
}
// 取得这个线程代表的用户信息
public UserInfo getUser() {
return this.user;
}
// 发送一条消息的方法
public void sendMsg(String msg) {
try {
msg += "\r\n";
ous.write(msg.getBytes());
ous.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
public void run(){
processThread(client);
}
public void processThread(java.net.Socket client){
try {
InputStream ins = client.getInputStream();
ous = client.getOutputStream();
String s = "欢迎你!\r\n";
this.sendMsg(s);
//将输入流ins封装成可以读取一行字符串的,即以\r\n结尾的一句话
BufferedReader brd = new BufferedReader(new InputStreamReader(ins));
//sendMsg("欢迎你来聊天!请输入你的用户名");
String name = brd.readLine();
System.out.println("用户名是:"+name);
//sendMsg(name+":请输入你的密码");
String pwd = brd.readLine();
System.out.println("密码是:"+pwd);
user=new UserInfo();
user.setName(name);
user.setPwd(pwd);
if(!DaoTools.checkLogin(user)){//如果没有验证成功,即登录失败
this.closeMe();
return;
}
//如果验证成功,就加入这个线程
ChatTools.addClient(this);
String input = brd.readLine();
while(!input.equals("bye")){
System.out.println("服务器收到的是"+input);
//读到一条消息后,就发送给其它的客户机
ChatTools.castMsg(user, input);
input = brd.readLine();
}
ChatTools.castMsg(user, "我下线了,再见!");
ChatTools.removeClient(this);
this.closeMe();
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭这个线程处理对象
public void closeMe(){
try{
client.close();
}catch(Exception ef){
ef.printStackTrace();
}
}
}
public class ChatServer extends Thread {
private java.net.ServerSocket sc; // 服务器对象
private int port; // 端口号
private boolean isrunning = false; // 服务器是否运行的标识
// 创建服务其对象的时候传入端口号
public ChatServer(int port) {
this.port = port;
}
public void run() {
setupServer();
}
public void setupServer() {
try {
sc=new ServerSocket(port);
isrunning = true;
System.out.println("服务器创建成功:"+port);
while(isrunning){
java.net.Socket client = sc.accept();
System.out.println("连进来的客户机是:"+client.getRemoteSocketAddress().toString());
//启动线程处理这些连上来的客户机
ServerThread th = new ServerThread(client);
th.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
//查询服务器是否在运行中,在运行中则为true
public boolean isrunning(){
return this.isrunning;
}
//关闭服务器
public void stopChatServer(){
this.isrunning = false;
try{
sc.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
/**
* 服务器端管理界面程序
* 1.启/停
* 2.发布公告消息
* 3.显示在线用户信息
* 4.踢人
* 5.对某一用户发消息
* @author Administrator
*/
public class MainServerUI extends JFrame{
private ChatServer cserver; //服务器对象
static JTable table_onlineUser; // 在线用户表
private JTextField jtf_msg; //发送消息输入框
private JTextField jtf_port; //服务器端口号输入框
private JButton control_chat; //启动服务器的按钮
public static void main(String[] args) {
MainServerUI mu = new MainServerUI();
mu.showUI();
}
//初始化界面
public void showUI(){
this.setTitle("聊天服务器");
this.setSize(500,300);
this.setLayout(new java.awt.FlowLayout());
JLabel la_port= new JLabel("服务器端口:");
this.add(la_port);
jtf_port = new JTextField(4);
this.add(jtf_port);
control_chat=new JButton("启动服务器");
this.add(control_chat);
control_chat.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
actionServer();
}
});
JLabel la_msg = new JLabel("要发送的消息:");
this.add(la_msg);
//服务器要发送消息的输入框
jtf_msg = new javax.swing.JTextField(30);
JButton send = new JButton("send");
ActionListener sendCastMsg = new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
sendAllMsg();
}
};
//给输入框加上时间监听器,按回车就发送
jtf_msg.addActionListener(sendCastMsg);
//给发送按钮加事件监听器发送广播消息
send.addActionListener(sendCastMsg);
this.add(jtf_msg);
this.add(send);
//界面上用以显示在线用户列表的表格
table_onlineUser = new JTable();
//创建我们自己的Model对象:创建时,传入处理所有的线程列表
List<ServerThread> sts = ChatTools.getAllThread();
UserInfoTableMode utm = new UserInfoTableMode(sts);
table_onlineUser.setModel(utm);//将模型加给表格
//将表格对象放到滚动面板对象上
javax.swing.JScrollPane scrollpane = new JScrollPane(table_onlineUser);
//设定表格在面板上的大小
table_onlineUser.setPreferredScrollableViewportSize(new Dimension(400,100));
//超出大小后,JScrollPane自动生成滚动条
scrollpane.setAutoscrolls(true);
this.add(scrollpane);//将scrollpane对象加到界面上
//取得表格上的弹出菜单对象,加到表格上
JPopupMenu pop=getTablePop();
table_onlineUser.setComponentPopupMenu(pop);
this.setDefaultCloseOperation(3);//关闭时彻底退出,关闭进程
this.setVisible(true);
}
/**
* 创建表格上的弹出菜单对象,实现发信,踢人功能
* @return:弹出菜单对象,将被加到表格上
*/
private JPopupMenu getTablePop(){
JPopupMenu pop = new JPopupMenu(); //弹出菜单对象
JMenuItem mi_send = new JMenuItem("发信"); //菜单项对象
mi_send.setActionCommand("send");
JMenuItem mi_del = new JMenuItem("踢掉");
mi_del.setActionCommand("del");
//弹出菜单上的事件监听器对象
ActionListener al = new ActionListener(){
public void actionPerformed(ActionEvent e) {
String s=e.getActionCommand();
popMenuAaction(s);
}
};
mi_send.addActionListener(al);
mi_del.addActionListener(al);
pop.add(mi_send);
pop.add(mi_del);
return pop;
}
/**
* 处理弹出菜单上的事件
* @param command:弹出菜单上的命令
*/
private void popMenuAaction(String command){
//得到在表格上选中的行
final int selectIndex = table_onlineUser.getSelectedRow();
if(selectIndex==-1){//如果未选中
JOptionPane.showMessageDialog(this, "请先选中一个用户");
return;
}
if(command.equals("del")){
//从队列中移除该线程
ChatTools.removeOneClient(selectIndex);
}
else if(command.equals("send")){
UserInfo user = ChatTools.getUser(selectIndex);
final JDialog jd = new JDialog(this,true); //发送对话框
jd.setLayout(new FlowLayout());
jd.setTitle("您将对"+user.getName()+"发信息");
jd.setSize(400,100);
jd.setLocationRelativeTo(null);
final JTextField jtd_m = new JTextField(20);
JButton jb=new JButton("发送!");
jd.add(jtd_m);
jd.add(jb);
ActionListener al = new ActionListener(){
public void actionPerformed(ActionEvent e) {
String msg = "系统悄悄说:"+jtd_m.getText();
ChatTools.sendMsg2One(selectIndex, msg);
jtd_m.setText("");//清空输入框
jd.dispose();
}
};
jb.addActionListener(al);
jtd_m.addActionListener(al);
jd.setVisible(true);
}else{
JOptionPane.showMessageDialog(this, "未知菜单:"+command);
}
//刷新表格
SwingUtilities.updateComponentTreeUI(table_onlineUser);
}
//响应启动/停止按钮!
public void actionServer(){
if(null==cserver){
//如果还没有服务器线程就启动服务器的线程,
int port = Integer.parseInt(jtf_port.getText());
cserver = new ChatServer(port);
cserver.start();
this.setTitle("服务器正在运行中");
control_chat.setText("stop!");
}else if(cserver.isrunning()){
cserver.stopChatServer();
cserver = null;
//清楚所有已在运行的程序
ChatTools.removeAllClient();
this.setTitle("服务器已停止!");
control_chat.setText("start!");
}
}
//按下发送服务器的消息,给所有的在线用户发送消息
private void sendAllMsg(){
String msg = jtf_msg.getText();
UserInfo user = new UserInfo();
user.setName("系统");
ChatTools.castMsg(user, msg);
jtf_msg.setText("");//清空输入框
}
}
public class UserInfoTableMode implements TableModel{ private List<ServerThread> list; public UserInfoTableMode(List<ServerThread> sts) { this.list = sts; } // 多少行 public int getRowCount() { return list.size(); } // 多少列 public int getColumnCount() { return 3; } // 得到列名 public String getColumnName(int columnIndex) { if (columnIndex == 0) { return "用户名"; } if (columnIndex == 1) { return "密码"; } if (columnIndex == 2) { return "IP地址"; } else { return null; } } // 每一列的数据类型:我们这里显示的都是String类型 public Class<?> getColumnClass(int columnIndex) { return String.class; } // 指定的单元格可否从界面上编辑 public boolean isCellEditable(int rowIndex, int columnIndex) { // TODO Auto-generated method stub return true; } // 取得单元格的值 public Object getValueAt(int rowIndex, int columnIndex) { String s = null; rowIndex = list.size(); for(int i=0;i<list.size();i++){ s=list.get(i).getUser().getName(); } return s; } // 从表格界面上改变了某个单元格的值后会调用这个方法 public void setValueAt(Object aValue, int rowIndex, int columnIndex) { String s = "Change at" + rowIndex + "--" + columnIndex + "newValue:" + aValue; System.out.println(s); } @Override public void addTableModelListener(TableModelListener l) { // TODO Auto-generated method stub } @Override public void removeTableModelListener(TableModelListener l) { // TODO Auto-generated method stub } }
以上就是做一个有界面的简单服务器的全部流程,当然我还有很多的地方需要改进。比如目前还不能实现踢人的功能,程序会报错。还有服务器端的客户列表也有问题,当然这些我会在以后的代码中努力改进的!
做完服务器之后,我们就来做一个简易的客户端。客户端的流程和服务器差不多,客户端通过写消息给服务器,再接收服务器发送过来的消息就可以啦!那么再写完服务器之后,我觉得做一个客户端不再是一件难的事情了。下面我分2步谈谈具体的实现!
1.NetClient类:负责对通信操作的方法封装,在独立的线程中运行,接收到消息后,显示到界面上。并提供写出消息给服务器以及验证用户的方法,供界面主类调用!具体代码如下:
public class NetClient extends Thread { private java.net.Socket client; private String IP; // IP地址 private int port; // 端口 private OutputStream ous; // 输出流对象 private BufferedReader brd; // 输入流对象 private JTextArea jta_input; // 显示消息的组件,从界面上传来,每读取一条消息就把它append到界面上 public NetClient(String IP, int port, JTextArea jta_input) { this.IP = IP; this.port = port; this.jta_input = jta_input; } public java.net.Socket getClient(){ return this.client; } /** * 判断是否和服务器连接 * * @return:连接上返回true */ public boolean connect2Server() { try { client = new Socket(this.IP, this.port); // 与客户端建立连接 InputStream ins = client.getInputStream(); // 得到输入输出流 brd = new BufferedReader(new InputStreamReader(ins)); ous = client.getOutputStream(); return true; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return false; } /** * 判断是否成功 * * @param name * :要写入的用户名 * @param pwd * :要写入的密码 * @return:登录成功返回true */ public boolean isLogin(String name, String pwd) { try { // .判断用户名密码是否正确 // String input = brd.readLine();//读取服务器发来的一条消息 // System.out.println("服务器说:"+input); // 写入用户名和密码 name += "\r\n"; ous.write(name.getBytes()); ous.flush(); // input = brd.readLine();//读取服务器发来的验证密码的消息; pwd += "\r\n"; ous.write(pwd.getBytes()); ous.flush(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public void run() { while (true) { readFromServer();//不停的接收消息 } } public void readFromServer() { try { String input = brd.readLine(); //System.out.println("服务器:" + input); if(!input.equals(null)){ jta_input.append(input + "\r\n"); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void sendMsg(String msg) { try { msg += "\r\n"; ous.write(msg.getBytes()); ous.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
2.MainNetUI类:负责客户端登陆,显示接收消息,发送消息界面提供。这主要还是调用NetClient类的方法,然后做一个Swing界面即可,具体代码如下:
public class MainClientUI { private JFrame jf_login; //登录主界面 private JFrame jf_chat; // 聊天主界面 private JTextField userName; // 登录界面上的用户名,密码和IP地址的输入框 private JTextField jta_pwd; private JTextField jta_IP ; private JTextArea jta_recive = new JTextArea(10,20); //显示接收到的消息组件 private NetClient conn; //界面所要用的连接对象 public static void main(String[] args) { MainClientUI mu = new MainClientUI(); mu.showLoginUI(); } public void showLoginUI(){ jf_login =new JFrame("聊天登录界面"); jf_login.setSize(250,250); jf_login.setLayout(new java.awt.FlowLayout()); jta_IP = new JTextField(15); //IP输入框,默认为localhost jta_IP.setText("localhost"); userName = new JTextField(15); //用户名密码输入框 jta_pwd = new JTextField(15); JLabel IP = new JLabel("IP地址:"); // IP地址,用户名,密码的标签 JLabel name = new JLabel("用户名:"); JLabel pwd = new JLabel("密码:"); jf_login.add(IP); jf_login.add(jta_IP); jf_login.add(name); jf_login.add(userName); jf_login.add(pwd); jf_login.add(jta_pwd); JButton but_login = new JButton("登录"); JButton but_reg = new JButton("注册"); but_login.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub LoginAction(); } }); jf_login.add(but_login); jf_login.add(but_reg); jf_login.setDefaultCloseOperation(3); jf_login.setLocationRelativeTo(null); jf_login.setVisible(true); } //登录事件的处理 public void LoginAction(){ String name = userName.getText();//读取输入框中的用户名和密码 String pwd = jta_pwd.getText(); String IP = jta_IP.getText(); conn = new NetClient(IP,9090,jta_recive); if(conn.connect2Server()){//如果连接上服务器,就验证用户名和密码 if(conn.isLogin(name, pwd)){//如果用户名和密码验证正确,就打来聊天界面,并启动通信线程,接收消息 showChatUI(); conn.start(); jf_login.dispose(); //关闭登陆界面 }else{ javax.swing.JOptionPane.showMessageDialog(jf_login, "用户名或密码不正确!"); } }else{ javax.swing.JOptionPane.showMessageDialog(jf_login, "没有连上服务器,请检查网络!"); } } public void showChatUI(){ jf_chat = new JFrame("聊天界面"); jf_chat.setSize(300,500); jf_chat.setLayout(new java.awt.FlowLayout()); JLabel la_recive = new JLabel("接收到的消息"); JLabel la_send = new JLabel ("要发送的消息"); final JTextArea jta_send = new JTextArea(10,20);//消息发送框 jf_chat.add(la_recive); jf_chat.add(jta_recive); jf_chat.add(la_send); jf_chat.add(jta_send); JButton bu_send = new JButton("发送!"); ActionListener al = new ActionListener(){ public void actionPerformed(ActionEvent e) { String msg = jta_send.getText(); conn.sendMsg(msg); jta_send.setText("");//清空输出框 } }; bu_send.addActionListener(al); if(conn.getClient().isClosed()){ System.out.println(">>>>>>>>>>"); jf_chat.dispose(); } jf_chat.add(la_recive); jf_chat.add(jta_recive); jf_chat.add(la_send); jf_chat.add(jta_send); jf_chat.add(bu_send); jf_chat.setDefaultCloseOperation(3); jf_chat.setLocationRelativeTo(null); jf_chat.setVisible(true); } }
以上一个简单的客户端就建成啦~当然这个客户端也是有很多弊端的 它必须得按照服务器的流程来读写消息,对于一个大型的通信的项目,这未免太过繁琐!所以下一篇总结中,我将用XMPP协议来解决这一问题~
总的来说,做一个通信这样的大项目,思路一定要清晰!要有全局观!最后就是要细心!拥有以上几点~攻克服务器和客户端就不是什么困难啦!
相关推荐
开发环境为Qt+VS2010 包括服务器聊天室创建,客户端登录,客户端和服务器字符串传递等
1. 用Java图形用户界面编写聊天室服务器端和客户端, 支持多个客户端连接到一个服务器。每个客户端能够输入账号。 2. 可以实现群聊(聊天记录显示在所有客户端界面)。 3. 完成好友列表在各个客户端上显示。 4. 可以...
这是整个程序运行时的第一个界面。出现一个界面,填入账号和密码,服务器验证成功,出现聊天界面。在登陆界面上有一个注册按钮,点击到注册界面 2. 注册功能 出现一个界面,填入客户的资料,如果发现账号重复,要求...
基础的基于TCP协议多线程服务器-客户端聊天室,无图形化界面,纯控制台输入输出内容,实现聊天室群聊、私聊功能,服务端部分配备简单日志输出,注释齐全,适合开发者新手学习使用!详细内容请移步我的博客查看...
用java 编写的模拟聊天器的一些代码 ,可以实现私聊群聊的一些功能用户的登陆
这是一个用vc++6.0写的聊天室服务器端和客户端的软件 支持悄悄话的功能 及其上下线的通知功能
1. 用Java图形用户界面编写聊天室服务器端和客户端, 支持多个客户端连接到一个服务器。每个客户端能够输入账号。 2. 可以实现群聊(聊天记录显示在所有客户端界面)。 3. 完成好友列表在各个客户端上显示。 4. 可以...
该聊天工具,是一个服务器对应多个客户端的,是我结合J2SE基础知识、网络、线程来进行开发的,虽然界面不够人性化,功能也不够全面,但实现了群聊和私聊的主要功能
jsp网络聊天系统——群聊系统,一个完整的工程,导入即可使用,一个不错的框架!
socket群组聊天服务器端客户端源码,简单的实现群组多人聊天
java编写客户端 服务端通讯程序。在服务端开启的基础上多客户端可同时连接服务端,并有上线提示,群聊,私聊等功能。
基于TCP实现一个服务端连接多个客户端后,客户端群聊和服务端发公告功能。
实现注册,登录,私聊,群聊,超级用户:禁言、踢人。等功能
安卓Andriod源码——HorizontalListView仿微信发起群聊.zip
实现在多个客户端中实现群聊的功能,并且是在图形用户界面下进行操作。
java聊天室网络通信客户端与服务端
安卓Android源码——HorizontalListView仿微信发起群聊.zip
这是基于Qt设计的网络聊天软件,资料包里包含了Qt...只不过博客里的这一份是改成了云端网络版本,而现在这份是本地版本,也就是服务器放在本地,不需要放在云服务器上,界面是一样的,本地版本的数据库采用的是QSLite。
Winsock 聊天程序VC++版(服务器+客户端) Winsock 聊天程序VC++版,包含服务器+客户端源码,点对点的聊天程序,运行服务端后启动默认端口0,客户端启动后设置相应参数
当客户端需要向另一个客户发送消息时,它首先将消息发送到服务器,由服务器根据目标用户帐户转发到目标主机。 2、群聊是采用多播技术实现的,也可以采用单播技术实现,但是服务器开销会增加。具体说来,若采用组播...