您的位置:首页 > 编程语言 > Java开发

Java Socket编程实现聊天小案例

2015-05-17 22:30 344 查看


一.用户需求:

1.实现使用用户名登陆(不能重复)
2.登陆后能获取所有在线用户
3.可以给所用用户群发信息
4.也可以给某个用户单独发送信息
5.用户退出时,一起登陆用户能立刻感知

二.初步分析:

1.需要在服务器端记录当前登陆的用户,便于用户登陆时用户名查重及消息群发
2.用户登陆成功后需要获取所有在线用户信息,并记录,能够单独给该用户发送信息
3.用户退出时,需要通知服务器从服务器数据中删除该用户并通知到所有用户

三.效果图:




图1.服务器启动Socket监听




图2 启动客户端登录




图3.用户1登录成功




图4.用户1再次登录,提示重复用户名




图5.用户2登录成功,用户1和用户2都能获取到当前登录用户




图6.用户1群发信息,用户1和用户2都能收到群发消息




图7.用户2与用户1私聊




图8用户2与用户1私聊,收到信息“hi,1,I am 2”




图9.用户1退出,用户2能感知到

四.源码


自定义消息结构


<span style="color:#000000;">package Common;
public enum Command {
Command,
Text,
}

package Common;
public enum CommandType {
Login,
Quit,
GetMember,
Null
}

package Common;
import java.io.*;
import java.net.Socket;

public class Member {
private String User;
private Socket s;
private DataInputStream dis;
private DataOutputStream dos;

public Member(String name, Socket s, DataInputStream dis,
DataOutputStream dos) {
this.User = name;
this.s = s;
this.dis = dis;
this.dos = dos;
}

public DataInputStream getDis() {
return dis;
}

public DataOutputStream getDos() {
return dos;
}

public String getUser() {
return User;
}

public Socket getS() {
return s;
}

public void setS(Socket s) {
this.s = s;
}
}</span>

自定义消息类
<span style="color:#000000;">package Common;
import java.util.*;
public class Message {
private String from;
private String to;
private String msgText;
private Date sendDate;
private Command cmd;
private CommandType cmdType;

public Message(){
}

public Message(String From, String To, String MsgText, Date SendDate,
Command CMD,CommandType CMDType) {
this.from = From;
this.to = To;
this.msgText = MsgText;
this.sendDate = SendDate;
this.cmd = CMD;
this.cmdType = CMDType;
}

public String getFrom() {
return from;
}

public void setFrom(String from) {
this.from = from;
}

public String getTo() {
return to;
}

public void setTo(String to) {
this.to = to;
}

public String getMsgText() {
return msgText;
}

public void setMsgText(String msgText) {
this.msgText = msgText;
}

public Date getSendDate() {
return sendDate;
}

public void setSendDate(Date sendDate) {
this.sendDate = sendDate;
}

public Command getCmd() {
return cmd;
}

public void setCmd(Command cmd) {
this.cmd = cmd;
}

public CommandType getCmdType() {
return cmdType;
}

public void setCmdType(CommandType cmdType) {
this.cmdType = cmdType;
}
}</span>

自定义异常
<span style="color:#000000;">package Common;
public class MyException extends Exception {
String ErrorMsg;
public MyException (String errMsg){
this.ErrorMsg = errMsg;
}

public String toStr(){
return ErrorMsg;
}
}</span>

自定义消息处理类
<span style="color:#000000;">package Common;
import java.util.*;
import java.text.*;
import javax.swing.*;
public class Utility {
public static Message ConvertSting2Msg(String str) throws MyException {
Message msg = new Message();
String[] param = new String[6];
int index = str.indexOf("\n");
int paraIndex = 0;
while (index > 0) {
String temp = str.substring(0, index);
param[paraIndex++]=temp;
str = str.substring(index + 1);
index = str.indexOf("\n");
}
param[paraIndex]=str;
if (paraIndex != 5)
throw new MyException("Parameter number Error!"+paraIndex);
msg.setFrom(param[0]);
msg.setTo(param[1]);
msg.setMsgText(param[2]);
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date sendDate =sdf.parse(param[3]);
msg.setSendDate(sendDate);
} catch (ParseException e) {
throw new MyException("Date Format Error!");
}
msg.setCmd(Command.valueOf(param[4]));
msg.setCmdType(CommandType.valueOf(param[5]));
return msg;
}

public static String ConvertMsg2String(Message msg) {
StringBuffer sb = new StringBuffer();
sb.append(msg.getFrom());
sb.append("\n");
sb.append(msg.getTo());
sb.append("\n");
sb.append(msg.getMsgText());
sb.append("\n");
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String SendDate = sdf.format(msg.getSendDate());
sb.append(SendDate);
sb.append("\n");
sb.append(msg.getCmd().toString());
sb.append("\n");
sb.append(msg.getCmdType().toString());
return sb.toString();
}

public static void Print(String str){
System.out.println(str);
}

public static void ShowMsg(String str){
JOptionPane.showMessageDialog(null, str);
}
}</span>

服务器端类
<span style="color:#000000;">package Main;
import Common.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class Server {
static Server server = new Server();
ServerSocket ss = null;
boolean ServerStarted = false;
List<Member> Members = new ArrayList<Member>();
MessageHandler mh = null;

private Server() {
}

public static Server GetServerInstance() {
if (server == null) {
server = new Server();
}
return server;
}

public void StartSvr() {
Utility.Print("Server Start to Run from: " + new Date().toString());
try {
ss = new ServerSocket(8888);
ServerStarted = true;
} catch (IOException e) {
Utility.ShowMsg("Server Starts failed!Please check whether this is another Server started!");
System.exit(0);
}

while (ServerStarted) {
try {
Socket s = ss.accept();
mh = new MessageHandler(s);
new Thread(mh).start();
} catch (IOException e) {
Utility.Print("server accept failed!");
ServerStarted = false;
}
}
}

private boolean IsContents(String user) {
int size = Members.size();
for (int i = 0; i < size; i++) {
if (Members.get(i).getUser().equals(user)) {
return true;
}
}
return false;
}

private void RemoveMember(String user) {
for (int i = 0; i < Members.size(); i++) {
if (Members.get(i).getUser().equals(user)) {
Members.remove(i);
}
}
}

private String MemberToString() {
StringBuffer sb = new StringBuffer("");
int size = Members.size();
for (int i = 0; i < size; i++) {
sb.append(Members.get(i).getUser() + ":");
}
if (sb.length() > 1) {
return sb.substring(0, sb.length() - 1);
}
return sb.toString();
}
//内部类,用于服务器处理客户端请求
class MessageHandler implements Runnable {
Socket s = null;
boolean started = false;
DataInputStream dis = null;
DataOutputStream dos = null;

public MessageHandler(Socket s) {
this.s = s;
}

public void run() {
try {
dis = new DataInputStream(s.getInputStream());
dos = new DataOutputStream(s.getOutputStream());
started = true;
} catch (IOException e) {
e.printStackTrace();
}
while (started) {
try {
String InputStr = dis.readUTF();
Utility.Print(InputStr);
Message ms = Utility.ConvertSting2Msg(InputStr);
MsgDispatch(ms);
} catch (IOException e) {
started = false;
} catch (MyException e) {
started = false;
Utility.Print(e.toStr());
}
}
if (!started) {
try {
if (dis != null) {
dis.close();
dis = null;
}
if (dos != null) {
dos.close();
dos = null;
}
} catch (IOException e) {
e.printStackTrace();
}
return;
}
}

public void MsgDispatch(Message msg) {
if (msg.getCmd() == Command.Command) {
if (!msg.getTo().equals("Server")) {
started = false;
Utility.Print("Error Command Message!");
return;
}
if (msg.getCmdType() == CommandType.Login) {
String user = msg.getFrom();
if (!IsContents(user)) {
Member m = new Member(user, s, dis, dos);
Members.add(m);
Utility.Print("A client add to member list!");
try {
Message Outmsg = new Message("Server",
msg.getFrom(), "Success", new Date(),
Command.Command, CommandType.Login);
dos.writeUTF(Utility.ConvertMsg2String(Outmsg));
} catch (IOException e) {
e.printStackTrace();
Members.remove(m);
}
} else {
Utility.Print("this client exists!");
try {
Message Outmsg = new Message("Server",
msg.getFrom(), "failed", new Date(),
Command.Command, CommandType.Login);
dos.writeUTF(Utility.ConvertMsg2String(Outmsg));
} catch (IOException e) {
e.printStackTrace();
}
}
} else if (msg.getCmdType() == CommandType.Quit) {
String user = msg.getFrom();
if (IsContents(user)) {
RemoveMember(user);
Utility.Print("User " + user + " is removed from member list!");
try {
int size = Members.size();
for (int i = 0; i < size; i++) {
Message Outmsg = new Message("Server", Members
.get(i).getUser(), MemberToString(),
new Date(), Command.Command,
CommandType.GetMember);
Members.get(i).getDos().writeUTF(Utility.ConvertMsg2String(Outmsg));
}
} catch (IOException e) {
e.printStackTrace();
}
return;
}
} else if (msg.getCmdType() == CommandType.GetMember) {
try {
for (int i = 0; i < Members.size(); i++) {
Message Outmsg = new Message("Server", Members.get(
i).getUser(), MemberToString(), new Date(),
Command.Command, CommandType.GetMember);
Members.get(i).getDos().writeUTF(Utility.ConvertMsg2String(Outmsg));
}
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
if (msg.getCmdType() != CommandType.Null) {
Utility.Print("Error Text Message");
return;
}
if (msg.getTo().equals("ALL")) {
try {
for (int i = 0; i < Members.size(); i++) {
Message Outmsg = new Message(msg.getFrom(), "ALL",
msg.getMsgText(), new Date(), Command.Text,
CommandType.Null);
Members.get(i).getDos().writeUTF(Utility.ConvertMsg2String(Outmsg));
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
for (int i = 0; i < Members.size(); i++) {
Message Outmsg = null;
if (Members.get(i).getUser().equals(msg.getTo())
|| Members.get(i).getUser()
.equals(msg.getFrom())) {
Outmsg = new Message(msg.getFrom(),
msg.getTo(), msg.getMsgText(),
new Date(), Command.Text,
CommandType.Null);
Members.get(i).getDos().writeUTF(Utility.ConvertMsg2String(Outmsg));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}</span>

服务器端主类,必须最先运行
<span style="color:#000000;">package Main;
public class StartServer {
public static void main(String[] args) {
Server server = Server.GetServerInstance();
server.StartSvr();
}
}</span>

客户端登录类
<span style="color:#000000;">package Main;
import Common.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.net.*;

public class Login extends Frame {
private Label la = new Label("UserName:");
private TextField tf = new TextField("", 10);
private Button login = new Button("Login");
Socket s = null;
boolean connect = false;

public void LaunchFrame() {
setLocation(200, 300);
setSize(300, 100);
setResizable(false);
setTitle("Login");
setLayout(new FlowLayout());
add(la, FlowLayout.LEFT);
add(tf);
add(login);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
login.addActionListener(new LoginAction());
setVisible(true);
}

class LoginAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String Username = tf.getText();
if (Username != null && !Username.trim().equals("")) {
Message msg = new Message(Username, "Server", "Login",
new Date(), Command.Command, CommandType.Login);
String Sendmsg = Utility.ConvertMsg2String(msg);
Connect(Sendmsg);
return;
} else {
Utility.ShowMsg("UserName can not be Null!");
return;
}
}

private void Connect(String str) {
try {
if (!connect) {
s = new Socket("127.0.0.1", 8888);
connect = true;
Utility.Print("Connect to Server success!");
}
} catch (UnknownHostException e) {
Utility.ShowMsg("Can't find the server host!");
return;
} catch (IOException e) {
e.printStackTrace();
Utility.ShowMsg("Can't connect to the server!");
return;
}
if (connect) {
try {
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeUTF(str);

DataInputStream dis = new DataInputStream(s.getInputStream());
Message msg = Utility.ConvertSting2Msg(dis.readUTF());
if (msg.getCmd() == Command.Command
&& msg.getCmdType() == CommandType.Login) {
if (msg.getMsgText().equals("Success")) {
connect = false;
new FullChat(s, msg.getTo(),dis,dos);
dispose();
} else {
Utility.ShowMsg("Duplicate Username!");
}
}
} catch (IOException e) {
Utility.ShowMsg("Can't connect the server!");
} catch (MyException e2) {
Utility.ShowMsg("Uncorrect message from server!");
}
return;
}
}
}
}</span>

客户端群发类
<span style="color:#000000;">package Main;
import java.awt.*;
import java.awt.List;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
import Common.*;
public class FullChat extends Frame {
TextField tf = new TextField(40);
TextArea ta = new TextArea(20, 40);
List list = new List(20);
private Socket s = null;
private String name;
DataOutputStream dos = null;
DataInputStream dis = null;
boolean connected = false;
Map<String, AloneChat> aloneChats = new HashMap<String, AloneChat>();

public FullChat(Socket s, String name, DataInputStream dis,
DataOutputStream dos) {
this.s = s;
this.name = name;
this.dis = dis;
this.dos = dos;
connected = true;
setTitle(name);
setLocation(200, 200);
setSize(440, 400);
setResizable(false);

Panel pl = new Panel();
pl.setPreferredSize(new Dimension(300, 380));
add(pl, BorderLayout.WEST);
Panel pr = new Panel();
pr.setPreferredSize(new Dimension(130, 380));
add(pr, BorderLayout.EAST);
pl.add(ta);
ta.setEditable(false);
pl.add(tf);
list.select(-1);
list.addItemListener(new ListClick());
pr.add(list);
tf.addActionListener(new TextFiledAction());
addWindowListener(new WindowClose());
setVisible(true);
RequestMember();
new Thread(new FullChatThread()).start();
}

private void RequestMember() {
// 通知server移除Socket
Message msg = new Message(name, "Server", "GetMember", new Date(),
Command.Command, CommandType.GetMember);
String Sendmsg = Utility.ConvertMsg2String(msg);
try {
dos.writeUTF(Sendmsg);
} catch (IOException e1) {
e1.printStackTrace();
}
}

class ListClick implements ItemListener {

@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
String user = list.getItem(list.getSelectedIndex());
if (!aloneChats.containsKey(user)) {
if (!user.equals(name)) {
AloneChat ac = new AloneChat(s, dis, dos, name, user);
aloneChats.put(user, ac);
Utility.Print(user + " add to Map!");
ac.Init();
}
} else {
AloneChat ac = aloneChats.get(user);
ac.setVisible(true);
Utility.Print("aa");
}
}
}
}

class WindowClose extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
// 通知server移除Socket
Message msg = new Message(name, "Server", "Quit", new Date(),
Command.Command, CommandType.Quit);
String Sendmsg = Utility.ConvertMsg2String(msg);
try {
dos.writeUTF(Sendmsg);
} catch (IOException e1) {
e1.printStackTrace();
}
connected = false;
dispose();
}
}

class TextFiledAction implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
String SndText = tf.getText();
tf.setText("");
if (SndText.equals("")) {
Utility.ShowMsg("Please Input Message");
return;
}
Message msg = new Message(name, "ALL", SndText, new Date(),
Command.Text, CommandType.Null);
String Sendmsg = Utility.ConvertMsg2String(msg);
try {
dos.writeUTF(Sendmsg);
} catch (IOException ex) {
Utility.Print("Send Message to Server failed");
}
}

}

class FullChatThread implements Runnable {
@Override
public void run() {
while (connected) {
try {
String str = dis.readUTF();
Utility.Print(str);
Message msg = Utility.ConvertSting2Msg(str);
if (msg.getCmdType() == CommandType.GetMember
&& msg.getCmd() == Command.Command) {
String[] members = msg.getMsgText().split(":");
list.removeAll();
for (String member : members) {
list.add(member);
}
} else if (msg.getCmd() == Command.Text) {
String ChatTo = msg.getTo();
if (ChatTo.equals("ALL")) {
String oldContent = ta.getText();
StringBuffer sb = new StringBuffer("\n");
sb.append(msg.getFrom());
sb.append("    ");
sb.append(msg.getSendDate());
sb.append("    ");
sb.append("对所有人说:");
sb.append("\n");
sb.append(msg.getMsgText());
ta.setText(oldContent + sb.toString());
} else {
if (ChatTo.equals(name)) {
String ChatFrom = msg.getFrom();
StringBuffer sb = new StringBuffer("\n");
sb.append(msg.getFrom());
sb.append("    ");
sb.append(msg.getSendDate());
sb.append("    ");
sb.append("对");
sb.append(msg.getTo());
sb.append("说:");
sb.append("\n");
sb.append(msg.getMsgText());
if (!aloneChats.containsKey(ChatFrom)) {

AloneChat ac = new AloneChat(s, dis, dos, name,
ChatFrom);
aloneChats.put(ChatFrom, ac);
ac.Init();
ac.ReceivedMsg(sb.toString());

} else {
AloneChat ac = aloneChats.get(ChatFrom);
ac.setVisible(true);
ac.ReceivedMsg(sb.toString());
}
}else if(msg.getFrom().equals(name)){
String ChatFrom = msg.getFrom();
StringBuffer sb = new StringBuffer("\n");
sb.append(msg.getFrom());
sb.append("    ");
sb.append(msg.getSendDate());
sb.append("    ");
sb.append("对");
sb.append(msg.getTo());
sb.append("说:");
sb.append("\n");
sb.append(msg.getMsgText());
AloneChat ac = aloneChats.get(msg.getTo());
ac.ReceivedMsg(sb.toString());
}

}
}
} catch (IOException e) {
System.exit(0);
} catch (MyException e) {
e.printStackTrace();
connected = false;
}
}

}
}
}</span>

客户端主类,起客户端时必须确保服务器主类已经运行,通过运行该类启动登录窗口。
<span style="color:#000000;">package Main;
public class Client {

public static void main(String[] args) {
new Login().LaunchFrame();
}
}</span>

私聊窗口类
<span style="color:#000000;">package Main;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.*;
import java.util.*;
import Common.Command;
import Common.CommandType;
import Common.Message;
import Common.MyException;
import Common.Utility;

public class AloneChat extends Frame {
Socket s = null;
DataInputStream dis = null;
DataOutputStream dos = null;
String from;
String to;
TextArea ta = new TextArea();
TextField tf = new TextField();
boolean connected = false;

public AloneChat(Socket s, DataInputStream dis, DataOutputStream dos,
String from, String to) {
this.s = s;
this.dis = dis;
this.dos = dos;
this.from = from;
this.to = to;
}

public void Init() {
connected = true;
setLocation(500, 300);
setSize(300, 300);
setTitle(from + " and " + to + " begains chat..");
add(ta, BorderLayout.NORTH);
add(tf, BorderLayout.SOUTH);
ta.setEditable(false);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
dispose();
}
});
tf.addActionListener(new TextFiledAction());
pack();
setVisible(true);
setResizable(false);
}

public void ReceivedMsg(String msg){
String oldContent = ta.getText();
ta.setText(oldContent+msg);
}

class TextFiledAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String SndText = tf.getText();
tf.setText("");
if (SndText.equals("")) {
Utility.ShowMsg("Please Input Message");
return;
}
Message msg = new Message(from, to, SndText, new Date(),
Command.Text, CommandType.Null);
String Sendmsg = Utility.ConvertMsg2String(msg);
try {
dos.writeUTF(Sendmsg);
} catch (IOException ex) {
Utility.Print("Send Message to Server failed");
}
}
}
}</span>

五.不足之处


1.在服务器端用容器存储当前用户,没有做数据同步处理,在多线程情况下回数据不一致,不过在单机上测试没问题,对学习Socket没多大影响
2.数据没有作抽象处理,仅仅只是从代码上实现了功能,有待优化
3.在启服务器监听时使用了单例模式,但在client端聊天时<私聊>没有使用观察者模式
4.仅仅是一时兴起,想回顾一下Socket编程和多线程编程,对内部原理及相关机制还有待进一步学习.
5.由于一时兴起,也没有对本项目仔细分析,从软件工程和面向对象角度进行思考

本文只是对自己学习过程的记忆,也希望能对初学者有所帮助。(2013-04)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: