Server 1
package day20150914socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服務端應用程序
* (MINA的本質也是使用server)
*/
public class Server {
//服務端的Socket
private ServerSocket server;
//構造方法,用於初始化服務端
public Server() throws IOException{
try {
System.out.println("初始化服務端");
/*
* 創建ServerSocket時需要指定的服務端口
*/
server = new ServerSocket(8088);
System.out.println("服務端初始化完畢");
} catch (IOException e) {
throw e;
}
}
public void start(){
try {
System.out.println("等待客戶端連接。。。");
/*
* ServerSocket的accept()方法:
* 用於監聽8088端口,等待客戶連接, 否則該方法阻塞。
* 若一個客戶端連接了,會返回給客戶端的Socket
*/
Socket socket = server.accept();
//獲取遠端(客戶端)地址
InetAddress address = socket.getInetAddress();
//獲取遠端IP地址
String ip = address.getHostAddress();
//獲取遠端端口號
int port = socket.getPort();
System.out.println(ip+":"+port+"客戶端連接上了");
/*
* 通過剛剛連接上來的客戶端的Socket獲取輸入流
* 來讀取客戶端發過來的信息
*/
InputStream in = socket.getInputStream();
//將字節輸入流包裝爲字符輸入流,這樣就可指定編碼集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//將字符流轉爲緩衝字符輸入流,這樣就可以以行爲單位來讀取字符串了
BufferedReader br = new BufferedReader(isr);
String message = null;
while((message=br.readLine())!=null){
System.out.println("客戶端說:"+message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Server server = new Server();
server.start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服務器初始化失敗");
}
}
}
Client 1
package day20150914socket;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class Client {
//用於連接服務器端的Socket
private Socket socket;
public Client() throws Exception{
try {
System.out.println("正在連接服務端。。。");
/*
* 創建Socket對象,
* 就會嘗試根據給定的地址與端口連接服務器
* 所以,若該對象創建成功,說明與服務器端連接正常
*/
//localhost:本機。連接其他計算機可寫IP
socket = new Socket("localhost",8088);
System.out.println("成功連接服務端。");
} catch (Exception e) {
throw e;
}
}
public void start(){
try{
/*
* 可以通過Socket的getOutputStream()方法獲取一條輸出流
* 用於將信息發送至服務器
*/
OutputStream out = socket.getOutputStream();
/*
*使用字符流指定編碼集將字符串轉爲字節後,
*再通過out發送給服務器
*/
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
/*
* 將字符流包裝爲緩衝字符流
* 就可以以行爲單位寫出字符串了。
*/
PrintWriter pw = new PrintWriter(osr);
Scanner sc = new Scanner(System.in);
while(true){
String str = sc.nextLine();
pw.println(str);
pw.flush();
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client client = new Client();
client.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("客戶端初始化失敗");
}
}
}
Server(2)
package day20150914socket2;
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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 服務端應用程序
* (MINA的本質也是使用server)
*/
public class Server2 {
//服務端的Socket
private ServerSocket server;
//線程池,用於管理客戶端連接的交互線程
private ExecutorService threadPool;
//保存所有客戶端輸出流的集合
private List<PrintWriter> allOut;
//構造方法,用於初始化服務端
public Server2() throws IOException{
try {
System.out.println("初始化服務端");
/*
* 創建ServerSocket時需要指定的服務端口
*/
server = new ServerSocket(8088);
//初始化線程池
threadPool = Executors.newFixedThreadPool(50);
/*
* 初始化存放所有客戶端輸出流的集合
* 使用ArrayList而不是linkedList的原因:
* 增刪元素不頻繁,而是使用遍歷頻繁
*/
allOut = new ArrayList<PrintWriter>();
System.out.println("服務端初始化完畢");
} catch (IOException e) {
throw e;
}
}
public void start(){
try {
/*
* ServerSocket的accept()方法:
* 用於監聽8088端口,等待客戶連接, 否則該方法阻塞。
* 若一個客戶端連接了,會返回給客戶端的Socket
*/
while(true){
System.out.println("等待客戶端連接。。。");
Socket socket = server.accept();
/*
* 當一個客戶端連接後,啓動一個線程ClientHandler
* 將客戶端的socket傳入,使得該線程處理與該客戶端的交互
* 這樣,可再次進入循環,接收下一個客戶端的連接
*/
Runnable handler = new ClientHandler(socket);
//Thread t = new Thread(handler);
//t.start();
/*
* 使用線程池分配空閒線程來處理當前連接的客戶端
*/
threadPool.execute(handler);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 將給定的輸出流存入共享集合
*
* 加synchronized關鍵字,鎖定Server對象,
* 下面3個方法鎖定後,互斥(3個方法鎖定同一個對象)
* 即一個線程訪問其中一個方法後,
* 其他線程不可訪問這3個方法中的任何一個
*/
public synchronized void addOut(PrintWriter pw){
allOut.add(pw);
}
/**
* 將給定的輸出流從共享集合中刪除
*/
public synchronized void removeOut(PrintWriter pw){
allOut.remove(pw);
}
/**
* 將給定的消息轉發給所有客戶端
*/
public synchronized void sendMessage(String message){
for(PrintWriter pw : allOut){
pw.println(message);
}
}
public static void main(String[] args) {
try {
Server2 server = new Server2();
server.start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服務器初始化失敗");
}
}
/**
* 服務器端的一個線程,用於與某個客戶端交互,
* 使用線程的目的是使得服務器可以處理多個客戶端了
*/
class ClientHandler implements Runnable{
//當前線程處理的客戶端的socket
private Socket socket;
//當前客戶端的IP
private String ip;
//當前客戶端的暱稱
private String nickname;
/**
* 根據給定的客戶端的Socket,創建線程體
*/
public ClientHandler(Socket socket){
this.socket = socket;
//獲取遠端(客戶端)地址
InetAddress address = socket.getInetAddress();
//獲取遠端IP地址
ip = address.getHostAddress();
//獲取遠端端口號
int port = socket.getPort();
//改爲使用暱稱,所以不在這裏通知了
//System.out.println(ip+":"+port+"客戶端連接上了");
}
/**
* 該線程會將當前socket中的輸入流獲取
* 用來循環讀取客戶端發送過來的消息
*/
@Override
public void run() {
PrintWriter pw = null;
try{
/*
* 爲了讓服務端向客戶端發送信息
* 通過socket獲取輸出流
*/
OutputStream out = socket.getOutputStream();
//轉爲字符流,用於指定編碼集
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
//創建緩衝字符輸出流,true自動行刷新
pw = new PrintWriter(osr,true);
/*
* 將該客戶端的輸出流存入共享集合
* 以便使得該客戶端也能接收服務器轉發的消息
*/
//allOut.add(pw);
addOut(pw);
System.out.println("當前在線人數:"+allOut.size());
/*
* 通過剛剛連接上來的客戶端的Socket獲取輸入流
* 來讀取客戶端發過來的信息
*/
InputStream in = socket.getInputStream();
//將字節輸入流包裝爲字符輸入流,這樣就可指定編碼集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//將字符流轉爲緩衝字符輸入流,這樣就可以以行爲單位來讀取字符串了
BufferedReader br = new BufferedReader(isr);
//當創建好當前客戶端的輸入流後,讀取的第一個字符串應當是暱稱
nickname = br.readLine();
//通知所有客戶端,當前用戶上線了
sendMessage("["+nickname+"]上線了");
sendMessage("當前在線人數爲:"+allOut.size());//告知所有客戶端在線人數
String message = null;
/*
* 讀取客戶端發來的一行字符串
* windows與linux的差異:
* linux:當客戶端斷開連接後,通過輸入流會讀取到null,
* 這是合乎邏輯的,
* 因爲緩衝流的readLine方法若返回null就無法通過該流讀取到信息
*
* windows:當客戶端與服務器端斷開接連後
* readLine()方法會拋出異常
*/
while((message=br.readLine())!=null){
//System.out.println("客戶端說:"+message);
//pw.println(message);//把客戶端發來的消息回給客戶端
//當前客戶端說話內容告訴給所有客戶端
sendMessage(nickname+"說:"+message);
}
}catch(Exception e){
/*
* 在windows中的客戶端
* 報錯通常是因爲客戶端斷開了連接
*
* 不用關流,可直接關Socket
*/
}finally{
/*
* 首先將該客戶端的輸出流從共享集合中刪除
*/
//allOut.remove(pw);
removeOut(pw);
//控制檯顯示該用戶下線了
System.out.println("["+nickname+"]下線了");
//通知其他用戶該用戶下線了
sendMessage("["+nickname+"]下線了");
//輸出當前在線人數(輸出流的個數)
System.out.println("當前在線人數爲:"+allOut.size());
sendMessage("當前在線人數爲:"+allOut.size());//告知所有客戶端在線人數
/*
* 無論是linux用戶還是windows用戶,
* 當客戶與服務端斷開連接後,
* 我們都應當在服務器端與客戶端斷開連接
*/
try {
socket.close();
//關閉之後,catch處理意義也不大,故catch塊裏的內容可爲空
} catch (IOException e) {
}
//System.out.println("一個客戶端下線了");
}
}
}
}
Client(2)
package day20150914socket2;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class Client2 {
//用於連接服務器端的Socket
private Socket socket;
public Client2() throws Exception{
try {
System.out.println("正在連接服務端。。。");
/*
* 創建Socket對象,
* 就會嘗試根據給定的地址與端口連接服務器
* 所以,若該對象創建成功,說明與服務器端連接正常
*/
//localhost:本機。連接其他計算機可寫IP
socket = new Socket("localhost",8088);
System.out.println("成功連接服務端。");
} catch (Exception e) {
throw e;
}
}
/**
* 客戶端啓動方法
*/
public void start(){
try{
//創建並啓動線程,來接收服務器端發送過來的消息
Runnable runn = new GetServerInfoHandler();
Thread t = new Thread(runn);
t.start();
/*
* 可以通過Socket的getOutputStream()方法獲取一條輸出流
* 用於將信息發送至服務器
*/
OutputStream out = socket.getOutputStream();
/*
*使用字符流指定編碼集將字符串轉爲字節後,
*再通過out發送給服務器
*/
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
/*
* 將字符流包裝爲緩衝字符流
* 就可以以行爲單位寫出字符串了。
*/
PrintWriter pw = new PrintWriter(osr,true);//true,自動行刷新
//創建一個Scanner,用於接收用戶輸入的字符串
Scanner sc = new Scanner(System.in);
//輸出歡迎語
System.out.println("歡迎來到傳奇的聊天室");
while(true){
System.out.println("請輸入暱稱");
String nickname = sc.nextLine();
if(nickname.trim().length()>0){
pw.println(nickname);
break;
}
System.out.println("暱稱不能爲空");
}
while(true){
String str = sc.nextLine();
pw.println(str);
//pw.flush();//PrintWriter自動行刷新就不要此句了
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client2 client = new Client2();
client.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("客戶端初始化失敗");
}
}
/**
* 該線程的作用是循環接收服務器端發送過來的信息,
* 並輸出到控制檯
*/
class GetServerInfoHandler implements Runnable{
@Override
public void run() {
try{
//通過socket獲取輸入流
InputStream in = socket.getInputStream();
//將字節輸入流轉爲字符輸入流,指定編碼集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//將字符流轉爲緩衝字符輸入流,這樣就可以以行爲單位來讀取字符串了
BufferedReader br = new BufferedReader(isr);
String message = null;
//循環讀取服務端發送過來的每個字符串
while((message=br.readLine())!=null){
//將服務端發送的字符串輸出到控制檯
System.out.println(message);
}
}catch(Exception e){
}
}
}
}