案例说明:
1.掌握Socket编程的基本步骤
2.熟练Socket有关常用API的使用
下面通过简单实例演示:
- 客户端代码:
/**
* 聊天室客户端
* @author Cher_du
*
*/
public class Client {
/*
* java.net.Socket 套接字
* 封装了TCP通讯.使用该类完成与服务端的连接
* 并进行相应的通讯.
*/
private Socket socket;
/**
* 构造方法用来初始化客户端
* @throws Exception
*/
public Client() throws Exception{
System.out.println("正在连接服务端...");
try {
/*
* 实例化Socket时需要传入两个参数
* 1:服务端的地址
* 2:服务端的端口
*
* 通过地址找到服务端的计算机,端口
* 则找到该计算机上的服务端应用程序.
*
* 实例化Socket的过程就是连接服务端
* 的过程.连接不成功该构造方法会抛出
* 异常.
*/
socket = new Socket("localhost",
8088);
System.out.println("与服务端建立连接!");
} catch (Exception e) {
throw e;
}
}
/**
* 客户端的启动方法
*/
public void start() {
try {
/*
* 先启动用于接收服务端发送过来的消息的
* 线程
*/
ServerHandler handler = new ServerHandler();
Thread t = new Thread(handler);
t.start();
Scanner scanner = new Scanner(System.in);
/*
* Socket提供了方法:
* OutputStream getOutputStream()
* 该方法可以获取一个字节输出流,通过
* 该输出流写出的数据会发送至远端计算机
* 对于客户端这边而言远端就是服务端.
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
PrintWriter pw = new PrintWriter(osw, true);
String message = null;
while (true) {
message = scanner.nextLine();
pw.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client client = new Client();
client.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 该线程负责循环接收服务端发送过来的消息
* 并输出到客户端的控制台
*/
class ServerHandler implements Runnable{
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(
new InputStreamReader(
socket.getInputStream(),
"utf-8"
)
);
String message = null;
while((message=br.readLine())!=null){
System.out.println("服务端:"+message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 服务端代码:
/** * 聊天室服务端 * @author Cher_du * */ public class Server { /* * java.net.ServerSocket * 运行在服务端的ServerSocket有两个作用 * 1.申请服务端口(客户端通过该端口与服务端建立连接) * 2.监听服务端口,等待客户端连接,一旦客户端连接则 * 创建一个Socket实例用于与该客户端交互. */ private ServerSocket server; /* *该集合用于保存所以客户端的Socket(数据流) */ private List<PrintWriter> allOut; public Server() throws Exception{ try { allOut = new ArrayList<PrintWriter>(); /* * 实例化ServerSocket需要指定 * 服务端口.该端口不能与当前操作系统 * 其他程序申请的端口冲突,否则会抛出 * 端口被占用异常 */ server = new ServerSocket(8088); } catch (Exception e) { throw e; } } /** * 将给定的输出流存入共享集合 */ private synchronized void addOut(PrintWriter out){ allOut.add(out); } /** * 将给定的输出流从共享集合中删除 */ private synchronized void removeOut(PrintWriter out){ allOut.remove(out); } /** * 将给定的消发送给所以的客户端 */ private synchronized void sendMessage(String message){ for(PrintWriter out :allOut){ out.println(message); } } public void start() { try { /* * ServerSocket提供一个方法: * Socket accept() * 该方法是一个阻塞方法,作用是监听 * ServerSocket开启的服务端口, * 直到一个客户端通过该端口连接,该方法 * 才会解除阻塞,并返回一个Socket实例 * 通过该Socket实例与刚刚建立连接的 * 客户端进行通信. */ while (true) { System.out.println("等待客户端连接..."); Socket socket = server.accept(); System.out.println("一个客户端连接了!"); /* *当一个客户端连接后,启动一个线程来处理 *与该客户端的交互工作. */ ClientHandlerWriter clientHandler = new ClientHandlerWriter(socket); Thread t = new Thread(clientHandler); t.start(); ClientHandlerRead clientHandler2 = new ClientHandlerRead(socket); Thread t2 = new Thread(clientHandler2); t2.start(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { try { Server server = new Server(); server.start(); } catch (Exception e) { e.printStackTrace(); } } /** * 该线程负责与指定的客户端进行交互 * @author Cher_du * */ class ClientHandlerWriter implements Runnable{ /** * 当前线程负责与指定客户端交互的Socket */ private Socket socket; //客户端的地址信息 private String host; public ClientHandlerWriter(Socket socket){ this.socket = socket; /* * 通过socket获取远程计算机地址信息 * 对于服务端而言,远程计算机就是客户端 */ InetAddress address = socket.getInetAddress(); //获取远端计算机的IP host = address.getHostAddress(); } @Override public void run() { PrintWriter pw = null; try { /* *通过Socket创建输出流,用于将消息 *发送给客户端 */ OutputStream out = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8"); pw = new PrintWriter(osw,true); /* * br.readLine读取客户端发送过来的一行字符串 * 时,客户端断开连接时,由于客户端所在系统不同, * 这里readLine方法的执行结果也不相同: * 当windows的客户端断开连接时,readLine方法 * 会直接抛出异常 * 当Linux的客户端断开连接时,readLine方法会 * 返回null. * */ // while((message = br.readLine())!=null){ // System.out.println(host+"说:"+message); // //pw.println(host+"说:"+message); // //转发给所有客户端 // sendMessage(host+"说:"+message); // } Scanner scanner = new Scanner(System.in); String message = null; while (true) { message = scanner.nextLine(); pw.println(message); } } catch (Exception e) { e.printStackTrace(); } } } class ClientHandlerRead implements Runnable{ /** * 当前线程负责与指定客户端交互的Socket */ private Socket socket; //客户端的地址信息 private String host; public ClientHandlerRead(Socket socket){ this.socket = socket; /* * 通过socket获取远程计算机地址信息 * 对于服务端而言,远程计算机就是客户端 */ InetAddress address = socket.getInetAddress(); //获取远端计算机的IP host = address.getHostAddress(); } @Override public void run() { PrintWriter pw = null; try { sendMessage(host+"上线了!"); /* *通过Socket创建输出流,用于将消息 *发送给客户端 */ OutputStream out = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8"); pw = new PrintWriter(osw,true); //将该客户端的输出流存入共享集合 addOut(pw); InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in,"UTF-8"); BufferedReader br = new BufferedReader(isr); String message = null; /* * br.readLine读取客户端发送过来的一行字符串 * 时,客户端断开连接时,由于客户端所在系统不同, * 这里readLine方法的执行结果也不相同: * 当windows的客户端断开连接时,readLine方法 * 会直接抛出异常 * 当Linux的客户端断开连接时,readLine方法会 * 返回null. * */ while((message = br.readLine())!=null){ System.out.println(host+"说:"+message); //pw.println(host+"说:"+message); //转发给所有客户端 //sendMessage(host+"说:"+message); } } catch (Exception e) { e.printStackTrace(); }finally{ //客户端与服务端断开连接. //客户端下线后从共享集合中删除输出流 removeOut(pw); sendMessage(host+"下线了!"); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
- 启动服务端再启动客户端后测试一下:
- 客户端录入发送给服务端的信息,即可在服务端看到,同样在服务端录入信息也可发送到客户端,从而实现简单的聊天系统。