如何实现基于网络通信的QQ聊天室


目录

  • 认识网络通信
  • 实现QQ聊天室
  • 项目总结
一.认识网络通信
1.前言

作为一名大一的学生,虽然马上就要奔大二啦,但是由于今年下学期学习都是在线上进行的,对课程学习的积极性也不是很高,课程也是学的一知半解的,对于学习Java编程就更没有什么较大的进展,自己就特别内疚,想一想暑假一定要做点什么。

期末考试结束之后,这几天一直在学习网络通信这一块。之前对于网络通信也有相关的了解,学习一些基本的输入输出流和一些基础的框架,但是这些都非常表面和零散,于是花了4天时间把网络通信深入学习了一下,把一个简单的QQ聊天室给实现了。虽然刚开始看到一堆代码头晕眼花的,但是结果还是不错的。ヾ(•ω•`)o

这里简单说一下网络通信,简单理解,网络通信顾名思义就是指通过网络进行通信。那网络是什么大家一定非常清楚了吧,你使用的手机等一些电子设备都需要联网才能外界进行通信(相互联系),这样你应该就知道网络通信的大致一些内容啦。所谓通信官方地说就是:人与人之间通过某种媒体进行信息交流 。而这种媒体就是互联网。

网络通信是通过网络将各个孤立的设备进行连接,通过信息的交互实现人与人,人与计算机,计算机与计算机的通信。而网络通信最基础最重要的就是网络通信协议。说到网络通信协议,现在的网络协议有很多,有关于局域网的ISO协议、TCP/IP协议,还有关于工控网和广域网的一堆协议,这里我就不举例了。废话不多说,今天着重带大家了解一下TCP/IP协议。

2.TCP/IP协议

虽然我也不想用QQ作为网络通信技术的代言人,虽然我不是很喜欢它,但是它对于我们肯定是再熟悉不过了!

但你在QQ消息框输入一段文字按下发送键之后,这段文字就会出现在另一台电脑或者手机设备上的QQ中--------这是如何实现的?是不是感到很神奇?如下图所示:

首先,你要想一想:为啥我发出的信息会到我指定的好友的机器(手机或电脑)上,而不是别人的手机上。这个很重要,因为你至少要知道,每一台设备都有一个独立的IP地址,包括手机和电脑及一些与外界通信的设备。IP地址在网络中标识了一台机器的位置,以便另一台机器可以找到它,所以当你发送信息给好友的时候,你发出不仅仅只有你说的话,其中还包括好友的IP地址,其实这其中还包括很多东西,具体的后面我还会说的。

其实仅仅知道IP地址是不够的,用QQ发送信息要到你的好友的电脑上,必须要指明哪一个端口,就像你要到某栋楼的好友家里做客,你光是知道是那一栋楼是不够的,你还必须知道朋友家的门牌号。在一台计算机中用端口号这个数字来标识机器上需要同通信的某一个程序。所以你发送信息指定的端口号应该是QQ通信模块所对应的端口号。

对于端口号,一定要知道它不是硬件端口,它只是TCP/UDP上的一个逻辑号码。这里我们在窗口命令行输入netstat -an即可知道自己的计算机哪些端口号是被使用的。如下图:

这里大家可以自己打开自己的命令行窗口去看看!!!!!

IP地址和端口号是密不可分的。对于IP地址和端口号我建议大家可以详细了解一下,我只就简单地说明它们的大致解释:

IP地址:IP地址是一个32位的二进制数,是由4个字节组成,我们都知道每个字节是一个8位二进制数。IP地址通常被表示为(a.b.c.d)的形式,如本机IP(127.0.0.1),IP地址分成五个类别,具体这里就不细说了,我们通常用的是B类地址。整体来说:如果把个人电脑比作一台电话的话,那么IP地址就相当于电话号码。

Port:端口号:每台机器都有0~65535个端口号,其中每一个数字可供一个程序通信使用,而且通常情况下0-1024的端口号我们一定要避免去使用它,我们一般称为它们为知名端口,就是一般网页和系统已经分配好的端口,例如打开网页的时候,则会连接服务器的80端口,在地址栏不需要输入这个端口号,因为它是默认的。

如果要实现与对方通信,我们可以在窗口命令行中用telnet命令(可能有些人该命令是用不了,因为没有电脑打开该功能,这里大家可以去网上搜搜如何打开的)输入对方的地址和对应的端口号,这里我就拿taobao.com来举例吧,如果我要连接淘宝网对应的服务器,因为上面说了它默认它是80端口,如下图:

按下回车键后出现如下界面:

虽然我们已经连上了淘宝网页的服务器,但是如果要发送字节的时候,它会切断与你的连接,因为你发送的东西并不符合其服务器制定的规范,如果在七八十年代,很多黑客可能会通过这种方式向服务器发送大量的字节,如果数据量太大,服务器就不能处理别的客户发来的信息,从而实现网络攻击服务器的现象,但是现在网络通信制定一系列的规范来达到管理大家的网络通信的安全。这里我来简单说说TCP/IP协议:

TCP/IP协议
TCP/IP协议在一定程度上参考了OSI的体系结构。建议先了解一下OSI模型的一个层次划分,它从下到上分为:物理层、数据链路层、网络层、运输层、会话层、表示层和应用层。而TCP/IP协议被简化为四个层次。


TCP和IP协议分别位于传输层和网络层,其实TCP/IP协议不仅仅指的是TCP和IP协议,它指的是一个由FTP\SMTP\TCP\UDP\IP等协议构成的协议簇,只是由于TCP\IP协议最具有代表性,所以被称为TCP\IP协议。

那么TCP\IP到底有啥呢?你咋不说清楚。下面就说说它的作用:

TCP/IP协议:它其实就是一种规则,它是方便去将所有的不同类型的计算机连接在一起的一套规范,但如果由设备要传输文件或信息的时候,它就要去遵循这套规范,它可以规定发送信息的组成和传输的方式。

总之:它就是一种机器之间交流的规则,规定通信双方的通信流程和数据格式的规则,要参与通信的双方都只有按照这个规则发送数据才可以正常通信,否则就不能进行通信,就像人与人之间要用同一种语言交流一样。

3.认识Java中的Socket类

对于该类的作用,大家想必已经知道了,它是Java负责网络通信的类,先来看看的API文档对该类的介绍:

由上面API文档的解释可知它是java网络编程中的一个类,而且它说它是客户端的一个套接字,只是两台机器通信的一个端点。这里的另一个端点就是服务器所在端点,它是ServerSocket:负责创建服务器并且等待客户的连接。其中一些详细的方法就不过多的说了。既然现在已经了解地差不多了,接下来就直接上代码了。

二.实现QQ聊天室

首先说一说为什么我们在编写代码前要进行一个框架的搭建,其实如果是一些简单的项目就不要这么大费周章,但是如果你要做的项目涉及到的类比较多,光代码就要1000多行的话,这可能就要进行一波框架的设计。
好处:

  • 验证编程项目的可行性
  • 避免代码的高耦合问题
  • 加快我们编写代码的速度
1.框架搭建
  • 服务器端框架
  1. UerInformation.java:用户数据模型类,功能:每个UserInformation类的对象保存一个用户的账号和身份信息。
  2. DaoTools.java:数据访问和验证类,功能:负责生成模拟的用户的信息,并在客户登入时验证账号和密码。
  3. UserModel.java:用户列表类,功能:在服务器管理界面上见在线用户以列表的形式展现,便于对用户进行处理。
  4. UserModelRender.java:用户列表渲染类,功能:对用户列表进行一些外观的设置和事件的处理。
  5. SocketServer.java:服务器线程类,功能:创建服务器并启动,等待连接,将进入的连接交给线程对象去处理,以防连接客户端的阻塞影响其他客户的连接和其他程序的运行。
  6. ServerThread.java:处理Socket对象的线程类,功能:每进入一个连接,就需要创建一个该类的对象,在run方法中负责对该连接的客户进行交互,对于服务器而言:一个ServerThread对象就对应了一个客户端。
  7. ServerThreadTools.java:服务器端的辅助类,功能:负责将客户机发来的消息转发给其他的客户机(群发),这里要创建一个队列的对象来存放服务器生成的每一个ServerThread对象,该类可以对客户机进行一些处理并且返回客户机的一些信息。
  8. MainServerUI.java:服务器界面管理程序类,功能:主要实现界面展示,事件处理的能力(1.启停服务器 2.发布公告消息 3.显示在线用户信息 4.踢人 5.对某一个人发消息)。
  • 客户端框架
  1. MainNetUI.java:客户端登录和聊天界面类,功能:产生登录界面,验证通过后显示聊天界面,进行与服务器的通信。
  2. NetClientThread.java:客户端连接服务器通信线程类,功能:连接服务器并且收发消息类的封装。
2.实现框架中的每一部分
  • 服务器端框架
  1. UerInformation.java:用户数据模型类
/*** 用户数据模型类* @author 梦想少年* 功能:每个UserInformation类的对象保存一个用户的账号和身份信息*/
public class UserInformation {//构造器public UserInformation(){  }public UserInformation(ImageIcon icon,String name,String password){this.icon=icon;this.name=name;this.password=password;} public UserInformation(String name,String password){this.name=name;this.password=password;}//用户头像private ImageIcon icon;//用户名private String name;//用户的密码private String password;//用户的登陆时的时间private String loginTime;//用户的地址private String address;//注册设置的方法public void setIcon(ImageIcon icon){this.icon=icon;}public void setName(String name){this.name=name;}public void setPassWord(String password){this.password=password;}public void setLoginTime(String loginTime){this.loginTime=loginTime;}public void setAddress(String address){this.address=address;}//取得用户信息的方法public ImageIcon getIcon(){return icon;}public String getName(){return name;}public String getPassWord(){return password;}public String getLoginTime(){return loginTime;}public String getAddress(){return address;}
}
  1. DaoTools.java:数据访问和验证类
/*** 数据访问和验证类* @author 梦想少年* 功能:负责生成模拟的用户的信息,并在客户登入时验证账号和密码*/
public class DaoTools {@SuppressWarnings({ "unchecked", "rawtypes" })//用户列表public static List<UserInformation> userList=new ArrayList();//生成模拟的用户信息@SuppressWarnings({ "rawtypes", "unchecked" })private static Map<String,UserInformation> userDataBase=new HashMap();//在程序启动时加载用户数据static {for(int i=0;i<10;i++){UserInformation user=new UserInformation();user.setIcon(new ImageIcon("D:\\常用\\矢量图标库\\listUser\\"+(i+1)+".png"));user.setName("梦想少年"+i);user.setPassWord("123456789");userDataBase.put(user.getName(),user);}}//登陆时验证用户信息public static boolean checkLogin(UserInformation user){if(userDataBase.containsKey(user.getName())){if(userDataBase.containsKey(user.getPassWord())){return true;}else{System.out.println("用户密码错误!"+user.getPassWord());  return false;}}else{System.out.println("用户名不存在!"+user.getName());return false;}}
}
  1. UserModel.java:用户列表类
/*** 用户列表类* @author 梦想少年* 功能:在服务器管理界面上见在线用户以列表的形式展现,便于对用户进行处理* @param <E>*/public class UserModel<E> implements ListModel<E> {  List<E> allUserThread;//构造器public UserModel(List<E> allUserThread) {// TODO Auto-generated constructor stubthis.allUserThread=allUserThread;}@Overridepublic int getSize() {// TODO Auto-generated method stubreturn allUserThread.size();}@Overridepublic E getElementAt(int index) {// TODO Auto-generated method stubreturn allUserThread.get(index);}@Overridepublic void addListDataListener(ListDataListener l) {// TODO Auto-generated method stub  }@Overridepublic void removeListDataListener(ListDataListener l) {// TODO Auto-generated method stub}} 
  1. ModelRender.java:用户列表渲染类
/*** 用户列表渲染类* @author 梦想少年* 功能:对用户列表进行一些外观的设置和事件的处理* @param <E>*/public class ModelRender<E> implements ListCellRenderer<E> {//容器JPanel container=new JPanel();//头像标签JLabel iconLable=new JLabel();//用户名标签JLabel nameLable=new JLabel();//密码标签JLabel passwordLable=new JLabel();//初始化public ModelRender() {// TODO Auto-generated constructor stubcontainer.setLayout(new FlowLayout());container.setPreferredSize(new Dimension(450,20));container.add(iconLable,BorderLayout.WEST);container.add(nameLable,BorderLayout.CENTER);container.add(passwordLable,BorderLayout.EAST);}@Overridepublic Component getListCellRendererComponent(JList<? extends E> list,E value, int index, boolean isSelected,boolean cellHasFocus) {// TODO Auto-generated method stub//获取当前用户UserInformation user=(UserInformation)value; //设置组件要显示的内容iconLable.setIcon(user.getIcon());nameLable.setText(user.getName());passwordLable.setText(user.getPassWord());//颜色交替if(index%2==0){container.setBackground(Color.lightGray);}else{container.setBackground(Color.lightGray);}    //选中后的颜色if(isSelected){container.setBackground(Color.PINK);nameLable.setBackground(Color.RED);}return container;}}
  1. SocketServer.java:服务器线程类
/*** 服务器线程类* @author 梦想少年* 功能:  创建服务器并启动,等待连接,*      将进入的连接交给线程对象去处理,以防连接客户端的阻塞影响其他客户的连接和其他程序的运行。*/public class SocketServer extends Thread {private int port;//端口号private boolean running;//服务器启停标志private ServerSocket socket;//服务器对象//构造器传入端口号public SocketServer(int port) {// TODO Auto-generated constructor stubthis.port=port;}//检查该服务器是否启动public boolean isRunning(){return running;}//在线程中启动服务器public void run(){setupServer(); }//启动服务器private void setupServer(){try {socket=new ServerSocket(this.port);running=true;System.out.println("服务器创建成功!!!"+"Port:"+this.port+"\r\n"+"正在等待客户的接入");while(running){//等待客户接入Socket client=socket.accept();System.out.println("A customer coming!!!"+"\r\n"+"客户连接为:"+client.getRemoteSocketAddress().toString());//启动一个线程去处理该客户对象ServerThread thread=new ServerThread(client);thread.start();}   } catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//关闭服务器的方法public void stopSocketServer(){this.running=false;try {socket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}  
  1. ServerThread.java:处理Socket对象的线程类
// An highlighted block
var foo = 'bar';
/*** 处理Socket对象的线程类* @author 梦想少年* 功能:  每进入一个连接,就需要创建一个该类的对象,在run方法中负责对*      该连接的客户进行交互,对于服务器而言:一个ServerThread对象就对应了一个客户端。*/
public class ServerThread extends Thread{ private Socket client;//客户端连接对象private InputStream ins;private OutputStream out;private UserInformation user;//客户对象//传入Socket对象public ServerThread(Socket client) {// TODO Auto-generated constructor stubthis.client=client;}//返回Socket对象对应客户对象的方法public UserInformation getOwerUser(){return this.user;}//服务器发送消息给该该线程对应的客户端的方法public void sendMsg(String msg){ try {msg+="\r\n";  out.write(msg.getBytes());out.flush();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//结束通信,服务器断开与客户连接的方法public void closeClient(){try {client.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//启动对应每一个客户线程的方法public void run(){processSocket();} private void processSocket(){try {ins=client.getInputStream();out=client.getOutputStream();//将输入流ins封装为可以读取一行字符串 ,也就是以\r\n结尾的字符串 BufferedReader read=new BufferedReader(new InputStreamReader(ins));//客户登陆操作sendMsg("欢迎你来聊天!!!"+"\r\n"+"请输入你的用户名:");String userName=read.readLine();sendMsg("请输入你的密码:");String userPassWord=read.readLine();//初始化用户信息user=new UserInformation();user.setName(userName);user.setPassWord(userPassWord);//调用用户数据模块,验证用户是否存在boolean loginState=DaoTools.checkLogin(user);if(!loginState){//如果账号密码出错,则关闭服务器,断开连接this.closeClient();}//登陆成功则将该客户线程对象加入到客户线程队列中去ServerThreadTools.addClient(this);//接收客户机发送来的消息,一行一读!!!!!String inputMsg=read.readLine();while(!inputMsg.equals("bye!")){System.out.println("服务器收到:"+inputMsg);//实现群发ServerThreadTools.castMsg(this.user,inputMsg);inputMsg=read.readLine();//读取下一条消息}//下线ServerThreadTools.castMsg(this.user,"我先下线啦,再见!!!");//从队列移除@@@@@未实现@@@@  //结束通信this.closeClient();  } catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
  1. ServerThreadTools.java:服务器端的辅助类
// An highlighted block
var foo = 'bar';public class ServerThreadTools {//客户线程队列@SuppressWarnings({ "rawtypes", "unchecked" })public static List<ServerThread> socketList=new ArrayList();//将线程客户对象加入到队列的方法public static void addClient(ServerThread client){castMsg(client.getOwerUser(),"我上线啦!!!"+"目前人数"+socketList.size());socketList.add(client);}  //利用队列实现服务器的群发消息功能public static void castMsg(UserInformation user,String msg){//转换人称msg=user.getName()+"说:"+msg;for(int i=0;i<socketList.size();i++){ServerThread client=socketList.get(i);client.sendMsg(msg);}}//根据表格选中的索引,服务器给某个用户发送消息public static void sendMsgOneClient(int index,String msg){msg+="系统说:"+"\r\n";socketList.get(index).sendMsg(msg);  }  //根据表格中选中的索引,返回该线程对象对应的用户型类public static UserInformation getUser(int index){return socketList.get(index).getOwerUser();}//返回线程客户列表的方法public static List<ServerThread> getSocketList(){return socketList;}//移除表格上选中的客户线程对象public static void removeSelectClient(int index){ServerThread client=socketList.get(index);castMsg(client.getOwerUser(),"我下线啦!!!");//通知大家自己下线啦socketList.remove(index);//从队列中移除client.closeClient();//断开与服务器的连接client=null;}  //在服务器关闭的时候移除所有的线程客户对象public static void removeAllClient(){for(int i=0;i<socketList.size();i++){ServerThread client=socketList.get(i);client.sendMsg("服务器即将关闭......");client.closeClient();;socketList.remove(i);System.out.println("关闭了"+i+"个");client=null;}}}
  1. MainServerUI.java:服务器界面管理程序类
// An highlighted block
var foo = 'bar';
/*** 服务器界面管理程序类* @author 梦想少年* 功能:主要实现界面展示,事件处理的能力(1.启停服务器  2.发布公告消息 *      3.显示在线用户信息 4.踢人 5.对某一个人发消息)*/public class MainServerUI {private SocketServer socket;//服务器对象private JFrame jf;//管理界面private static List<UserInformation> userList;//用户列表private JList<UserInformation> list;private JTextField text_msg;//发送消息的输入框private JTextField text_port;//服务器端口号输入框private JButton button_start;//启停服务器按钮//主函数public static void main(String[] args) {// TODO Auto-generated method stubMainServerUI ui=new MainServerUI();ui.showUI();}//初始化界面private void showUI(){jf=new JFrame("Java服务器管理应用程序");jf.setSize(500,300);jf.setLayout(new FlowLayout());//流式结构JLabel lable_port=new JLabel("服务器端口号:");jf.add(lable_port);text_port=new JTextField(4);jf.add(text_port);button_start=new JButton("启动服务器");jf.add(button_start);button_start.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// TODO Auto-generated method stubactionServer();}});JLabel lable_msg=new JLabel("系统要发送的消息:");jf.add(lable_msg);//消息输入框text_msg=new JTextField();text_msg.setPreferredSize(new Dimension(380,25));//定义一个监听器对象,发送广播消息!!!!ActionListener sendCaseMsgAction=new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// TODO Auto-generated method stubsendAllMsg();}};//给输入框加入事件监听器,按回车键发送消息text_msg.addActionListener(sendCaseMsgAction);JButton bu_send=new JButton("Send");//给按钮加上发送广播消息的监听器bu_send.addActionListener(sendCaseMsgAction);jf.add(lable_msg);jf.add(text_msg);jf.add(bu_send);//用户列表标签JLabel listLable=new JLabel("用户列表");jf.add(listLable,BorderLayout.CENTER);//传入用户列表userList=DaoTools.userList;//创建ModelUserModel<UserInformation> model=new UserModel<>(userList);//创建RenderModelRender<UserInformation> render=new ModelRender<>();//用户列表list=new JList<>(model);//设置渲染器list.setCellRenderer(render);//设置滚动窗口JScrollPane scroll=new JScrollPane(list);scroll.setAutoscrolls(true);jf.add(scroll,BorderLayout.SOUTH);//对列表加入鼠标监听器list.addMouseListener(new MouseAdapter() {public void mouseClicked(MouseEvent e) {//取出表格上弹出的菜单对象,加到表格上JPopupMenu pop=getTablePop();list.setComponentPopupMenu(pop);}});      //显示窗体jf.setVisible(true);jf.setResizable(false);jf.getContentPane().setBackground(Color.LIGHT_GRAY);jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//界面关闭程序退出} //创捷表格上的弹出菜单对象,实现发信,踢人功能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");//设置菜单命令关键字JMenuItem mi_reset=new JMenuItem("刷新");//菜单项选项mi_reset.setActionCommand("Reset");//设置菜单命令关键字//弹出菜单上的事件监听器对象ActionListener al=new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// TODO Auto-generated method stubString s=e.getActionCommand();//哪个菜单项点击了,这个s就是其设定的getActionCommandpopMenuAction(s);}};//给菜单添加监听器mi_send.addActionListener(al);mi_del.addActionListener(al);mi_reset.addActionListener(al);pop.add(mi_send);pop.add(mi_del);pop.add(mi_reset);return pop;}//处理弹出菜单上的事件private void popMenuAction(String command){//得到在表格上选中的行final int selectIndex=list.getSelectedIndex();System.out.println(selectIndex);if(selectIndex==-1){//如果未选中行JOptionPane.showMessageDialog(jf,"请先选择一个用户");return;}if(command.equals("del")){//从队列中移除线程对象ServerThreadTools.removeSelectClient(selectIndex);}else if(command.equals("send")){//对某一位在线用户发送消息!!!!!!!!//判断是否有客户登入UserInformation user=ServerThreadTools.getUser(selectIndex);final JDialog jd=new JDialog(jf,true);//发送对话框jd.setTitle("你将对"+user.getName()+"发送信息");jd.setSize(400,180);jd.setLayout(new FlowLayout());final JTextField jtd_m=new JTextField();jtd_m.setPreferredSize(new Dimension(300,40));JButton jb=new JButton("发送");jb.setPreferredSize(new Dimension(80,50));jd.add(jtd_m);jd.add(jb);//发送按钮的事件实现jb.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// TODO Auto-generated method stubSystem.out.println("发送一条消息啊!!!");String msg="系统悄悄地说:"+jtd_m.getText();ServerThreadTools.sendMsgOneClient(selectIndex,msg);jtd_m.setText("");//清空输入框jd.dispose();}});jd.setVisible(true);}else if(command.equals("Reset")){System.out.println("呵呵!!!");}else{JOptionPane.showMessageDialog(jf,"未知菜单:"+command);}  }//启停服务器的方法private void actionServer(){//得到服务器的状态if(null==socket){String Port=text_port.getText();//得到输入的端口号int port=Integer.parseInt(Port);socket=new SocketServer(port);socket.start();jf.setTitle("QQ服务器管理程序:正在运行中");button_start.setText("Stop!");}else if(socket.isRunning()){socket.stopSocketServer();socket=null;//清除所有已在运行的客户端处理线程ServerThreadTools.removeAllClient();jf.setTitle("QQ服务器管理程序:已停止");button_start.setText("Start!");}}//按下发送服务器消息的按钮,给所有的在线用户发送消息private void sendAllMsg(){String msg=text_msg.getText();//获得输入框的消息//创建系统用户对象UserInformation user=new UserInformation();user.setName("系统");user.setPassWord("password");ServerThreadTools.castMsg(user, msg);//群发消息text_msg.setText("");//清空输入框}}
  • 客户端框架
  1. MainNetUI.java:客户端登录和聊天界面类
// An highlighted block
var foo = 'bar';
/*** 客户端登陆和聊天界面类* @author 梦想少年* 功能:产生登录界面,验证通过后显示聊天界面,进行与服务器的通信*/public class MainNetUI {private JFrame jf_login;//登入界面private JFrame jf_chat;//聊天主界面private JTextField jta_userName;private JTextField jta_pwd;private JTextArea jta_input=new JTextArea(8,20);//显示收到的消息组private NetClientThread conn;public static void main(String[] args) {MainNetUI ui=new MainNetUI();ui.showLoginUI();}//显示登入界面public void showLoginUI(){jf_login=new JFrame();jf_login.setTitle("仿QQ登录界面");//设置界面标题jf_login.setSize(400,250);//设置界面大小jf_login.setLocationRelativeTo(null);//设置界面居中jf_login.setResizable(false);//设置界面不可改变大小jf_login.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭程序,退出运行状态//西JPanel panelwest=new JPanel();panelwest.setBackground(Color.LIGHT_GRAY);panelwest.setPreferredSize(new Dimension(120,200));jf_login.add(panelwest,BorderLayout.WEST);ImageIcon image=new ImageIcon("D:\\常用\\图片\\java技术博客\\QQ.png");JLabel lable=new JLabel(image);lable.setPreferredSize(new Dimension(120,120));panelwest.add(lable);//东JPanel paneleast=new JPanel();paneleast.setBackground(Color.LIGHT_GRAY);paneleast.setPreferredSize(new Dimension(80,200));jf_login.add(paneleast,BorderLayout.EAST);JLabel lable1=new JLabel("注册账号");paneleast.add(lable1);JLabel lable2=new JLabel("忘记账号");paneleast.add(lable2);JLabel lable3=new JLabel("忘记密码");paneleast.add(lable3);//中JPanel panelcenter=new JPanel();panelcenter.setBackground(Color.LIGHT_GRAY);panelcenter.setPreferredSize(new Dimension(250,200));jf_login.add(panelcenter,BorderLayout.CENTER);jta_userName=new JTextField();jta_userName.setPreferredSize(new Dimension(180,30));panelcenter.add(jta_userName);jta_pwd=new JPasswordField();jta_pwd.setPreferredSize(new Dimension(180,30));panelcenter.add(jta_pwd);JCheckBox checkbox1=new JCheckBox("记住账号");checkbox1.setPreferredSize(new Dimension(180,30));panelcenter.add(checkbox1);JCheckBox checkbox2=new JCheckBox("记住密码");checkbox2.setPreferredSize(new Dimension(180,30));panelcenter.add(checkbox2);//南JPanel panelsouth=new JPanel();panelsouth.setBackground(Color.darkGray);panelcenter.setPreferredSize(new Dimension(400,50));jf_login.add(panelsouth,BorderLayout.SOUTH);JButton button=new JButton("登陆");button.setPreferredSize(new Dimension(100,30));panelsouth.add(button);button.addActionListener(new ActionListener(){/*** 添加动作监听器,判断登入是否成功*/@Overridepublic void actionPerformed(ActionEvent e) {// TODO Auto-generated method stubloginAction();}});jf_login.setVisible(true);   }//登陆事件处理private void loginAction(){String name=jta_userName.getText();String pwd=jta_pwd.getText();//创建连接对象conn=new NetClientThread("localhost",8080,jta_input);//连接上服务器if(conn.conn2Server()){//登陆if(conn.loginServer(name, pwd)){//显示聊天界面showMainUI();;  //启动连接线程conn.start();//关闭登陆界面//jf_login.dispose();}}else{JOptionPane.showMessageDialog(jf_login, "账号有误!!!user0~9");}} //显示聊天界面private void showMainUI(){jf_chat=new JFrame("聊天客户端v1.0");jf_chat.setLayout(new FlowLayout());jf_chat.setResizable(false);jf_chat.getContentPane().setBackground(Color.lightGray);jf_chat.setSize(350,450);//显示接收到的消息JLabel la=new JLabel("你将要发送的消息为:");//发送消息区final JTextArea jta_output=new JTextArea(5,20);//发送按钮JButton bu_send=new JButton("send");bu_send.setPreferredSize(new Dimension(120,80));bu_send.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// TODO Auto-generated method stubString msg=jta_output.getText();conn.sendMsg(msg);}});jf_chat.add(jta_input);jf_chat.add(la);jf_chat.add(jta_output);jf_chat.add(bu_send);jf_chat.setVisible(true);  }
}
  1. NetClientThread.java:客户端连接服务器通信线程类
// An highlighted block
var foo = 'bar';
/*** 客户端连接服务器通信线程类* @author 梦想少年* 功能:连接服务器并且收发消息类的封装*/public class NetClientThread extends Thread{private String serverIp;//服务器IPprivate int port;//服务器端口private OutputStream out;//到服务器的输出流对象private BufferedReader read;//到服务器的输入流对象//显示收到消息的组件,要从界面上传过来private JTextArea jta_receive;//构造器,创建客户机对象,传入服务器IP和端口public NetClientThread(String serverIp,int port,JTextArea jta_receive){this.serverIp=serverIp;this.port=port;this.jta_receive=jta_receive;}  //连接上服务器,判断是否连接成功public boolean conn2Server(){//创建一个到服务器端的Socket对象try {@SuppressWarnings("resource")Socket client=new Socket(serverIp, port);InputStream ins=client.getInputStream();//读取一行字符串,也就是以\r\n结尾的字符串read=new BufferedReader(new InputStreamReader(ins));out=client.getOutputStream();return true;} catch (UnknownHostException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return false;}   //登陆服务器/*1.用户名*2.密码*3.判断是否登陆成功                    */public boolean loginServer(String name,String pwd){try {//1.读取服务器发来的一条消息String input=read.readLine();System.out.println("服务器说:"+input);jta_receive.append(input+"\r\n");//将消息显示在界面上//按照服务器流程,发送用户名和密码name+="\r\n";//发送字符串后面必须跟上\r\n!!!out.write(name.getBytes());out.flush();System.out.println("客户机已将用户名发送,等服务器回应");input=read.readLine();System.out.println("服务器说:"+input);pwd+="\r\n";out.write(pwd.getBytes());out.flush();return true;} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return false;  }//线程中读取服务器发送过来的消息public void run(){while(true){readFromServer();}} //从服务器中读取消息,这个线程会阻塞,必须在独立的线程public void readFromServer(){try {String input=read.readLine();System.out.println("服务器说:"+input);jta_receive.append("服务器说:"+input+"\r\n");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//发送一条消息到服务器的方法public void sendMsg(String msg){try {msg+="\r\n";this.out.write(msg.getBytes());this.out.flush();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
3.实现的结果及问题分析

上面就是实现QQ聊天的全部代码,可能有一些功能还没有完善,如果有问题的小伙伴可以和我一起探讨更好的方案和一些问题(≧︶≦))( ̄▽ ̄ )

当我们运行客户端和服务器端的程序时,就会出现聊天界面啦!!!如下图:

相关的问题分析:

  • 代码的高耦合问题
    这个问题我之前也提到过,所谓代码的高耦合问题是一个比较常见的但程序员在编写代码时遇到的问题,特别是当面向对象编程时。

高耦合是指各个模块之间相互联系程度的紧密,如果模块之间的联系十分的紧密,这是我们不希望看到的,因为它的可拓展性就十分的差,如果代码很多的话,到时候要修改就十分困难,所谓牵一发而动全局,说的就是这个结果。到时后你可能就是这个状态:

为了避免高耦合问题,这里我也有一些意见:

  • 少使用类之间的继承,多用接口隐藏实现细节
  • 每个模块的功能尽可能单一,尽量减少被其他模块的调用
  • 类的属性和方法声明尽可能少用public,要多用private
  • 尽可能少使用全局变量,多使用局部变量

上面就是一些避免高耦合问题的方法,总之就是要体现封装的思想,这个思想非常重要,大家有兴趣可以了解一下。

  • 线程的应用
    上面的代码出现了很多次线程的使用,这里我就稍微说明一些原因:
    比如:
  //等待客户接入Socket client=socket.accept();

当创建服务之后,它就要一直处于Running的状态,知道有客户接入从才会进入到通信的线程中去。所以这里存在阻塞机制,在编写代码的时候我们一般为了避免某一个程序的阻塞而影响其他程序的运行,我们常常使用线程,因为每个线程是独立的,所以某个线程的阻塞不会影响到其他线程的正常运行。
同理,还有几处线程应用也是同一个道理,比如输入输出流这一块,这里我就不去过多的阐述了,相信你一定的会知道的。₯₯₯

其实上面的代码还是有一些不足的, 比如这里:

// An highlighted block
var foo = 'bar';//线程中读取服务器发送过来的消息public void run(){while(true){readFromServer();}} //从服务器中读取消息,这个线程会阻塞,必须在独立的线程public void readFromServer(){try {String input=read.readLine();System.out.println("服务器说:"+input);jta_receive.append("服务器说:"+input+"\r\n");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}  }

因为它是一直读取服务器发来的消息的,如果你的客户端突然和服服务端开的话,它读到只有null,所以你会看到一推null在输出,这大可不必惊讶

来,带你实现基于网络通信QQ聊天室-----QQ有这么强!!!相关推荐

  1. html制作类似qq聊天室,QQ聊天室主页设计

    下面是编程之家 jb51.cc 通过网络收集整理的代码片段. 编程之家小编现在分享给大家,也给大家做个参考.  无标题页 .style2 { font-family: 华文新魏; text-alig ...

  2. 网络编程-基于MFC的仿QQ聊天室-2020

    基于MFC的仿QQ聊天室(2020) 有幸学习过网络编程的一些知识,出于对编程的热爱,把曾经的一次简单实践编程作业进行了自定义的完成. 编程所需: 编程工具为VS 2010,需要掌握MFC的基本操作以 ...

  3. 基于JavaSwing开发聊天室(QQ聊天 群聊)系统+论文+PPT 大作业 毕业设计项目源码

    基于JavaSwing开发聊天室(QQ聊天 群聊)系统+论文+PPT(毕业设计/大作业) 开发环境: Windows操作系统 开发工具: Eclipse/Myeclipse+Jdk 演示视频: jav ...

  4. 基于java的聊天室系统设计与实现(项目报告+开题报告+答辩PPT+源代码+部署视频)

    项目报告 Java网络聊天室系统的设计与实现 计算机从出现到现在有了飞速的发展,现阶段的计算机已经不单单是用于进行运算的独立的个体了,跟随计算机一同发展的还有互联网技术,经过了长久的发展,互联网技术有 ...

  5. 基于WebSocket实现聊天室(Node)

    基于WebSocket实现聊天室(Node) WebSocket是基于TCP的长连接通信协议,服务端可以主动向前端传递数据,相比比AJAX轮询服务器,WebSocket采用监听的方式,减轻了服务器压力 ...

  6. java开发websocket聊天室_java实现基于websocket的聊天室

    [实例简介] java实现基于websocket的聊天室 [实例截图] [核心代码] chatMavenWebapp └── chat Maven Webapp ├── pom.xml ├── src ...

  7. Android 基于Socket的聊天室

    原文地址为: Android 基于Socket的聊天室 Socket是TCP/IP协议上的一种通信,在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路.一旦建立了虚拟的网络链路, ...

  8. SpringBoot + Vue 实现基于 WebSocket 的聊天室(单聊)

    前言 在前一篇文章SpringBoot 集成 STOMP 实现一对一聊天的两种方法中简单介绍了如何利用 STOMP 实现单聊,本文则将以一个比较完整的示例展示实际应用,不过本文并未使用 STOMP,而 ...

  9. 从0实现基于Linux socket聊天室-实现聊天室的公聊、私聊功能-4

    前面文章链接如下: <从0实现基于Linux socket聊天室-多线程服务器模型-1> <从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2> &l ...

最新文章

  1. win10内建子系统Linux
  2. 关于“中国大妈”的用户画像
  3. animation of android (1)
  4. [JavaWeb-HTTP]request对象和response对象的原理
  5. Linux 下的帮助命令
  6. Vue+element 解决浏览器自动填充记住的账号密码问题
  7. 2014年中国B2B行业十大预测
  8. 【学习笔记】流畅的Python第二版【第一章】
  9. tensorflow keras 构建神经网络、Alex net、VGG、CNN网络
  10. .Net core----使用容联云短信推送
  11. Linux中write函数
  12. STM32绝对地址赋值
  13. AI火爆干货最全整理!五套深度学习和算法学习教程和三套Python学习视频!!!限时无套路免费领取!...
  14. ad如何计算电路板的pin数量_各类EDA软件统计pin数方法
  15. numpy如何对txt文件读取_NumPy——文件读取与写入
  16. 潮光讲堂--3步搞懂光电耦合器
  17. fwrite和fread函数的用法小结
  18. ios视频直播SDK集成指引
  19. 宋图图的工学课程12
  20. ansys仿真论文描述

热门文章

  1. 加速C++程序编译的方法
  2. css样式 向下补白,div+css[3]:css中边框border与补白padding属性设置
  3. 我欲封神——JAVA封神之路
  4. 做一个可以赚钱的英文网站
  5. 概念模型(conceptualDataModel)
  6. python提取word中的表格写入excel
  7. CAD软件如何合并区间
  8. rsync不覆盖已经存在文件的方法
  9. 踢球游戏-运用list切片
  10. [laravel]laravel8自动生成api文档