聊天室

功能:

1.群聊  

2.私聊:发送信息格式  @昵称:xxxx(xxxx为信息内容)

代码如下:

客户端

package chat_socket.copy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
 * 客户端 
 */
public class Cilent {
	
	private Socket socket;
	
	//构造方法初始化客户端--两个参数:1:服务端计算机的IP地址 2.服务端应用程序的端口(int类型 0-65535)
	public Cilent() throws Exception{
		//创建Socket对象时就会尝试根据给定的地址(localhost)和端口(8088)连接服务器
		socket = new Socket("localhost",8088);
	}
	
	//调用开始方法
	public void start(){
		//单独建立一个线程来读取服务端发送过来的信息
		Thread t = new Thread(new ClientThread());
		t.start();
		Scanner scan = new Scanner(System.in);
		//输入流-向服务端发送信息
		try {
			//通过socket获取输出流(抽象类)
			OutputStream out = socket.getOutputStream();
			
			PrintWriter pw = new PrintWriter(
					new OutputStreamWriter(
							out,"UTF-8"),true);
			System.out.println("欢迎来到聊天室!");
			//输入昵称
			System.out.println("请输入昵称:");
			String regex = "\\w+";
			while(true){
				String nickname = scan.nextLine();
				if(nickname.matches(regex)){
					pw.println(nickname);
					break;
				}
				System.out.println("请从新输入:");
			}
			
			String message = null;
			while(true){
				message = scan.nextLine();
				pw.println(message);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
	
	public static void main(String[] args) {
		try {
			Cilent c = new Cilent();
			c.start();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("客户端初始化失败!");
		}
	}
	
	//建立一个线程来读取服务器发过来的信息,并输出到控制台
	private class ClientThread implements Runnable{
		public void run(){
			try{
				InputStream in = socket.getInputStream();
				BufferedReader br = new BufferedReader(
						new InputStreamReader(
								in,"UTF-8"));
				String message = null;
				//循环读取服务器发送的每一个字符串
				while((message = br.readLine())!= null){
					System.out.println(message);
				}
			} catch (Exception e) {
				e.printStackTrace();
			} 
		}
	}
}
服务端代码如下:

package chat_socket.copy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 服务端 
 */
public class Server {
	
	private ServerSocket server;
	
	//创建线程池,用于管理客户端的连接-在构造方法里初始化
	private ExecutorService threadPool;
	
	//输出流的一个集合-每创建一个连接,就将该连接的输出流加入集合
	private Map<String,PrintWriter> map;
	
	//构造方法,初始化服务端,指定服务端应用程序端口-抛出异常,让调用者处理
	public Server() throws Exception{
		server = new ServerSocket(8088);
		threadPool = Executors.newFixedThreadPool(50);
		//存放所有客户端输出流的集合--key是昵称   value是输出流
		map = new HashMap<String,PrintWriter>();
	}
	
	//调用开始方法
	public void start(){
		try {
			while(true){
				//用于监听8088端口,若一个客户端连接,则返回一个Socket类型实例
				System.out.println("等待客户端连接......");
				Socket socket = server.accept();
				//用连接池,没建立一个连接就放入线程池
				ServerThread m = new ServerThread(socket);
				threadPool.execute(m);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
	public static void main(String[] args) {
		try {
			Server s = new Server();
			s.start();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("服务端初始化失败!");
		}
	}
	
	//锁的是方法所属的对象(Server类型的s对象)
	//将给定的输出流存入共享集合----注意线程的安全性使用synchronized
	public synchronized void addMap(String nickname,PrintWriter pw){
		map.put(nickname, pw);
	}
	
	//将给定的输出流从共享集合中删除----注意线程的安全性使用synchronized
	public synchronized void removeMap(String nickname){
		map.remove(nickname);
	}
	
	//将线程收到的消息通过集合中的流依次发送给每个客户端---群发----注意线程的安全性使用synchronized
	public synchronized void sendMessageAll(String message){//群聊
		Collection<PrintWriter> c = map.values();
		for(PrintWriter pw : c){
			pw.println(message);
		}
	}
	
	//根据制定的昵称输出--私聊
	public synchronized void sendMessageSingle(String nickname,String message){
		String name = message.substring(1, message.indexOf(":"));
		String mess = message. substring(message.indexOf(":")+1);
		map.get(name).println("["+nickname+"]说:"+mess);
	}
	
	//建立线程,每连接一个客户端,开启一个线程--内部类(在外部建立内部类对象,并调用外部类中属性和方法)
	public class ServerThread implements Runnable{
		private Socket socket;
		private String ipname;//备用ip地址
		private int port;//备用端口号
		
		private String nickname;//昵称
		
		public ServerThread(Socket socket){
			this.socket = socket;
		}
		
		public void run(){
			//因为流是用socket创建的,所以不用在finally中关闭流,即不用在外部声明null
			//但是因为要在finally中要使用(remove去掉pw输出流)),所以要在外面进行声明
			PrintWriter pw = null;
			try{
				//通过socket获取输入流、输出流(抽象类)
				InputStream in = socket.getInputStream();
				OutputStream out = socket.getOutputStream();
				//创建输入流读取信息
				BufferedReader br = new BufferedReader(
						new InputStreamReader(
								in,"UTF-8"));
				
				//创建好输入流后读取到的第一个字符串为昵称
				nickname = br.readLine();
				
				//创建输出流写信息
				pw = new PrintWriter(
						new OutputStreamWriter(
								out,"UTF-8"),true); 
				
				//将输出流加入集合
				addMap(nickname, pw);
				
				//输出当前在线人数(服务端显示)
				System.out.println("当前在线人数:"+map.size());
				
				//通知所有在线用户xxx上线了
				sendMessageAll("["+nickname+"]上线了");
				
				/*
				//获取客户端地址对象
				InetAddress address = socket.getInetAddress();
				获取客户端IP(完全限定名)
				String ipname = address.getCanonicalHostName();
				获取IP地址--一般使用这个
				ipname = address.getHostAddress();
				获取客户端的端口号
				port = socket.getPort();
				*/
				
				/*
				 * 读取客户端发送过来的消息,当客户端断开连接
				 * 时,由于客户端系统不同,这里readLine方法的
				 * 执行结果也不同:
				 * 当windows的客户端断开后:这里会抛出异常
				 * 当linux的客户端断开后:这里会返回null
				 */
				
				String message = null;
				//linux在此处会跳出循环
				while((message = br.readLine())!= null){
					if(message.startsWith("@")){
						sendMessageSingle(nickname,message);
					}else{
						sendMessageAll("["+nickname+"]说:"+message);
					}
				}
			} catch(Exception e){
				//此处可以不抛异常,这样断开后就不会报异常
				//e.printStackTrace();
			}finally {
				sendMessageAll("["+nickname+"]下线了");
				//将该连接的输出流从共享集合中删除
				removeMap(nickname);
				//输出当前在线人数
				System.out.println("当前在线人数为:"+map.size());
				//断开后,不用关闭流,直接关闭socket
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

项目关键:
1.建立连接通过

2.能够从Client发送至Server,且Server接收后返回给Cilent

3.用线程池创建线程,实现一对一与服务器建立连接通讯(每创建一个连接,即创建一个线程去运行)

4.为每一个线程指定nickname。将每个线程中的PrintWriter放入map集合(key为nickname  value为pw),当该线程结束时,在finally中remove去掉

5.群聊时遍历map,私聊时指定私聊输入文字格式: @nickname:xxxx,截取nickname在map中get输出流,截取xxxx并发送

注意事项:

1.客户端不用关流,断开连接后,客户端会抛出异常后,自动结束

2.服务端不用关闭流,直接在finally中关闭连接socket即可

3.传输文字时候,最好指定字符集,不要默认

4.遍历map,put,remove时候要规定为互斥





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

荒--

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值