模拟QQ聊天
一、要求
1、一个服务器可以与多个用户同时通讯
2、用户可以通过服务器与用户之间通讯
3、用户可以选择和所有人发消息,也可以选择和某个用户单独发消息
4、服务器要显示当前所有在线人员
5、用户要显示当前在线的人员
6、当有新用户登录时或在线用户退出时,服务器要向所有其他在线用户发送提示信息,并且服务器也要显示相应的提示信息
7、不能有相同的用户名同时登陆
8、不能发送空消息
9、客户端可以设置连接的服务器IP和端口
二、了解B/S模式的底层socket通讯原理
QQ聊天可以利用协议方式发送消息。所以先要了解浏览器和服务器直接的协议,从而仿照。
浏览器的请求
GET / HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 192.168.31.169:9090
DNT: 1
Connection: Keep-Alive
请求行,包含: 请求方式(GET或POST) 空格 请求的资源路径 空格 http的协议版本
接下来是请求消息头(...)
空行
请求体(包括浏览器向服务器提交的表单数据等)
HTTP/1.1 200 OK
Date: Fri, 11 Sep 2015 12:33:43 GMT
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Set-Cookie: JSESSIONID=409C8CF8220AD78D26D47B15DCEADCD3; Path=/; HttpOnly
Vary: Accept-Encoding
Connection: close
Transfer-Encoding: chunked
Content-Language: zh-CN
应答行,包含:http协议版本 空格 应答状态码 空格 应答状态码信息码描述
应答消息头(...)
空行
应答体(页面内容)
三、QQ聊天协议
在服务器端 用一个HashMap<userName,socket> 维护所有用户相关的信息,从而能够保证和所有的用户进行通讯。
客户端的动作:
(1)连接(登录):发送userName 服务器的对应动作:1)界面显示,2)通知其他用户关于你登录的信息, 3)把其他在线用户的userName通知当前用户 4)开启一个线程专门为当前线程
服务
(2)退出(注销):
(3)发送消息
※※发送通讯内容之后,对方如何知道是干什么,通过消息协议来实现:
客户端向服务器发的消息格式设计:
命令关键字@#接收方@#消息内容@#发送方
连接:userName ----握手的线程serverSocket专门接收该消息,其它的由服务器新开的与客户进行通讯的socket来接收
退出:exit@#全部@#null@#userName
发送: on @# JList.getSelectedValue() @# tfdMsg.getText() @# tfdUserName.getText()
服务器向客户端发的消息格式设计:
命令关键字@#发送方@#消息内容
登录:
1) msg @#server @# 用户[userName]登录了 (给客户端显示用的)
2) cmdAdd@#server @# userName (给客户端维护在线用户列表用的)
退出:
1) msg @#server @# 用户[userName]退出了 (给客户端显示用的)
2) cmdRed@#server @# userName (给客户端维护在线用户列表用的)
发送:
msg @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
四、注解和实现代码
ClientForm.java类
package com.sina.chat;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.border.TitledBorder;
public class ClientForm extends JFrame implements ActionListener{
private JTextField userName;//用户名
private DefaultListModel lm;//用于维护在线用户列表
private JList list;
private JTextField msg;//发送消息口
private JTextArea allMsg;
private JButton btnConn;
private String HOST="127.0.0.1";//服务器地址
private int PORT = 9090;//服务器端口号
public ClientForm(){
addJMnuBar();//添加并处理自定义菜单
JPanel upP = new JPanel();
upP.add(new JLabel("用户标识"));
userName = new JTextField(10);
upP.add(userName);
//上部面板
btnConn = new JButton("连接");
btnConn.setActionCommand("conn");
btnConn.addActionListener(this);
upP.add(btnConn);
JButton btnExit = new JButton("退出");
btnExit.setActionCommand("exit");
btnExit.addActionListener(this);
upP.add(b