创建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
版权声明:本文为thm0805原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。