聊天室

来源:互联网 发布:淘宝3000多的实体娃娃 编辑:程序博客网 时间:2024/05/03 01:16

功能:

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对象)//将给定的输出流存入共享集合----注意线程的安全性使用synchronizedpublic synchronized void addMap(String nickname,PrintWriter pw){map.put(nickname, pw);}//将给定的输出流从共享集合中删除----注意线程的安全性使用synchronizedpublic synchronized void removeMap(String nickname){map.remove(nickname);}//将线程收到的消息通过集合中的流依次发送给每个客户端---群发----注意线程的安全性使用synchronizedpublic 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());//断开后,不用关闭流,直接关闭sockettry {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时候要规定为互斥





0 0