00023.11 TCP协议编程:群聊(TCP通信原理,多线程、线程阻塞)

系列文章目录

一、前言

我们平时玩QQ或者微信的群聊,是怎么实现的呢?
是你发一个消息直接全部给每个人发一遍吗?
不,不是的,而是通过服务器

一、需求

在这里插入图片描述
有两个前提,第一B跟C也要依赖Socket,第二要保持跟服务器连接 即在线状态
在这里插入图片描述
同理,B如果发一句消息,那么 A和 C也应该要收到

在这里插入图片描述
我们QQ,微信的群聊其实也是一样的,不是直接发给所有人的,而是通过服务器转发的
服务器起到了转发作用(所以大家不要发一些敏感词,其实服务器上面全部能看到)

根据上面的分析
服务器端肯定是要用到多线程,那么客户端要不要用到多线程呢?大家思考一下
在这里插入图片描述
答案是,客户端也是要多线程的,同时接收和发送消息,这两个操作互不影响
在这里插入图片描述
在这里插入图片描述

二、使用步骤

客户端

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

上面是客户端发送消息的线程,下面我们看客户端接收消息的线程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
客户端到此就写完了

服务端

在这里插入图片描述
在这里插入图片描述
线程里面要做什么事情?
(1)接收当前的客户端发过来的消息
(2)给其它在线的客户端转发
这就意味这不仅要接收还要临时保存
在这里插入图片描述
为什么报错?
静态的方法是不能调用非静态的方法的
怎么解决?
把ArrayList改成静态的就行了
在这里插入图片描述
在这里插入图片描述
上图的处理办法
1、传过来
2、改成内部类
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在java的 foreach中 这里是不能使用.remove的,详细可以参考:
https://blog.csdn.net/weixin_41133233/article/details/88743897
https://blog.csdn.net/kongshaohao/article/details/79666914
在这里插入图片描述
在这里插入图片描述

那怎么处理,可以用迭代器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面代码挪一下位置
在这里插入图片描述
下面思考另外一个问题,是谁发的消息,我们因为没有用户名、昵称 这些,暂时可以用IP地址来显示
但是怎么做呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
现在就可以调试了:
现在调试看看
在这里插入图片描述
可以实现群发了,但是给自己也转发了
如果不想给自己发,怎么处理?
在这里插入图片描述
在这里插入图片描述

发了bye之后没有下线的问题
在这里插入图片描述
有什么办法可以让线程强制停下来呢?
在这里插入图片描述
在这里插入图片描述

这样的话,服务器就不用一直等待了
bug处理:
在这里插入图片描述
在这里插入图片描述

另外一个需求:谁谁谁上线了,谁谁谁下线了,这个怎么做

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、完整代码

客户端

package com.atguigu.test16;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

/*
 * 群聊
 */
public class TestClient {
	public static void main(String[] args) throws UnknownHostException, IOException {
		//1、连接服务器
		Socket socket = new Socket("192.168.30.142",9999);
		
		//2、开启两个线程,一个收消息,一个发消息
		SendThread st = new SendThread(socket);
		ReceiveThread rt = new ReceiveThread(socket);
		
		st.start();
		rt.start();
		
		//等发送线程停下来再往下走
		try {
			st.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//让接收数据的线程停下
		rt.setFlag(false);
		
		//等接收线程停下来,再往下走,断开连接
		try {
			rt.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		socket.close();
	}
}
class SendThread extends Thread{
	private Socket socket;
	
	public SendThread(Socket socket) {
		super();
		this.socket = socket;
	}

	public void run(){
		try {
			//键盘输入
			Scanner input = new Scanner(System.in);
			OutputStream out = socket.getOutputStream();
			PrintStream ps = new PrintStream(out);
			while(true){
				//从键盘输入
				System.out.print("请输入要发送的消息:");
				String content = input.nextLine();
				System.out.println("content:" + content);
				
				//给服务器发送
				ps.println(content);
				
				//如果bye,就结束发送
				if("bye".equals(content)){
					break;
				}
			}
			input.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
class ReceiveThread extends Thread{
	private Socket socket;
	private boolean flag = true;
	
	public ReceiveThread(Socket socket) {
		super();
		this.socket = socket;
	}
	
	public void run(){
		try {
			InputStream in = socket.getInputStream();
			InputStreamReader isr = new InputStreamReader(in);
			BufferedReader br = new BufferedReader(isr);
			
			while(flag){
				String line = br.readLine();
				System.out.println(line);
				if("bye".equals(line)){
					break;
				}
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
}

服务器

package com.atguigu.test16;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;

/*
 * 
 */
public class TestServer {
	private static ArrayList<Socket> online = new ArrayList<Socket>();
	
	public static void main(String[] args) throws IOException {
		//1、开启服务器
		ServerSocket server = new ServerSocket(9999);
		
		while(true){
			//2、接收客户端的连接
			Socket socket = server.accept();
			
			//把这个客户端加入到online中
			online.add(socket);
			
			//每一个客户端独立的线程
			MessageHandler mh = new MessageHandler(socket);
			mh.start();
		}
	}

	//私有的静态的内部类
	//这里用内部类的原因,是为了用上面的online集合
	private static class MessageHandler extends Thread{
		private Socket socket;
		private String ip;
		
		public MessageHandler(Socket socket) {
			super();
			this.socket = socket;
			this.ip = socket.getInetAddress().getHostAddress();
		}

		public void run(){
			//这个客户端的一连接成功,线程一启动,就可以告诉其他人我上线了
			sendToOthers(ip+"上线了");
			
			/*
			 * (1)接收当前的客户端发送的消息
			 * (2)给其他在线的客户端转发
			 */
			//(1)接收当前的客户端发送的消息
			try {
				InputStream in = socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(in);
				BufferedReader br = new BufferedReader(isr);
				
				String content;
				while((content = br.readLine()) !=null){
					if("bye".equals(content)){
						//给自己发一句bye
						OutputStream out = socket.getOutputStream();
						PrintStream ps = new PrintStream(out);
						ps.println("bye");
						
						break;
					}
					
					//收到一句,转发一句
					sendToOthers(ip+"说:" + content);
				}
				
				sendToOthers(ip+"下线了");
			} catch (IOException e) {
				sendToOthers(ip+"掉线了");
			}
		}
		
		//因为转发的代码也很长,独立为一个方法
		public void sendToOthers(String str){
			//遍历所有online的客户端
			Iterator<Socket> iterator = online.iterator();
			while(iterator.hasNext()){
				Socket on = iterator.next();
				if(!on.equals(socket)){//只给其他客户端转发
					try {
						OutputStream out = on.getOutputStream();
						PrintStream ps = new PrintStream(out);
						
						ps.println(str);
					} catch (IOException e) {
						//说明on这个客户端要么下线了,要么掉线了
						iterator.remove();
					}
				}
			}
		}
	}
	
	
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值