要求:自定义一个服务器,接收浏览器发来的信息。显示浏览器发送了什么信息,并向浏览器发送简单的网页信息。
设计思想
在服务器端 用一个HashMap
客户端的动作:
(1)连接(登录):发送userName 服务器的对应动作:1)界面显示,2)通知其他用户关于你登录的信息, 3)把其他在线用户的userName通知当前用户 4)开启一个线程专门为当前线程服务
(2)退出(注销):
(3)发送消息
※※发送通讯内容之后,对方如何知道是干什么,通过消息协议来实现:
客户端向服务器发的消息格式设计:
命令关键字@#接收方@#消息内容@#发送方
1)连接:userName —-握手的线程serverSocket专门接收该消息,其它的由服务器新开的与客户进行通讯的socket来接收
2)退出:exit@#全部@#null@#userName
3)发送: 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])
这是客户端代码:
package cn.hncu.chat;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.List;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.TileObserver;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;
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.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
public class ClientForm extends JFrame implements ActionListener{
private JTextField tfdUserName;
private JTextArea allMsg ;
private JList list;
private JTextField tfdMsg;
private DefaultListModel lm;
private PrintWriter pw;
private static String HOST="127.0.0.1";//必须是静态的,因为内部的内部类要用,要改值,又不能是final
private static int PORT=9090;
private Socket socketClient;
public ClientForm() {
setBounds(300, 200, 400, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
//上边布局
JPanel panelUp=new JPanel();
panelUp.add(new JLabel("用户标识 : "));
tfdUserName=new JTextField(10);
tfdUserName.setFont(new Font("a", Font.BOLD, 16));
panelUp.add(tfdUserName);
JButton btnCon=new JButton("连接");
btnCon.setActionCommand("con");
panelUp.add(btnCon);
JButton btnExit=new JButton("断开");
btnExit.setActionCommand("exit");
panelUp.add(btnExit);
getContentPane().add(panelUp,BorderLayout.NORTH);
//中部布局
JPanel panelMid=new JPanel(new BorderLayout());
getContentPane().add(panelMid);
allMsg =new JTextArea();
allMsg.setEditable(false);
lm=new DefaultListModel();//分层设计,有利于维护
list=new JList(lm);
lm.addElement("全部");
list.setSelectedIndex(0);
// list.setVisibleRowCount(2);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);//此方法是在选择模型上直接设置选择模式的覆盖方法。
panelMid.add(new JScrollPane(allMsg) , BorderLayout.CENTER);
JScrollPane jp=new JScrollPane(list);//jp.setViewportView(list);//如果构造函数为空用着个添加
getContentPane().add(jp,BorderLayout.EAST);
jp.setBorder(new TitledBorder("在线"));//具有指定边框和标题
jp.setPreferredSize(new Dimension(70, panelMid.getWidth()));//注意添加的顺序
//底层信息面板
JPanel panelMsg=new JPanel();
getContentPane().add(panelMsg,BorderLayout.SOUTH);
panelMsg.add(new JLabel("信息:"));
tfdMsg=new JTextField(20);
tfdMsg.setFont(new Font("a", Font.BOLD, 13));
panelMsg.add(tfdMsg);
JButton btnMsg=new JButton("发送");
panelMsg.add(btnMsg);
btnMsg.setActionCommand("send");
btnCon.addActionListener(this);
btnExit.addActionListener(this);
btnMsg.addActionListener(this);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
sendExitMsg();
}
} );//用windowAdatpter继承WindowListener
addMenu();
this.setVisible(true);
}
private void addMenu() {
JMenuBar menubar=new JMenuBar();
this.setJMenuBar(menubar);
JMenu menu=new JMenu("选项");
menubar.add(menu);
JMenuItem menuItemSet=new JMenuItem("设置");
menu.add(menuItemSet);
menuItemSet.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
final JDialog dlg=new JDialog();//用这个框架,可以添加东西
dlg.setLayout(new FlowLayout());
dlg.setBounds(ClientForm.this.getX(), ClientForm.this.getY(), 380, 100);
dlg.add(new JLabel("服务器地址:"));
final JTextField tfdHost=new JTextField(HOST,15);
dlg.add(tfdHost);
final JTextField tfdPort=new JTextField(String.valueOf(PORT),5);
dlg.add(tfdPort);
JButton btnSet=new JButton("设置");
dlg.add(btnSet);
btnSet.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
//按理,应该先解析并判断ip和端口号是否合法,此处省略了
ClientForm.HOST=tfdHost.getText();
ClientForm.PORT=Integer.parseInt(tfdPort.getText());
dlg.dispose();
}
});
dlg.setVisible(true);
}
});
JMenuItem menuItemHelp=new JMenuItem("帮助");
menu.add(menuItemHelp);
menuItemHelp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
JDialog dlg = new JDialog(ClientForm.this);
dlg.setBounds(ClientForm.this.getX()+30, ClientForm.this.getY()+30, 340, 100);
dlg.setLayout(new FlowLayout());
dlg.add(new JLabel("版本所有@城院.2016-5-15,QQ:747205398"));
dlg.setVisible(true);
}
});
}
public static void main(String[] args) {
ClientForm.setDefaultLookAndFeelDecorated(true);//设置为装饰
new ClientForm();
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equalsIgnoreCase("con")){
try {
connection(e);
tfdUserName.setEditable(false);
((JButton)e.getSource()).setEnabled(false);
} catch (UnknownHostException e1) {
JOptionPane.showMessageDialog(null, "ip地址和端口号错误,请重新输入:");
return;
} catch (IOException e1) {
JOptionPane.showMessageDialog(null, "ip地址和端口号错误,请重新输入:");
return;
}
}else if(e.getActionCommand().equalsIgnoreCase("exit")){
sendExitMsg();
}else if(e.getActionCommand().equalsIgnoreCase("send")){
sendMsg();
tfdMsg.setText("");
}
}
// 向服务器发送退出消息
private void sendExitMsg() {
if(socketClient==null){
System.exit(0);
}
String msgs="exit@#全部@#null@#"+tfdUserName.getText();//exit@#全部@#null@#userName
String str[]=msgs.split("@#");
if(str.length!=4){
JOptionPane.showMessageDialog(null, "信息不应该包含\"@#\"");//防黑处理
return;
}
pw.println(msgs);
System.exit(0);
}
private void sendMsg() {
if(!(tfdMsg.getText().length()>0)){
JOptionPane.showMessageDialog(this, "请输入聊天消息");
return;
}
String msgs="on@#"+list.getSelectedValue()+"@#"+tfdMsg.getText()+"@#"+tfdUserName.getText();
String str[]=msgs.split("@#");
if(str.length!=4){
JOptionPane.showMessageDialog(null, "信息不应该包含\"@#\"");//防黑处理
return;
}
pw.println(msgs);
pw.flush();//刷新
}
public void connection(ActionEvent e) throws UnknownHostException, IOException {
String userName = tfdUserName.getText();//最好验证
if(userName==null||userName.length()<=0){
JOptionPane.showMessageDialog(null, "用户名不能为空");
return;
}
socketClient=new Socket(HOST, PORT);
pw=new PrintWriter(socketClient.getOutputStream(),true);
pw.println(userName);//向服务器报上自己的名字
pw.flush();
this.setTitle("用户["+userName+"]在线...");
new ClientThread().start();
}
//用来读取服务器发送来的消息
class ClientThread extends Thread{
@Override
public void run() {
try {
Scanner sc = new Scanner(socketClient.getInputStream());
while(sc.hasNextLine()){
String str=sc.nextLine();
String msgs[]=str.split("@#");
if("msg".equals(msgs[0])){
if("server".equals(msgs[1])){
str="[通知]:"+msgs[2];
}else{//服务器进行转发
str=msgs[1]+" 说 :"+msgs[2];
}
allMsg.append("\r\n"+"("+new Date(System.currentTimeMillis())+") "+str);
}else if("cmdAdd".equals(msgs[0])){
if("server".equals(msgs[1])){
lm.addElement((msgs[2]));
setVisible(true);
}
}else if("cmdRed".equals(msgs[0])){
if("server".equals(msgs[1])){
lm.removeElement(msgs[2]);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这是服务器代码:
package cn.hncu.chat;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketImpl;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.border.TitledBorder;
public class ServerForm extends JFrame {
private JTextArea tfdArea;
private DefaultListModel lm;
private HashMap usersMap=new HashMap();
public ServerForm(){
setDefaultCloseOperation(EXIT_ON_CLOSE);
final int winWeight=(int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
final int winHeight=(int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
setBounds(winWeight/4, winHeight/4, winWeight/2, winHeight/2);
//消息窗口
tfdArea=new JTextArea();
tfdArea.setEditable(false);
getContentPane().add(new JScrollPane(tfdArea),BorderLayout.CENTER);
//在线列表控件
lm=new DefaultListModel();//分层设计,有利于维护
JList list=new JList(lm);
list=new JList(lm);
lm.addElement("全部");
JScrollPane jp=new JScrollPane(list);
getContentPane().add(jp,BorderLayout.EAST);
jp.setBorder(new TitledBorder("在线"));//具有指定边框和标题
jp.setPreferredSize(new Dimension(100, this.getWidth()));//注意添加的顺序
//设置菜单栏
JMenuBar bar=new JMenuBar();
this.setJMenuBar(bar);
JMenu menu=new JMenu("控制(C)");
menu.setMnemonic('C');//设置菜单或按钮的助记符(ALT+C激活
bar.add(menu);
final JMenuItem menuOpen=new JMenuItem("开启");//内部访问
menuOpen.setAccelerator(KeyStroke.getKeyStroke('O',KeyEvent.CTRL_MASK));//设置快捷键:Ctrl+O
menuOpen.setActionCommand("run");
menu.add(menuOpen);
menu.addSeparator();
JMenuItem menuExit=new JMenuItem("退出");
menuExit.setAccelerator(KeyStroke.getKeyStroke('E',KeyEvent.CTRL_MASK));//设置快捷键:Ctrl+E
menuExit.setActionCommand("exit");
menu.add(menuExit);
//监听
ActionListener al=new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("run")){
startServer();
menuOpen.setEnabled(false);
}else{
System.exit(0);
}
}
};
menuOpen.addActionListener(al);
menuExit.addActionListener(al);
setVisible(true);
}
public static void main(String[] args) {
ServerForm.setDefaultLookAndFeelDecorated(true);//设置为装饰
new ServerForm();
}
private final int PORT=9090;
private void startServer() {
try {
ServerSocket server=new ServerSocket(PORT);
tfdArea.append("启动服务:"+server);
new ServerThread(server).start();
} catch (IOException e) {
e.printStackTrace();
}
}
class ServerThread extends Thread{
private ServerSocket server;
public ServerThread(ServerSocket server) {
this.server = server;
}
@Override
public void run() {
//握手
while(true){
try {
Socket socketClient=server.accept();
Scanner sc=new Scanner(socketClient.getInputStream());
if(sc.hasNextLine()){//注意用if判断第一行
String userName = sc.nextLine();//用户名
tfdArea.append("\r\n用户:["+userName+"]"+socketClient+" ("+new Date(System.currentTimeMillis())+")");
lm.addElement(userName);
new ClientThread(socketClient).start();
msgAll(userName);//把“当前用户登录的消息即用户名”通知给所有其他已经在线的人
msgSelf(socketClient);//通知当前登录的用户,有关其他在线人的信息
usersMap.put(userName, socketClient);
//把当前登录的用户加到“在线用户”池中
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class ClientThread extends Thread{
private Socket socketClient;
public ClientThread(Socket socketClient) {
this.socketClient = socketClient;
}
@Override
public void run() {
try {
Scanner sc=new Scanner(socketClient.getInputStream());
while(sc.hasNextLine()){
String str=sc.nextLine();
String msgs[]=str.split("@#");
if("on".equals(msgs[0])){
sendMsgToSb(msgs);
}else if("exit".equals(msgs[0])){
//从 在线用户池 中把该用户删除
usersMap.remove(msgs[3]);
//从服务器的在线用户显示列表中移除该用户的姓名
lm.removeElement(msgs[3]);
//通知其他人有关该用户退出的消息
sendExitMsgToAll(msgs);
//在服务器窗口显示该用户退出的消息
tfdArea.append("\r\n用户:["+msgs[3]+"] ("+new Date(System.currentTimeMillis())+")"+"退出了...");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务器把客户端的退出的消息转发给相应的其他客户端
private void sendExitMsgToAll(String[] msgs) throws IOException {
Iterator usersName=usersMap.keySet().iterator();
while(usersName.hasNext()){
Socket s=usersMap.get(usersName.next());
PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
String str="msg@#server@#用户["+msgs[3]+"]退出了...";
pw.println(str);
str="cmdRed@#server@#"+msgs[3];
pw.println(str);
}
}
//服务器把客户端的聊天消息转发给相应的其他客户端
private void sendMsgToSb(String[] msgs) throws IOException {
if("全部".equals(msgs[1])){
Iterator usersName=usersMap.keySet().iterator();
while(usersName.hasNext()){
Socket s=usersMap.get(usersName.next());
PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
String str="msg@#"+msgs[3]+"@#"+msgs[2];// msg @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
pw.println(str);
}
}else{
Socket s=usersMap.get(msgs[1]);
PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
String str="msg@#"+msgs[3]+"@#"+msgs[2];// msg @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
pw.println(str);
}
}
//通知当前登录的用户,有关其他在线人的信息---把原先已经在线的那些用户的名字发给该登录用户,让他给自己界面中的lm添加相应的用户名
private void msgSelf(Socket socketClient) {
try {
PrintWriter pw=new PrintWriter(socketClient.getOutputStream(), true);
Iterator it=usersMap.keySet().iterator();
while(it.hasNext()){
String userName=it.next();
String msg = "cmdAdd@#server@#"+userName;
pw.println(msg);
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//把“当前用户登录的消息即用户名”通知给所有其他已经在线的人
//技术思路:从池中依次把每个socket(代表每个在线用户)取出,向它发送userName
private void msgAll(String userName) {
Iterator it=usersMap.values().iterator();
while(it.hasNext()){
Socket socketClient=it.next();
try {
PrintWriter pw=new PrintWriter(socketClient.getOutputStream(), true);
String msg = "msg@#server@#用户["+userName+"]登录了.";
pw.println(msg);
pw.flush();
msg = "cmdAdd@#server@#"+userName;
pw.println(msg);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}