Java Socket編程基礎篇
原文地址:Java Socket編程----通信是這樣煉成的
Java最初是作為網絡編程語言出現的,其對網絡提供了高度的支持,使得客戶端和服務器的溝通變成了現實,而在網絡編程中,使用最多的就是Socket。像大家熟悉的QQ、MSN都使用了Socket相關的技術。下面就讓我們一起揭開Socket的神秘面紗。
Socket編程
網絡基礎知識點:
- 兩臺計算機間進行通訊需要以下三個條件
IP地址、協議、端口號:- IP地址:定位應用所在機器的網絡位置。(比如家庭住址:北京市朝陽區XX街道XX小區)
- 端口號:唯一識別到機器的指定應用。(門牌號)
- 協議:數據傳輸的形式。(TCP、UDP等)
IP地址
端口
區分一臺主機的多個不同應用程序,端口號範圍為0-65535,其中0-1023位為系統保留。
如:HTTP:80 FTP:21 Telnet:23
IP地址+端口號組成了所謂的Socket,Socket是網絡上運行的程序之間雙向通信鏈路的終結點,是TCP和UDP的基礎。Socket套接字:
網絡上具有唯一標識的IP地址和端口組合在一起才能構成唯一能識別的標識符套接字。Socket原理機制:
通信的兩端都有Socket。
網絡通信其實就是Socket間的通信。
數據在兩個Socket間通過IO傳輸 。- Java中的網絡支持
- InetAddress:用於標識網絡上的硬件資源,主要是IP地址。
- URL:統一資源定位符,通過URL可以直接讀取或寫入網絡上的數據。
- Sockets:使用TCP協議實現的網絡通信Socket相關的類
- Datagram:使用UDP協議,將數據保存在用戶數據報中,通過網絡進行通信。
InetAddress
InetAddress類代表了一個網絡目標地址,包括主機名和IP地址信息。
該類沒有任何構造方法。
// 獲得本機的 主機名和IP地址
InetAddress address = InetAddress.getLocalHost ();
String hostName = address.getHostName();//獲取計算機名(本機主機名就是計算機名)
String hostAddress = address.getHostAddress();//獲取IP地址
System.out.println("hostName: " + hostName +" &hostAddress:" + hostAddress);
// 獲取遠程機器的 主機名和IP地址
InetAddress addr = InetAddress.getByName("www.baidu.com"); // 這裏不要加協議 比如:http://www.baidu.com
String domainName = addr.getHostName();//獲得主機名
String ipName = addr.getHostAddress();//獲得IP地址
System.out.println("domainName:" + domainName + " &ipName:" + ipName);
使用URL網絡資源
通過URL對象的openStream()方法可以得到指定資源的輸入流,通過流能夠讀取或訪問網頁上的資源。
//使用URL讀取網頁內容
URL url = new URL("http://www.baidu.com");
InputStream is = url.openStream();//通過openStream方法獲取資源的字節輸入流
InputStreamReader isr = new InputStreamReader(is,"UTF-8");//將字節輸入流轉換為字符輸入流,如果不指定編碼,中文可能會出現亂碼
BufferedReader br = new BufferedReader(isr);//為字符輸入流添加緩沖,提高讀取效率
String data = br.readLine();//讀取數據
while(data!=null){
System.out.println(data);//輸出數據
data = br.readLine();
}
br.close();
is.close();
TCP編程模型
TCP協議是面向連接的、可靠的、有序的、以字節流的方式發送數據,通過三次握手方式建立連接,形成傳輸數據的通道,在連接中進行大量數據的傳輸,效率會稍低。
Java中基於TCP協議實現網絡通信的類:
- 客戶端的Socket類。
- 服務器端的ServerSocket類。
Socket通信的步驟:
- 創建ServerSocket和Socket。
- 打開連接到Socket的輸入和輸出流。
- 按照TCP協議對Socket進行讀寫操作。
- 關閉輸入輸出流,關閉Socket。
服務器端:
- 創建ServerSocket對象,綁定監聽端口。
- 通過accept()方法監聽客戶端請求。
- 連接建立後,通過輸入流讀取客戶端發送的請求信息。
- 通過輸出流向客戶端發送響應信息。
- 關閉相關資源。
/**
* 基於TCP協議的Socket通信,實現用戶登錄,服務端
*/
//1、創建一個服務器端Socket,即ServerSocket,指定綁定的端口,並監聽此端口
ServerSocket serverSocket = new ServerSocket(10086);//1024-65535的某個端口
//2、調用accept()方法開始監聽,等待客戶端的連接
Socket socket = serverSocket.accept();
//3、獲取輸入流,並讀取客戶端信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("我是服務器,客戶端說:" + info);
}
socket.shutdownInput();//關閉輸入流
//4、獲取輸出流,響應客戶端的請求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("歡迎您!");
pw.flush();
//5、關閉資源
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
客戶端:
- 創建Socket對象,指明需要連接的服務器的地址和端口號。
- 連接建立後,通過輸出流向服務器端發送請求信息。
- 通過輸入流獲取服務器響應的信息
- 關閉相關資源。
//1、創建客戶端Socket,指定服務器地址和端口
Socket socket = new Socket("localhost",10086);
//2、獲取輸出流,向服務器端發送信息
OutputStream os = socket.getOutputStream();//字節輸出流
PrintWriter pw = new PrintWriter(os);//將輸出流包裝成打印流
pw.write("用戶名:admin;密碼:123");
pw.flush();
socket.shutdownOutput();
//3、獲取輸入流,並讀取服務器端的響應信息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while((info=br.readLine())!= null){
System.out.println("我是客戶端,服務器說:"+info);
}
//4、關閉資源
br.close();
is.close();
pw.close();
os.close();
socket.close();
多線程實現服務器與客戶端之間的通信。
服務器端循環調用accept()等待客戶端連接。服務器端接收到數據後即交給一個線程進行異步處理,然後再次等待客戶端連接。
static ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws Exception {
/**
* 基於TCP協議的Socket通信,實現用戶登錄,服務端
*/
//1、創建一個服務器端Socket,即ServerSocket,指定綁定的端口,並監聽此端口
ServerSocket serverSocket = new ServerSocket(10086);//1024-65535的某個端口
//2、調用accept()方法開始監聽,等待客戶端的連接
while(true) {
executor.execute(new ServerTask(serverSocket.accept()));
}
}
static class ServerTask implements Runnable {
Socket socket ;
public ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//3、獲取輸入流,並讀取客戶端信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("我是服務器,客戶端說:" + info);
}
socket.shutdownInput();//關閉輸入流
//4、獲取輸出流,響應客戶端的請求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("歡迎您!");
pw.flush();
// demo 就不放在finally裏關閉了
is.close();
isr.close();
os.close();
pw.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
UDP編程模型
UDP協議(用戶數據報協議)是無連接的、不可靠的、無序的,速度快。
進行數據傳輸時,首先將要傳輸的數據定義成數據報(Datagram),大小限制在64k,在數據報中指明數據索要達到的Socket(主機地址和端口號),然後再將數據報發送出去。
DatagramPacket類:表示數據報包
DatagramSocket類:進行端到端通信的類
服務器端實現步驟:
+ 創建DatagramSocket,指定端口號
+ 創建DatagramPacket
+ 接受客戶端發送的數據信息
+ 讀取數據
//服務器端,實現基於UDP的用戶登錄
//1、創建服務器端DatagramSocket,指定端口
DatagramSocket socket =new DatagramSocket(10010);
//2、創建暫存的數據報文,用於接受客戶端發送的數據
byte[] data = new byte[1024];
DatagramPacket packet =new DatagramPacket(data,data.length);
//3、接受客戶端發送的數據
socket.receive(packet);//此方法在接受數據報之前會一致阻塞
//4、讀取數據
String info = new String(data);
System.out.println("我是服務器,客戶端告訴我:"+info);
//向客戶端響應數據
//1、定義客戶端的地址、端口號、數據
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "歡迎您!".getBytes();
//2、創建數據報,包含響應的數據信息
DatagramPacket packet2 = new DatagramPacket(data2,data2.length,address,port);
//3、響應客戶端
socket.send(packet2);
//4、關閉資源
socket.close();
客戶端實現步驟:
- 定義發送消息。
- 創建DatagramPacket,包含將要發送的消息。
- 創建DatagramSocket。
- 發送數據。
//客戶端
//1、定義服務器的地址、端口號、數據
InetAddress address = InetAddress.getByName("localhost");
int port = 10010;
byte[] data = "用戶名:admin;密碼:123".getBytes();
//2、創建數據報,包含發送的數據信息
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
//3、創建DatagramSocket對象
DatagramSocket socket = new DatagramSocket();
//4、向服務器發送數據
socket.send(packet);
//接受服務器端響應數據
//======================================
//1、創建數據報,用於接受服務器端響應數據
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
//2、接受服務器響應的數據
socket.receive(packet2);
String raply = new String(data2, 0, packet2.getLength());
System.out.println("我是客戶端,服務器說:" + raply);
//4、關閉資源
socket.close();
註意問題:
1、多線程的優先級問題:
根據實際的經驗,適當的降低優先級,否側可能會有程序運行效率低的情況
2、是否關閉輸出流和輸入流:
對於同一個socket,如果關閉了輸出流,則與該輸出流關聯的socket也會被關閉,所以一般不用關閉流,直接關閉socket即可
3、使用TCP通信傳輸對象,IO中序列化部分
4、socket編程傳遞文件,IO流部分
Java Socket編程基礎篇