天天看点

JAVA进阶案例 TCP编程之网络聊天工具(服务端)

实现分析、

1.开启服务端

客户端选择‘登录以后’后,提示输入用户名和密码,验证成功则进入好友列表界面

2.用户聊天

双击好友,进入好友聊天界面。在信息框编辑信息 点击发送 

当客户端向服务端发送数据时  根据指令的不同来处理不同的业务。

如果是登录,服务端向数据库查询用户信息。然后把处理后的信息发送给服务端。

JAVA进阶案例 TCP编程之网络聊天工具(服务端)

如果是聊天业务,首先判断当前好友是否在线

如果好友不在线,处理后的信息发送给发送者,如果好友在线 处理后的信息发送给接收者(朋友)

JAVA进阶案例 TCP编程之网络聊天工具(服务端)

服务端界面:

JAVA进阶案例 TCP编程之网络聊天工具(服务端)

简单介绍一下 服务端的运行流程

当点击开启服务后 新建一个线程开启服务器循环监听客户端的连接

try {
      ServerSocket ss = new ServerSocket(4096);
      Socket socket = null;
      // 循环监听客户端的连接 每连接一个客户端 就为其实例化一个线程
      // 在该线程 I/O阻塞监听客户端发送的信息 不然你只能收到一次信息~
      while (true) {
        socket = ss.accept();
        ServiceThread thread = new ServiceThread(socket);
        thread.start();
      }
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }      

至于为何要新建线程开启服务,归结于while循环里面的ss.accept();  如果一直没接收到客户端的连接,会导致线程阻塞。

如果你使用的还是main线程,那么关闭服务会无法点击。

而为了实现多客户端的连接  所以在上面我们为每一个客户端都开启一个线程。

线程的run方法为

@Override
  public void run() {
    ObjectInputStream ois = null;
    ObjectOutputStream oos = null;
    // 时刻监听 客户端发送来的数据
    while (socket != null) {
      try {
        ois = new ObjectInputStream(socket.getInputStream());
        CommandTranser msg = (CommandTranser) ois.readObject();
        // 处理客户端发送来的信息
        msg = execute(msg);
        if ("message".equals(msg.getCmd())) {
          /*
           * 如果 msg.ifFlag即 服务器处理成功 可以向朋友发送信息 如果服务器处理信息失败 信息发送给发送者本人
           */
          if (msg.isFlag()) {
            oos = new ObjectOutputStream(SocketList.getSocket(
                msg.getReceiver()).getOutputStream());
          } else {
            oos = new ObjectOutputStream(socket.getOutputStream());
          }
        }
        // 如果是登录请求 发送给发送者本人
        if ("login".equals(msg.getCmd())) {
          oos = new ObjectOutputStream(socket.getOutputStream());
        }
        oos.writeObject(msg);
      } catch (IOException e) {
        socket = null;
      } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }

    }

  }      

也是一个while循环  I/O阻塞接收客户端的信息。因为你不能只接收客户端一次的信息吧  

如果你发现你只能接受一次信息  问题就出在这里了   因为我也在这里错了~~~~~~

在这里出现了CommandTranser类,execute(CommandTranser msg)方法和SocketList类里面的静态方法getSocket

SocketList类很简单  主要是保存已经和服务器成功连接的客户端

package cn.edu.xynu.util;

import java.net.Socket;
import java.util.HashMap;

import cn.edu.xynu.entity.SocketThread;



/**
 * @author scx
 * 所有已经成功登录服务器的socket和昵称
 */
public class SocketList {
  private static HashMap<String, Socket> map=new HashMap<String, Socket>();
  //将SocketThread入集合
  public static void addSocket(SocketThread socketThread){
    map.put(socketThread.getName(), socketThread.getSocket());
  }
  //通过昵称返回socket
  public static Socket getSocket(String name){
    return map.get(name);
  }
}      

SocketThread实体类

所有成功连接客户端的socket实体类 包括一个socket (客户端)和昵称

package cn.edu.xynu.entity;

import java.net.Socket;

/**
 * @author scx 所有成功连接客户端的socket实体类 包括一个socket 一个昵称
 */
public class SocketThread {
  private Socket socket;
  private String name;

  public SocketThread() {
    super();
    // TODO Auto-generated constructor stub
  }

  public SocketThread(Socket socket, String name) {
    super();
    this.socket = socket;
    this.name = name;
  }

  public Socket getSocket() {
    return socket;
  }

  public void setSocket(Socket socket) {
    this.socket = socket;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

}      

CommandTranser这个类主要负责服务端和客户端的通信数据。

具体代码及注释如下:

package cn.edu.xynu.util;

import java.io.Serializable;

/**
 * @author scx
 * 客户端与服务器交互的数据
 */
public class CommandTranser implements Serializable{
  private static final long serialVersionUID = 1L;
  private String sender=null;//发送者
  private String receiver=null;//接受者
  private Object data=null;//传递的数据
  private boolean flag=false;//指令的处理结果
  private String cmd=null;//服务端要做的指令
  private String result=null;//处理结果
  
  public String getSender() {
    return sender;
  }
  public void setSender(String sender) {
    this.sender = sender;
  }
  public String getReceiver() {
    return receiver;
  }
  public void setReceiver(String receiver) {
    this.receiver = receiver;
  }
  public Object getData() {
    return data;
  }
  public void setData(Object data) {
    this.data = data;
  }
  public boolean isFlag() {
    return flag;
  }
  public void setFlag(boolean flag) {
    this.flag = flag;
  }
  public String getResult() {
    return result;
  }
  public void setResult(String result) {
    this.result = result;
  }
  public String getCmd() {
    return cmd;
  }
  public void setCmd(String cmd) {
    this.cmd = cmd;
  }
  
}      

execute(CommandTranser msg)方法主要处理客户端向服务端发送的数据

由于我们只实现了两个功能  即登录 和聊天 

所以execute方法主要代码如下

// 如果是登录请求
    if ("login".equals(msg.getCmd())) {
      UserService userService = new UserService();
      User user = (User) msg.getData();
      msg.setFlag(userService.checkUser(user));
      /*
       * 如果登陆成功,将该客户端加入已经连接成功的map集合里面 并且开启此用户的接受线程
       */
      if (msg.isFlag()) {
        // 将该线程加入连接成功的map集合
        SocketThread socketThread = new SocketThread();
        socketThread.setName(msg.getSender());
        socketThread.setSocket(socket);
        SocketList.addSocket(socketThread);
        msg.setResult("登陆成功");
      } else {
        msg.setResult("登录失败");
      }
    }

    // 如果是发送消息的指令 判断当前用户是否在线

    if ("message".equals(msg.getCmd())) {
      // 如果要发送的用户在线 发送信息
      if (SocketList.getSocket(msg.getReceiver()) != null) {
        msg.setFlag(true);
      } else {
        msg.setFlag(false);
        msg.setResult("当前用户不在线");
      }
    }
    return msg;
  }      

当用户为login请求时,通过userservice查询数据库中是否有此用户,并返回结果

package cn.edu.xynu.service;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.edu.xynu.entity.User;
import cn.edu.xynu.util.DBHelper;

/**
 * @author scx 客户的业务处理 只查询用户是否在数据库中存在
 */
public class UserService {
  public boolean checkUser(User user) {
    PreparedStatement stmt = null;
    Connection conn = null;
    ResultSet rs = null;
    conn = DBHelper.getConnection();
    String sql = "select * from user where username=? and password =?";
    try {
      stmt = conn.prepareStatement(sql);
      stmt.setString(1, user.getUsername());
      stmt.setString(2, user.getPassword());
      rs = stmt.executeQuery();
      if (rs.next()) {
        return true;
      }
    } catch (SQLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } finally {
      try {
        if (rs != null)
          rs.close();
        if (stmt != null)
          stmt.close();
      } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    return false;
  }
}      

User类用户的实体类,只包括帐号 密码信息

package cn.edu.xynu.entity;

import java.io.Serializable;

/**
 * @author scx
 * 用户信息的实体类
 */
public class User implements Serializable{
  private static final long serialVersionUID = 1L;
  private String username;
  private String password;
  
  public User() {
    super();
  }

  public User(String username, String password) {
    super();
    this.username = username;
    this.password = password;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }
  
}      

而DBhelpher为连接数据库工具类

package cn.edu.xynu.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @author scx
 * 数据库
 */
public class DBHelper {
  private static final String driver="com.mysql.jdbc.Driver";
  private static final String url="jdbc:mysql://localhost:3306/myqquser?useUnicode=true&charcterEncoding=UTF-8";
  private static final String username="root";
  private static final String password="";
  private static  Connection con=null;
  //静态块代码负责加载驱动
  static
  {
    try {
      Class.forName(driver);
    } catch (ClassNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  public static Connection getConnection(){

    if(con==null){
      try {
        con=DriverManager.getConnection(url, username, password);
      } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    return con;
  }
}      

下一篇  

​​JAVA进阶案例 TCP编程之网络聊天工具(客户端)​​

继续阅读