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();
}
}
}
}
效果: