创建Client聊天室客户端类
package socket; import java.io.*; import java.net.Socket; import java.util.Scanner; /** * 聊天室客户端 * */ public class Client { /* javc.net.Socket套接字 Socket封装可TCP协议的通讯细节,使用它可以与远端计算机建立连接 并使用俩个流(一个输入,一个输出)完成与远端计算机的数据交互。 */ private Socket socket; /** * 客户端构造方法,用于初始化客户端 */ public Client(){ try { /* Socket常用构造方法: Socket(String host,int port) 实例化Socket的过程就是与服务器建立连接的过程,如果 指定的位置找不到服务器则会抛出异常! 参数1:服务器的IP地址信息 参数2:服务器开启的服务端口 我们可以通过IP找到网络上服务端所在的计算机,通过端口 可以连接到该计算机上的服务端应用程序 */ System.out.println("正在连接服务端...."); socket=new Socket("localhost",8090); System.out.println("与服务端建立连接!"); } catch (IOException e) { e.printStackTrace(); } } public void start(){ try { //启动读取服务端消息的线程 ServerHander hander=new ServerHander(); Thread t=new Thread(hander); t.start(); /* Socket方法: OutputStream getOutputStream() 通过Scoket获取的输出的字节发送到远程的计算机 */ OutputStream out=socket.getOutputStream(); OutputStreamWriter osw =new OutputStreamWriter(out,"UTF-8"); BufferedWriter bw=new BufferedWriter(osw); PrintWriter pw=new PrintWriter(bw,true); Scanner scanner=new Scanner(System.in); while (true) { String line = scanner.nextLine(); if ("exit".equals(line)){ System.out.println("程序结束"); break; } pw.println(line); } } catch (IOException e) { e.printStackTrace(); }finally { try { /* 当不在需要通讯时,应当调用socket的close方法。 该方法会给远端计算机发送一个断开连接的信号,然后 将连接关闭 */ socket.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { Client client=new Client(); client.start(); } /** * 该线程用于循环读取服务端发送过来的消息 */ class ServerHander implements Runnable{ public void run(){ try { InputStream in=socket.getInputStream(); InputStreamReader isr =new InputStreamReader(in,"UTF-8"); BufferedReader br=new BufferedReader(isr); String line; while ((line=br.readLine())!=null){ System.out.println(line); } }catch (IOException e){ // e.printStackTrace(); } } } }
创建聊天室服务端Server类
package socket; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Arrays; /** * 聊天室服务端 */ public class Server { /* serverSocket时运行在服务端的,主要有俩个作用: 1:向系统申请服务端口,客户端就是通过这个端口与服务器建立连接 2:监听服务端口,一旦一个客户端连接了就会立即返回一个Socket 通过这个Socket与客户端对等交互 如果我们将Socket比喻“电话”,那么ServerSocket相当与“总机” */ private ServerSocket serverSocket; /* 存放所有客户端输出流,便于广播消息使用 */ private PrintWriter[] allOut = {}; public Server() { try { /* 实例化ServerSocket的同时指定服务端口,客户端就是通过 这个端口与服务端建立连接的,该端口不能与当前系统 其他应用程序申请的端口一致,否则会抛出异常 java.net.BindException:address already in use */ System.out.println("正在启动服务端...."); serverSocket = new ServerSocket(8090); System.out.println("服务端启动完毕!"); } catch (IOException e) { e.printStackTrace(); } } public void start() { try { /* ServerSocket提供的方法: Server accept() 该方法是一个阻塞方法,调用后程序“卡主”,开始等待客户端 的链接,一旦一个客户端建立连接,此时该方法会立即返回一个 Socket.通过这个Socket就可以与该客户端交互了. 相当于时“接电话的操作” */ while (true) { System.out.println("等待客户端连接..."); Socket socket = serverSocket.accept(); System.out.println("一个客户端连接了!"); //启动一个线程来处理客户端交互 ClientHandler handler = new ClientHandler(socket); Thread t = new Thread(handler); t.start(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Server server = new Server(); server.start(); } /** * 该线程任务是负责并发处理某个客户端的交互工作 */ private class ClientHandler implements Runnable { private Socket socket; private String host;//记录客户端IP地址信息 public ClientHandler(Socket socket) { this.socket = socket; //通过socket获取远端计算机(客户端)的地址信息 host = socket.getInetAddress().getHostAddress(); } public void run() { PrintWriter pw = null; try { InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in, "UTF-8"); BufferedReader br = new BufferedReader(isr); OutputStream out = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8"); BufferedWriter bw = new BufferedWriter(osw); pw = new PrintWriter(bw, true); /* 选取对象的原则:多个线程看到的锁对象必须是同一个 通常我们可以指定临界资源作为锁对象 但是这里多个线程抢临界资源是 allout,这里不行的原因: 同步块中的操作包含对数组的扩容,而扩容会导致allout指向 别的对象,那么就等于说锁对象一直在发生变化 */ synchronized (ClientHandler.class) { // 将该输出流存入共享数组allOut中 //1.扩容allOut allOut = Arrays.copyOf(allOut, allOut.length + 1); //2.将输出流存入数组的最后一个位置 allOut[allOut.length - 1] = pw; } sendMessage(host + "上线了,当前在线人数:" + allOut.length); //读取客户端发送过来的一行字符串 String line; /* 当客户端断开连接时,由于断开方式不同,这里体现的也不同 当客户端强行停止客户端程序(导致socket.close方法没有调用) 那么服务端这里的readLine方法会抛出异常 如果客户端正常停止程序(输入exit停止,则会调用socket.close() 那么readine方法会返回 null表示读取到了末尾,自然停止读取操作 */ while ((line = br.readLine()) != null) { System.out.println(host + "说:" + line); //将消息发给所有客户端 sendMessage(host + "说:" + line); } } catch (IOException e) { e.printStackTrace(); } finally { //处理客户端断开连接后的操作 //将当前客户端的输出流从数组allout中删除 synchronized (ClientHandler.class) { for (int i = 0; i < allOut.length; i++) { if (allOut[i] == pw) { allOut[i] = allOut[allOut.length - 1]; allOut = Arrays.copyOf(allOut, allOut.length - 1); break; } } } sendMessage(host + "下线了,当前在线人数:" + allOut.length); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 将消息发送给客户端 * * @param message */ public void sendMessage(String message) { synchronized (ClientHandler.class) { for (int i = 0; i < allOut.length; i++) { allOut[i].println(message); } } } }
***注意要在同一局域网下
局域网ip地址查看方式:win+R>>cmd>>ipcomfig