1.0
实现功能:只有一个客户端,实现可以进行多次输入,返回输入内容
public class server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket=new ServerSocket(8888);
Socket client=serverSocket.accept();
//这里这个数据流是可以实时同步的
DataInputStream dis=new DataInputStream(client.getInputStream());
DataOutputStream dos=new DataOutputStream(client.getOutputStream());
boolean isRunning=true;
while(isRunning){
String msg=dis.readUTF();
dos.writeUTF(msg);
dos.flush();
}
dos.close();
dis.close();
client.close();
}
}
public class Client {
public static void main(String[] args) throws IOException {
Socket client=new Socket("localhost",8888);
DataOutputStream dos=new DataOutputStream(client.getOutputStream());
DataInputStream dis=new DataInputStream(client.getInputStream());
boolean isRunning=true;
while(isRunning){
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str=br.readLine();
dos.writeUTF(str);
String response=dis.readUTF();
System.out.println(response);
}
}
}
现在想要运行多个客户端,但是服务器只能接收一次,所以要加入多线程
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
2.0 加入多线程,实现多个客户端与服务器连接,此时客户端不发生改变。
定义服务器端放在最外面,其他内容丢入死循环当中,作用是让多个客户端可以进入,一次循环完成对应一个客户端的进入。
里面多线程包装,作用是避免出现客户端等待前面客户端的情况。
public class server {
public static void main(String[] args) throws IOException {
//服务器应该是可以和多个客户端进行连接的
ServerSocket serverSocket = new ServerSocket(8888);
while(true){
Socket client = serverSocket.accept();
new Thread(()->{
DataInputStream dis = null;
DataOutputStream dos=null;
try {
dis = new DataInputStream(client.getInputStream());
dos= new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
boolean isRunning = true;
while (isRunning) {
String msg = null;
try {
msg = dis.readUTF();
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
isRunning=false;
}
}
try {
if(dos!=null){
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(dis!=null){
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(client!=null){
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
用兰巴德套了这么多内容,会使代码不好维护,所以要封装一下,封装不对功能做出改动。
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
主要是两个方面的改动,
特殊的,用一个工具类单独实现释放资源,两个方面都可以公用
public class Utils {
public static void close(Closeable... targets) {
for (Closeable target : targets) {
if (target != null) {
try {
target.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器方面,用类实现多线程,用方法封装各个功能,
这里再说一下服务器多线程的原理,服务器的端口是不用进入循环的,接收端口数据需要进入循环,而且一般服务器都是无限循环,工作也不会停止,会有很多端口同时进行连接,所以要循环接收多个端口,一次成功的接受就表示一个端口连接成功,每个端口都开一个线程,因为这里的Channel对象每次都会new一次,所以不用担心锁的问题,这里没有资源共享,只是用了同样的方法。然后Channel类里面就封装了2.0服务器的所有操作,提高程序可维护性。
public class server {
public static void main(String[] args) throws IOException {
//服务器应该是可以和多个客户端进行连接的
ServerSocket serverSocket = new ServerSocket(8888);
while(true){
Socket client = serverSocket.accept();
new Thread(new Channel(client)).start();
}
}
}
//一个客户代表一个channel
class Channel implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private Socket client;
private boolean isRunning=true;
public Channel( Socket client) {
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos= new DataOutputStream(client.getOutputStream());
} 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);
} catch (IOException e) {
release();
}
}
//释放资源
private void release(){
this.isRunning=false;
Utils.close(dis,dos,client);
}
@Override
public void run() {
while(isRunning){
String msg=receive();
if(!msg.equals("")){
send(msg);
}
}
}
}
Client方面
相对于2.0,在客户端方面主要实现了发送和接收消息的分离,之前受限于程序的连续性,必须发送消息之后才会收到消息,而正常的聊天室应该是可以任意收发的。这里就要用多进程将两个功能分开。
public class Client {
public static void main(String[] args) throws IOException {
Socket client=new Socket("localhost",8888);
new Thread(new Send(client)).start();
new Thread(new Receive(client)).start();
}
}
public class Send implements Runnable {
private boolean isRunning=true;
private Socket client;
private BufferedReader br;
private DataOutputStream dos=null;
public Send(Socket client) {
this.client=client;
br=new BufferedReader(new InputStreamReader(System.in));
try {
dos=new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
this.release();
}
}
public void release(){
this.isRunning=false;
Utils.close(dos,client);
}
public void send(String str){
try {
dos.writeUTF(str);
} catch (IOException e) {
e.printStackTrace();
}
}
private String getMsg(){
try {
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
@Override
public void run() {
while(isRunning){
String msg=getMsg();
if(!msg.equals("")){
send(msg);
}
}
release();
}
}
public class Receive implements Runnable{
private boolean isRunning=true;
private Socket client=null;
private BufferedReader br=null;
private DataInputStream dis=null;
public Receive(Socket client) {
this.client = client;
try {
dis=new DataInputStream(client.getInputStream());
} catch (IOException e) {
System.out.println("111");
release();
}
}
public String receive(){
String response= null;
try {
response = dis.readUTF();
return response;
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
public void release(){
this.isRunning=false;
Utils.close(dis,client);
}
@Override
public void run() {
while(isRunning){
String msg=receive();
if(!msg.equals("")){
System.out.println(msg);
}
}
release();
}
}
3.0版本仍然没有实现群聊和私聊的具体功能,不同的客户端不能看到对方的消息。
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
聊天室4.0
服务器端,
在上面的基础上,加入了容器,将所有连接的客户端都放在这个容器里面,使得能够共享信息。
sendOthers在send的基础之上建立,每一个客户端在自己的线程当中发消息过来服务器,服务器都将消息传给除了它本身的所有客户端,实现消息的同步。具体的操作上呢,只不过原来是返回给本身线程,现在变成了将信息返回给其他的所有线程,这里就要使用这个容器来进行遍历,一个sendOthers方法和容器的使用就可以实现这个功能
CopyOnWriteArrayList可以实现多线程的快速同步
除此之外,还加入了群聊成员身份的标记,用户名作为客户端群聊第一条信息,并用字符串类型保留成员的名字,在操作开始,循环开始之前就进行一次sendOthers,让其他成员知道该成员到来,而且提醒该成员已经进入群聊。在run方法开始的时候实现成员进入提醒,在返回字符串时加上成员称谓。
public class server {
static CopyOnWriteArrayList<Channel> all=new CopyOnWriteArrayList<>();
public static void main(String[] args) throws IOException {
//服务器应该是可以和多个客户端进行连接的
ServerSocket serverSocket = new ServerSocket(8888);
while(true){
Socket client = serverSocket.accept();
Channel c=new Channel(client);
all.add(c);//管理所有成员
new Thread(c).start();
}
}
}
//一个客户代表一个channel
class Channel implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private Socket client;
private boolean isRunning=true;
private String name;
public Channel( Socket client) {
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos= new DataOutputStream(client.getOutputStream());
} 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);
} catch (IOException e) {
release();
}
}
private void sendOthers(String msg){
for (Channel other:all
) {
if(other==this){
continue;
}else{
other.send(msg);
}
}
}
//释放资源
private void release(){
this.isRunning=false;
Utils.close(dis,dos,client);
}
@Override
public void run() {
name=receive();
this.send("你进入群聊");
sendOthers(name+"进入群聊");
while(isRunning){
String msg=receive();
if(!msg.equals("")){
sendOthers(name+":"+msg);
}
}
}
}
客户端
再循环之外进行一次输入用户名操作,并将其作为线程名字,将其传入Send当中,然后发送第一条信息就是用户名。
public class Client {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入用户名:");
String uname=br.readLine();
Socket client=new Socket("localhost",8888);
new Thread(new Send(client),uname).start();
new Thread(new Receive(client)).start();
}
}
public class Send implements Runnable {
private boolean isRunning=true;
private Socket client;
private BufferedReader br;
private DataOutputStream dos=null;
public Send(Socket client) {
this.client=client;
br=new BufferedReader(new InputStreamReader(System.in));
try {
dos=new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
this.release();
}
}
public void release(){
this.isRunning=false;
Utils.close(dos,client);
}
public void send(String str){
try {
dos.writeUTF(str);
} catch (IOException e) {
e.printStackTrace();
}
}
private String getMsg(){
try {
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
@Override
public void run() {
//4.0只有这里变化了//4.0只有这里变化了//4.0只有这里变化了
//4.0只有这里变化了//4.0只有这里变化了//4.0只有这里变化了
//4.0只有这里变化了//4.0只有这里变化了//4.0只有这里变化了
try {
dos.writeUTF(Thread.currentThread().getName());
} catch (IOException e) {
e.printStackTrace();
}
while(isRunning){
String msg=getMsg();
if(!msg.equals("")){
send(msg);
}
}
release();
}
}
receive没有改变,参考3.0代码。
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
聊天室5.0
实现私聊功能
我这里设置私聊的格式是@+名字+?+内容
在服务器sendOthers当中进行信息的筛选,内容中有@的和没有的分别用if
else,有@时将数据解析,得到被@的用户名,和传输的内容,然后单独调用send方法进行数据返回
这里面要注意的是用户名和字符串比较混乱,进入服务器内容进入,进入sendOthers是sendOthers(name+":"+msg);
所以在实现5.0版本时将上面4.0也做了一点小改动,将用户名单独拎出来,作为Channel的属性,方便私聊的有关实现。(上面4.0的代码已经改了)
所以私聊的时候进入send的字符串是 本客户端用户名+":"+@+接收人名字+?+内容
在sendOthers中筛选时,就要筛选出接收者名字 和 内容,将接收者名字一一与容器中的客户端比较,名字相同时发出,用接收者的send方法,
other.send(this.name+":"+msg);
反馈给接收者,这样就是单项发送。
代码只改变server
public class server {
static CopyOnWriteArrayList<Channel> all=new CopyOnWriteArrayList<>();
public static void main(String[] args) throws IOException {
//服务器应该是可以和多个客户端进行连接的
ServerSocket serverSocket = new ServerSocket(8888);
while(true){
Socket client = serverSocket.accept();
Channel c=new Channel(client);
all.add(c);//管理所有成员
new Thread(c).start();
}
}
}
//一个客户代表一个channel
class Channel implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private Socket client;
private boolean isRunning=true;
private String name;
public Channel( Socket client) {
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos= new DataOutputStream(client.getOutputStream());
} 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);
} catch (IOException e) {
release();
}
}
private void sendOthers(String msg){
System.out.println(msg);
boolean isPrivate=msg.contains("@");
if(isPrivate){
int temp1=msg.indexOf("@");
System.out.println(temp1);
int temp2=msg.indexOf("?");
System.out.println(temp2);
String targetName=msg.substring(temp1+1,temp2);
System.out.println(targetName);
msg=msg.substring(temp2+1);
for (Channel other:all
) {
if(other.name.equals(targetName)){
other.send(this.name+":"+msg);
}
}
}else{
for (Channel other:all
) {
if(other==this){
continue;
}else{
other.send(msg);
}
}
}
}
//释放资源
private void release(){
this.isRunning=false;
Utils.close(dis,dos,client);
}
@Override
public void run() {
this.name=receive();
this.send("你进入群聊");
sendOthers(name+"进入群聊");
while(isRunning){
String msg=receive();
if(!msg.equals("")){
sendOthers(name+":"+msg);
}
}
}
}