TCP/UDP套接字 java socket編程實例


網絡協議七層結構:

 

什么是Socket?

  socket(套接字)是兩個程序之間通過雙向信道進行數據交換的端,可以理解為接口。使用socket編程也稱為網絡編程,socket只是接口並不是網絡通信協議。

HTTP協議和Socket的區別

  http協議是應用層,其模式是請求-應答,客戶端發送請求,服務器端進行響應。傳輸的數據是原始格式的數據,eg :json、xml、text等數據格式。

  socket不是協議是接口,socket提供TCP/UDP socket 的實例,供java 或者其他語言操作數據的傳輸,socket是對傳輸層(TCP/UPD協議)的封裝。

Socket通信分為兩種

  TCP Socket :使用流傳輸,提供inputStream 和 outputStream 方法對數據進行流操作。要理解TCP套接字首先要對TCP協議有所理解。

    1)TCP協議是傳輸層的協議,他的下一層是IP協議(網絡層),IP協議在網絡數據傳輸是通過ip尋址,將源地址和目的地址進行連接。TCP協議是在IP協議上多加一層端口尋址,光只通過IP尋址只能定位到主機,tcp通過端口找到對應的應用程序。

    2)TCP 建立連接需要三次握手,將源應用程序和目的應用程序之間搭建一個連接,所以源應用和目的應用程序之間必須是one by one。IP 協議只管數據的傳輸,不保證數據是否丟失,重復傳,順序是否正確,TCP會對這些問題做一些補償機制,丟失數據重傳,用隊列保證數據的順序。

    3) TCP 缺點:因為每個客戶端和服務器端傳輸數據都要建立連接,三次握手是不傳輸數據並且有耗時,當有大量短連接的時候並且對數據的正確性要求不高的時候,將會占用帶寬。

  UDP Socket:使用數據報文進行傳輸,創建UDP socket 發送和接收數據報文。

    1)UDP協議同TCP協議一樣都是應用層協議,也是通過端口尋址,找到對應的應用程序。

    2)UDP傳輸數據報文不需要和目的應用程序建立連接,他在數據報文中指定目的主機和目的端口號,發送出的數據自動尋址到對應的主機和端口號。因為不用和目的主機建立連接,所以一個源應用程序可以以廣播的形式將數據報文傳輸給多主機。因為不用建立連接,耗時和帶寬占用量都比TCP協議更優秀

    3)UDP缺點:數據有可能丟失,丟失的數據不會重傳

java socket 實例

  TCP Socket client

 

package socket.transmission.tcp;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

//TCP 套接字 客戶端負責發送請求
public class TcpClient {
    private static  final  int BUF_SIZE=32;
    /**
     * TCP客戶端發送一個請求要有三個步驟:
     *  1.創建一個socket的實例,創建一個指向server主機ip和端口號的TCP連接
     *  2.通過套接字的輸入和輸出流進行通信
     *  3.使用socket close關閉
     */
    public static void main(String[] args){
      String ip="192.168.197.1";
        int port=8080;
        try {
            // 創建一個socket實例
            Socket socket=new Socket(ip,port); // 1 設置TCP SOCKET,初始化目的主機ip和端口號,建立和目的主機的連接,若目的主機沒有開啟服務,則會彈出server refused
            System.out.println("創建一個socket連接");
            InputStream inputStream=socket.getInputStream();  // 2 獲取回饋服務器的輸入流
            OutputStream outputStream=socket.getOutputStream(); // 3 將要傳輸的數據數據寫入到輸出流中,傳輸給目的主機
            //向socket中寫入數據
            outputStream.write("this is a word".getBytes());  // 4 傳輸數據到目的主機
            int totalByrecive=0;  //到目前為止接收到的數據
            byte[] readBuff=new byte[BUF_SIZE];
            int lastReadByte;  //最后接收的字節
            System.out.println("從服務器中接收的數據:");
            int receiveMsgSize;
            while ((receiveMsgSize=inputStream.read(readBuff))!=-1){   // 5.從回饋服務器中獲取數據,
                System.out.println(new String(readBuff));
            }
             socket.close();  //關閉
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

 

tcp sokect server

package socket.transmission.tcp;

//TCP 服務器端進行接收請求

import sun.java2d.pipe.OutlineTextRenderer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;

/**
 * TCP服務器對客戶端發送的請求會進行以下處理
 *  1.創建serverSocket實例並且指定本機端口,功能:監聽指定端口發送過來的連接
 *  2.重復執行:
 *      1).調用的serverSocket 的accept() 監聽客戶端發送過來的請求,並創建socket
 *      2).使用socket的inputStream 和  outputStream 進行通訊
 *      3).通信完使用socket.close() 方法將連接關閉
 */
public class TcpServer {

    private static  final  int BUF_SIZE=32;

    public static void main(String[] args){
        int port=8080;
        Socket socket = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            ServerSocket serverSocket=new ServerSocket(port);//創建一個socket實例用於監聽客戶端發送的連接,指定本服務器的端口號
            System.out.println("創建serverSocket 實例");
            int reviceMsgSize;   // 接收msg的大小
            byte[] receiveBuf=new byte[BUF_SIZE];  //創建一個信息接收的緩沖區
                System.out.println("開始處理接收的數據");
                while (true) {
                    socket = serverSocket.accept();  //接收客戶端的連接,每接收一個數據都會創建一個連接,當沒有數據的接收的時候會阻塞
                    SocketAddress socketAddress = socket.getRemoteSocketAddress(); //
                    System.out.println("訪問的地址:" + socketAddress);
                    inputStream = socket.getInputStream();
                    outputStream = socket.getOutputStream();
                    while ((reviceMsgSize = inputStream.read(receiveBuf)) != -1) {
                        System.out.println(new String(receiveBuf));
                        outputStream.write("aaaaa".getBytes(), 0, 4);
                    }
                    outputStream.flush();
                    socket.close();
                }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
                try {
                    if(socket!=null){
                        socket.close();
                    }
                    if(inputStream!=null){
                        inputStream.close();
                    }
                    if(outputStream!=null){
                        outputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }


}

 UDP Socket client

package socket.transmission.udp;


import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.*;

//UDP 套接字傳輸的是數據報文

/**
 * UDP 客戶端發送數據報文的步驟:
 *  1.創建UPD 套接字 DataGramSocket ,使用UDP協議通信是不需要和服務器端創建的連接的,UPD協議只是在IP協議上
 *    多加了一層端口尋址,設置超時
 *  2.創建發送的數據報文實例DataGramPacket,若是單播的則要指明目的端的ip地址和port,IP地址通過創建InetAddress 的實例
 *  3.創建接收數據報文實例DataGramPacket ,指定接收數據的緩沖區
 *  4.調用socket的send方法將數據報文發送出去
 *  5.循環接收數據報文,當數據報文丟失的時候,發起重試。否則設置響應標志位true,將數據打印
 */
public class UdpClient {
    /**
     * 當客戶端發送給server端信息,收到回饋信息的時候,通過read讀取數據,當沒有數據返回(數據丟失)
     * read 方法會發生阻塞,若沒有設置超時重發,則程序會一直阻塞
     */
    private static final int TIME_OUT=2000;  //設置超時重發時間

    private static final int MAX_RENTRY=3;  // 設置最大重試次數

    public  static void main(String[] args) throws IOException {
        try {
            int serverPort=8080; // 指定
            byte[] sendMsg="this is a test".getBytes();
            DatagramSocket socket=new DatagramSocket(); //創建一個數據報文
            socket.setSoTimeout(TIME_OUT);  //設置read阻塞超時時間
            byte[] ipByte={10,1,1,100};
            /**
             * "10.1.1.100".getbytes()的方式不能正確的創建server端,調用InetAddress.getByAddress() 方法將會做兩個長度判斷,IPV4 的入參長度要==4
             *  IPV6 的長度要== 16 而通過10.1.1.100 getbytes的方式獲取的長度是10 拋出違法的長度
             */
            InetAddress inetAddress= InetAddress.getByAddress(ipByte);  //創建server主機的ip地址
            DatagramPacket sendPacket=new DatagramPacket(sendMsg,sendMsg.length,inetAddress,8080);  //發送的數據報文
            DatagramPacket receivePacket=new DatagramPacket(new byte[sendMsg.length],sendMsg.length);  //接收的數據報文
            int tryTimes=0;  //數據報文可能丟失,設置重試計數器
            Boolean receiveResponse=false;
            socket.send(sendPacket);  //將數據報文發送出去
            do{
                try {
                    socket.receive(receivePacket);   //嘗試去循環接收數據報文
                    if (!receivePacket.getAddress().equals(inetAddress)) { //檢查回饋過來的數據報文
                        throw new IOException("未知的Server端數據報文");
                    }
                    receiveResponse=true;
                }catch (InterruptedIOException e){ //數據報文中斷異常
                    tryTimes++;
                    System.out.println("超時還有"+(MAX_RENTRY-tryTimes)+"次重試機會");
                }
            }while(!receiveResponse&&(tryTimes<MAX_RENTRY));
            if(receiveResponse){
                System.out.println("從服務器端獲取的數據:"+new String(receivePacket.getData()));
            }else{
                System.out.println("沒有獲取到數據");
            }
            socket.close(); //關閉套接字
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }
}

UPD Soceket server

package socket.transmission.udp;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.*;

//UDP 套接字接收客戶端的數據報文
public class UdpServer {
    private static final int ECHO_MAX=255;  //設置緩沖區的長度

    public static void main(String[] args)  {
        try {
            DatagramSocket datagramSocket=new DatagramSocket(8080);
            DatagramPacket reveiveMsg=new DatagramPacket(new byte[ECHO_MAX],ECHO_MAX);
            while(true){
                    datagramSocket.receive(reveiveMsg);
                System.out.println("從客戶端接收的來數據:"+new String(reveiveMsg.getData()));
                //在服務器端將發送的信息修改
                byte[] newData="啦啦啦啦".getBytes();
               // reveiveMsg=new DatagramPacket(newData,newData.length);
                //將轉化后的數據發送
                datagramSocket.send(reveiveMsg);
                /**
                 * 重置接收包的長度,因為接收數據的時候已經接收包的長度設置為接收信息的長度,當下次再接收數據的時候,
                 * 新數據的長度大於上一次數據的長度時,多出的數據將被截斷,所以要重置接收包緩沖區的長度
                 */
                reveiveMsg.setLength(ECHO_MAX);
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }catch (SocketException se){
            se.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

 上面的代碼還有一些未補足的:要在finally 中將所有的流關閉。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM