目的
利用学习的socket编程,完成简易QQ以实现私聊与群聊
Socket实现简易QQ
要求
1.每个客户端都有一个名称
2.一个客户端可以向另一个客户端发起私聊
3.一个客户端可以发起群聊
注意:
1.客户端只能向服务器端发送文件或字符
2.服务器端只能得到客户端发过来的数据
分析
程序流程
首先,运行服务器端;接着,运行客户端,客户端需要一个登陆机制,判断是否能登陆过;登陆失败就继续登陆,直到登陆成功;而登陆成功后,就可以选择发起私聊或者群聊
判断是否登录
判断是否已登录,就是与已存在的用户作对比
;第一,需要保存下来已经登录过的用户的信息;然后,当有新用户登录时,就需要将其与已保存的用户对比;若已经登录,就登录失败,需重新登录;如果没有登陆过,就登录成功;因为要实现网络通信,必须得使用IP和端口号,所以可以借用用户名——socket这种映射关系,同时使用Map集合来保存多个映射关系,来达到目的
规范
因为我们使用的是Android Studio来编写这个代码,只有一个端口,没有一个具体的界面来实现私聊窗口或群聊窗口;而为了实现聊天功能,客户端和服务器端就必须制定一个规范,来让客户端的需求在发送的字符里面体现,如:
而之前就学习过,接口可以用来指定一套规范
代码实例
定义规范
定义一套规范,来帮助沟通客户端和服务器端
public interface ChatProtocol {
//登录
String LOGIN_FLAG = "u+";
//私聊
String PRITAVE_FLAG = "p+";
//群聊
String PUBLIC_FLAG = "a+";
//分隔符
String SPLIT_FLAG = "♥";
//成功的状态
String SUCCESS = "1";
String FAILYRE = "-1";
}
定义UserManager类
管理所有登录用户的信息
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* 管理所有的登录的用户Map
* 判断某个用户是否已经登录
*/
public class UserManager {
// 保存所有用户信息
private Map users = new HashMap<>();
/**
* 判断用户是否已经登录
*/
public synchronized boolean isLogined(String name){
// 遍历数组
for (String key: users.keySet()){
if (key.equals(name)){
return true;
}
}
return false;
}
/**
* 保存当前登录的用户信息
*/
public 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;
}
/**
* 获取所有人的socket对象
*/
public synchronized Collection allUsers(){
return users.values();
}
}
服务器端
主线程
管理监听客户端的连接 判断是不是登录
public class Server {
// 用于保存每一个用户对应的姓名和socket
public static UserManager manager = new UserManager();
public static void main(String[] args){
// 胡藏剑ServerSocket
try (ServerSocket ss = new ServerSocket(8888)){
// 监听所有来链接的客户端
while (true){
Socket socket = ss.accept();
//让子线程处理这个socket
new ServerThread(socket).start();
}
} catch (IOException e) {
}
}
}
子线程
处理每个客户端的输入输出
class ServerThread extends Thread{
private Socket socket;
public ServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader br ;
PrintStream ps ;
try {
// 登录
// 1.得到对应的输入流对象
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 得到对应的输出流
ps = new PrintStream(socket.getOutputStream());
String line ;
while ((line = br.readLine()) != null){
// 登录 u+......u+
if (line.startsWith(ChatProtocol.LOGIN_FLAG)&&line.endsWith(ChatProtocol.LOGIN_FLAG)){
// String[] items = line.substring(2).split("u+");
// String name = items[0];
// 获取名字
String name = line.substring(2,line.length()-2);
// 判断用户是否已经登录
// 发生结果给客户端
if (Server.manager.isLogined(name)){
// 已经登录
ps.println(ChatProtocol.FAILYRE);
}else{
// 没有登录
// 保存当前登录的用户信息
Server.manager.save(name,socket);
// 发生结果给客户端
ps.println(ChatProtocol.SUCCESS);
}
}
处理私聊
// 判断是不是私聊
else if (line.startsWith(ChatProtocol.PRITAVE_FLAG)&&line.endsWith(ChatProtocol.PRITAVE_FLAG)){
// 获取信息
int endIndex = line.length()-2;
String msg = line.substring(2,endIndex);
// 分割
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{
// 群聊
// 处理数据
int endIndex = line.length()-2;
String msg = line.substring(2,endIndex);
// 获取当前用户的名称
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);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
主线程
处理登录 接收终端输入 传到服务器
public class Client {
public static void main(String[] args){
BufferedReader br ;
PrintStream ps ;
BufferedReader brServer ;
// 连接服务器
try (Socket socket = new Socket("10.129.19.144",8888)){
// 登录
// 接收终端输入流
br = new BufferedReader(new InputStreamReader(System.in));
// 发给服务器端的输出流
ps = new PrintStream(socket.getOutputStream());
// 接收服务器端的输入流
brServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true){
// 接收终端输入信息
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 ;
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();
}
}
}
}
心得体会
今天也算是很有收获的一天,虽然有些地方没有怎么弄到,但是晚上写的时候还渐渐理解了