文章目录
- 前言
-
- 客户端
- 服务端
- 服务端的源代码:
- 实现效果
前言
在学TCP的时候就做了一个多用户登录并得到响应的一个小项目,这个聊天室也是基于TCP通信原理的交互,在服务端对接收的Socket对象封装成线程类,开启多线程实现多用户同时接发数据。
不同的点是:
客户端
对接收和发送的代码进行了封装,封装成独立出来的两个线程类,就可以实现读写分离,不再是请求响应式的交流。
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("------client------");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入用户名");
String name = br.readLine();
//建立连接:socket创建客户端
Socket client = new Socket("localhost",8888);
//客户端发送信息
new Thread(new ClientSend(client,name)).start(); //导入发送线程类
//获取消息
new Thread(new ClientReceive(client)).start(); //导入接收线程类
}
}
上面导入name变量之后在创建线程体时会先把name导入,这样在服务端就可以接收到这个线程对象的name属性,方便后面群发消息可以调用说话的客户端的名字。
下面就是客户端的接收和发送的两个线程类。
接收:
public class ClientReceive implements Runnable {
DataInputStream dis ;
Socket client;
boolean isRunning = true;
public ClientReceive(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
release();
}
}
public void run() {
while(isRunning) {
//接收消息
String datas;
try {
datas = dis.readUTF();
System.out.println(datas);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//释放资源
private void release() {
this.isRunning = false;
try {
dis.close();
client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
发送类:
public class ClientSend implements Runnable {
BufferedReader console;
DataOutputStream dos;
Socket client;
boolean isRunning = true;
String name;
public ClientSend(Socket client,String name) {
this.name = name;
this.client = client;
console = new BufferedReader(new InputStreamReader(System.in));
try {
dos = new DataOutputStream(client.getOutputStream());
//发送名称
dos.writeUTF(name);
dos.flush();
} catch (IOException e) {
release();
}
}
public void run() { //线程体
while(isRunning) {
//客户端发送信息
String msg;
try {
msg = console.readLine();
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//释放资源
private void release() {
this.isRunning = false;
try {
dos.close();
client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
服务端
1.服务器是请求响应式的,所以不需要用到上面创建的接收发送线程类。
2.对Socket进行封装成线程类,在类内加入输入输出流和发送接收消息的两个类,然后在run线程体中执行。
static class Channel implements Runnable{
private DataInputStream dis = null;
private DataOutputStream dos= null;
private Socket client;
private boolean isRunning = true;
private String name;
public Channel(Socket client) throws IOException {
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
this.name = receive();
this.send("欢迎来到聊天室");
this.sendOther(this.name+"来到了聊天室",true);
} catch (IOException e) {
release();
}
}
//接收消息
private String receive(){
String msg ="";
try {
msg = dis.readUTF();
} catch (IOException e) {
release();
}
return msg;
}
//发送消息
private void send(String msg) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
release();
}
}
//线程体
public void run() {
while(isRunning) {
String msg = receive();
//send(msg); 自己不用发给自己了
sendOther(msg,false); //发给其他人
}
}
}
3.要想把一个客户端发送的数据发送给其他流的客户端,需要先建立一个容器,存放每一个新建立的socket。
static private CopyOnWriteArrayList all = new CopyOnWriteArrayList();
//比较安全,发生并发时数据不会混乱
ServerSocket socket = new ServerSocket(8888);
//阻塞式的连接
while(true) {
Socket client = socket.accept();
System.out.println("客户端建立了连接");
Channel c = new Channel(client);
all.add(c); //管理所有的成员
//利用多线程实现多个客户端同时运行
new Thread(c).start();
}
然后在线程对象中加入sendOther的关键方法。这里面还实现了私聊的功能。
//发送消息给其他人,实现群聊
//私聊约定数据格式:@xxx:msg
private void sendOther(String msg,boolean isSys) {
if(msg.startsWith("@")) { //判断是不是私聊的语句
String name = msg.substring(1,msg.indexOf(":"));
msg = msg.substring(msg.indexOf(":")+1); //分离名称和数据
for(Channel other:all) {
if(other.name.equals(name)) { //找到目标
other.send(this.name+"悄悄地对您说:"+msg);
}
}
}else { //不是私聊那就是群聊了
for(Channel other:all) {
if(other == this) { //自己不发送给自己
continue;
}
if(!isSys)
other.send(this.name+"对所有人说:"+msg); //群聊消息
else other.send(msg); //系统消息
}
}
}
服务端的源代码:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 在线聊天室:服务端
* 目标 加入容器实现群聊 私聊在sendOther里面
*
* @author Little Black
*
*/
public class Chat {
static private CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();//比较安全,发生并发时数据不会混乱
public static void main(String[] args) throws IOException {
System.out.println("------server------");
//指定端口
ServerSocket socket = new ServerSocket(8888);
//阻塞式的连接
while(true) {
Socket client = socket.accept();
System.out.println("客户端建立了连接");
Channel c = new Channel(client);
all.add(c); //管理所有的成员
//利用多线程实现多个客户端同时运行
new Thread(c).start();
}
}
//一个客户代表一个channel
static class Channel implements Runnable{
private DataInputStream dis = null;
private DataOutputStream dos= null;
private Socket client;
private boolean isRunning = true;
private String name;
public Channel(Socket client) throws IOException {
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
this.name = receive();
this.send("欢迎来到聊天室");
this.sendOther(this.name+"来到了聊天室",true);
} catch (IOException e) {
release();
}
}
//接收消息
private String receive(){
String msg ="";
try {
msg = dis.readUTF();
} catch (IOException e) {
release();
}
return msg;
}
//发送消息
private void send(String msg) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
release();
}
}
//发送消息给其他人,实现群聊
//私聊约定数据格式:@xxx:msg
private void sendOther(String msg,boolean isSys) {
if(msg.startsWith("@")) { //判断是不是私聊的语句
String name = msg.substring(1,msg.indexOf(":"));
msg = msg.substring(msg.indexOf(":")+1); //分离名称和数据
for(Channel other:all) {
if(other.name.equals(name)) { //找到目标
other.send(this.name+"悄悄地对您说:"+msg);
}
}
}else {
for(Channel other:all) {
if(other == this) { //自己不发送给自己
continue;
}
if(!isSys)
other.send(this.name+"对所有人说:"+msg); //群聊消息
else other.send(msg); //系统消息
}
}
}
//释放资源
private void release() {
try {
dos.close();
dis.close();
isRunning = false;
all.remove(this);
sendOther(this.name+"离开了聊天室", true) ;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//线程体
public void run() {
while(isRunning) {
String msg = receive();
//send(msg); 自己不用发给自己了
sendOther(msg,false); //发给其他人
}
}
}
}