【橘猫笔记】TCP网络编程 ServerSocket和Socket实现多客户端聊天

目标

使用SeverSocket 和 Socket 实现双人、多人聊天

过程分析

思路

大致说一下,整体思路
1.实现一个群发效果,且需要执行判断,当前的客户端发送的信息,并不需要通过Server端发送给自己。
2.实现循环侦听,因为有多个套接字请求,所以需要将它们“存起来”。
3.根据循环侦听结果创建多个线程,每条线程都是平级关系,每条线程需要实现发送功能,读取功能(将读取到的信息发送出去)

详细分析

1.每条线程需要单独运行一遍,才能实现群发效果
2.每条线程内的:发送方法、run方法、读取方法 都是不同的socket
3.将每一条线程存到集合,使用当前的线程调取群发方法时,遍历判断,然后切换到其他线程进行发送操作。
4.大概就是:循环(侦听、创建新线程)——存储线程——当前线程接收信息——发送信息——调取群发——切换线程完成发送
5.服务器只有及时转发,和循环监听
原理图

代码示例

Server端

首先要侦听所有的套接字,侦听到一个,就启动一个线程,并将其存至集合
子线程写了发送方法,run方法,接收方法
还有一个单例,需要保证该群发类只有一个实例对象

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Map.Entry;
import java.util.Vector;

/*服务器端:一个线程专门发送和接收消息  一个线程监听所有Socket。
 客户端:一个线程专门发送消息,一个线程专门接收消息。*/
public class QunLiaoServer {
	public static void main(String[] args) throws IOException {
		ServerAccept sa = new ServerAccept();
		sa.start();
	}
}
//创建侦听类
class ServerAccept extends Thread{
	
	@Override
	public void run() {
		try {
			System.out.println("等待客户端连接");
			ServerSocket ss = new ServerSocket(35555);
    //由于可能当有多个客户端连接时,accept方法就会产生多个Socket对象,需加一个while循环监听来自客户端的连接			
			while(true){
				//侦听套接字的连接,accept是一个阻塞的方法,会阻塞当前的线程
				Socket s = ss.accept();
				//建立连接,表示serverSocket在监听,如果监听到有客户端连接则会调用accept方法,然后返回一个Socket,最后建立连接
				System.out.println("连接成功"+s.getPort());
				//传递当前连接的客户端套接字
				SocketShouFa rs = new SocketShouFa(s);//会产生多条线程
				rs.start();
				//传递该线程
				QunFa.getQunFa().add(rs,s);//只有一个实例
				//当第一个socket被侦听到,且被传递下去,会死循环侦听下一个socket,并传递下去
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}
//创建接收和发送类线程 (客户端信息接收,发送方法)
 class SocketShouFa extends Thread{
	Socket socket;
	public SocketShouFa (Socket s){
		this.socket=s;
	}
	public void firstSend(String str,Socket nowsocket){
		try {
			socket.getOutputStream().write((Thread.currentThread().getName()+"端口:"+nowsocket.getPort()+"-->发送:"+str+"\r\n").getBytes());
		} catch (IOException e) {
			 System.out.println("断开了客户端,没有客户端接收信息");//当前接收的客户端断开,无法写给客户端信息会报错
			e.printStackTrace();
			//需要写一个清除当前该客户端的方法
			QunFa.getQunFa().remove(this,socket);
		}	
	}
	//重写run方法
	//线程执行至此,需要接收客户端的信息
	@Override
	public void run() {
		firstSend("您已经连接到服务器",socket);
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String line=null;
			//接收客户端的数据
			while ((line=br.readLine())!=null) {
			//然后服务器再将所有的信息转发给每一个客户端,调用qunFa方法 
			  QunFa.getQunFa().qunFa(this, line);
   }
		} catch (IOException e) {
			 System.out.println(Thread.currentThread().getName()+"客户端已断开");
			e.printStackTrace();
		}
	}
}
//创建群发类(创建单例模型;集合存储 SocketShouFa线程 和套接字 ;实现群发,调取发送方法)
class QunFa{
	//创建单例模型
	private QunFa(){}
	private static final QunFa si= new QunFa();
	public static QunFa getQunFa(){
		return si;
	}
	//创建一个存储 线程 和端口  的Hashtable集合
	Hashtable<SocketShouFa,Socket> ht = new Hashtable<SocketShouFa,Socket>();
	 public void add(SocketShouFa ri,Socket nowsocket){
		 //将每一个线程 和 对应的socket 加入集合中
		 ht.put(ri, nowsocket);
	 }
	 //线程清除
	 public void remove(SocketShouFa ri,Socket nowsocket) {
		  ht.remove(ri, nowsocket);
	 }
	//表示当前的线程给集合中的 非自己 的线程发送的信息
	 public void qunFa(SocketShouFa ri,String img){
		 //遍历集合
		 for (Entry<SocketShouFa, Socket> entry : ht.entrySet()) {
			 SocketShouFa nowRi = entry.getKey();
			 	//如果遍历线程 和 当前线程不一致,就进入
				if(!ri.equals(nowRi)){
					//传递 不一致客户端线程,并且发送当前 线程的socket信息
					nowRi.firstSend(img, ht.get(ri));
				}
			}
	 }
}

Client端

这里有两个线程被启用了,一个是主线程去读取信息,一个子线程发送信息

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class ClientB {
	public static void main(String[] args) {
		// 创建客户端对象
		try {
			Socket s = new Socket("192.168.0.243", 35555);
			SendImgClientB sendclient = new SendImgClientB(s);
			sendclient.start();
			// 创建读取字符流
			BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
			while(true){
				//读取服务端发过来的信息
				String serverimg = br.readLine();
				System.out.println(serverimg);
			}
		} catch (IOException e) {
			System.out.println("服务器下线了");
			e.printStackTrace();
		}
		
	}
}
class SendImgClientB extends Thread {
	Socket ss;
	PrintWriter pw;
	Scanner sc =new Scanner(System.in);

	public SendImgClientB(Socket s) {
		this.ss = s;
		try {
			this.pw = new PrintWriter(s.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	@Override
	public void run() {
		try {
			while (true) {
				// 客户端发送信息
				String in = sc.next();
				pw.println(in);
				pw.flush();// 输出流刷新
				if (in.equals("end")) {
					break;
				}
			}
		} catch (Exception e) {
			System.out.println("服务器下线了");
			e.printStackTrace();
		} finally {
			if (pw != null) {
				pw.close();
			}
			if (ss != null) {
				try {
					ss.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

结果

在这里插入图片描述

发布了6 篇原创文章 · 获赞 16 · 访问量 3307

猜你喜欢

转载自blog.csdn.net/qq_35341203/article/details/90083505
今日推荐