技术:
- 客户端Client
- 服务器端Server
- 多线程
实际内容:
今天来实现的是一个网络编程的低配版QQ聊天。实现一个私聊和群聊的功能。
大概的过程:
1.每个客户端一个名称
2.像某个客户端发起私聊
3.群聊
* 1.登录 u+ 姓名 u+
* 2.返回结果 成功1 失败-1
* 3.私聊 p+ 姓名内容 P+
* 4.群聊 a+ 聊天内容 a+
* 5.发文件 f+
* 6.发语音 v+
* map //用Map集合来管理所有的登陆的用户
* [jack-socket]
* [Map,Map]
* u+ jack u+//用Jack作为用户名登录
* p+ jack你好啊 p+//向jack发起了私聊
- 客户端只能向服务器端发送文件或字符
- 服务器端只能得到客户端发过来的数据
- 必须客户端和服务器端有一个规范
- 客户端的需求可以在发送的字符里面体现
第一步:
创建一个聊天的规范ChatProtocol:
public interface ChatProtocol {
//登录
String LOGIN_FLAG = "u+";
//私聊
String PRIVATE_FLAG = "p+";
//群聊
String PUBLIC_FLAG = "a+";
//分隔符
String SPLIT_FLAG = "♥";
//成功的状态
String SUCCESS = "1";
String FAILURE = "-1";
}
第二步:
接下来就是我们的一个主程序。先来创建我们实现聊天
的服务器端Server。
class Server {
//用于保存每一个用户对应的姓名和socket
public static UserManager manager = new UserManager();
public static void main(String[] args){
//创建ServerSocket
try (ServerSocket ss = new ServerSocket(8823)){
//监听所有来连接的客户端
while(true){
Socket socket = ss.accept();
//让子线程处理这个socket
new ServerThread(socket).start();
}
}catch (IOException e){
}
}
}
在这里我运行的时候,出了一点问题,就是运行Server服务器端它没有经过try catch这个抛出异常,直接一步到底,导致我的服务器端只运行一次就关闭了,代码也没有提示错误。经过仔细的查找,发现是我的ip地址错了,导致我的服务器端不经过try-catch。改了之后就行了。
在这里我们还需要一个服务器的子线程来处理从客户端输入过来的数据和把处理好的数据从终端输出给客户端。
/**
* 服务器端子线程
*/
class ServerThread extends Thread{
private Socket socket;
//构造方法
public ServerThread(Socket socket){
this.socket=socket;
}
@Override
public void run() {
//读取
//我们把变量发在try的外面,扩大了其作用域,是为了防止BufferedReader在有异常的时候断掉。
BufferedReader br = null;
PrintStream ps =null;
try {
//登陆
//1.得到对应的输入流对象
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//得到对应的输出流
ps= new PrintStream(socket.getOutputStream());
/**
*判断是否登录
*/
String line = null;
while ((line = br.readLine())!=null){
//登陆 u+...u+
if (line.startsWith(ChatProtocol.LOGIN_FLAG)
&&line.endsWith(ChatProtocol.LOGIN_FLAG)){
//u+jacku+
//获取名字
String name= line.substring(2,line.length()-2);
//判断这个用户是否已经登陆
if (Server.manager.isLogined(name)){
//登录过了
//发送结果给客户端
ps.println(ChatProtocol.FAILURE);
}else {
//没有登陆
//保存当前登陆的用户信息
Server.manager.save(name,socket);
//发送结果给客户端
ps.println(ChatProtocol.SUCCESS);
}
}
/**
*判断是不是私聊
*/
else if (line.startsWith(ChatProtocol.PRIVATE_FLAG)
&&line.endsWith(ChatProtocol.PRIVATE_FLAG)){
//P+jack♥hello+
//获取信息
String msg = line.substring(2,line.length()-2);
//分割
String[] items = msg.split(ChatProtocol.SPLIT_FLAG);
//用户名
String name= items[0];
//聊天内容
String message = items[1];
//通过用户名找到对应的socket
Socket desSocket = Server.manager.socketByName(name);
PrintStream desPs = new PrintStream(desSocket.getOutputStream());
//获取当前用户的名称
String currentName = Server.manager.nameBySocket(socket);
//发送私聊信息
desPs.println(currentName+"向你法来私聊"+message);
}
else if(line.startsWith(ChatProtocol.PUBLIC_FLAG)
&&line.endsWith(ChatProtocol.PUBLIC_FLAG)){
/**
*群聊
*/
//处理数据
String msg = line.substring(2,line.length()-2);
System.out.println(msg);
//获取当前用户 的名称
String currentName = Server.manager.nameBySocket(socket);
//遍历所有的用户信息
Collection sockets = Server.manager.allUsers();
for (Socket s: sockets){
PrintStream tempps = new PrintStream(s.getOutputStream());
tempps.println(currentName+"发来群聊:"+msg);
}
}else{
String currentName = Server.manager.nameBySocket(socket);
PrintStream tempps = new PrintStream(socket.getOutputStream());
tempps.println("消息格式有误 请重新输入:");
}
}
} catch (IOException e) {
e.printStackTrace();
}
//私聊
//群聊
super.run();
}
}
这里我们创建了一个接口用实现客户端和服务器端的规范
第三步:
定义一个UserManager来管理用户的登录信息。
**
* 管理所有的登陆的用户Map
* 判断某个用户是否已经登陆
*
*/
public class UserManager {
//保存所有用户信息
private Map users = new HashMap<>();
/**
* 判断用户是否已经登录
* @param name
*/
public synchronized boolean isLogined(String name) {
//遍历数组
for (String key : users.keySet()) {
if (key.equals(name)) {
return true;
}
}
return false;
}
/**
* 保存当前登陆的用户信息
*/
public synchronized void save(String name, Socket socket){
users.put(name,socket);
}
/**
* 通过用户名找到对应的socket
*/
public synchronized Socket socketByName(String name){
return users.get(name);
}
/**
* 通过socket对象找到对应的名称
*/
public synchronized String nameBySocket(Socket socket){
for (String key : users.keySet()){
//取出这个key对应大的socket
if (socket == users.get(key)){
return key;
}
}
return null;
}
/**
* 获取所有人的对象
*/
public synchronized Collection allUsers(){
return users.values();
}
}
既然我们的服务器端创建好了,接下来就是客户端的搭建。
第四步:
客户端:
public class Client {
public static void main(String[] args){
BufferedReader br= null;
PrintStream ps = null;
BufferedReader brServer =null;
//连接服务器端
try (Socket socket = new Socket("10.129.15.36",8823)){
//登陆
//从终端接收收入流
br = new BufferedReader(new InputStreamReader(System.in));
//发给服务器端的输出流
ps= new PrintStream(socket.getOutputStream());
//接收服务器端的收入流
brServer = new BufferedReader(new InputStreamReader
(socket.getInputStream()));
/**
*处理登录
*/
while (true){
//接收终端输入信息
// BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// String line = br.readLine();
String line= JOptionPane.showInputDialog("请输入用户名");
//拼接登录格式
String loginStr = ChatProtocol.LOGIN_FLAG+line+ChatProtocol.LOGIN_FLAG;
//发送给服务器端
ps.println(loginStr);
//接收服务器端返回的结果
String result = brServer.readLine();
//判断登录结果
if (result.equals(ChatProtocol.SUCCESS)){
//登陆成功
System.out.println("登陆成功");
break;
}else {
System.out.println("用户名已存在 请重新登录");
}
}
//登录成功
/**
*开启子线程处理服务器端的输入
*/
new ClientThread(socket).start();
//主线程接收终端输入 发送给服务器端
String line;
while ((line = br.readLine())!=null){
//发送给服务器端
ps.println(line);
}
}catch (IOException e){
}
}
}
创建客户端的子线程:
/**
*子线程
*/
class ClientThread extends Thread{
private Socket socket;
public ClientThread(Socket socket){
this.socket=socket;
}
/**
*接收服务器端的输入发送给客户端
*/
@Override
public void run() {
BufferedReader br = null;
try {
br= new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line =null;
while ((line =br.readLine())!= null){
System.out.println(line);
}
} catch (IOException e) {
System.out.println("网络出错!");
}finally {
try {
if (br != null) {
br.close();
}
if (socket != null) {
socket.close();
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
到这里一个低配版的QQ就创建好了。再来回顾一下它的搭建过程。
- 创建一个接口来规范服务器端和客户端
- 创建其服务器端和子线程处理客户端的数据
- 创建一个类UserManager来管理所有用户登录信息
- 创建客户端和子线程接收处理服务器端的数据
实现私聊我们就创建一个客户端Client就行了,群聊就直接创建多个客户端Client1\Client2...其内容和第一个创建的客户端是一样的。子线程不需多创建,一个就行了。
运行结果:
私聊:
群聊:
从结果里面可以看出我们通过 p+ 姓名♥内容 P+实现了私聊,a+内容a+实现群聊。当我们的Client的用户名为1时,Client2的用户名就不能再为1了,只能领取。这样我们基本实现了一个低配版的QQ聊天功能。