package chat;
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.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* TCP通讯聊天室服务端
* @author Administrator
*
*/
public class Server {
public static void main(String[] args){
try{
Server server = new Server();
server.start();
}catch(Exception e){
e.printStackTrace();
}
}
/*
* 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;
}
}
/**
* 将给定的输出流存入共享集合
* @param out
*/
private synchronized void addOut(PrintWriter out){
allOut.add(out);
}
/**
* 将给定的输出流从共享集合中删除
* @param out
*/
private synchronized void removeOut(PrintWriter out){
allOut.remove(out);
}
/**
* 将给定的消息发给所有客户
* @param message
*/
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("一个客户端连接了!");
/*
* 当一个客户端连接后,启动一个线程来处理与该客户端的交互工作。
*/
ClientHandler handler = new ClientHandler(socket);
Thread t = new Thread(handler);
t.start();
}
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 该线程负责与指定的客户端进行交互。
* @author Administrator
*
*/
class ClientHandler implements Runnable{
/*
* 负责当前线程与指定客户端交互的socket
*/
private Socket socket;
//客户端地址信息
private String host;
public ClientHandler(Socket socket){
this.socket = socket;
/*
* 通过socket获取远程计算机地址信息
* 对于服务端而言,远程计算机就是客户端。
*/
InetAddress address = socket.getInetAddress();
//获取远程计算机IP
host = address.getHostAddress();
}
public void run(){
PrintWriter pw = null;
try{
System.out.println(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方法的执行结果也不同:
* 当windons客户端断开连接时,readLine方法会直接抛出异常。
* 当linux的客户端断开连接时,readLine方法会返回null。
*/
while((message = br.readLine())!=null){
//转发给所有客户端
sendMessage(host+"说:"+message);
}
}catch(Exception e){
}finally{
//客户端与服务端断开连接
//客户端下线后从共享集合删除输出流。
removeOut(pw);
sendMessage(host+"客户端下线了!");
try {
socket.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
}
}
package chat;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* 聊天室客户端
* @author Administrator
*
*/
public class Client {
public static void main(String[]args){
try{
Client client = new Client();
client.start();
}catch(Exception e){
e.printStackTrace();
}
}
/*
* /sbin/ifconfig
* java.net.Socket 套接字
* 封装了TCP通讯,使用该类完成与服务端的链接,并进行相应的通讯。
*/
private Socket socket;
/**
* 构造方法用来初始化客户端
*/
public Client() throws Exception{
try{
/*
* 实例化Socket时,需要传入两个参数
* 1:服务端的地址
* 2:服务端的端口
*
* 获取本地ip localhost
* 通过地址找到服务端的计算机,端口则找到该计算机上的服务端应用程序。
* 实例化Socket的过程就是连接服务端的过程,连接不成功该构造方法会抛出异常。
*/
System.out.println("正在连接服务端。。。。");
socket = new Socket("10.0.2.68",8088);
System.out.println("连接服务端成功!");
}catch(Exception e){
throw e;
}
}
/*
* 客户端的启动方法
*/
public void start(){
try{
/*
* 先启动用于接收服务端发送过来的消息的线程
*/
ServerHandler handler = new ServerHandler();
Thread t = new Thread(handler);
//可以将此线程设为守护线程
t.setDaemon(true);
t.start();
/*
* Socket提供了方法:
* OutputStream getOutputStream();
* 该方法可以捕获一个字节输出流,通过该字节输出流写出的数据会发送至远端计算机,
* 对于客户端这边而言,远端是服务端。
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");
PrintWriter pw = new PrintWriter(osw,true);
Scanner scan = new Scanner(System.in);
String message = null;
while(true){
message = scan.next();
pw.println(message);
}
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 该线程负责循环接收服务端发送过来的消息,并输出到本控制台。
* @author Administrator
*
*/
class ServerHandler implements Runnable{
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){
}
}
}
}