设计思想:
首先:在服务器端 用一个HashMap(userName,socket)维护所有用户相关的信息,从而能够保证和所有的用户进行通讯。
其次:客户端的动作:
(1)连接(登录):发送userName
服务器的对应动作:1)界面显示,
2)通知其他用户关于你登录的信息,
3)把其他在线用户的userName通知当前用户
4)开启一个线程专门为当前线程服务
(2)发送消息
发送通讯内容之后,对方如何知道是干什么,通过消息协议来实现:
客户端向服务器发的消息格式设计:
命令关键字@#接收方@#消息内容@#发送方
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])
(3)退出(注销)
服务器端:
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.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.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.border.TitledBorder;
public class serverForm extends JFrame{
private final static int winWidth=500;
private final static int winHeight=300;
private final static int PORT=9090;
private JTextArea area;
private JList list;
private DefaultListModel dlm;
private HashMap<String, Socket> userMaps=new HashMap<String, Socket>();
public serverForm(){
super("服务器端...");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
Toolkit toolKit=Toolkit.getDefaultToolkit();
int width=(int) toolKit.getScreenSize().getWidth();
int height=(int) toolKit.getScreenSize().getHeight();
setBounds(width/2-winWidth/2, height/2-winHeight/2, winWidth, winHeight);
JMenuBar mb=new JMenuBar();
JMenu m=new JMenu("控制");
m.setMnemonic('C');//快捷键:alt+c
JMenuItem miRun=new JMenuItem("启动");
miRun.setActionCommand("run");
miRun.setAccelerator(KeyStroke.getKeyStroke('R',KeyEvent.CTRL_MASK));
m.add(miRun);
m.addSeparator();//分割线
JMenuItem miExit=new JMenuItem("停止");
miExit.setActionCommand("exit");
miExit.setAccelerator(KeyStroke.getKeyStroke('E', KeyEvent.CTRL_MASK));
m.add(miExit);
mb.add(m);
this.setJMenuBar(mb);
area =new JTextArea();
area.setEditable(false);
this.add(new JScrollPane(area), BorderLayout.CENTER);
dlm=new DefaultListModel();
list=new JList(dlm);
list.setVisibleRowCount(7);
JScrollPane jc=new JScrollPane(list);
jc.setBorder(new TitledBorder("在线用户"));
jc.setPreferredSize(new Dimension(100, this.getHeight()));
this.add(jc,BorderLayout.EAST);
//事件监听
ActionListener al=new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("run")){
try {
ServerSocket ss=new ServerSocket(PORT);
area.append("服务器"+ss+"开启了");
new ServerThread(ss).start();
} catch (IOException e1) {
e1.printStackTrace();
}
}else{
System.exit(0);
}
}
};
miRun.addActionListener(al);
miExit.addActionListener(al);
this.setVisible(true);
}
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
new serverForm();
}
class clientThread extends Thread{
public Socket s;
public clientThread(Socket s){
this.s=s;
}
@Override
public void run() {
Scanner sc;
try {
sc = new Scanner(s.getInputStream());
while(sc.hasNextLine()){
//on@#JList.getSelectedValue()@#tfdMsg.getText()@#tfdUserName.getText()
String str=sc.nextLine();
String strs[]=str.split("@#");
PrintWriter pw=null;
if ("on".equals(strs[0])) {
if ("全部".equals(strs[1])) {
Iterator<Socket> it = userMaps.values().iterator();
while (it.hasNext()) {
Socket s = it.next();
pw = new PrintWriter(s.getOutputStream(), true);
pw.println("msg@#" + strs[3] + "@#" + strs[2]);
}
} else {
Socket s1 = userMaps.get(strs[1]);
pw = new PrintWriter(s1.getOutputStream(), true);
pw.println("msg@#" + strs[3] + "私聊你@#" + strs[2]);
Socket s2 = userMaps.get(strs[3]);
pw = new PrintWriter(s2.getOutputStream(), true);
pw.println("msg@#你私聊" + strs[1] + "@#" + strs[2]);
}
}else{
//显示,在线人,userMaps,客户端在线人
area.append("\r\n用户:"+strs[3]+"退出了。。。");
dlm.removeElement(strs[3]);
userMaps.remove(strs[3]);
Iterator<Socket> it = userMaps.values().iterator();
while (it.hasNext()) {
Socket s = it.next();
pw = new PrintWriter(s.getOutputStream(), true);
//msg@#server @# 用户[userName]退出了
pw.println("msg@#server@#"+strs[3]+"退出了");
pw.println("cmdRed@#server@#"+str);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ServerThread extends Thread{
private ServerSocket ss;
public ServerThread(ServerSocket ss){
this.ss=ss;
}
@Override
public void run() {
try {
while (true) {
Socket s = ss.accept();
Scanner sc = new Scanner(s.getInputStream());
if (sc.hasNextLine()) {
String str = sc.nextLine();
area.append("\r\n用户:" + str + "登录了--" + s);
msgAll(str);
msgSelf(s);
dlm.addElement(str);
userMaps.put(str, s);
new clientThread(s).start();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//把当前其他在线用户发给自己更新
private void msgSelf(Socket s) {
Iterator<String> it=userMaps.keySet().iterator();
try {
PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
while(it.hasNext()){
String name=it.next();
pw.println("cmdAdd@#server@#"+name);
}
} catch (IOException e) {
e.printStackTrace();
}
}
//把当前的信息转发给其他在线用户
private void msgAll(String str) {
Iterator<Socket> it=userMaps.values().iterator();
while(it.hasNext()){
Socket s=it.next();
try {
PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
pw.println("msg@#server@#"+str+"登录了");
pw.println("cmdAdd@#server@#"+str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
用户端:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Label;
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.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.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
public class clientForm extends JFrame implements ActionListener{
private static int PORT=9090;
private static String HOST="127.0.0.1";
private JTextField userName;
private JList list=null;
private DefaultListModel dlm;
private JTextArea allMsg;
private JTextField msg;
public clientForm(){
super("客户端...");
setBounds(300, 300, 400, 300);
addJMenuBar();
JPanel northP=new JPanel();
northP.add(new JLabel("用户:"));
userName=new JTextField(15);
northP.add(userName);
JButton btnLogin=new JButton("登录");
btnLogin.setActionCommand("login");
JButton btnExit=new JButton("退出");
btnExit.setActionCommand("exit");
northP.add(btnLogin);
northP.add(btnExit);
northP.setBorder(new LineBorder(Color.green));
this.add(northP,BorderLayout.NORTH);
JPanel centerP=new JPanel(new BorderLayout());
dlm=new DefaultListModel();
list=new JList(dlm);
dlm.addElement("全部");
list.setSelectedIndex(0);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setVisibleRowCount(5);
JScrollPane jc=new JScrollPane(list);
jc.setBorder(new TitledBorder("在线"));
jc.setPreferredSize(new Dimension(70, centerP.getHeight()));
centerP.add(jc,BorderLayout.EAST);
allMsg=new JTextArea();
allMsg.setEditable(false);
centerP.add(new JScrollPane(allMsg));
this.add(centerP,BorderLayout.CENTER);
JPanel southP=new JPanel();
southP.add(new JLabel("消息:"));
msg=new JTextField(20);
southP.add(msg);
JButton btnSend=new JButton("发送");
btnSend.setActionCommand("send");
southP.add(btnSend);
southP.setBorder(new LineBorder(Color.green));
this.add(southP,BorderLayout.SOUTH);
//监听
btnLogin.addActionListener(this);
btnExit.addActionListener(this);
btnSend.addActionListener(this);
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
sendExitMsg();
}
});
this.setVisible(true);
}
private void addJMenuBar() {
JMenuBar mb=new JMenuBar();
JMenu m=new JMenu("菜单");
JMenuItem miSet=new JMenuItem("设置");
m.add(miSet);
JMenuItem miHelp=new JMenuItem("help");
m.add(miHelp);
mb.add(m);
miSet.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
final JDialog dl=new JDialog(clientForm.this);
dl.setTitle("重置ip地址和端口号");
dl.setBounds(clientForm.this.getX()+20, clientForm.this.getY()+20, 400, 100);
dl.setLayout(new FlowLayout());
dl.add(new Label("ip地址:"));
final JTextField tfdHost=new JTextField(10);
tfdHost.setText(HOST);
dl.add(tfdHost);
dl.add(new Label("端口号:"));
final JTextField tfdPort=new JTextField(5);
tfdPort.setText(""+PORT);
dl.add(tfdPort);
JButton btnSet=new JButton("设置");
dl.add(btnSet);
btnSet.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
HOST=tfdHost.getText();
PORT=Integer.parseInt(tfdPort.getText());
dl.dispose();
}
});
dl.setVisible(true);
}
});
miHelp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
JDialog dl=new JDialog(clientForm.this);
dl.setBounds(clientForm.this.getX()+20, clientForm.this.getY()+20, 200, 100);
dl.add(new JLabel(" 湖南城市学院,2016-8-16"),BorderLayout.CENTER);
dl.setVisible(true);
}
});
this.setJMenuBar(mb);
}
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
new clientForm();
}
private Socket s;
private PrintWriter pw;
@Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("login")){
try {
s=new Socket(HOST, PORT);
String strName=userName.getText();
pw=new PrintWriter(s.getOutputStream(),true);
//userName
pw.println(strName);
this.setTitle(strName);
userName.setEditable(false);
((JButton)e.getSource()).setEnabled(false);
new clientThread().start();
} catch (IOException e1) {
e1.printStackTrace();
}
}else if(e.getActionCommand().equals("exit")){
sendExitMsg();
}else{
//客户端-->服务器端
on @# JList.getSelectedValue() @# tfdMsg.getText() @# tfdUserName.getText()
if(msg.getText()!=null){
String str="on@#"+list.getSelectedValue()+"@#"+msg.getText()+"@#"+userName.getText();
// System.out.println(str);
msg.setText("");
pw.println(str);
}
}
}
private void sendExitMsg() {
if(s==null){
System.exit(0);
}
//exit@#全部@#null@#userName
String str="exit@#全部@#null@#"+userName.getText();
pw.println(str);
System.exit(0);
}
class clientThread extends Thread{
@Override
public void run() {
try {
Scanner sc=new Scanner(s.getInputStream());
while(sc.hasNextLine()){
String str=sc.nextLine();
String strs[]=str.split("@#");
if("msg".equals(strs[0])){
if("server".equals(strs[1])){
allMsg.append("\r\n公告:"+strs[2]);
}else{
allMsg.append("\r\n"+strs[1]+":"+strs[2]);
}
}
if("cmdAdd".equals(strs[0])){
if(!userName.getText().equals(strs[2])){
dlm.addElement(strs[2]);
}
}
if("cmdRed".equals(strs[0])){
dlm.removeElement(strs[2]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}