这个程序主要是吃透socket的tcp传输协议,但这里还有一个比较烧脑的就是几个线程之间的关系一定要搞清楚,因为我们是用了专门的线程去维护,以保证多用户的通信。
TCP知识点:即Socket类和ServerSocket类。ServerSocket:accept();
Socket包含输入、输出流:getImputStream()、 getOutputStream();
我们要注意的是要给服务器发送结束标志否则服务器在数据接收完毕再调用read或者readline时会出异常(server.shutdownOutput())。
另外就是要有要有如下架构思想:
在服务器端 用一个HashMap< userName,socket> 维护所有用户相关的信息,从而能够保证和所有的用户进行通讯。
客户端的动作:
(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])
通过上面的思想架构再来写代码就显得简单很多。另外要注意的地方除了我在代码的备注中提到的就是内部类用外部的成员变量这个变量要final是比较麻烦的,所以我们再写一个函数专门来做这个动作比较方便。
代码如下:
客户端:
package cn.hncu;
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 tfdUserName;
private JTextArea allMsg;
private JList list;
private DefaultListModel lm;
private JTextField tfdMsg;
private JButton btnCon;
private JButton btnExit;
public ClientForm() {
//造一个主面板
setBounds(400, 400, 500, 400);
//菜单
addMenu();
//上部面板
JPanel topP = new JPanel();
getContentPane().add(topP,BorderLayout.NORTH);
topP.add(new JLabel("用户标识:"));
tfdUserName = new JTextField(10);
topP.add(tfdUserName);
btnCon = new JButton("连接");
btnCon.setActionCommand("c");
btnCon.addActionListener(this);
topP.add(btnCon);
btnExit = new JButton("注销");
btnExit.setActionCommand("exit");
btnExit.addActionListener(this);
topP.add(btnExit);
//中间面板
JPanel centerP = new JPanel();
centerP.setLayout(new BorderLayout());
getContentPane().add(centerP,BorderLayout.CENTER);
//中间面板的消息文本框
allMsg= new JTextArea();
allMsg.setEditable(false);
centerP.add(new JScrollPane(allMsg),BorderLayout.CENTER);
//中间面板的用户列表
lm = new DefaultListModel();
list= new JList(lm);
lm.addElement("全部");
list.setSelectedIndex(0);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setVisibleRowCount(2);
JScrollPane jc = new JScrollPane(list);
jc.setBorder(new TitledBorder("在线"));
jc.setPreferredSize(new Dimension(100,centerP.getHeight()));
centerP.add(jc,BorderLayout.EAST);
//下部面板
JPanel sendP = new JPanel();
getContentPane().add(sendP,BorderLayout.SOUTH);
sendP.add(new JLabel("消息:"));
tfdMsg = new JTextField(20);
sendP.add(tfdMsg);
JButton btnSend = new JButton("发送");
btnSend.setActionCommand("send");
btnSend.addActionListener(this);
sendP.add(btnSend);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if(clientSocket==null){
System.exit(0);
}
sendExitMsg();
System.exit(0);
}
});
setVisible(true);
}
private void addMenu() {
JMenuBar menubar = new JMenuBar();
setJMenuBar(menubar);
JMenu menu = new JMenu("选项");
menubar.add(menu);
JMenuItem setMenuItem= new JMenuItem("设置");
JMenuItem helpMenuItem= new JMenuItem("帮助");
menu.add(setMenuItem);
menu.addSeparator();
menu.add(helpMenuItem);
setMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final JDialog dlg = new JDialog();
dlg.setLayout(new FlowLayout());
dlg.add(new JLabel("服务器:"));
final JTextField tfdIp = new JTextField(10);
tfdIp.setText(HOST);
dlg.add(tfdIp);
dlg.add(new JLabel(":"));
final JTextField tfdPort = new JTextField(5);
tfdPort.setText(""+PORT);
dlg.add(tfdPort);
dlg.setBounds(ClientForm.this.getX(), ClientForm.this.getY(), 400, 100);
JButton setBtn = new JButton("设置");
dlg.add(setBtn);
setBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String ip = tfdIp.getText();
String port = tfdPort.getText();
HOST = ip ;
PORT = Integer.parseInt(port);
dlg.dispose();
}
});
dlg.setVisible(true);
}
});
helpMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JDialog dlg = new JDialog();
dlg.setLayout(new FlowLayout());
dlg.setBounds(ClientForm.this.getX(), ClientForm.this.getY(), 300, 100);
dlg.add(new JLabel("版权所有@军街.2016.05.15,qq:469547258"));
dlg.setVisible(true);
}
});
}
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
new ClientForm();
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("c")){
System.out.println("连接服务器...");
tfdUserName.setEditable(false);
btnCon.setEnabled(false);
connecting();
btnExit.setEnabled(true);
btnCon.setEnabled(false);
}else if(e.getActionCommand().equals("send")){
try {
PrintWriter pw = new PrintWriter(clientSocket.getOutputStream(),true);
//我对谁说了什么消息
if(tfdMsg.getText()==null || tfdMsg.getText().trim().length()==0){
JOptionPane.showMessageDialog(this, "请输出信息");
return ;
}
String msg = "on@#"+list.getSelectedValue()+"@#"+tfdMsg.getText()+"@#"+tfdUserName.getText();
pw.println(msg);
//添加到自己的面板 作为记录
allMsg.append("\r\n[我]说:"+tfdMsg.getText());
tfdMsg.setText("");
} catch (IOException e1) {
e1.printStackTrace();
}
}else if(e.getActionCommand().equals("exit")){
sendExitMsg();
}
}
private void sendExitMsg() {
try {
PrintWriter pw = new PrintWriter(clientSocket.getOutputStream(),true);
//向服务器发送退出消息
String msg = "exit@#"+"全部"+"@#@#"+tfdUserName.getText();
pw.println(msg);
btnCon.setEnabled(true);
btnExit.setEnabled(false);
tfdUserName.setEditable(true);
lm.removeAllElements();//点了退出要把lm全部移除 不然再点连接会导致之前列表的还在
} catch (IOException e1) {
e1.printStackTrace();
}
}
private String HOST = "192.168.1.103";
private int PORT = 9090;
private Socket clientSocket;
private PrintWriter pw ;
private void connecting() {
try {
clientSocket = new Socket(HOST,PORT);
String userName = tfdUserName.getText();
pw= new PrintWriter(clientSocket.getOutputStream(),true);
pw.println(userName);
this.setTitle("用户["+userName+"]在线...");
new ClientThread().start();//专门负责通讯的线程
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
class ClientThread extends Thread{
@Override
public void run() {
try {
//接受服务器发过来的信息
Scanner sc = new Scanner(clientSocket.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"+str);
}else if("cmdAdd".equals(msgs[0])){//列表添加消息
lm.addElement(msgs[2]);
}else if("cmdRed".equals(msgs[0])){//列表删除消息
lm.removeElement(msgs[2]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端:
package cn.hncu;
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.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
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.Map;
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;
import com.sun.org.apache.bcel.internal.generic.ACONST_NULL;
public class ServerForm extends JFrame{
private static final int PORT=9090;
private JTextArea area;
private DefaultListModel lm ;
private Map<String, Socket> usersMap = new HashMap<String, Socket>();
public ServerForm() {
//主面板
super("这是服务器");
final int hight = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
final int width = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
this.setBounds(width/4,hight/4, width/3,hight/2);
//菜单
JMenuBar bar = new JMenuBar();
setJMenuBar(bar);
JMenu menu = new JMenu("控制(c)");
bar.add(menu);
menu.setMnemonic('c');//设置助记符
final JMenuItem itemRun = new JMenuItem("启动");
itemRun.setActionCommand("run");
menu.add(itemRun);
menu.addSeparator();
itemRun.setAccelerator(KeyStroke.getKeyStroke('R',KeyEvent.CTRL_MASK));//设置快捷键
JMenuItem itemExit = new JMenuItem("退出");
itemExit.setAccelerator(KeyStroke.getKeyStroke('E',KeyEvent.CTRL_MASK));//设置快捷键
itemExit.setActionCommand("exit");
menu.add(itemExit);
//消息面板
area = new JTextArea();
area.setEditable(false);
getContentPane().add(area,BorderLayout.CENTER);
//用户列表
lm= new DefaultListModel();
JList list = new JList(lm);
JScrollPane jc = new JScrollPane(list);
jc.setBorder(new TitledBorder("在线"));
jc.setPreferredSize(new Dimension(100, this.getHeight()));
getContentPane().add(jc,BorderLayout.EAST);
//监听
ActionListener al = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("run")){
startServer();
itemRun.setEnabled(false);
}
if(e.getActionCommand().equals("exit")){
System.exit(0);
}
}
};
itemRun.addActionListener(al);
itemExit.addActionListener(al);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
setVisible(true);
}
private void startServer() {
try {
ServerSocket server = new ServerSocket(PORT);
area.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 clientSocket = server.accept();
Scanner sc = new Scanner(clientSocket.getInputStream());
if(sc.hasNextLine()){
String userName = sc.nextLine();
area.append("\r\n用户:["+userName+"]登录,"+clientSocket);
lm.addElement(userName);
new ClientThread(clientSocket).start();
msgAll(userName);
msgSelf(clientSocket);
usersMap.put(userName, clientSocket);//将此时登录的用户加入池
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void receiveAndSendMsg(Socket clientSocketReceive) {
try {
Scanner sc = new Scanner(clientSocketReceive.getInputStream());
while(sc.hasNextLine()){
String str = sc.nextLine();
String msgs[]=str.split("@#");
if("on".equals(msgs[0])){
if("全部".equals(msgs[1])){
String msg = "msg@#"+msgs[3]+"@#"+msgs[2];
Iterator<Socket> it= usersMap.values().iterator();
while(it.hasNext()){
Socket clientSocketSend = it.next();
PrintWriter pw = new PrintWriter(clientSocketSend.getOutputStream(), true);
pw.println(msg);
pw.flush();
}
}else{
Socket clientSocketSend = usersMap.get(msgs[1]);
String msg = "msg@#"+msgs[3]+"@#"+msgs[2];
PrintWriter pw = new PrintWriter(clientSocketSend.getOutputStream(), true);
pw.println(msg);
pw.flush();
}
}else if("exit".equals(msgs[0])){
usersMap.remove(msgs[3]);
Socket removeSocket = usersMap.get(msgs[3]);
area.append("\r\n用户:["+msgs[3]+"]退出登录");
lm.removeElement(msgs[3]);
Iterator<Socket> it= usersMap.values().iterator();
String msg = "msg@#server@#用户["+msgs[3]+"]退出登录";
String msg2 ="cmdRed@#server@#"+msgs[3];
while(it.hasNext()){
Socket clientSocketSend = it.next();
PrintWriter pw = new PrintWriter(clientSocketSend.getOutputStream(), true);
pw.println(msg);
pw.flush();
pw.println(msg2);
pw.flush();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void msgSelf(Socket clientSocket) {
try {
Iterator<String> it = usersMap.keySet().iterator();
PrintWriter pw = new PrintWriter(clientSocket.getOutputStream(), true);
while(it.hasNext()){
String userName = it.next();
String msg = "cmdAdd@#server@#"+userName;
pw.println(msg);
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//通知其他在线的用户,此时登录的用户登陆了
private void msgAll(String userName) {
Iterator<Socket> it = usersMap.values().iterator();
while(it.hasNext()){
try {
PrintWriter pw = new PrintWriter(it.next().getOutputStream(), true);
pw.println("msg@#server@#用户["+userName+"]登陆了.");
pw.flush();
pw.println("cmdAdd@#server@#"+userName);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientThread extends Thread{
private Socket clientSocket;
public ClientThread(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
receiveAndSendMsg(clientSocket);
}
}
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
new ServerForm();
}
}