1、用户 --------> 服务器

用户与服务器间交互,而不是用户与用户间交互,也可以理解成只有自己一个人的聊天室,此时不用加入多线程。
过程:client A向server发送数据B,server接收此数据B,并将数据B返回发送至A,A再接收server返回回来的数据。
(1)server

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 使用TCP协议:简易聊天室
 * 多人聊天多线程加到服务器端
 * @author majinbuu
 *
 */
public class TCPChat_server {
	public static void main(String[] args) throws IOException {
		System.out.println("启动服务器......");
		//1、创建服务器端套接字,指定接口
		ServerSocket ssk=new ServerSocket(9100);
		//2、由于多个用户一起聊天,while循环多个accept(),甚至同时发送消息,这里要使用多线程
		Socket client=ssk.accept();
		DataInputStream data=new DataInputStream(client.getInputStream());
		DataOutputStream data2=new DataOutputStream(client.getOutputStream());
		boolean isRunning=true;
		while(isRunning) {
			String data1=data.readUTF();
			//System.out.println(data1);
			//3、将数据返回至客户端显示(聊天时,自己和别人都能看到自己发送的消息)
			data2.writeUTF(data1);
			data2.flush(); //writeUTF后必须清空
		}
		data.close();
		data2.close();
		ssk.close();
	}
}

(2)client

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class TCPChat_client2 {
	public static void main(String[] args) throws IOException{
		System.out.println("启动客户端2......");
		//1、创建客户端套接字,并指明目的地,端口
		Socket sk=new Socket("localhost",9100);
		BufferedReader data1=new BufferedReader(new InputStreamReader(System.in));
		DataOutputStream data3=new DataOutputStream(sk.getOutputStream());
		DataInputStream getd=new DataInputStream(sk.getInputStream());
		boolean isRunning=true;
		while(isRunning) {
			//2、发送数据
			String data2=data1.readLine();
			data3.writeUTF(data2);
			data3.flush();
			
			//3、接收数据
			String getdata=getd.readUTF();
			System.out.println(getdata);
	   }
		//4、释放资源123
		data1.close();
		data3.close();
		getd.close();
		sk.close();
		
	}
}

2、加入多线程并《封装》

对服务器端进行改进:上面的代码只允许有一个客户端,这里加入while后,允许多个客户端的存在。然后加入多线程并封装,以简化代码:

package com.chatroom.tcp;

import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class chatServer {
	public static void main(String[] args) throws IOException {
		ServerSocket ssk=new ServerSocket(8889);
		boolean isRunning=true;
		while(isRunning) {
			Socket channel=ssk.accept();
			new Thread(new Channels(channel)).start();
		}
	}
}

class Channels implements Runnable{
	private DataInputStream data1;
	private DataOutputStream getback;
	private Socket channel;
	private boolean isRunning=true;
	
	Channels(Socket channel){
		this.channel=channel;
		try {
			data1=new DataInputStream(channel.getInputStream());
			getback=new DataOutputStream(channel.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
			release(data1,getback,channel);
		}
	}
	
	//封装接收数据
	public String receive() {
		String rec="";
		try {
			rec=data1.readUTF();
		} catch (IOException e) {
			e.printStackTrace();
			release(data1,getback,channel);
		}
		return rec;
	}
	
	public void send(String str) {
		try {
			getback.writeUTF(str);
			getback.flush();
		} catch (IOException e) {
			e.printStackTrace();
			release(data1,getback,channel);
		}
	}
	
	public void release(Closeable... items) { //... 表示可以传入可变个数量的参数
		isRunning=false;
		for(Closeable item:items) {
			try {
				item.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	@Override
	public void run() {
		while(isRunning) {
			String recv=receive();
			if(!recv.equals(""))
				send(recv);
		}
		
	}
	
}

在客户端,这里暂时不考虑用户收、发消息的同时性(如果考虑,则要在客户端收发处 加入多线程),故不做修改。下一节会添加。

3、群聊

在上面的基础上改进:上面两个部分都只允许client与server交流,这里通过再服务器端加入容器,来使得不同的client之间能够传递信息。容器不使用ArrayList,而使用CopyOnWriteArrayList,它具有线程安全等优势。
CopyOnWriteArrayList参考1CopyOnWriteArrayList参考2

对于客户端,其收发操作也需要加入多线程,否则只能按照默认的顺序(比如先发送数据,后接收数据)来执行单个线程。这样就会出现一个问题,即当A客户端启动并发送消息给B客户端时,B客户端此时阻塞在了发送数据的阶段,无法进行到接收数据的阶段,只有当B客户端发送一个数据后,才能执行接收数据的阶段,此时的A客户端才会收到B传过来的数据。

//假如不加入多线程,则客户端之间的收发无法同步
while(isRunning) {
			//2、发送数据
			String data2=data1.readLine();
			data3.writeUTF(data2);
			data3.flush();
			
			//3、接收数据
			String getdata=getd.readUTF();
			System.out.println(getdata);
	   }

因此,假如客户端收发程序段不加入多线程,则客户端之间的收发无法同步。
具体代码如下:
1、服务器端

package com.chatroom.tcp;

import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;

public class chatServer {
	static CopyOnWriteArrayList<Channels> allsoc=new CopyOnWriteArrayList<Channels>();
	public static void main(String[] args) throws IOException {
		System.out.println("enter the server......");
		ServerSocket ssk=new ServerSocket(9093);
		boolean isRunning=true;
		while(isRunning) {
			Socket channel=ssk.accept();
			Channels its=new Channels(channel,allsoc);
			allsoc.add(its);
			new Thread(its).start();
		}
	}
}

class Channels implements Runnable{
	private DataInputStream data1;
	private DataOutputStream getback;
	private Socket channel;
	private boolean isRunning=true;
	private CopyOnWriteArrayList<Channels> allsoc;
	
	Channels(Socket channel,CopyOnWriteArrayList<Channels> allsoc){
		this.channel=channel;
		this.allsoc=allsoc;
		try {
			data1=new DataInputStream(channel.getInputStream());
			getback=new DataOutputStream(channel.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
			release(data1,getback,channel);
		}
	}
	
	//为了实现群聊功能,这里再封装一个方法,把消息发送到全部用户
	public void sendToEveryone(String str) {
		for(Channels i:allsoc) {
			if(this==i) continue; //不用把自己的消息返回给自己
			//这里是i.send(),不是send()
			i.send(str);  //调用send方法
		}
	}
	
	//封装接收数据
	public String receive() {
		String rec="";
		try {
			rec=data1.readUTF();
		} catch (IOException e) {
			e.printStackTrace();
			release(data1,getback,channel);
		}
		return rec;
	}

	public void send(String str) {
		try {
			getback.writeUTF(str);
			getback.flush();
		} catch (IOException e) {
			e.printStackTrace();
			release(data1,getback,channel);
		}
	}
	
	public void release(Closeable... items) { //... 表示可以传入可变个数量的参数
		isRunning=false;
		for(Closeable item:items) {
			try {
				item.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	@Override
	public void run() {
		while(isRunning) {
			String recv=receive();
			if(!recv.equals(""))
				sendToEveryone(recv);
		}	
	}	
}

2、客户端:

package com.chatroom.tcp;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;

public class chatClient {
	public static void main(String[] args) throws UnknownHostException, IOException{
		System.out.println("enter the client......");
		//1、创建客户端套接字,并指明目的地,端口
		Socket sk=new Socket("localhost",9093);
		new Thread(new Send(sk)).start();
		new Thread(new Receive(sk)).start();
	}
}

class Send implements Runnable{
	private Socket sk;
	BufferedReader data1;
	DataOutputStream data3;
	boolean isRunning;
	Send(Socket sk) throws IOException{
		this.sk=sk;
		this.isRunning=true;
		data1=new BufferedReader(new InputStreamReader(System.in));
		data3 = new DataOutputStream(sk.getOutputStream());
	}
	@Override
	public void run() {
		while(isRunning) {
			String data2;
			try {
				data2 = data1.readLine();
				data3.writeUTF(data2);
				data3.flush();
			} catch (IOException e) {
				e.printStackTrace();
				release(sk,data1,data3);
			}
		}
	}
	
	public void release(Closeable... items) {
		isRunning=false;
		for(Closeable item:items) {
			try {
				item.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

class Receive implements Runnable{
	private Socket sk;
	DataInputStream getd;
	boolean isRunning;
	Receive(Socket sk){
		this.sk=sk;
		this.isRunning=true;
		try {
			getd=new DataInputStream(sk.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		while(isRunning) {
			try {
				String getdata=getd.readUTF();
				System.out.println(getdata);
			} catch (IOException e) {
				e.printStackTrace();
				release(sk,getd);
			}
		}
	}
	
	public void release(Closeable... items) {
		for(Closeable item:items) {
			try {
				item.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

效果:

java实现聊天室文件传输 java实现聊天室功能_java