实现简单聊天室
能够实现简单功能
1 查看在线人名单 2 私聊 3 群发 4 退出
客户端代码
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 5000);
System.out.println("已连接服务器");
//建立连接
System.out.println("请输入一个昵称");
Scanner input = new Scanner(System.in);
String s = input.next();
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write(s.getBytes());
//发送请求
new Thread(()->{
while (true){
try {
byte[] bt = new byte[1024*8];
int l= 0;
while(true){
l =in.read(bt);
if(l==-1) break;
System.out.println(new String(bt, 0, l));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//输出接收的消息
new Thread(()->{
String s1;
System.out.println("请选择功能:");
System.out.println("1.查看在线人名单");
System.out.println("2.私聊(2 人名 信息)");
System.out.println("3.群发(3 群发信息)");
System.out.println("4.退出");
while(true){
try {
s1= input.next();
out.write(s1.getBytes());
switch (s1) {
case "2":
s1=input.next();
out.write(s1.getBytes());
s1=input.next();
out.write(s1.getBytes());
break;
case "3":
s1=input.next();
out.write(s1.getBytes());break;
case "4":System.exit(0);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
客户端需要两个线程同时工作,一个线程从控制台读取数据,一个线程将从服务器返回的数据显示在控制台上。
服务器代码
public class task implements Runnable {
private Socket socket;
final static ConcurrentHashMap<Socket, String> map = new ConcurrentHashMap<>();
public task(Socket socket) {
this.socket=socket;
}
@Override
public void run() {
try {
Login(socket);
handle(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handle(Socket socket) throws IOException {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] bt = new byte[1024*8];
int l= 0;
Set<Socket> sockets= map.keySet();
Socket sockett = null;
String s1,s2;
//读取请求
while (true){
l = in.read(bt);
String s =new String(bt,0,l);
switch (s){
case "1":
//响应名单
Collection<String> list = map.values();
out.write(list.toString().getBytes());break;
case "2"://读取目的用户
l = in.read(bt);
s1 =new String(bt,0,l);
//寻找目的用户的socket
for (Socket socket1 : sockets) {
if(map.get(socket1).equals(s1)){
sockett=socket1;
break;
}
}
//读取内容
l = in.read(bt);
s2 =new String(bt,0,l);
//响应输出
OutputStream out1 = sockett.getOutputStream();
out1.write((map.get(socket)+":"+s2).getBytes());break;
case "3": l = in.read(bt);
s2 =new String(bt,0,l);
for (Socket socket1 : sockets) {
OutputStream out2 = socket1.getOutputStream();
out2.write((map.get(socket)+":"+s2).getBytes());
}break;
case "4":
String s3=map.get(socket);
map.remove(socket);
System.out.println(s3+"离开了");
Thread thread =Thread.currentThread();
thread.stop();
}
}
}
//建立连接
private static void Login(Socket socket) throws IOException {
try {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] bt = new byte[1024*8];
int l=in.read(bt);
String s =new String(bt,0,l);
System.out.println(s+"已连接");
map.put(socket,s);
s="欢迎您"+s;
out.write(s.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(5000);
System.out.println("服务已启动,等待连接");
ExecutorService service = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
while(true) {
// 2. 调用accept 等待客户端连接
Socket socket = serverSocket.accept();
service.submit(new task(socket));
}
}
}
在实现过程中,每次先给服务器发送请求代号。如果是1则服务器之间返回Map信息,如果是2则服务器再接受两条信息,分别是发送对象和信息内容,如果是3则服务器再接受信息内容。
一般客户端-服务器编程应该使用同一的协议,而且客户端与服务器的接受和发送内容很相似,可以抽象为一个父类,只是接受信息后的处理不同。而子类只需要完成接受后的处理操作就好。
协议:
先发送一个字节的请求代号,然后发送两个字节的消息长度,最后发送消息。
抽象父类
public abstract class AbstractCS {
//接受消息
protected void receive(Socket socket, InputStream in, OutputStream out) throws IOException {
while (true) {
int cmd = in.read();
if (cmd == -1) {
break;
}
int hi = in.read();
int lo = in.read();
int length = (hi << 8) + lo;
byte[] content = new byte[length];
in.read(content);
String str = new String(content, "utf-8");
handle(socket, out, cmd, str);
}
}
//发送消息
protected void send(OutputStream out, int cmd, String content) throws IOException {
out.write(cmd);
byte[] bytes = content.getBytes("utf-8");
int length = bytes.length;
out.write(0xFF & length >> 8);
out.write(0xFF & length);
out.write(bytes);
}
//自定义接受消息后的处理
protected abstract void handle(Socket socket, OutputStream out, int cmd, String str) throws IOException;
}
客户端
public class Client extends AbstractCS {
public static void main(String[] args) throws IOException {
Client client = new Client();
client.start();
}
public void start() throws IOException {
Socket socket = new Socket("localhost", 5000);
// 第一个线程负责从控制台获取输入
new Thread(() -> {
try (
Scanner scanner = new Scanner(System.in);
OutputStream out = socket.getOutputStream();
) {
System.out.println("请输入昵称:");
Scanner s = new Scanner(System.in);
String name = s.nextLine();
send(socket.getOutputStream(),1,name);
input(scanner, out);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
// 第二个线程负责接收服务器端的输入
new Thread(() -> {
try (
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
) {
receive(socket, in, out);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
@Override
protected void handle(Socket socket, OutputStream out, int cmd, String content) throws IOException {
switch (cmd){
case 5:
System.out.println(content);break;
case 6:
System.out.println(content);break;
case 7:
System.out.println(content);break;
case 8:
System.out.println(content);break;
}
}
protected void input(Scanner scanner, OutputStream out) {
try {
while (scanner.hasNextLine()) {
String str = scanner.nextLine();
char cmd = str.charAt(0);
// 发送消息
// 2 获取用户
// 3 内容 群聊
// 4 内容 对方名字 私聊
// 对命令进行分析
switch (cmd) {
case '2':
send(out, 2, "");
break;
case '3':
String content = str.substring(2);
send(out, 3, content);
break;
case '4':
String content1 = str.substring(2);
send(out, 4, content1);
break;
default:
System.out.println("不支持的命令");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器
public class Server extends AbstractCS {
public static void main(String[] args) {
Server server = new Server();
server.start();
}
public void start() {
System.out.println("==== 【畅聊】聊天室,版本 v2");
ExecutorService service = Executors.newFixedThreadPool(10);
try (ServerSocket serverSocket = new ServerSocket(5000)) {
while (true) {
// 来一个连接,开一个空闲线程处理请求,但上限是10
Socket socket = serverSocket.accept();
service.submit(() -> {
try (
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
) {
this.receive(socket, in, out);
}
catch (SocketException ex){
map.remove(socket);
}
catch (IOException e) {
e.printStackTrace();
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
private Map<Socket, String> map = new ConcurrentHashMap<>();
@Override
protected void handle(Socket socket, OutputStream out, int cmd, String content) throws IOException {
switch (cmd) {
case 1:
map.put(socket, content);
send(out,5, "欢迎[" + content + "]来到聊天室");
break;
case 2:
send(out,6, map.values().toString());
break;
case 3:
String nick = map.get(socket);
String c = nick+": "+content;
map.forEach((s, value)->{
try {
send(s.getOutputStream(),7,c);
} catch (IOException e) {
e.printStackTrace();
}
});
break;
case 4:
String[] array = content.split(" ");
String n = array[0]; // 对方的名字
String c2 = array[1]; // 内容
boolean found = false;
for (Map.Entry<Socket, String> entry :map.entrySet()) {
// 找到了
if(entry.getValue().equals(n)) {
send(entry.getKey().getOutputStream(),8, map.get(socket)+":"+c2);
found = true;
break;
}
}
if(!found) {
send(out,8, "聊天室没有这个人");
}
break;
}
}
}