实现分析、

1.开启服务端

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

2.用户聊天

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

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

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

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

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

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

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

服务端界面:

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

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

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


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编程之网络聊天工具(客户端)​


​全部源码点击下载​