㈠ java Socket初步詳解
網路編程的基本模型就是客戶機到伺服器模型 簡單的說就是兩個進程之間相互通訊 然後其中一個必須提供一個固定的位置 而另一個則只需要知道這個固定的位置 並去建立兩者之間的聯系 然後完成數據的通訊就可以了 這里提供畝悉猜固定位置的通常稱為伺服器 而建立聯系的通常叫做客戶端 基於這個簡單的模型 就可以進入網路編程啦
Java對這個模型的支持有很多種Api 而這里我只想介紹有關Socket的編程介面 對於Java而言已經簡化了Socket的編程介面 首先我們來討論有關提供固定位置的服務方是如何建立的 Java提供了ServerSocket來對其進行支持 事實上當你創建該類的一個實力對象並提供一個埠資源你就建立了一個固定位置可以讓其他計算機來訪問你 ServerSocket server=new ServerSocket( );這里稍微要注意的是埠的分配必須是唯一的 因為埠是為了唯一標識每台計算機唯一服務的 另外埠號是從 ~ 之間的 前 個埠已經被Tcp/Ip 作為保留埠 因此你所分配的埠只能是 個之後的 好了 我們有了固定位置 現在所需要的就是一根連接線了 該連接線由客戶方首先提出要求 因此Java同樣提供了一個Socket對象來對其進行支持 只要客戶方創建一個Socket的實例對象進行支持就可以了 Socket client
=new Socket(InetAddress getLocalHost() );客戶機必須知道有關伺服器的IP地址 對於著一點Java也提供了一個相關的類InetAddress 該對象的實例必須通過它的靜態方法來提供 它的靜態方法主要提供了得到本機IP 和通過名字或IP直接得到InetAddress的方法
上面的方法基本可以建立一條連線讓兩台計算機相互交流了 可是數據是如何傳輸的呢?事實上I/O操作總是和網路編程息息相關的 因為底層的網路是繼續數據的 除非遠程調用 處理問題的核心在執行上 否則數據的陸帆交互還是依賴於IO操作的 所以你也必須導入java io這個包 java的IO操作也不復雜 它提供了針對於位元組流和Unicode的讀者和寫者 然後也提供了一個緩沖用於數據的讀寫
BufferedReader in=new BufferedReader(new InputStreamReader(server getInputStream()));
PrintWriter out=new PrintWriter(server getOutputStream());
上面兩句就是建立緩沖並把原始的位元組流轉變為Unicode可以操作 而原始的位元組流來源於Socket的兩個方法 getInputStream()和getOutputStream()方 分別用來得到輸入和輸出 那麼現在有了基本的模型和基本的操作工具 我們可以做一個簡單的Socket常式了
服務方:
import java io *;
import *;
public class MyServer {
public static void main(String[] args) throws IOException{
ServerSocket server=new ServerSocket( );
Socket client=server accept();
BufferedReader in=new BufferedReader(new InputStreamReader(client getInputStream()));
迅型PrintWriter out=new PrintWriter(client getOutputStream());
while(true){
String str=in readLine();
System out println(str);
out println( has receive );
out flush();
if(str equals( end ))
break;
}
client close();
}
}
這個程序的主要目的在於伺服器不斷接收客戶機所寫入的信息只到 客戶機發送 End 字元串就退出程序 並且伺服器也會做出 Receive 為回應 告知客戶機已接收到消息
客戶機代碼:
import *;
import java io *;
public class Client{
static Socket server;
public static void main(String[] args)throws Exception{
server=new Socket(InetAddress getLocalHost() );
BufferedReader in=new BufferedReader(new InputStreamReader(server getInputStream()));
PrintWriter out=new PrintWriter(server getOutputStream());
BufferedReader wt=new BufferedReader(new InputStreamReader(System in));
while(true){
String str=wt readLine();
out println(str);
out flush();
if(str equals( end )){
break;
}
System out println(in readLine());
}
server close();
}
}
客戶機代碼則是接受客戶鍵盤輸入 並把該信息輸出 然後輸出 End 用來做退出標識
這個程序只是簡單的兩台計算機之間的通訊 如果是多個客戶同時訪問一個伺服器呢?你可以試著再運行一個客戶端 結果是會拋出異常的 那麼多個客戶端如何實現呢?
其實 簡單的分析一下 就可以看出客戶和服務通訊的主要通道就是Socket本身 而伺服器通過accept方法就是同意和客戶建立通訊 這樣當客戶建立Socket的同時 伺服器也會使用這一根連線來先後通訊 那麼既然如此只要我們存在多條連線就可以了 那麼我們的程序可以變為如下:
伺服器:
import java io *;
import *;
public class MyServer {
public static void main(String[] args) throws IOException{
ServerSocket server=new ServerSocket( );
while(true){
Socket client=server accept();
BufferedReader in=new BufferedReader(new InputStreamReader(client getInputStream()));
PrintWriter out=new PrintWriter(client getOutputStream());
while(true){
String str=in readLine();
System out println(str);
out println( has receive );
out flush();
if(str equals( end ))
break;
}
client close();
}
}
}
這里僅僅只是加了一個外層的While循環 這個循環的目的就是當一個客戶進來就為它分配一個Socket直到這個客戶完成一次和伺服器的交互 這里也就是接受到客戶的 End 消息 那麼現在就實現了多客戶之間的交互了 但是 問題又來了 這樣做雖然解決了多客戶 可是是排隊執行的 也就是說當一個客戶和伺服器完成一次通訊之後下一個客戶才可以進來和伺服器交互 無法做到同時服務 那麼要如何才能同時達到既能相互之間交流又能同時交流呢?很顯然這是一個並行執行的問題了 所以線程是最好的解決方案
那麼下面的問題是如何使用線程 首先要做的事情是創建線程並使得其可以和網路連線取得聯系 然後由線程來執行剛才的操作 要創建線程要麼直接繼承Thread要麼實現Runnable介面 要建立和Socket的聯系只要傳遞引用就可以了 而要執行線程就必須重寫run方法 而run方法所做的事情就是剛才單線程版本main所做的事情 因此我們的程序變成了這樣:
import *;
import java io *;
public class MultiUser extends Thread{
private Socket client;
public MultiUser(Socket c){
this client=c;
}
public void run(){
try{
BufferedReader in=new BufferedReader(new InputStreamReader(client getInputStream()));
PrintWriter out=new PrintWriter(client getOutputStream());
//Mutil User but can t parallel
while(true){
String str=in readLine();
System out println(str);
out println( has receive );
out flush();
if(str equals( end ))
break;
}
client close();
}catch(IOException ex){
}finally{
}
}
public static void main(String[] args)throws IOException{
ServerSocket server=new ServerSocket( );
while(true){
//transfer location change Single User or Multi User
MultiUser mu=new MultiUser(server accept());
mu start();
}
}
}
lishixin/Article/program/Java/hx/201311/27013
㈡ 到底什麼是Socket
本系列文章前面那些主要講解的是計算機網路的理論基礎,但對於即時通訊IM這方面的應用層開發者來說,跟計算機網路打道的其實是各種API介面。
本篇文章就來聊一下網路應用程序員最熟悉的Socket這個東西,拋開生澀的計算機網路理論,從應用層的角度來理解到底什純喊么是Socket。
對於 Socket 的認識,本文將從以下幾個方面著手介紹:
1) Socket 是什麼;
2) Socket 是如何創建的;
3) Socket 是如何連接的;
4) Socket 是如何收發數據的;
5) Socket 是如何斷開連接的;
6) Socket 套接字的刪除等。
特別說明: 本文中提到的「Socket」、「網路套接字」、「套接字」,如無特殊指明,指的都是同一個東西哦。
Socket 是什麼
一個數據包經由應用程序產生,進入到協議棧中進行各種報文頭的包裝,然後操作系統調用網卡驅動程序指揮硬體,把數據發送到對端主機。
我們大家知道,協議棧其實是位於操作系統中的一些協議的堆疊,這些協議包括 TCP、UDP、ARP、ICMP、IP等。即時通訊開發可以找蔚可雲開發。
通常某個協議的設計都是為了解決特定問題的,比如:
1) TCP 的設計就負責安全可靠的傳激盯輸數據;
2) UDP 設計就是報文小,傳輸效率高;
3) ARP 的設計是能夠通過 IP 地址查詢物理(Mac)地址;
4) ICMP 的設計目的是返回錯誤報文給主機;
5) IP 設計的目的是為了實現大規模主機的互聯互通。
應用程序比如瀏覽器、電子郵件、文件傳輸伺服器等產生的數據,會通過傳輸層協議進行傳輸。而應用程序是不會和傳輸層直接建立聯系的,而是有一個能夠連接應用層和傳輸層之間的套件,這個套件就是 Socket 。
應用程序的下面: 就是操作系統內部,操作系統內部包括協議棧,協議棧是一系列協議的堆疊。
操作系統下面: 就是網卡驅動程序,網卡驅動程序負責控制網卡硬體,驅動程序驅動網卡硬體完成收發工作。
在操作系統內部有一塊用於存放控制信息的存儲空明褲和間,這塊存儲空間記錄了用於控制通信的控制信息。其實這些控制信息就是 Socket 的實體,或者說存放控制信息的內存空間就是Socket的實體。
這里大家有可能不太清楚所以然,所以我用了一下 netstat 命令來給大夥看一下Socket是啥玩意。
Socket 是如何創建的
通過上節的講解,現在你可能對 Socket 有了一個基本的認識,先喝口水,休息一下,讓我們繼續探究 Socket。
現在我有個問題, Socket 是如何創建的呢?
Socket 是和應用程序一起創建的。
應用程序中有一個 socket 組件,在應用程序啟動時,會調用 socket 申請創建Socket,協議棧會根據應用程序的申請創建Socket:首先分配一個Socket所需的內存空間,這一步相當於是為控制信息准備一個容器,但只有容器並沒有實際作用,所以你還需要向容器中放入控制信息;如果你不申請創建Socket所需要的內存空間,你創建的控制信息也沒有地方存放,所以分配內存空間,放入控制信息缺一不可。至此Socket的創建就已經完成了。
Socket創建完成後,會返回一個Socket描述符給應用程序,這個描述符相當於是區分不同Socket的號碼牌。根據這個描述符,應用程序在委託協議棧收發數據時就需要提供這個描述符。
Socket 是如何連接的
Socket創建完成後,最終還是為數據收發服務的。但是,在數據收發之前,還需要進行一步「連接」(術語就是 connect),建立連接有一整套過程。
實際上這個「連接」是應用程序通過 TCP/IP 協議標准從一個主機通過網路介質傳輸到另一個主機的過程。
Socket剛剛創建完成後,還沒有數據,也不知道通信對象。
在這種狀態下: 即使你讓客戶端應用程序委託協議棧發送數據,它也不知道發送到哪裡。所以瀏覽器需要根據網址來查詢伺服器的 IP 地址,查詢到目標主機後,再把目標主機的 IP 告訴協議棧。至此,客戶端這邊就准備好了。
在伺服器上: 與客戶端一樣也需要創建Socket,但是同樣的它也不知道通信對象是誰,所以我們需要讓客戶端向伺服器告知客戶端的必要信息: IP 地址和埠號 。
現在通信雙方建立連接的必要信息已經具備,可以開始「連接」過程了。
首先: 客戶端應用程序需要調用 Socket 庫中的connect方法,提供 socket 描述符和伺服器 IP 地址、埠號。
以下是connect的偽碼調用:
1connect(<描述符>、<伺服器IP地址和埠號>)
這些信息會傳遞給協議棧中的 TCP 模塊,TCP 模塊會對請求報文進行封裝,再傳遞給 IP 模塊,進行 IP 報文頭的封裝,然後傳遞給物理層,進行幀頭封裝。
之後通過網路介質傳遞給伺服器,伺服器上會對幀頭、IP 模塊、TCP 模塊的報文頭進行解析,從而找到對應的Socket。
Socket收到請求後,會寫入相應的信息,並且把狀態改為正在連接。
請求過程完成後: 伺服器的 TCP 模塊會返回響應,這個過程和客戶端是一樣的
Socket 是如何收發數據的
當控制流程上節中的連接過程回到應用程序之後,接下來就會直接進入數據收發階段。
數據收發操作是從應用程序調用 write 將要發送的數據交給協議棧開始的,協議棧收到數據之後執行發送操作。
協議棧不會關心應用程序傳輸過來的是什麼數據,因為這些數據最終都會轉換為二進制序列,協議棧在收到數據之後並不會馬上把數據發送出去,而是會將數據放在發送緩沖區,再等待應用程序發送下一條數據。
為什麼收到數據包不會直接發送出去,而是放在緩沖區中呢?
因為只要一旦收到數據就會發送,就有可能發送大量的小數據包,導致網路效率下降(所以協議棧需要將數據積攢到一定數量才能將其發送出去)。
至於協議棧會向緩沖區放多少數據,這個不同版本和種類的操作系統有不同的說法。
㈢ Socket詳解
1、 Socket(套接字)概念
網路上兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一段稱為一個 socket ,socket是通信的基石,是支持TCP/IP協議的網路通信的基本操作單元。它是網路通信過程中端點的抽象表示,包含進行網路通信必須的五種信息:連接使用的協議,本地主機的IP地址,本地進襪橡程的協議埠,遠地主機的IP地址,遠地進程的協議埠。
Socket是對TCP/IP協議的封裝,它把復雜的TCP/IP協議族隱藏在Socket介面後面,提供一個易用的介面,所以Socket本身並不是協議,而是一個調用介面(API)。
在一定程度可以認為Socket位於應用層和傳輸層之間。創建Socket連接時,可以指定使用的傳輸層協議,Socket可以支持不同的傳輸層協議(TCP或UDP),當使用TCP協議進行連接時,該Socket連接就是一個TCP連接。
2、 建立Socket連接
建立Socket連接至少需要一對套接字,其中一個運行於客戶端,稱為ClientSocket,另一個運行於伺服器端,稱為ServerSocket。
套接字之間的連接過程分為 三個步驟 :
(1)伺服器監聽:伺服器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網路狀態,等待客戶端的連接請求。
(2)客戶端請求:指客戶端的套接字提出連接請求,要連接的目標是伺服器端的套接字。為此,客戶端的套接字必須首先描述它要連接的伺服器的套接字,指出伺服器端套接字的地址和埠號,然後就向伺服器端套接字提出連接請求。
(3)連接確認:當伺服器端套接字監聽到或者說接收到客戶端套接字的連接請求時,就響應客戶端套接字的請求,建立一個新的線程,把伺服器端套接字的描述發給客戶 端,一旦客戶端確認了此描述,雙方就正式建立連接。而伺服器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。
3、 Socket連接與HTTP連接
由於通常情況下Socket連接就是TCP連接,因此Socket連接一旦建立,通信雙方即可開始相互發送數據內容,直到雙方連接斷開。但在實際網路應用 中,客戶端到伺服器之間的通信往往需要穿越多個中間節點,例如路由器、網關、防火牆等,大部分防火牆默認會關閉長時間處於非活躍狀態的連接而導致 Socket 連接斷連,因此需要通過輪詢告訴網路,該連接處於活躍狀態。
而HTTP連接使用的是「請求—響應」的方式,不僅在請求時需要先建立連接,而且需要客戶端向伺服器發出請求後,伺服器端才能回復數據。
4、 關於Socket長連接的心跳包
心跳包就是為了避免一個連接長時間不活躍被關閉而定時發送的一個」騷擾」數據包。
Socket本身就是長連接的,那麼為什麼還要心跳包呢?
理論上說,這個連接是一直保持連接的,但是實際情況中,如果中間節點出現什麼故障是難以知道的。更要命的是,有的節點(防火牆)會自動把一定時間之內沒有數據交互的連接給斷掉。在這個時候,就需要我們的心跳包了,用於維持粗好老長連接,保活。在獲知了斷線之後,伺服器邏輯可能需要做一些事情,比如斷線後的數據清理,重新連接……當然,這個自然是要由邏輯層根據需求去做了。總的來說,心跳包主要也就是用於長連接的保活和斷線處理。一般的應用下,判定時間在30-40秒比較不錯。如果實在要求高,那就在6-9秒。
如果不主動關閉socket的話,系統不會自動關閉的,除非當前進程掛掉了,操作系統把佔用的socket回收了才會關閉。為什麼需要心跳連接?主要是為了判斷當前連接是否是有效的、可被使用的。在實際應用中假設一段時間沒有數據傳輸時候理論上說應該連接是沒有問題的,但是網路復雜,中途出現問題也是常見的,網線被掐斷了、對方進程掛掉了、頻繁丟包等,這時候TCP連接是不可使用的,但是對於應用層並不知道,如果需知道網路情況則要很復雜的超時進行了解,TCP從底層就實現了這樣的功能。心跳機制是TCP在一段時間間隔後發送確認連接端是否還存在,如果存在的話就會回傳一個包確定網路有效,如果岩升心跳包有問題,則通知上層應用當前網路有問題了。
這取決於你的server端的超時配置, 每個socket連接都是長連接,它是一個相當佔用系統資源的通信管道, 如果這個長連接什麼事也沒干硬是要佔著資源,則server端可以選擇關閉這個連接,以省下資源讓更多的用戶連接進來。
所以,即便客戶端的是採用死循環while(true)方式連到服務端,對於特定的客戶端和服務端類型來說也需要一定時間間隔的心跳(告訴服務端,我還活著,雖然我沒幹活也沒說話,但別把我關了)