项目名称: JavaChatRoom
项目功能:用户登录、用户下线、群聊、私聊
涉及技术:
1.网络编程(JavaSocket API)
2.Java多线程技术
3.使用C/S架构
项目主体:
服务器:
1.使用ServerSocket来建立起一个连接,public ServerSocket(int port): 此方法默认绑定本地ip地址 127.0.0.1 及指定端口号
2.建立连接accept:
public Socket accept(): 等待客户端连接,线程阻塞,当有客户端连接时,返回客户端socket
3.服务器与客户端通信
当客户端与服务器建立起连接后,通过输入输出流(客户端socket)来进行通信
对于服务器端,获取客户端输入流,读取客户端发来的信息
public InputStream getInputStream()
获取客户端输出流,向客户端发送信息
public OutputStream getOutputStream()
服务器端功能:
注册用户:userName: name
用户群聊:G:聊天内容
用户私聊:P:test-聊天内容
用户退出:byebye
客户端:
1.Socket类:
使用Socket方法: public Socket(String host,int port):绑定指定域名,端口号的服务器,只要不报错就建立连接
2.客户端与服务器通信:
获取客户端输入流,读取服务器发来的信息
public InPutStream getInputStream()
获取客户端输出流,向服务器发送信息
public OutputStream getOutputSteam()
主体代码实现:
分为单线程和多线程版本:
单线程版本:
由于是单线程版本,当没有客户端连接时,服务器线程一直处于阻塞状态,直到有客户端连接时,才会返回客户端socket
服务端代码:
public class SingleThreadServer {
public static void main(String[] args) {
try {
// 建立服务端ServerScoket 并绑定本地 6666 端口号
ServerSocket serverSocket = new ServerSocket(6666);
// 等待客户端连接
System.out.println("等待客户端连接ing...");
// 服务器线程一直阻塞,直到有客户端连接,返回客户端连接Socket
Socket client = serverSocket.accept();
System.out.println("有客户端连接,客户端端口号为"+client.getPort());
//获取客户端输出流,向客户端输出信息 自动刷新
PrintStream out = new PrintStream(client.getOutputStream(),true ,"UTF-8");
//获取客户端输入流,读取客户端信息
Scanner in = new Scanner(client.getInputStream());
if(in.hasNextLine()){
//读取一整行输入
System.out.println("客户端发来的信息为:"+in.nextLine());
}
out.println("Server : Hello sir, I am Jarvis,may I help you?");
//关闭流
in.close();
out.close();
serverSocket.close();
} catch (IOException e) {
System.err.println("服务器建立连接失败,异常为 "+e);
}
}
}
服务端界面:
客户端代码:
1.客户端建立与服务器的连接,绑定本地ip地址和 指定端口号 6666
2.由于是单线程,服务器是先得到客户端信息,才能向客户端发送消息,为了防止线程阻塞,客户端先向服务器发送信息,再获取服务器端信息
public class SingleThreadClient {
public static void main(String[] args) {
try {
// 建立客户端Socket并绑定服务器
Socket client = new Socket("127.0.0.1",6666);
// 获取输入、输出流与服务器通信
// 获取输出流,向服务器发送信息
PrintStream out = new PrintStream(client.getOutputStream(),
true,"UTF-8");
out.println("Hi,I am Client");
//获取输入流,读取服务器发送的信息
Scanner in = new Scanner(client.getInputStream());
if(in.hasNextLine()){
System.out.println("服务器发来的信息为 "+in.nextLine());
}
in.close();
out.close();
client.close();
} catch (Exception e) {
System.err.println("客户端出现异常 :"+e);
}
}
}
客户端界面:
多线程版本:
多线程服务器:
1.使用固定大小线程池实现多线程,这里使用的线程池大小为20
2.在这里使用内部类实现客户端的请求,每当有新的客户端请求,就新建线程处理这个请求
直到线程池满就关闭线程池,关闭服务器
3.在服务器端中,使用concurrentHashMap来存储所有连接到服务器的客户端信息
4.由于每个平台中的换行符不同,识别windows下的换行符,将其替换为 " "
// 识别windows下换行符,将 \r 替换为 ""
// pattern为模式,matcher为匹配,匹配后做一个替换
Pattern pattern = Pattern.compile("\r");
Matcher matcher = pattern.matcher(str);
matcher.replaceAll("");
5.四大功能实现
// 获取客户端输入流,读取客户端发来的信息
Scanner in = new Scanner(client.getInputStream());
根据这行代码读取客户端发来的信息
// 一行一行读取客户端信息
if(in.hasNextLine()){
str = in.nextLine();
1.注册功能实现
如果识别到 输入信息中有 "userName",就说明有用户注册,使用 split()方法进行字符串分割,将用户名信息分割出来,然后使用 userRegister()方法,将用户信息,及socket保存在 map中,并且打印聊天室中人数。
if(str.startsWith("userName")){
// userName:test
String userName = str.split("\\:")[1];
userRegister(userName,client);
continue;
}
// 注册方法
private static void userRegister(String userName,Socket socket){
System.out.println("用户"+userName+"上线了!");
// 将用户保存在map中
clientMap.put(userName,socket);
System.out.println("当前聊天室中一共有"+clientMap.size()+"人");
// 告知客户端注册成功
try {
// 获取每个Socket的输出流
PrintStream out = new PrintStream(socket.getOutputStream());
out.println("用户注册成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
用户 one two three 都上线,并且聊天室中人数随之更新
2.群聊功能实现
如果识别到输入信息中有 "G",就说明是群聊信息,使用 split() 方法,分割出有用信息
再使用 groupChat(msg) 方法,遍历取出每个soceket,并且获取每个socket的输出流,向每个客户端发送群聊消息
// 群聊方法
private static void GroupChat(String msg){
Set<Map.Entry<String,Socket>> clientSet =
clientMap.entrySet();
for(Map.Entry<String,Socket> entry : clientSet){
// 遍历取出每个Socket
Socket socket = entry.getValue();
// 获取每个Socket 的输出流
try {
PrintStream out = new PrintStream(socket.getOutputStream());
out.println("群聊消息为 "+msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.私聊方法实现
如果输入信息中有" P ",就说明是私聊方法,获取用户名,聊天信息,使用PrivatedChat()方法,传入用户名,信息
然后根据用户名获取指定的socekt,然后获取该socket的输出流,使用 socket.getOutputStream() 向用户发送信息
// 私聊方法
private static void PrivateChat(String userName,String msg){
// 根据用户名获取指定Socket
Socket socket = clientMap.get(userName);
try {
// 获得指定Socket输出流
PrintStream out = new PrintStream(socket.getOutputStream());
out.println("私聊消息 "+msg);
} catch (IOException e) {
e.printStackTrace();
}
}
4.用户退出实现
如果输入的信息中包含 "byebye", 那么遍历map中的key,找到与client相等的 key,移除掉 username ,并同步聊天室中剩余人数
// 客户端退出
if(str.contains("byebye")){
// 遍历Map,获取userName
String userName = "";
// 获取所有的方法
for(String key : clientMap.keySet()){
// 遍历 Map中的key,找到与client相等的value
if(clientMap.get(key).equals(client)){
userName = key;
}
}
System.out.println("用户"+userName+"下线了...");
clientMap.remove(userName);
System.out.println("当前聊天室一共 "+clientMap.size()+"人");
continue;
}
当有客户端下线时,服务器显示 具体哪个客户端下线,并显示聊天室中还有几人
多线程客户端:
1.socket 绑定服务器 ip 地址及 端口号
2.客户端使用全双工,一个读线程,一个写线程,实现信息的收发
客户端读写线程代码:
public class MultiThreadClient {
public static void main(String[] args) {
try {
Socket client = new Socket("127.0.0.1",6666);
Thread readThread = new Thread(new ReadFromServer(client));
Thread writeThread = new Thread(new WriteToServer(client));
readThread.start();
writeThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
读线程实现:
获取客户端输入流,使用 Scanner in = new Scanner(client.getInputStream()) 读取服务器发来的信息,
class ReadFromServer implements Runnable{
private Socket client;
public ReadFromServer(Socket client){
this.client = client;
}
@Override
public void run() {
try {
// 获取客户端输入流,读取服务器发送的信息
Scanner in = new Scanner(client.getInputStream());
while(true){
// 若client已经被关闭,此处自动退出了
if(in.hasNextLine()){
System.out.println("从服务器发来的信息为 "+in.nextLine());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
写线程实现:
// 获取键盘输入流,读取用户从键盘发送的信息
Scanner in = new Scanner(system.in)
// 获取客户端输出流,将用户键入的信息发送给服务器
PrintStream out = new PrintStream(client.getOutputStream())
当用户的输入信息含有 "byebye",关闭键盘输入流,输出流,及客户端
class WriteToServer implements Runnable{
private Socket client;
public WriteToServer(Socket client){
this.client = client;
}
@Override
public void run() {
try {
// 获取键盘输入流,读取用户从键盘发来的信息
Scanner scanner = new Scanner(System.in);
String string = "";
// 获取客户端输入流,将用户键入的信息发送给服务器
PrintStream out = new PrintStream(client.getOutputStream(),
true,"UTF-8");
while(true){
System.out.println("请输入向服务器发送的信息");
if(scanner.hasNextLine()){
string = scanner.nextLine();
out.println(string);
}
//设置退出标志
if(string.contains("byebye")){
System.out.println("客户端退出,不聊了");
scanner.close();
out.close();
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端注册界面:
客户端one,two,three 都进行注册
客户端群聊界面:
客户端one发起群聊,所有客户端都可以接收到此消息,发送信息为:testtest!
客户端 two 接收到 one发送的消息
客户端 three 接收到 one发送的消息
客户端私聊界面:
three 向 one 送消息
one 接收到消息
由于是私聊,two 没有接收到消息
客户端退出界面:
当 one 发送信息中含有 byebye时,客户端 one 下线