前言
在上一篇博客中,我們通過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);
以爲解決了問題 ,但是。。。。但是我引用的是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傳遞信息。