unity網絡編程學習(4)與java服務器的Socket通信

前言

在上一篇博客中,我們通過unity中的www類來和web服務器進行數據的交互,所使用的方式就是http通信,那麼http通信的原理是什麼呢,socket通信原理又是什麼呢,這裏推薦兩篇寫的比較不錯的博文:這裏還有這裏。

http通信原理

HTTP協議即超文本傳送協議(Hypertext Transfer Protocol ),是Web聯網的基礎,也是手機聯網常用的協議之一,HTTP協議是建立在TCP協議之上的一種應用。HTTP連接最顯著的特點是客戶端發送的每次請求都需要服務器回送響應,在請求結束後,會主動釋放連接。採用了請求/響應模型。客戶端向服務器發送一個請求,請求頭包含了請求的方法、URI、協議版本,以及包含請求修飾符、 客戶信息和內容的類似於MIME的消息結構。服務器以一個狀態行作爲響應,響應的內容包括消息協議的版本、成功或者錯誤編碼,還包含服務器信息、實體元信息以及可能的實體內容。它是一個屬於應用層的面向對象的協議,由於其簡潔、快速,它適用於分佈式超媒體信息系統。
http通信中使用最多的就是Get和Post,Get請求可以獲取靜態頁面,也可以把參數放在URL字符串後面,傳遞給服務器。Post與Get的不同之處在於Post的參數不是放在URL字符串裏面,而是放在http請求數據中。所以更爲安全,由於HTTP在每次請求結束後都會主動釋放連接,因此HTTP連接是一種“短連接”,要保持客戶端程序的在線狀態,需要不斷地向服務器發起連接請求。通常 的做法是即時不需要獲得任何數據,客戶端也保持每隔一段固定的時間向服務器發送一次“保持連接”的請求,服務器在收到該請求後對客戶端進行回覆,表明知道 客戶端“在線”。若服務器長時間無法收到客戶端的請求,則認爲客戶端“下線”,若客戶端長時間無法收到服務器的回覆,則認爲網絡已經斷開。

socket通信原理

  套接字(socket)是通信的基石,是支持TCP/IP協議的網絡通信的基本操作單元。它是網絡通信過程中端點的抽象表示,包含進行網絡通信必須的五種信息:連接使用的協議,本地主機的IP地址,本地進程的協議端口,遠地主機的IP地址,遠地進程的協議端口。有兩種主要的操作方式:面向連接(TCP協議)和無連接(UDP協議)的。面向連接的操作比無連接操作的效率更低,但是數據的安全性更高。可以說,網絡通信的核心就是socket通信,
在socket通信中,需要了解的TCP協議的三次握手連接和四次握手斷開連接,都可以通過各種搜索以及上面推薦的博客做詳細的瞭解,socekt通信中常用的函數有socket()函數bind()函數listen()、connect()函數、accept()函數、read()、write()函數、close()函數

socket通信和http通信的區別

由於通常情況下Socket連接就是TCP連接,因此Socket連接一旦建立,通信雙方即可開始相互發送數據內容,直到雙方連接斷開。但在實際網絡應用中,客戶端到服務器之間的通信往往需要穿越多箇中間節點,例如路由器、網關、防火牆等,大部分防火牆默認會關閉長時間處於非活躍狀態的連接而導致 Socket 連接斷連,因此需要通過輪詢告訴網絡,該連接處於活躍狀態。
而HTTP連接使用的是“請求—響應”的方式,不僅在請求時需要先建立連接,而且需要客戶端向服務器發出請求後,服務器端才能回覆數據。
很多情況下,需要服務器端主動向客戶端推送數據,保持客戶端與服務器數據的實時與同步。此時若雙方建立的是Socket連接,服務器就可以直接將數據傳送給客戶端;若雙方建立的是HTTP連接,則服務器需要等到客戶端發送一次請求後才能將數據傳回給客戶端,因此,客戶端定時向服務器端發送連接請求,不僅可以保持在線,同時也是在“詢問”服務器是否有新的數據,如果有就將數據傳給客戶端

unity中的socket通信

這裏,客戶端是用c#編寫,服務器端使用java編寫,所以就是unity和java服務器端的scoket通信,連接成功後,客戶端和服務器端之間傳遞字符串(真正的項目中應該是數據包,對象,列表啥的,恩,我自己認爲的)先看看客戶端代碼吧,這裏將開啓socket通信寫成一個單例模式。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
/*
 * 
 *Socket客戶端通信類
 * 
 */
public class SocketHelper
{

    private static SocketHelper socketHelper = new SocketHelper();

    private Socket socket;

    //單例模式
    public static SocketHelper GetInstance()
    {
        return socketHelper;
    }

    private SocketHelper()
    {

        //採用TCP方式連接
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        //服務器IP地址
        IPAddress address = IPAddress.Parse("127.0.0.1");

        //服務器端口
        IPEndPoint endpoint = new IPEndPoint(address, 8000);

        //異步連接,連接成功調用connectCallback方法
        IAsyncResult result = socket.BeginConnect(endpoint, new AsyncCallback(ConnectCallback), socket);

        //這裏做一個超時的監測,當連接超過5秒還沒成功表示超時
        bool success = result.AsyncWaitHandle.WaitOne(5000, true);
        if (!success)
        {
            //超時
            Closed();
            Debug.Log("connect Time Out");
        }
        else
        {
            //與socket建立連接成功,開啓線程接受服務端數據。
            Thread thread = new Thread(new ThreadStart(ReceiveSorket));
            thread.IsBackground = true;
            thread.Start();
        }

    }

    private void ConnectCallback(IAsyncResult asyncConnect)
    {
        Debug.Log("connect success");
    }

    private void ReceiveSorket()
    {
        //在這個線程中接受服務器返回的數據
        while (true)
        {

            if (!socket.Connected)
            {
                //與服務器斷開連接跳出循環
                Debug.Log("Failed to clientSocket server.");
                socket.Close();
                break;
            }
            try
            {
                //接受數據保存至bytes當中
                byte[] bytes = new byte[4096];
                //Receive方法中會一直等待服務端回發消息
                //如果沒有回發會一直在這裏等着。
                int i = socket.Receive(bytes);
                if (i <= 0)
                {
                    socket.Close();
                    break;
                }
                Debug.Log(System.Text.Encoding.Default.GetString(bytes));
            }
            catch (Exception e)
            {
                Debug.Log("Failed to clientSocket error." + e);
                socket.Close();
                break;
            }
        }
    }

    //關閉Socket
    public void Closed()
    {
        if (socket != null && socket.Connected)
        {
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }
        socket = null;
    }
    //向服務端發送一條字符串
    //一般不會發送字符串 應該是發送數據包
    public void SendMessage(string str)
    {
        byte[] msg = Encoding.UTF8.GetBytes(str);

        if (!socket.Connected)
        {
            socket.Close();
            return;
        }
        try
        {
            IAsyncResult asyncSend = socket.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
            bool success = asyncSend.AsyncWaitHandle.WaitOne(5000, true);
            if (!success)
            {
                socket.Close();
                Debug.Log("Failed to SendMessage server.");
            }
        }
        catch
        {
            Debug.Log("send message error");
        }
    }



    private void SendCallback(IAsyncResult asyncConnect)
    {
        Debug.Log("send success");
    }


}
在unity中創建一腳本,繼承MonoBehaviour,掛在MainCamera上
using UnityEngine;

public class SocketTest : MonoBehaviour {

	void Start () {
        //創建socket連接
        SocketHelper s = SocketHelper.GetInstance();
        //發送信息向服務器端
        s.SendMessage("i am client");
	}

}
--------------服務器端--------------
package u3d_server;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;


/**
 * unity3d 服務端
 * @author lm
 *
 */
public class U3dServer implements Runnable {

	public void run() {
		
		ServerSocket u3dServerSocket = null;

		while(true){
			
			try {
				
				u3dServerSocket=new ServerSocket(8000);			
				System.out.println("u3d服務已經啓動,監聽8000端口");		
				while (true) {
					Socket socket = u3dServerSocket.accept();
					System.out.println("客戶端接入");
					new RequestReceiver(socket).start();
				}
			} catch (IOException e) {
				System.err.println("服務器接入失敗");
				if (u3dServerSocket != null) {
					try {
						u3dServerSocket.close();
					} catch (IOException ioe) {
					}
					u3dServerSocket = null;
				}
			}
			
			// 服務延時重啓
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {

			}
			
		}
		
		
	}
	/**
	 * 客戶端請求接收線程
	 * @author lm
	 *
	 */
	class RequestReceiver extends Thread {

		/** 報文長度字節數 */
		private int messageLengthBytes = 1024;

		private Socket socket;

		/** socket輸入處理流 */
		private BufferedInputStream bis = null;
		

		public RequestReceiver(Socket socket) {
			this.socket = socket;
		}

		@Override
		public void run() {
			try {
				//獲取socket中的數據
				bis = new BufferedInputStream(socket.getInputStream());
				byte[] buf = new byte[messageLengthBytes];
				
				/**
				 * 在Socket報文傳輸過程中,應該明確報文的域
				 */
				while (true) {
			
					/*
					 * 這種業務處理方式是根據不同的報文域,開啓線程,採用不同的業務邏輯進行處理
					 * 依據業務需求而定 
					 */
					//讀取字節數組中的內容
					bis.read(buf);
					//輸出
					System.out.println(new String(buf,"utf-8"));
					OutputStream out = socket.getOutputStream();
					//向客戶端傳輸數據的字節數組
					out.write(new String("i am server").getBytes());			
					
				}
				
			} catch (IOException e) {
				System.err.println("讀取報文出錯");
			} finally {
				if (socket != null) {
					try {
						socket.close();
					} catch (IOException e) {
					}
					socket = null;
				}
			}
			
		}
	}
}
package u3d_server;

public class U3dApplication {
	private static U3dApplication instance = null;  
	  
    private boolean stop;  
  
    private U3dApplication() {  
    }  
  
    public static synchronized U3dApplication getApplication() {  
        if (instance == null) {  
            instance = new U3dApplication();  
        }  
        return instance;  
    }  
      
    public void start() {  
        init();  
        new Thread(new U3dServer(), "U3d Server").start();  
  
        while (!stop) {  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
            }  
        }  
    }  
      
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        Runtime.getRuntime().addShutdownHook(new Thread() {  
            @Override  
            public void run() {  
                getApplication().stopMe();  
            }  
        });  
        getApplication().start();  
    }  
      
    public void stopMe() {  
        System.out.println("系統即將關閉...");  
    }  
      
      
    /** 
     * 初始化系統 
     */  
    private void init() {  
          
    }  
  
}  
完成了,開啓服務器和客戶端,讓我們看看效果如何
看來成功了
下面要嘗試在socket通信中傳遞對象,最基本的就是字節數組,通過將對象序列化和反序列化,即可以達到對象的傳遞,關於c#中字節數組和對象之間的相互轉換,可以學習 這篇博客,它介紹了c#中序列化對象的三種方式。
開始我認爲,在java和c#之間進行socket通信,就是將c#這邊對象序列化爲字節數組,之後傳遞到java服務器,再將字節數組反序列化就行了,最後得到的結果就是:java得到的字節數組反序列化出錯。
原因就是:由於是跨語言的交互,我們即不能用Java特有的序列化方式,也不能用C#特有的序列化方式,必須找一個通用的序列化格式才能交互。所以我又開始嘗試通過json來進行數據傳遞
對於c#的json序列化,我使用了System.Runtime.Serialization.Json命名空間中的DataContractJsonSerializer 這個類,不過想使用這個類還真是複雜,在引用System.Runtime.Serialization.Json之前,你要先添加,System.ServiceModel , System.ServiceModel.Web這兩個引用,然而我在寫using時,卻找不到這個引用,爲什麼呢,是因爲.net的版本不同導致的,對於.net開發,我也是新手,上網搜索後,得出解決辦法,項目 右鍵 引用 選中 .Net 找到 System.ServiceModel 引用,3.5裏有 System.ServiceModel.Web,啊終於ok了,媽的好累~~~~~
        DataContractJsonSerializer dJson = new DataContractJsonSerializer(typeof(Person));
        MemoryStream stream = new MemoryStream();
        dJson.WriteObject(stream, p);
        byte[] dataBytes = new byte[stream.Length];

        stream.Position = 0;
        stream.Read(dataBytes, 0, (int)stream.Length);
        string dataString = Encoding.UTF8.GetString(dataBytes);
以爲解決了問題 ,但是。。。。
unit報錯說,引用不存在??什麼情況?? 頓時累覺不愛。。。。趕緊百度之,缺少引用dll?是因爲: 雖然可以用VisualStudio編寫Unity代碼,但實際上Unity生成遊戲時還要自己再編譯一遍。因此,在VS中的引用設置不能被Unity所使用。我曾經嘗試過把要引用的程序集放在GAC中也不行。正確的做法是把dll放在Asset下,Unity能很好地識別它。
但是我引用的是vs自身的dll啊?,爲什麼不行呢。。算啦,另謀出路,通過引入外來的dll總可以了吧, C#端可以用開源項目JSON.NET,下載後根據自己的.NET版本,選擇相應的Newtonsoft.Json.dll。
這裏我又發現了一個問題,在網上download後,將.net版本爲3.5的文件夾放入unity assets文件下,發現:

版本錯誤,這裏我unity的版本是4.6,難道他不支持.net3.5嗎,我去~~~~什麼情況?
嘗試將版本爲2.0的文件拖入unity後 。。。。沒報錯! 這裏的原因,不懂啊,不管什麼樣終於可以進行下一步了,引用後添加:using Newtonsoft.Json;using Newtonsoft.Json.Converters;即可使用。趕緊新建一個類,測試一下效果
        //新建一個學生信息類
        StudentsInfo p = new StudentsInfo();
        //賦值
        p.StuId = 1001;
        p.StuAge = "20";
        p.StuName = "小王";
        p.StuClass = "1004班";
        //調用函數將對象轉換爲json字符串
        string str = JsonConvert.SerializeObject(p);
        Debug.Log(str);
        //在將字符串轉換成對象,並輸出對象屬性信息
        StudentsInfo p1 = new StudentsInfo();
        p1 = JsonConvert.DeserializeObject<StudentsInfo>(str);
        Debug.Log(p1.StuId+"  "+p1.StuName+"  "+p1.StuAge+"  "+p1.StuClass);
成功了
---------java服務器端---------
Java端可以用開源項目google-gson,下載後是一個jar格式的包,直接在項目中導入這個包,並添加引用:import com.google.gson.Gson;即可使用。
		//測試代碼
		Gson gson = new Gson();
		Person p = new Person(1001,"小明");
		String str = gson.toJson(p);
		System.out.println(str);
		Person p1 = gson.fromJson(str, Person.class);
		System.out.println(p1.getId()+"  "+p1.getName());
要注意的是string和字節數組轉化時的編碼格式要統一,爲utf-8。去除字節數組中的空格可以使用string類中的trim(),下面就是讓他們在socket中走一走,看看能否成功傳遞對象信息呢
學生對象的信息,通過json的方式,在c#客戶端和java服務器端傳遞~~~~~

總結

socket通信是網絡通信的基礎,其中涉及到服務器的創建,監聽客戶端的連接,和客戶端之間的信息傳遞,需要對IO流的一些知識進行認真的學習,也可以嘗試http通信中通過json傳遞信息。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章