import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/*
* 聊天室客户端
*/
public class Client {
/*
* java.net.Socket
* Socket封装了TCP协议的通讯细节,使用它就可以和远端计算机建立TCP连接,
* 并基于两条流(一个输入一个输出)与与远端计算机交互数据
*/
private Socket socket;
public Client(){
try {
System.out.println("连接服务端");
/*
* 实例化Socket就是与远端计算机建立连接的过程。
* 这里参数1:服务端计算机的IP地址,参数2:服务端打开的服务端口
* 我们通过IP找到服务器计算机通过端口连接到服务器上的服务端应用程序。
*/
socket = new Socket("localhost",8088);
System.out.println("已连接服务端");
} catch (IOException e) {
e.printStackTrace();
}
}
public void start(){
try {
//启动用来读取服务端消息的线程
ServerHandler sh = new ServerHandler();
Thread thread = new Thread(sh);
thread.setDaemon(true); //守护线程
thread.start();
OutputStream ops = socket.getOutputStream();
OutputStreamWriter opsw = new OutputStreamWriter(ops, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(opsw);
PrintWriter pw = new PrintWriter(bw,true);
Scanner scanner = new Scanner(System.in);
while (true){
String str;
str = scanner.nextLine();
if ("exit".equals(str)) {
break;
}
pw.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//关闭socket时会与服务端进行挥手操作
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
/*
* 该线程负责读取服务端发送过来的消息
*/
private class ServerHandler implements Runnable{
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String message;
while ((message = br.readLine()) != null){
System.out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
多线程
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/*
* 聊天室服务端
*/
public class Server {
private ServerSocket serverSocket;
//该集合存放所有客户端发送的消息
private List<PrintWriter> allout = new ArrayList<>();
public Server() {
try {
System.out.println("启动服务端");
/*
* 创建ServerSocket的同时要申请服务端口,该端口不能与系统其他程序
* 开启的端口一致,否则会抛出异常:
* java.io.BindException:address already in use
* 解决办法:
* 更换端口
* 或
* 杀死占用该端口的程序进程:实际开发中8088很少被占用,通常都是由于我们
* 启动了两次服务端导致的(第一次启动已经占用了8088,那么第二次启动时
* 端口会显示被占用)
*/
serverSocket = new ServerSocket(8088);
System.out.println("服务端启动成功");
} catch (IOException e) {
e.printStackTrace();
}
}
public void start() {
try {
while (true) {
System.out.println("等待客户端连接");
/*
* ServerSocket提供的一个重要方法:
* Socket accept()
* 该方法是一个阻塞方法,调用会程序会卡住,直到一个客户端与服务端建立连接
* 为止,此时返回的Socket就可与连接的客户端进行交互了。
*/
Socket socket = serverSocket.accept();
String host = socket.getInetAddress().getHostAddress();
System.out.println("客户端" + host + "连接成功");
/*
* 启用一个线程处理与该客户端的交互
*/
ClientHanlder ch = new ClientHanlder(socket);
Thread thread = new Thread(ch);
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
/*
* 将处理客户端交互的操作在单独的线程上进行,
* 这里主要工作就是与某个客户端交互
*/
private class ClientHanlder implements Runnable {
private Socket socket;
private String host; //记录客户端的IP地址信息
public ClientHanlder(Socket socket) {
this.socket = socket;
/*
* getInetAddress()获取客户端ip地址
* getHostAddress()将IP地址转换为String类型
*/
host = socket.getInetAddress().getHostAddress();
}
@Override
public void run() {
PrintWriter pw = null;
/*
* 通过Socket获取输入流可以读取来自远端计算机发送过来的消息
*/
try {
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
//输出消息给客户端
OutputStream ops = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(ops, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw, true);
//将输出流存入共享集合allout
synchronized (allout) {
allout.add(pw);
}
System.out.println(host + "上线了,当前在线人数:" + allout.size());
String message;
/*
* 这里循环读取客户端传递过来的消息可能会出现异常:
* java.net.SocketException: Connection reset
* 这个错误是由于远端异常断开导致的(没有进行TCP挥手断开操作),
* 该异常无法通过逻辑来避免
* */
while ((message = br.readLine()) != null) {
System.out.println(host + ": " + message);
//将消息回复给客户端
synchronized (allout) {
for (PrintWriter p : allout) {
p.println(host + ":" + message);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//处理客户端断开后的操作
synchronized (allout) {
allout.remove(pw); //将当前客户端输出流从共享集合中删除
}
System.out.println(host + "下线了,当前人数:" + allout.size());
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}