Socket和HTTP的區別
1. Socket是基於TCP/IP協議,是傳輸層的連接;而HTTP是基於應用層的連接。
2. HTTP連接最顯著的特點是客戶端發送的每次請求都需要服務器回送響應,在請求結束後,會主動釋放連接,下次建立連接需要tcp重新進行三次握手。因此HTTP連接是一種“短連接”。要保持客戶端程序的在線狀態,需要不斷地向服務器發起連接請求。通常的做法是即使不需要獲得任何數據,客戶端也保持每隔一段固定的時間向服務器發送一次“保持連接”的請求,服務器在收到該請求後對客戶端進行回覆,表明知道客戶端“在線”。若服務器長時間無法收到客戶端的請求,則認爲客戶端“下線”;
由於通常情況下Socket連接就是TCP連接,因此Socket連接一旦建立,通信雙方即可開始相互發送數據內容,直到雙方連接斷開,"長鏈接"。
3. HTTP連接使用的是“請求—響應”的方式,不僅在請求時需要先建立連接,而且需要客戶端向服務器發出請求後,服務器端才能回覆數據;而Socket連接,服務器就可以直接將數據傳送給客戶端,實現雙向通信。
Socket優點
傳輸數據爲字節級,傳輸數據可自定義,數據量小;
傳輸數據時間短,性能高;
適合C/S之間信息實時交互;
可以加密,數據安全性高
缺點: 需要對傳輸的數據進行解析,轉化爲應用層的數據
相對於Http協議傳輸,增加了開發量
HTTP優點
基於應用層的接口使用方便
缺點: 傳輸速度慢,數據包大。
如實現實時交互,服務器性能壓力大
數據傳輸安全性差
應用場景
Socket適用於對實時性,安全性要求較高,或連接較長的場景,比如網絡遊戲,銀行支付,收發消息。
HTTP適用於對信息傳輸實時性安全性要求不高,可以一次連接解決的場景,如互聯網服務。
Socket
消息目的地:socket地址 = ip地址+端口號
進程間通信需要一對socket,數據在兩個Socket間通過IO傳輸。
對於Java Socket編程而言,有兩個概念,一個是ServerSocket,一個是Socket。
套接字之間的連接過程分爲三個步驟:
1. 服務器監聽(ServerSocket將在服務端監聽某個端口);
2. 客戶端請求(客戶端的Socket提出連接請求,指出服務器端套接字的地址和端口號)
3. 連接確認(ServerSocket監聽到客戶端套接字的連接請求,accept該Socket的連接請求,同時在服務端建立一個對應的Socket與之進行通信)
Socket編程有兩種通信協議可以選擇:
1. UDP 數據報通信
UDP是一種無連接的協議,每次發送數據報時,需要同時發送本機的socket描述符和接收端的socket描述符。
每次通信都需要發送額外的數據。
2. TCP 流通信
TCP是一種基於連接的協議,在使用流通信之前,我們需要在通信的一對socket之間建立連接。其中一個socket作爲服務器進行監聽連接請求,另一個作爲客戶端進行連接請求。
區別
UDP中,數據報大小有64k的限制,而TCP無限制;
UDP不可靠,數據報不一定按照發送順序被接受,而TCP所有數據按照接受時的順序讀取。
總之TCP適合於如遠程登錄和文件傳輸這類的網絡服務,因爲這些服務需要傳輸數據大小不確定;
而UDP更加簡單輕量,用來實現實時性較高或丟包不重要的一些服務。
TCP通信的Java實現
所有socket相關的類都位於java.net包下。
客戶端Client
1.創建Socket對象,指明需要連接的服務器的ip地址和端口號
2.連接建立後,通過輸出流OutputStreamWriter向服務器端發送請求信息
3.通過輸入流獲取服務器響應的信息
4.關閉響應資源
Socket client = new Socket(host, port);
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello From Client");
writer.flush();
writer.close();
client.close();
服務器Server
1.創建ServerSocket對象,綁定監聽端口
2.通過accept()方法監聽客戶端請求(Accept是阻塞方法,服務器端與客戶端建立聯繫前會一直等待阻塞。)
3.連接建立後,通過輸入流讀取客戶端發送的請求信息
4.通過輸出流向客戶端發送響應信息
5.關閉相關資源
常見服務器Server模型
1. 阻塞服務器
當一個消息執行發出後,這個消息在發送端的socket中處於排隊狀態,直到下層網絡協議將這些消息發送出去。
當消息到達接收端的socket後,也會處於排隊狀態,直到接收端的進程對這條消息進行了接收處理。
前一個請求沒有處理完成,服務器不會處理後面的請求。
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[1024];
int len;
StringBuilder builder = new StringBuilder();
while ((len=reader.read(chars)) != -1) {
builder.append(new String(chars, 0, len));
}
System.out.println("Receive from client message=: " + builder);
reader.close();
socket.close();
server.close(); // 關閉socket
2. 併發服務器
每接收到一個請求,就啓動一個線程處理該請求。
這種模式的服務器,好處是不會出現阻塞式服務器請求被擁堵的情況。
存在問題:服務器啓動線程有一定開銷,請求過多時,將會導致服務器的資源耗盡。
解決:建立線程池來處理請求,每當請求到來時,向線程池申請線程進行處理。
ServerSocket serverSocket = new ServerSocket(SERVER_PORT, 5, serverAddr);
Executor executor = Executors.newFixedThreadPool(100);
//有客戶端向服務器發起tcp連接時,accept會返回一個Socket
//該Socket的対端就是客戶端的Socket
//具體過程可以查看TCP三次握手過程
Socket connection = serverSocket.accept();
//利用線程池,啓動線程
executor.execute(new Runnable() {
@Override
public void run() {
//使用局部引用,防止connection被回收
Socket conn = connection;
try {
InputStream in = conn.getInputStream();
與阻塞服務器比較,差別僅在於當accept返回socket後啓動線程處理,這裏使用了Excutor,基於線程池進行處理。
3. 異步服務器
一般藉助於系統的異步IO機制(NIO),當一個請求到達時,我們可以先將請求註冊,當有數據可以讀取時,會得到通知,這時候我們處理請求,這樣服務器進程沒有必要阻塞處理,也不會存在很大的系統開銷,因此,目前對於併發量要求比較高的服務器,一般都是採用這種方式。
UDP通信的java實現
Java通過DatagramPacket類和DatagramSocket類來使用UDP套接字,客戶端和服務器端都通過DatagramSocket的send()方法和receive()方法來發送和接收數據,用DatagramPacket來包裝需要發送或者接收到的數據。
發送信息時,Java創建一個包含待發送信息的DatagramPacket實例,並將其作爲參數傳遞給DatagramSocket實例的send()方法;接收信息時,Java程序首先創建一個DatagramPacket實例,該實例預先分配了一些空間,並將接收到的信息存放在該空間中,然後把該實例作爲參數傳遞給DatagramSocket實例的receive()方法。
在創建DatagramPacket實例時,要注意:如果該實例用來包裝待接收的數據,則不指定數據來源的遠程主機和端口,只需指定一個緩存數據的byte數組即可(在調用receive()方法接收到數據後,源地址和端口等信息會自動包含在DatagramPacket實例中),而如果該實例用來包裝待發送的數據,則要指定要發送到的目的主機和端口。
客戶端
1. 定義發送信息
2. 創建DatagramPacket,包含將要發送的信息
3. 創建DatagramSocket(可以有選擇地對本地地址和端口號進行設置)
4. 發送數據
//1、定義服務器的地址、端口號、數據
InetAddress address =InetAddress.getByName("localhost");
int port =10010;
byte[] data ="用戶名:admin;密碼:123".getBytes();
//2、創建數據報,包含發送的數據信息
DatagramPacket packet = newDatagramPacket(data,data,length,address,port);
//3、創建DatagramSocket對象
DatagramSocket socket =newDatagramSocket();
//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.getLenth());
System.out.println("我是客戶端,服務器說:"+reply);
//4、關閉資源
socket.close();
服務器
1. 創建DatagramSocket,指定端口號
2. 創建DatagramPacket
3. 接受客戶端發送的數據信息
4. 讀取數據
//服務器端,實現基於UDP的用戶登錄
//1、創建服務器端DatagramSocket,指定端口,UDP服務器爲所有通信使用同一套接字
DatagramSocket socket =new datagramSocket(10010);
//2、創建數據報,用於接受客戶端發送的數據
byte[] data =newbyte[1024];//
DatagramPacket packet =newDatagramPacket(data,data.length);
//3、接受客戶端發送的數據
socket.receive(packet); //此方法在接受數據報之前會一直阻塞
//4、讀取數據
String info =newString(data,o,data.length);
System.out.println("我是服務器,客戶端告訴我"+info);
//=========================================================
//向客戶端響應數據
//1、定義客戶端的地址、端口號、數據
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "歡迎您!".geyBytes();
//2、創建數據報,包含響應的數據信息
DatagramPacket packet2 = new DatagramPacket(data2,data2.length,address,port);
//3、響應客戶端
socket.send(packet2);
//4、關閉資源
socket.close();
由於UDP協議是不可靠協議,如果數據報在傳輸過程中發生丟失,那麼程序將會一直阻塞在receive()方法處,這樣客戶端將永遠都接收不到服務器端發送回來的數據,但是又沒有任何提示。爲了避免這個問題,我們在客戶端使用DatagramSocket類的setSoTimeout()方法來制定receive()方法的最長阻塞時間,並指定重發數據報的次數,如果每次阻塞都超時,並且重發次數達到了設置的上限,則關閉客戶端。
int TIMEOUT = 5000; //設置接收數據的超時時間
//客戶端在9000端口監聽接收到的數據
DatagramSocket ds = new DatagramSocket(9000);
ds.setSoTimeout(TIMEOUT);