我们每天都在使用着微信、QQ等聊天软件,但不知你是否有想过这些聊天软件是如何实现的?是否想过要制作一个属于自己的聊天室?

本篇博客将带你打造一个简单的属于自己的聊天室,将cmd作为聊天窗口,可通过内网,与周围的小伙伴相互通信,当然也可以挂到服务器上,实现通过外网的通信。同时还能通过服务端窗口对连入的用户进行管理。

先来看看我做的效果

这是服务器控制界面

输入端口号,点击启动,再打开cmd,输入telnet localhost 端口号,然后输入账号密码登陆

输入消息

下面就来讲讲如何实现的吧

首先我们需要先建立好用户的信息

UserInfo.java

public class UserInfo {
private String name;
private String possward; public String getName(){
return this.name;
}
public void setName(String name) {
this.name=name;
} public void setPwd(String possward) {
this.possward=possward;
}
}

一个聊天室,我们可以将其分为服务端和客户端,而通信的简易过程如下图所示

对于客户端,我们需要做的是1、验证用户登陆信息。2、接收用户发送的信息并转发给目标用户

服务端目前则使用cmd进行

首先,我们先建立一个简易的储存账号密码的数据库

DaoTools.java

public class DaoTools {
//内存用户信息数据库
private static Map<String,UserInfo>userDB=new HashMap(); public static boolean checkLogin(UserInfo user) {
//验证用户名是否存在
if(userDB.containsKey((user.getName()))){
return true;
}
System.out.println("认证失败!:"+user.getName());
return false;
}
//系统内部自动创建10个账号
static {
for(int i=0;i<10;i++) {
UserInfo user=new UserInfo();
user.setName("user"+i);
user.setPwd("pwd"+i);
userDB.put(user.getName(), user);
}
} }

服务端服务器创建

ChatServer.java

public class ChatServer extends Thread {
private int port;// 端口
private boolean running = false;// 服务器是否运行中的标记
private ServerSocket sc;// 服务器对象 /*
* 创建服务器对象时,必须传入端口号
*
* @param port:服务器所在端口号
*/
public ChatServer(int port) {
this.port = port;
} // 线程中启动服务器
public void run() {
setupServer();
} // 在指定端口上启动服务器
public void setupServer() {
try {
ServerSocket sc = new ServerSocket(this.port);
running = true;
System.out.println("服务器创建成功:" + port);
while (running) {
Socket client = sc.accept();// 等待连结进入
System.out.println("进入了一个客户机连接" + client.getRemoteSocketAddress());
// 启动一个处理线程,去处理这个连结对象
ServerThread ct = new ServerThread(client);
ct.start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} /*
* 查询服务器是否在运行
*
* @return: true为运行中
*/
public boolean isRunning() {
return this.running;
} // 关闭服务器
public void stopchatServer() {
this.running = false;
try {
sc.close();
} catch (Exception e) {}
}
}

验证用户登陆信息,创建服务器线程

ServerThread.java

public class ServerThread extends Thread {
private Socket client;//线程中处理的客户对象
private OutputStream ous;//输出流对象
private UserInfo user;//这个线程处理对象代表的用户的信息
//创建对象时必须传入一个Socket对象
public ServerThread(Socket client) {
this.client=client;
}
//获取这个线程对象代表的用户对象
public UserInfo getOwerUser() {
return this.user;
}
public void run() {
processSocket();
} public void sendMsg2Me(String msg) {
try {
msg+="\r\n";
ous.write(msg.getBytes());
ous.flush();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//读取客户机发来的消息
private void processSocket() {
try {
InputStream ins=client.getInputStream();
ous=client.getOutputStream();
//将输入流ins封装为可以读取一行字符串也就是以\r\n结尾的字符串
BufferedReader brd=new BufferedReader(new InputStreamReader(ins));
sendMsg2Me("欢迎您来聊天!请输入你的用户名:");
String userName=brd.readLine();
sendMsg2Me(userName+"请输入你的密码");
String pws=brd.readLine();
user=new UserInfo();
user.setName(userName);
user.setPwd(pws);
//调用数据库模块,验证用户是否存在
boolean loginState=DaoTools.checkLogin(user);
if(!loginState) {//不存在则账号关闭
this.closeMe();
return;
}
ChatTools.addClient(this);//认证成功:将这个对象加入服务器队列
String input=brd.readLine();//一行一行的读取客户机发来的消息
while(!"bye".equals(input)) {
System.out.println("服务器收到的是"+input);
//读到一条消息后,就发送给其他的客户机
ChatTools.castMsg(this.user, input);
input=brd.readLine();//读取下一条
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ChatTools.castMsg(this.user, "我下线了,再见!");
this.closeMe();
}
//关闭这个线程
public void closeMe() {
try {
client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }

通过服务端对用户操作

ChatTools.java

public class ChatTools {
// 保存处理线程的队列对象
private static List<ServerThread> stList = new ArrayList(); private ChatTools() {
} /*
* 取得保存处理线程对象的队列
*/
public static List<ServerThread> getAllThread() {
return stList;
} /*
* 将一个客户对应的处理线程对象加入到队列中
*
* @param ct:处理线程对象
*/
public static void addClient(ServerThread ct) {
// 通知大家一下,有人上限了
castMsg(ct.getOwerUser(), "我上线啦!!目前人数" + stList.size());
stList.add(ct);
SwingUtilities.updateComponentTreeUI(MainServerUI.table_onlineUser);
} // 给队列中某一个用户发消息
public static void sendMsg2One(int index, String msg) {
stList.get(index).sendMsg2Me(msg);
} // 根据表中选中索引,取得处理线程对象对应的用户对象
public static UserInfo getUser(int index) {
return stList.get(index).getOwerUser();
} /*
* 移除队列中指定位置的处理线程对象,界面踢人时调用
*
* @param index:要移除的位置
*/
public static void removeClient(int index) {
stList.remove(index).closeMe();
} /*
* 从队列中移除一个用户对象对应的处理线程
*
* @param user:要一处的用户对象
*/
public static void removeAllClient(UserInfo user) {
for (int i = 0; i < stList.size(); i++) {
ServerThread ct = stList.get(i);
stList.remove(i);
ct.closeMe();
ct = null;
castMsg(user, "我下线啦");
}
} /*
* 服务器关闭时,调用这个方法,移除,停止队列中所有处理线程对象
*/
public static void removeAllClient() {
for (int i = 0; i < stList.size(); i++) {
ServerThread st = stList.get(i);
st.sendMsg2Me("系统消息:服务器即将关闭");
st.closeMe();
stList.remove(i);
System.out.println("关闭了一个..." + i);
st = null;
}
} /*
* 将一条消息发送给队列中的其他客户机处理对象
*
* @param sender:发送者用户对象
*
* @param msg:要发送的消息内容
*/
public static void castMsg(UserInfo sender, String msg) {
msg = sender.getName() + "说:" + msg;
for (int i = 0; i < stList.size(); i++) {
ServerThread st = stList.get(i); // ServerThread类中定义有一个将消息发送出去的方法
st.sendMsg2Me(msg);// 发送给每一个客户机
}
}
}

服务端界面

MainServerUI.java

package MyChatV1;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List; import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities; /*
* 服务器端管理界面程序
* 1.启/停
* 2.发布公告消息
* 3.显示在线用户信息
* 4.踢人
* 5.对某一个人发消息
*
*/
public class MainServerUI { private ChatServer cserver;// 服务器对象
private JFrame jf;// 管理界面
static JTable table_onlineUser;// 在线用户表
private JTextField jta_msg;// 发送消息输入框
private JTextField jta_port;// 服务器端口号输入端
private JButton bu_control_chat;// 启动服务器的按钮 public static void main(String[] args) {
// TODO Auto-generated method stub
MainServerUI mu = new MainServerUI();
mu.showUI();
} // 初始化界面
public void showUI() {
jf = new JFrame("javaKe服务器管理程序");
jf.setSize(500, 300);
FlowLayout f1 = new FlowLayout();
jf.setLayout(f1); JLabel la_port = new JLabel("服务器端口:");
jf.add(la_port);
jta_port = new JTextField(4);
jta_port.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent e) {
int keyChar = e.getKeyChar();
if (keyChar >= KeyEvent.VK_0 && keyChar <= KeyEvent.VK_9) { } else {
e.consume();// 屏蔽掉非法输入
}
}
});
jf.add(jta_port);
bu_control_chat = new JButton("启动服务器");
jf.add(bu_control_chat);
//启动的事件监听器
bu_control_chat.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
actionServer();
}
}); JLabel la_msg = new JLabel("要发送的消息");
jf.add(la_msg);
// 服务器要发送消息的输入框
jta_msg = new JTextField(30);
// 定义一个监听器对象:发送广播消息
ActionListener sendCaseMsgAction = new ActionListener() {
public void actionPerformed(ActionEvent e) {
sendAllMsg();
}
}; // 给输入框加航事件监听器,按回车时发送
jta_msg.addActionListener(sendCaseMsgAction);
JButton bu_send = new JButton("Send");
// 给按钮加上发送广播消息的监听器
bu_send.addActionListener(sendCaseMsgAction);
jf.add(jta_msg);
jf.add(bu_send); // 界面上用以显示在线用户列表的表格
table_onlineUser = new JTable();
// 创建我们自己的Model对象:创建时,传入处理线程列表
List<ServerThread> sts = ChatTools.getAllThread();
UserInfoTableModel utm = new UserInfoTableModel(sts);
table_onlineUser.setModel(utm);
// 将表格放到滚动面板对象上
JScrollPane scrollPane = new JScrollPane(table_onlineUser);
// 设定表格在面板上的大小
table_onlineUser.setPreferredScrollableViewportSize(new Dimension(400, 100));
// 超出大小后,JScrollPane自动出现滚动条
scrollPane.setAutoscrolls(true);
jf.add(scrollPane);// 将scrollPane对象加到界面上 // 取得表格上的弹出菜单对象,加到表格上
JPopupMenu pop = getTablePop();
table_onlineUser.setComponentPopupMenu(pop);
jf.setVisible(true);
jf.setDefaultCloseOperation(3);// 界面关闭时,程序退出 } /*
* 创建表格上的弹出菜单对象,实现发信,踢人功能
*/
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();
// 哪个菜单项点击了,这个s就是其设定的ActionCommand
popMenuAction(s);
}
};
mi_send.addActionListener(al);
mi_del.addActionListener(al);// 给菜单加上监听器
pop.add(mi_send);
pop.add(mi_del);
return pop;
} // 处理弹出菜单上的事件
private void popMenuAction(String command) {
// 得到在表格上选中的行
final int selectIndex = table_onlineUser.getSelectedRow();
if (selectIndex == -1) {
JOptionPane.showMessageDialog(jf, "请选中一个用户");
return;
}
if (command.equals("del")) {
// 从线程中移除处理线程对象
ChatTools.removeClient(selectIndex);
} else if (command.equals("send")) {
UserInfo user = ChatTools.getUser(selectIndex);
final JDialog jd = new JDialog(jf, true);// 发送对话框
jd.setLayout(new FlowLayout());
jd.setTitle("您将对" + user.getName() + "发信息");
jd.setSize(200, 100);
final JTextField jtd_m = new JTextField(20);
//jtd_m.setPreferredSize(new Dimension(150,30));
JButton jb = new JButton("发送!");
// jb.setPreferredSize(new Dimension(50,30));
jd.add(jtd_m);
jd.add(jb);
// 发送按钮的事件实现
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("发送了一条消息啊...");
String msg = "系统悄悄说:" + jtd_m.getText();
ChatTools.sendMsg2One(selectIndex, msg);
jtd_m.setText("");// 清空输入框
jd.dispose();
}
});
jd.setVisible(true);
} else {
JOptionPane.showMessageDialog(jf, "未知菜单:" + command);
}
// 刷新表格
SwingUtilities.updateComponentTreeUI(table_onlineUser);
} // 按下发送服务器消息的按钮,给所有在线用户发送消息
private void sendAllMsg() {
String msg = jta_msg.getText();// 得到输入框的消息
UserInfo user = new UserInfo();
user.setName("系统");
user.setPwd("pwd");
ChatTools.castMsg(user, msg); // 发送
jta_msg.setText("");// 清空输入框 } // 响应启动/停止按钮事件
private void actionServer() {
// 1.要得到服务器状态
if (null == cserver) {
String sPort = jta_port.getText();// 得到输入的端口
int port = Integer.parseInt(sPort);
cserver = new ChatServer(port);
cserver.start();
jf.setTitle("QQ服务器管理程序 :正在运行中");
bu_control_chat.setText("Stop!");
} else if (cserver.isRunning()) {// 己经在运行
cserver.stopchatServer();
cserver = null;
// 清除所有己在运行的客户端处理线程
ChatTools.removeAllClient();
jf.setTitle("QQ服务器管理程序 :己停止");
bu_control_chat.setText("Start!");
} }
}

其中用到了一个自己创建的类UserInfoTableModel,用于菜单中的信息显示

public class UserInfoTableModel implements TableModel {
List<ServerThread> sts = ChatTools.getAllThread();
public UserInfoTableModel(List<ServerThread> sts) {
this.sts=sts; }
@Override
public void addTableModelListener(TableModelListener l) {
// TODO Auto-generated method stub } @Override
public Class<?> getColumnClass(int columnIndex) {
// TODO Auto-generated method stub
return String.class;
} @Override
public int getColumnCount() {
// TODO Auto-generated method stub
return 1;
} @Override
public String getColumnName(int columnIndex) {
// TODO Auto-generated method stub
return null;
} @Override
public int getRowCount() {
// TODO Auto-generated method stub
return sts.size();
} @Override
public Object getValueAt(int rowIndex, int columnIndex) {
// TODO Auto-generated method stub
return sts.get(rowIndex).getOwerUser().getName();
} @Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
// TODO Auto-generated method stub
return false;
} @Override
public void removeTableModelListener(TableModelListener l) {
// TODO Auto-generated method stub } @Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// TODO Auto-generated method stub } }

这样我们的聊天室就大功告成了。(这个聊天室的客户端界面我还没做,打算对上面代码重新整理之后再写。当然也期待你为这个聊天室添加上一个界面)

JAVA-Socket通信 打造属于自己的聊天室(服务端)的更多相关文章

  1. AgileEAS.NET SOA 中间件平台.Net Socket通信框架-简单例子-实现简单的服务端客户端消息应答

    一.AgileEAS.NET SOA中间件Socket/Tcp框架介绍 在文章AgileEAS.NET SOA 中间件平台Socket/Tcp通信框架介绍一文之中我们对AgileEAS.NET SOA ...

  2. JAVA Socket通信 打造属于自己的网盘

    近一个月没敲JAVA代码了,最近老师布置了一个写JAVA网盘的作业,总共花了十几个小时,总算写完了,debug真的累,感觉自己还是菜了,没有那种有一个想法就能马上用代码实现的能力....不扯了,下面开 ...

  3. java 用socket制作一个简易多人聊天室

    代码: 服务器端Server import java.io.*; import java.net.*; import java.util.ArrayList; public class Server{ ...

  4. 与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室

    原文:与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...

  5. 与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室

    原文:与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...

  6. Java Socket通信实例

    一.简单的客户端与服务器一对一连接: Socket通信的步骤: 1.创建ServerSocket和Socket 2.打开连接到Scket的输入/输出流 3.按照协议对Socket进行读/写操作 4.关 ...

  7. java socket通信-传输文件图片--传输图片

    ClientTcpSend.java   client发送类 package com.yjf.test; import java.io.DataOutputStream; import java.io ...

  8. java Socket通信使用BufferedReader和BufferedWriter的注意事项

    注意事项:readLine()要求有换行标识,write()要输出换行标识,要调用flush()刷新缓冲区. 以下是取自java socket通信中的一小段代码. BufferedReader rea ...

  9. Java Socket通信读取相关信息代码

    转自:http://developer.51cto.com/art/201003/190206.htm Java Socket通信读取有不少需要我们注意的知识点.当我们在使用的时候有很多的问题摆在我们 ...

随机推荐

  1. [学习笔记]凸优化/WQS二分/带权二分

    从一个题带入:[八省联考2018]林克卡特树lct——WQS二分 比较详细的: 题解 P4383 [[八省联考2018]林克卡特树lct] 简单总结和补充: 条件 凸函数,限制 方法: 二分斜率,找切 ...

  2. HDU 4280 ISAP+BFS 最大流 模板

    Island Transport Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Other ...

  3. Java基础-JVM调优策略简介

    Java基础-JVM调优策略简介 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.JVM结构分析 1>.JVM结构图 2>.JVM运行时数据区功能说明 JVM管理的内 ...

  4. Python常用模块-时间模块(time&datetime)

    Python常用模块-时间模块(time & datetime) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.初始time模块 #!/usr/bin/env pyth ...

  5. [C]语法, 知识点总结(一. 进制, 格式化输入/出, 指针)

    进制 概念: n进制, 最大的数是n-1, 逢n进1位. 数据类型 概念: 其实就是占的位数不同, 转换到计算机当中都是0和1. 常用: 类型名 占字节数 描述 char 1字节=8个二进制位 字符类 ...

  6. (64位)本体学习程序(ontoEnrich)系统使用说明文档

    系统运行:文件夹system下,可执行文件ontoEnrichment 概念学习 --------------------------------------------------------1.简 ...

  7. Java面试题系列(一)描述一下JVM加载class文件的原理机制

    JVM系列第4讲:从源代码到机器码,发生了什么? https://www.cnblogs.com/chanshuyi/p/jvm_serial_04_from_source_code_to_machi ...

  8. ASP.NET Web API queryString访问的一点总结

    自从开始使用ASP.NET Web API,各种路由的蛋疼问题一直困扰着我,相信大家也都一样. Web API的路由配置与ASP.MVC类似,在App_Start文件夹下,有一个WebApiConfi ...

  9. shell 判断文件出现次数

    判断 file 文件中 第一个变量 出现次数 awk '{print $1}' file |sort |uniq -c|sort -k1r

  10. Linux - awk 文本处理工具三

    AWK 文件打印匹配 格式示例 awk '/Tom/' file # 打印匹配到得行 awk '/^Tom/{print $1}' # 匹配Tom开头的行 打印第一个字段 awk '$1 !~ /ly ...