Java Socket编程实现聊天小案例
2015-05-17 22:30
344 查看
2.登陆后能获取所有在线用户
3.可以给所用用户群发信息
4.也可以给某个用户单独发送信息
5.用户退出时,一起登陆用户能立刻感知
2.用户登陆成功后需要获取所有在线用户信息,并记录,能够单独给该用户发送信息
3.用户退出时,需要通知服务器从服务器数据中删除该用户并通知到所有用户
一.用户需求:
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)
相关文章推荐
- Java Socket 实现UDP实时聊天小案例
- java socket编程(2)——利用socket实现聊天之单聊
- java socket编程(4)——利用socket实现聊天之上传文件
- java socket编程--聊天小案例
- Java网络编程:简单聊天机器人实现案例
- Java Socket聊天室编程(一)之利用socket实现聊天之消息推送
- java socket编程(3)——利用socket实现聊天之群聊
- Java网络编程案例--CS模型的简单实现
- Socket编程实现简易的聊天功能
- PHP数据库编程③基于mysql的在线词典案例(只实现中英文互查)
- linux网络编程:用C语言实现的聊天程序(异步通信)
- 更改课堂案例功能,实现即时通信功能,即两个客户端进行文字聊天。
- Java Socket(四)编程实现基于 TCP 的 Socket 通信
- Socket通讯编程实现简单的任意聊天程序
- java网络socket编程(七)之java中NIO实现聊天系统的群聊功能
- Java Socket编程 - 基于TCP方式的客户服务器聊天程序
- java Socket编程--多线程实现为多个客户端服务
- 【python网络编程】多线程实现多用户全双工聊天
- linux网络编程:用C语言实现的聊天程序(同步通信)
- Java一步一脚印—通过简单的TCP网络编程实现局域网的聊天对话