Socket:所謂套接字(Socket),就是對網絡中不同主機上的應用程序之間進行雙向通信的端點的抽象。一個套接字就是網絡上程序通信的一端,提供了應用層程序利用網絡協定交換資料的機制。從所處的地位來講,套接字上聯應用程序,下聯網絡協定棧,是應用程式通過網絡協定進行通信的接口,是應用程式與網絡協定根進行互動的接口
我們就使用java開發一個簡單的聊天工具,看代碼:
用戶端代碼:
package com.zhang.test.socket;
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;
/**
* 聊天室用戶端
* @author Administrator
*
*/
public class Client {
/*
* 套接字
* 封裝着TCP協定的通訊
* 使用它可以進行基于TCP協定的網絡通訊
*/
private Socket socket;
/**
* 構造方法,用來初始化用戶端
*/
public Client(){
try {
/*
* 執行個體化Socket的時候需要傳入
* 兩個參數:
* 1:遠端計算機的IP位址
* 2:遠端計算機的端口
* 通過IP位址可以找到服務端的
* 計算機,通過端口可以找到運作
* 在該機器上的服務端應用程式。
*
* 執行個體化Socket的過程就是連接配接
* 遠端計算機的過程,若遠端計算機
* 沒有響應,會抛出異常導緻執行個體化
* 失敗。
*/
System.out.println("正在連接配接服務端...");
socket = new Socket("localhost",8088);
System.out.println("連接配接服務端完畢!");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 用戶端開始工作的方法
*/
public void start(){
try {
/*
* 首先将讀取服務端發送過的消息的
* 線程啟動起來。
*/
ServerHandler handler= new ServerHandler();
Thread t = new Thread(handler);
t.start();
Scanner scanner = new Scanner(System.in);
String nickname = null;
while(true){
System.out.println("請輸入昵稱:");
nickname = scanner.nextLine();
if(nickname.length()==0){
System.out.println("請至少輸入一個字元.");
continue;
}
break;
}
/*
* Socket提供了方法:
* OutputStream getOutputStream()
* 擷取一個位元組輸出流,通過該流
* 寫出的位元組會被發送到遠端計算機
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
PrintWriter pw = new PrintWriter(osw,true);
//單獨發送昵稱
pw.println(nickname);
System.out.println("你好!"+nickname+",開始聊天吧!");
String message = null;
while(true){
message = scanner.nextLine();
pw.println(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client client = new Client();
client.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 該線程用于讀取服務端發送過來的消息,
* 并輸出到用戶端的控制台
* @author Administrator
*
*/
private 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) {
}
}
}
}
服務端代碼:
package com.zhang.test.socket;
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;
/**
* 聊天室服務端
* @author Administrator
*
*/
public class Server {
/*
* java.net.ServerSocket
* 運作在服務端的ServerSocket負責申請
* 服務端口,用戶端就是通過這個端口與
* 這邊服務端應用程式建立連接配接的。
* ServerSocket的另一個職責就是監聽該
* 端口,一旦一個用戶端連接配接,這邊就會
* 自動建立一個Socket,通過這個Socket就
* 可以與剛剛連接配接的用戶端通訊了。
*/
private ServerSocket server;
/*
* 該集合使用者存放所有用戶端的輸出流
* 以便于服務端廣播消息
*/
private List<PrintWriter> allOut;
public Server(){
try {
/*
* 初始化ServerSocket時要指定
* 服務端口,用戶端通過該端口
* 進行連接配接,該端口号不能與操作
* 系統其它應用程式申請的重複,
* 否則會抛出異常
*/
server = new ServerSocket(8088);
allOut = new ArrayList<PrintWriter>();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 向共享集合中添加一個新的輸出流
* @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 {
/*
* 該方法是一個阻塞方法,用于一直
* 監聽服務端口(8088),直到一個客戶
* 端連接配接,然後就建立一個與該用戶端
* 通訊的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) {
}
}
public static void main(String[] args) {
try {
Server server = new Server();
server.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 該線程負責與一個用戶端進行互動
* @author Administrator
*
*/
private class ClientHandler implements Runnable{
private Socket socket;
//用戶端的位址資訊
private String host;
//用戶端的昵稱
private String nickname;
public ClientHandler(Socket socket){
this.socket = socket;
/*
* 擷取遠端計算機的位址資訊
*/
InetAddress address = socket.getInetAddress();
//擷取用戶端ip位址的字元串格式
host = address.getHostAddress();
}
public void run(){
PrintWriter pw = null;
try {
/*
* 通過Socket擷取輸出流,以便可以将
* 消息發送給用戶端
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
pw = new PrintWriter(osw,true);
//将該輸出流存入共享集合
addOut(pw);
/*
* Socket提供的方法:
* InputStream getInputStream()
* 該方法擷取的輸入流是用來讀取遠端計算機
* 發送過來的資料的。
*/
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,"UTF-8");
BufferedReader br = new BufferedReader(isr);
//用戶端發送的第一個字元串是昵稱
nickname = br.readLine();
//通知所有用戶端該使用者上線了
sendMessage(nickname+"上線了!目前線上人數:"+allOut.size()+"人");
String message = null;
while((message = br.readLine())!=null){
/*
* 當用戶端斷開連接配接後,由于用戶端的
* 作業系統不同,服務端這裡br.readLine
* 方法的執行結果也不同:
* 當windows用戶端斷開連接配接後:br.readLine
* 方法會直接抛出異常
* 當linux用戶端斷開連接配接後:br.readLine
* 方法會傳回null。
*/
// message = br.readLine();
// System.out.println(host+"說:"+message);
//将讀到的話回複給目前用戶端(暫時)
// pw.println(host+"說:"+message);
//将讀到的話轉發給所有用戶端
sendMessage(nickname+"說:"+message);
}
} catch (Exception e) {
} finally{
//處理用戶端斷開連接配接後的操作
//将該用戶端的輸出流從共享集合中删除
removeOut(pw);
sendMessage(nickname+"下線了.目前線上人數:"+allOut.size()+"人");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用的時候,要先啟動服務端,再啟動用戶端,
控制台列印出這些,就連接配接成功了,可以給兩個機器上分别裝上,測試一下,就可以互相聊天了