这篇文章实现了丐版的聊天室。用到的主要技术是多线程和TCP,一共有三各类:服务器Server,服务器线程类Worker,客户端Client。话不多说,直接看代码。
Server.java
Server类用于启动监听服务和管理Worker类。接收到客户端的连接请求后会新建一个线程运行Worker类并放到threadMap
中。
import java.net.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.io.*;
public class Server {
//创建了一个线程安全的键值对集合
public static Map<String, Worker> threadMap = new ConcurrentHashMap<String, Worker>();
public static void main(String[] args) {
try {
//在22222端口上运行服务
ServerSocket ss = new ServerSocket(22222);
System.out.println("服务器启动...");
while(true) {
Socket s = ss.accept();
//启动线程
new Thread(new Worker(s)).start();
System.out.println("连接到一个用户" );
}
}
catch(Exception ex){
ex.printStackTrace();
}
}
}
Worker.java
Worker类负责文本的输入输出。Worker接受到客户端发送来的消息后会进行遍历,判定是否是发给自己的消息。
import java.io.*;
import java.net.*;
class Worker implements Runnable{
Socket s;
InputStream ips;
OutputStream ops;
BufferedReader br;
DataOutputStream dos;
public Worker(Socket s) {
this.s = s;
}
@Override
public void run() {
try {
ips = s.getInputStream();
ops = s.getOutputStream();
//输入流
br = new BufferedReader(new InputStreamReader(ips));
//输出流
dos = new DataOutputStream(ops);
while (true) {
String strWord = br.readLine();
//获取用户名
String name = strWord.split(":")[0];
//将客户端添加到threadmap
Server.threadMap.put(name, this);
System.out.println(strWord +"---" + (strWord.length()-name.length()-1));
//quit正常关闭
if (strWord.equalsIgnoreCase("quit"))
break;
//遍历map集合,给不是自己的线程发送消息
for(Worker w : Server.threadMap.values()) {
if(w != this){
w.dos.writeBytes(strWord + System.getProperty("line.separator"));
}
}
}
br.close();
// 关闭包装类,会自动关闭包装类中所包装的底层类。所以不用调用ips.close()
dos.close();
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Client.java
客户端实现的功能是在本机发送和接受文本,打开连接后会启动一个while循环监听服务器的消息,如果接受到消息就会打印出来。发送消息时用输入输出流向服务端进行发送。
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
try {
//此处换成本机的ip地址,只在本地测试可以直接getHostAddress()
Socket s = new Socket(InetAddress.getByName("192.168.56.1"), 22222);
//打开输入输出流
InputStream ips = s.getInputStream();
BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
OutputStream ops = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(ops);
BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Input your name:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
while(true) {
String strWord = brKey.readLine();
if(strWord.equalsIgnoreCase("quit")) {
break;
}
else {
//System.out.println("i sent:" + strWord);
//后面这个System.getProperty("line.separator")是换行的意思
dos.writeBytes(name +":" + strWord + System.getProperty("line.separator"));
System.out.println(brNet.readLine());
}
}
dos.close();
brNet.close();
brKey.close();
s.close();
}catch(Exception ex) {
ex.printStackTrace();
}
}
}
其中Server类和Worker类是一个工程,Client是另一个工程。运行的时候要先运行服务器,再运行客户端,不然会产生异常。运行效果就不放了。
这里需要强调的是Server类的
public static Map<String, Worker> threadMap = new ConcurrentHashMap<String, Worker>();
其中的ConcurrentHashMap是线程安全的hasmap。由于换成不安全的Map时,由于Worker类会对threadMap进行访问,会出现消息和目标用户不对应的情况。关于线程、并行串行、同步异步方面的知识目前还在学习中,以后会分享出来。