使用基于 tcp 协议的编程模型实现多人同时在线聊天,要求每个客户端将发 送的聊天内容发送到服务器,服务器接收到后转发给当前所有在线的客户端。
思路分析
- 创建一个服务器类,声明一个Server Socket对象并提供一个端口号,无限循环接收客户端的连接,模拟服务器永远在运转的情况,用一个List集合存放客户端连接的Socket
- 创建一个服务器通信线程类,重写run方法,使用输入输出流和客户端进行通信,无线接收并转发消息;转发消息要遍历List集合,一个Sokcet转发一次
- 创建客户端操作类,声明一个Socket连接服务器端,用一个user变量作为客户端标识,声明一个有参构造,参数为Socket和user。
- 创建客户端的线程方法分为接收信息和发送信息,一直无限循环,当发送和接收为bye时break跳出循环,当接收信息中的user为本类user时,continue跳出循环
- 创建客户端实例类创建操作类的对象,并调用接收和发送信息的方法来创建线程,使用线程的join方法保证主线程在子线程执行完以后再执行
代码示例
服务器整体类
package com.lagou.homework.homework4_3;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 服务器类
*/
public class Server {
private static List<Socket> sockets = new ArrayList<>();//创建一个集合存储客户端的socket
private static ServerSocket ss = null;
private static Socket s = null;
public static void main(String[] args) {
server();
}
//连接客户端开始服务
public static void server(){
while (true){
try {
ss = new ServerSocket(10086);
System.out.println("等待连接ing");
s= ss.accept();//接收客户端的连接并赋值给Socket
sockets.add(s);
System.out.println("客户端"+s.getInetAddress()+"连接成功");
//每连接一个客户使用一个线程为之服务
new ServerThread(sockets,s).start();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null !=ss){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
服务器线程通信
package com.lagou.homework.homework4_3;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Scanner;
/**
* 服务器通信线程类
*/
public class ServerThread extends Thread{
private List<Socket> sockets;
private BufferedReader br;
private Socket s;
public ServerThread(List<Socket> sockets,Socket s){
this.sockets = sockets;
this.s = s;
}
@Override
public void run() {
try {
br = new BufferedReader(new InputStreamReader(s.getInputStream()));//获取输入流接收数据
while (true){
String str = br.readLine();//接收客户端发来的字符串
System.out.println(str);
String[] strings = str.split(":");
System.out.println("客户端"+strings[0]+"发来了:"+strings[1]);
for (Socket socket:sockets
) {
boolean flag = false;
try {
flag = socket.getKeepAlive();
}catch (Exception e){
continue;
}
if (!flag){
PrintStream ps = new PrintStream(socket.getOutputStream());//获取输出流回发数据
ps.println(str);
ps.flush();
}
}
System.out.println("已将发来的数据"+str+"转发给其他客户端!");
if ("bye".equals(strings[1])){
System.out.println("客户端"+s.getInetAddress()+"已下线");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null != s){
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null!=br){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端操作类
package com.lagou.homework.homework4_3;
import com.lagou.homework.homework4_1.SysMain;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
*
*/
public class ClientClass {
private String user;//客户端标识
private PrintStream ps= null;
private BufferedReader br = null;
private Scanner sc = null;
private String str ;
public ClientClass(String user,Socket socket){
try {
this.user = user;
ps = new PrintStream(socket.getOutputStream());//打印流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//输入流
sc = new Scanner(System.in);//保证一直处于可输入状态
} catch (IOException e) {
e.printStackTrace();
}
}
//发送消息线程
public Thread send(){
Thread threadSend = new Thread(){
@Override
public void run() {
while (true){
str= sc.next();//获取键盘输入信息
ps.println(user+":"+str);//将用户名和输入信息一起发送给服务器
ps.flush();
if ("bye".equalsIgnoreCase(str)){
System.out.println("退出聊天室");
break;
}
}
}
};
return threadSend;
}
//接收消息线程
public Thread receive(){
Thread threadReceive = new Thread(){
@Override
public void run() {
while (true){
String s = null;//读取服务器传来的消息
try {
s = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
//进行消息拆分
String[] strings = null;
try {
strings = s.split(":");//以:作为拆分点
}catch (Exception e){
break;
}
if (user.equalsIgnoreCase(strings[0])){
System.out.println("我:"+strings[1]);//如果是本人发送的消息,此客户端界面显示我:+消息
continue;
}
System.out.println(strings[0]+":"+strings[1]);//否则显示user+消息
if ("bye".equalsIgnoreCase(strings[1])){
break;//如果服务端传来的时bye 则跳出循环
}
}
}
};
return threadReceive;
}
//关闭流
public void close(){
if (null!=null){
sc.close();
}
if (null!=br){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null!=ps){
ps.close();
}
}
}
客户端实例1
package com.lagou.homework.homework4_3;
import java.io.IOException;
import java.net.Socket;
/**
* 客户端实例
*/
public class Client01 {
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket("127.0.0.1",10086);
System.out.println("连接服务器成功");
ClientClass c1 = new ClientClass("小明",socket);//创建一个客户端实例
Thread t1 = c1.send();//获取发送消息线程
Thread t2 = c1.receive();//获取接收消息线程
t1.start();
t2.start();
t1.join();
t2.join();
c1.close();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//关闭socket
if (null!=socket){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:实例不只一个,客户端实例一为参照
运行结果
—————————服务器显示内容———————————
等待连接ing
客户端/127.0.0.1连接成功
等待连接ing
客户端/127.0.0.1连接成功
等待连接ing
小明:我是小明你好
客户端小明发来了:我是小明你好
已将发来的数据小明:我是小明你好转发给其他客户端!
小红:你好我是小红
客户端小红发来了:你好我是小红
已将发来的数据小红:你好我是小红转发给其他客户端!
小明:好的
客户端小明发来了:好的
已将发来的数据小明:好的转发给其他客户端!
小红:bye
客户端小红发来了:bye
已将发来的数据小红:bye转发给其他客户端!
客户端/127.0.0.1已下线
小明:bye
客户端小明发来了:bye
已将发来的数据小明:bye转发给其他客户端!
客户端/127.0.0.1已下线
—————————客户端一显示内容———————————
连接服务器成功
我是小明你好
我:我是小明你好
小红:你好我是小红
好的
我:好的
小红:bye
bye
退出聊天室
Process finished with exit code 0
—————————客户端二显示内容———————————
连接服务器成功
小明:我是小明你好
你好我是小红
我:你好我是小红
小明:好的
bye
退出聊天室
我:bye