客户端设计
启动客户端界面 ,登陆,刷新在线
目标
启动客户端界面,登陆,刷新在线人数列表
实现步骤
- 客户端界面主要是GUI设计,主体页面分为登陆界面和聊天窗口,以及在线用户列表。
- GUI界面读者可以自行复制使用。
- 登陆输入服务端ip和用户名后,要请求与服务端的登陆,然后立即为当前客户端分配一个读线程处理客户端的读数据消息。因为客户端可能随时会接收到服务端那边转发过来的各种即时消息信息。
- 客户端登陆完成,服务端收到登陆的用户名后,会立即发来最新的用户列表给客户端更新。
代码实现
客户端主体代码
public class ClientChat implements ActionListener {
/** 1.设计界面 */
private JFrame win = new JFrame();
/** 2.消息内容框架 */
public JTextArea smsContent =new JTextArea(23 , 50);
/** 3.发送消息的框 */
private JTextArea smsSend = new JTextArea(4,40);
/** 4.在线人数的区域 */
/** 存放人的数据 */
/** 展示在线人数的窗口 */
public JList<String> onLineUsers = new JList<>();
// 是否私聊按钮
private JCheckBox isPrivateBn = new JCheckBox("私聊");
// 消息按钮
private JButton sendBn = new JButton("发送");
// 登录界面
private JFrame loginView;
private JTextField ipEt , nameEt , idEt;
private Socket socket ;
public static void main(String[] args) {
new ClientChat().initView();
}
private void initView() {
/** 初始化聊天窗口的界面 */
win.setSize(650, 600);
/** 展示登录界面 */
displayLoginView();
/** 展示聊天界面 */
//displayChatView();
}
private void displayChatView() {
JPanel bottomPanel = new JPanel(new BorderLayout());
//-----------------------------------------------
// 将消息框和按钮 添加到窗口的底端
win.add(bottomPanel, BorderLayout.SOUTH);
bottomPanel.add(smsSend);
JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
btns.add(sendBn);
btns.add(isPrivateBn);
bottomPanel.add(btns, BorderLayout.EAST);
//-----------------------------------------------
// 给发送消息按钮绑定点击事件监听器
// 将展示消息区centerPanel添加到窗口的中间
smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
// 让展示消息区可以滚动。
win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
smsContent.setEditable(false);
//-----------------------------------------------
// 用户列表和是否私聊放到窗口的最右边
Box rightBox = new Box(BoxLayout.Y_AXIS);
onLineUsers.setFixedCellWidth(120);
onLineUsers.setVisibleRowCount(13);
rightBox.add(new JScrollPane(onLineUsers));
win.add(rightBox, BorderLayout.EAST);
//-----------------------------------------------
// 关闭窗口退出当前程序
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.pack(); // swing 加上这句 就可以拥有关闭窗口的功能
/** 设置窗口居中,显示出来 */
setWindowCenter(win,650,600,true);
// 发送按钮绑定点击事件
sendBn.addActionListener(this);
}
private void displayLoginView(){
/** 先让用户进行登录
* 服务端ip
* 用户名
* id
* */
/** 显示一个qq的登录框 */
loginView = new JFrame("登录");
loginView.setLayout(new GridLayout(3, 1));
loginView.setSize(400, 230);
JPanel ip = new JPanel();
JLabel label = new JLabel(" IP:");
ip.add(label);
ipEt = new JTextField(20);
ip.add(ipEt);
loginView.add(ip);
JPanel name = new JPanel();
JLabel label1 = new JLabel("姓名:");
name.add(label1);
nameEt = new JTextField(20);
name.add(nameEt);
loginView.add(name);
JPanel btnView = new JPanel();
JButton login = new JButton("登陆");
btnView.add(login);
JButton cancle = new JButton("取消");
btnView.add(cancle);
loginView.add(btnView);
// 关闭窗口退出当前程序
loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setWindowCenter(loginView,400,260,true);
/** 给登录和取消绑定点击事件 */
login.addActionListener(this);
cancle.addActionListener(this);
}
private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
/** 得到所在系统所在屏幕的宽高 */
Dimension ds = frame.getToolkit().getScreenSize();
/** 拿到电脑的宽 */
int width1 = ds.width;
/** 高 */
int height1 = ds.height ;
System.out.println(width1 +"*" + height1);
/** 设置窗口的左上角坐标 */
frame.setLocation(width1/2 - width/2, height1/2 -height/2);
frame.setVisible(flag);
}
@Override
public void actionPerformed(ActionEvent e) {
/** 得到点击的事件源 */
JButton btn = (JButton) e.getSource();
switch(btn.getText()){
case "登陆":
String ip = ipEt.getText().toString();
String name = nameEt.getText().toString();
// 校验参数是否为空
// 错误提示
String msg = "" ;
// 12.1.2.0
// \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\
if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
msg = "请输入合法的服务端ip地址";
}else if(name==null || !name.matches("\\S{1,}")){
msg = "姓名必须1个字符以上";
}
if(!msg.equals("")){
/** msg有内容说明参数有为空 */
// 参数一:弹出放到哪个窗口里面
JOptionPane.showMessageDialog(loginView, msg);
}else{
try {
// 参数都合法了
// 当前登录的用户,去服务端登陆
/** 先把当前用户的名称展示到界面 */
win.setTitle(name);
// 去服务端登陆连接一个socket管道
socket = new Socket(ip, Constants.PORT);
//为客户端的socket分配一个线程 专门负责收消息
new ClientReader(this,socket).start();
// 带上用户信息过去
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1); // 登录消息
dos.writeUTF(name.trim());
dos.flush();
// 关系当前窗口 弹出聊天界面
loginView.dispose(); // 登录窗口销毁
displayChatView(); // 展示了聊天窗口了
} catch (Exception e1) {
e1.printStackTrace();
}
}
break;
case "取消":
/** 退出系统 */
System.exit(0);
break;
case "发送":
break;
}
}
}
客户端socket处理线程:
public class ClientReader extends Thread {
private Socket socket;
// 接收客户端界面,方便收到消息后,更新界面数据。
private ClientChat clientChat ;
public ClientReader(ClientChat clientChat, Socket socket) {
this.clientChat = clientChat;
this.socket = socket;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
/** 循环一直等待客户端的消息 */
while(true){
/** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
int flag = dis.readInt();
if(flag == 1){
// 在线人数消息回来了
String nameDatas = dis.readUTF();
// 展示到在线人数的界面
String[] names = nameDatas.split(Constants.SPILIT);
clientChat.onLineUsers.setListData(names);
}else if(flag == 2){
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
小结
- 此处说明了如果启动客户端界面,以及登陆功能后,服务端收到新的登陆消息后,会响应一个在线列表用户回来给客户端更新在线人数!
客户端发送消息逻辑
客户端发送群聊消息,@消息,以及私聊消息。
实现步骤
- 客户端启动后,在聊天界面需要通过发送按钮推送群聊消息,@消息,以及私聊消息。
- 如果直接点击发送,默认发送群聊消息
- 如果选中右侧在线列表某个用户,默认发送@消息
- 如果选中右侧在线列表某个用户,然后选择右下侧私聊按钮默,认发送私聊消息
代码实现
客户端主体代码:
public class ClientChat implements ActionListener {
/** 1.设计界面 */
private JFrame win = new JFrame();
/** 2.消息内容框架 */
public JTextArea smsContent =new JTextArea(23 , 50);
/** 3.发送消息的框 */
private JTextArea smsSend = new JTextArea(4,40);
/** 4.在线人数的区域 */
/** 存放人的数据 */
/** 展示在线人数的窗口 */
public JList<String> onLineUsers = new JList<>();
// 是否私聊按钮
private JCheckBox isPrivateBn = new JCheckBox("私聊");
// 消息按钮
private JButton sendBn = new JButton("发送");
// 登录界面
private JFrame loginView;
private JTextField ipEt , nameEt , idEt;
private Socket socket ;
public static void main(String[] args) {
new ClientChat().initView();
}
private void initView() {
/** 初始化聊天窗口的界面 */
win.setSize(650, 600);
/** 展示登录界面 */
displayLoginView();
/** 展示聊天界面 */
//displayChatView();
}
private void displayChatView() {
JPanel bottomPanel = new JPanel(new BorderLayout());
//-----------------------------------------------
// 将消息框和按钮 添加到窗口的底端
win.add(bottomPanel, BorderLayout.SOUTH);
bottomPanel.add(smsSend);
JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
btns.add(sendBn);
btns.add(isPrivateBn);
bottomPanel.add(btns, BorderLayout.EAST);
//-----------------------------------------------
// 给发送消息按钮绑定点击事件监听器
// 将展示消息区centerPanel添加到窗口的中间
smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
// 让展示消息区可以滚动。
win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
smsContent.setEditable(false);
//-----------------------------------------------
// 用户列表和是否私聊放到窗口的最右边
Box rightBox = new Box(BoxLayout.Y_AXIS);
onLineUsers.setFixedCellWidth(120);
onLineUsers.setVisibleRowCount(13);
rightBox.add(new JScrollPane(onLineUsers));
win.add(rightBox, BorderLayout.EAST);
//-----------------------------------------------
// 关闭窗口退出当前程序
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.pack(); // swing 加上这句 就可以拥有关闭窗口的功能
/** 设置窗口居中,显示出来 */
setWindowCenter(win,650,600,true);
// 发送按钮绑定点击事件
sendBn.addActionListener(this);
}
private void displayLoginView(){
/** 先让用户进行登录
* 服务端ip
* 用户名
* id
* */
/** 显示一个qq的登录框 */
loginView = new JFrame("登录");
loginView.setLayout(new GridLayout(3, 1));
loginView.setSize(400, 230);
JPanel ip = new JPanel();
JLabel label = new JLabel(" IP:");
ip.add(label);
ipEt = new JTextField(20);
ip.add(ipEt);
loginView.add(ip);
JPanel name = new JPanel();
JLabel label1 = new JLabel("姓名:");
name.add(label1);
nameEt = new JTextField(20);
name.add(nameEt);
loginView.add(name);
JPanel btnView = new JPanel();
JButton login = new JButton("登陆");
btnView.add(login);
JButton cancle = new JButton("取消");
btnView.add(cancle);
loginView.add(btnView);
// 关闭窗口退出当前程序
loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setWindowCenter(loginView,400,260,true);
/** 给登录和取消绑定点击事件 */
login.addActionListener(this);
cancle.addActionListener(this);
}
private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
/** 得到所在系统所在屏幕的宽高 */
Dimension ds = frame.getToolkit().getScreenSize();
/** 拿到电脑的宽 */
int width1 = ds.width;
/** 高 */
int height1 = ds.height ;
System.out.println(width1 +"*" + height1);
/** 设置窗口的左上角坐标 */
frame.setLocation(width1/2 - width/2, height1/2 -height/2);
frame.setVisible(flag);
}
@Override
public void actionPerformed(ActionEvent e) {
/** 得到点击的事件源 */
JButton btn = (JButton) e.getSource();
switch(btn.getText()){
case "登陆":
String ip = ipEt.getText().toString();
String name = nameEt.getText().toString();
// 校验参数是否为空
// 错误提示
String msg = "" ;
// 12.1.2.0
// \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\
if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
msg = "请输入合法的服务端ip地址";
}else if(name==null || !name.matches("\\S{1,}")){
msg = "姓名必须1个字符以上";
}
if(!msg.equals("")){
/** msg有内容说明参数有为空 */
// 参数一:弹出放到哪个窗口里面
JOptionPane.showMessageDialog(loginView, msg);
}else{
try {
// 参数都合法了
// 当前登录的用户,去服务端登陆
/** 先把当前用户的名称展示到界面 */
win.setTitle(name);
// 去服务端登陆连接一个socket管道
socket = new Socket(ip, Constants.PORT);
//为客户端的socket分配一个线程 专门负责收消息
new ClientReader(this,socket).start();
// 带上用户信息过去
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1); // 登录消息
dos.writeUTF(name.trim());
dos.flush();
// 关系当前窗口 弹出聊天界面
loginView.dispose(); // 登录窗口销毁
displayChatView(); // 展示了聊天窗口了
} catch (Exception e1) {
e1.printStackTrace();
}
}
break;
case "取消":
/** 退出系统 */
System.exit(0);
break;
case "发送":
// 得到发送消息的内容
String msgSend = smsSend.getText().toString();
if(!msgSend.trim().equals("")){
/** 发消息给服务端 */
try {
// 判断是否对谁发消息
String selectName = onLineUsers.getSelectedValue();
int flag = 2 ;// 群发 @消息
if(selectName!=null&&!selectName.equals("")){
msgSend =("@"+selectName+","+msgSend);
/** 判断是否选中了私法 */
if(isPrivateBn.isSelected()){
/** 私法 */
flag = 3 ;//私发消息
}
}
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(flag); // 群发消息 发送给所有人
dos.writeUTF(msgSend);
if(flag == 3){
// 告诉服务端我对谁私发
dos.writeUTF(selectName.trim());
}
dos.flush();
} catch (Exception e1) {
e1.printStackTrace();
}
}
smsSend.setText(null);
break;
}
}
}
客户端socket处理线程:
class ClientReader extends Thread {
private Socket socket;
private ClientChat clientChat ;
public ClientReader(ClientChat clientChat, Socket socket) {
this.clientChat = clientChat;
this.socket = socket;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
/** 循环一直等待客户端的消息 */
while(true){
/** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
int flag = dis.readInt();
if(flag == 1){
// 在线人数消息回来了
String nameDatas = dis.readUTF();
// 展示到在线人数的界面
String[] names = nameDatas.split(Constants.SPILIT);
clientChat.onLineUsers.setListData(names);
}else if(flag == 2){
//群发,私聊 , @消息 都是直接显示的。
String msg = dis.readUTF() ;
clientChat.smsContent.append(msg);
// 让消息界面滾動到底端
clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
小结
- 此处实现了客户端发送群聊消息,@消息,以及私聊消息。
- 如果直接点击发送,默认发送群聊消息
- 如果选中右侧在线列表某个用户,默认发送@消息
- 如果选中右侧在线列表某个用户,然后选择右下侧私聊按钮默,认发送私聊消息。