1. 程式人生 > >Socket通訊——C++伺服器端和Java客戶端

Socket通訊——C++伺服器端和Java客戶端

//更新 這件事可以用現有的序列化框架來做 比如 protobuf

一句話來說就是,C++和Java 通過socket進行通訊、資料傳輸,通過傳送“位元組流”即可。

位元組對於C++和java來說是通用的,但是傳輸的過程有許多問題需要注意,我為了弄清楚這個過程,查了一些資料,做了一些整理。

 不瞭解C++ socket程式設計,可以看這篇部落格:

Linux 下:socket通訊(Linux下,C/C++語言):http://blog.csdn.net/giantpoplar/article/details/47657303

Windows下:winsock:http://blog.csdn.net/giantpoplar/article/details/47657317

不瞭解Java socket程式設計,可以看這篇部落格:

Java socket通訊:http://blog.csdn.net/giantpoplar/article/details/47657325

不瞭解位元組陣列和基本資料型別的轉換,可以看這篇部落格

byte[]和 整形、浮點型資料的轉換-java程式碼:http://blog.csdn.net/giantpoplar/article/details/47657333

在伺服器向客戶端發資料的時候,可能採取以下兩種方式

伺服器端直接傳送struct

如果在程式設計時在C++端直接傳送一個結構體,需要把這個結構體的指標強制轉換為char*,通過一個個的位元組傳輸

Msg message;
retVal = send(Client, (char*)&message,sizeof(Msg), 0);

如果採用這種方式,在客戶端收到資料後進行解釋,需要對C++物件的記憶體佈局有一個簡單的瞭解,同樣,如果你不瞭解,可以看這篇:簡單C++物件的記憶體佈局:http://blog.csdn.net/giantpoplar/article/details/47658679

另外如果你的伺服器端用了java,以ObjectOutputStream的方式寫出一個物件,需要對java物件的記憶體佈局有所瞭解,同樣,如果你不瞭解,可以看這篇: 簡單Java物件的記憶體佈局:http://blog.csdn.net/giantpoplar/article/details/47657377

 伺服器端把struct的每一個成員單獨傳送

還有一種方式是不把整個結構體整個發過去,而是一個一個變數發過去,這時候伺服器和客戶端兩邊要知道這些變數的相互順序,一個一個一次接受即可,這就涉及到位元組陣列和基本資料型別之間的轉換。

 這個時候要注意伺服器和客戶端整數的表示方式是否一樣,是不是都是大端法或者都是小端法,浮點數表示是否都符合IEEE 754規範。否則兩邊要協調好,或者浮點數直接傳字串,雖然會帶來效率損失,但是可以統一起來。

這兩種問題都有缺點,首先就是大端表示,小端表示,不同的處理器採用的表示方式可能不同,可能當前執行正常,但可能帶來的潛在的錯誤。

第二就是如果struct裡面包含指標,它所指向的資料並不在struct的例項裡面,可能會和使用者的想法相違背,這種情況下使用“序列化”可能是更好的選擇,這裡找到一個簡單的介紹http://www.infoq.com/cn/news/2014/06/flatbuffers。

我遇到的一些問題

伺服器端在傳送資料時一次傳送的大小不要太大,比如我在傳送一個538位元組的Msg時,發生了傳輸異常的問題,我在本機測試正常,不再同一臺機器上時就會出問題。

要注意:傳送端和接收端接受的資料大小要相同,比如一次發128位元組,接受也要一次接受128位元組,否則容易造成丟包,粘包之類的問題,資料傳輸異常;

一次發的不要太大,太大也容易出問題;

要想更可靠,自己可以再定義一個協議,每次發資料時包含進去包的長度,校驗碼等資訊

//2016年1月5日補

學習了計算機網路之後,知道了問題的原因,先佔個坑

http://stackoverflow.com/questions/4835893/tcp-socket-question

//2016年1月5日補

下面附上我最近在寫的一個程式,基本功能有了,但還不是很完善

因為我的伺服器和客戶端肯定都是小端模式存貯整數,所以就暫時沒考慮傳輸時的位元組順序的問題

寫一個Message類是為了將來把一些方法放進去用著比較方便。

Java客戶端

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

public class JavaClient{
	public static void main(String[] args) throws UnknownHostException,
			IOException, ClassNotFoundException {
		//client
		Socket s = new Socket("127.0.0.1", 8899);
		InputStream is = s.getInputStream();
		byte msg[] = new byte[536];
		
		while (is.read(msg, 0, 536) > -1) {
			Message m = new Message(msg);
			if(m.guesture!=0)System.out.println(m.guesture);
		}
}
}



public class Message {
	float left_hand[] = new float[66];//左手關節資料[X0 Y0 Z0 X1 Y1 Z1...X21 Y21 Z21]
	float right_hand[] = new float[66];//右手關節資料
	short left;//是否檢測到左手  1 為檢測到, 0為未檢測到
	short right;//是否檢測到右手
	short guesture;//手勢
	short alert_type;//警告型別

	public Message(byte[] msg){//從位元組陣列構造一個message物件
		for(int i=0 ; i<66 ; i++){
			left_hand[i] = byte2float(msg, 4* i);
		}
		for(int i=0 ; i<66 ; i++){
			right_hand[i] = byte2float(msg,264 + 4 * i);
		}
		left 		 =  getShort(msg, 528);
		right		 =  getShort(msg, 530);
		guesture	 =  getShort(msg, 532);
		alert_type   =  getShort(msg, 534);
	}
//陣列順序按照“小端順序”
	private float byte2float(byte[] b, int index) {//4個位元組轉float 
	    int l;                                             
	    l = b[index + 0];                                  
	    l &= 0xff;                                         
	    l |= ((long) b[index + 1] << 8);                   
	    l &= 0xffff;                                       
	    l |= ((long) b[index + 2] << 16);                  
	    l &= 0xffffff;                                     
	    l |= ((long) b[index + 3] << 24);                  
	    return Float.intBitsToFloat(l);                    
	}  
	private short getShort(byte[] bytes,int index){//兩個位元組轉short  
        return (short) ((0xff & bytes[index+0]) | (0xff00 & (bytes[index+1] << 8)));  
    }  
	
}

C++服務端
#include<Windows.h>
#include<iostream>
#include<string.h>

#pragma comment(lib, "ws2_32.lib")

#ifndef MSG_
#define MSG_
struct Msg
{
	pxcF32 left_hand[66];
	pxcF32 right_hand[66];
	pxcI16 left;
	pxcI16 right;
	pxcI16 guesture;
	pxcI16 alert_type;
};//訊息結構
#endif

Int main(){
WSADATA            wsad;            //WSADATA變數
	SOCKET            Server;        //伺服器套接字
	SOCKET            Client;        //客戶端套接字
	SOCKADDR_IN        addrServ;        //伺服器地址
	int                retVal;        //返回值

	//初始化套接字動態庫
	if (WSAStartup(MAKEWORD(2, 2), &wsad) != 0)
	{
		std::printf("初始化套接字動態庫失敗!\n");
		return 1;
	}

	//建立套接字
	Server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == Server)
	{
		std::printf("建立套接字失敗!\n");
		WSACleanup();//釋放套接字資源;
		return  -1;
	}

	//伺服器套接字地址 
	addrServ.sin_family = AF_INET;
	addrServ.sin_port = htons(8899);
	addrServ.sin_addr.s_addr = INADDR_ANY;
	//繫結套接字
	retVal = bind(Server, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));
	if (SOCKET_ERROR == retVal)
	{
		std::printf("繫結套接字失敗!\n");
		closesocket(Server);    //關閉套接字
		WSACleanup();            //釋放套接字資源;
		return -1;
	}

	//開始監聽 
	retVal = listen(Server, 1);
	if (SOCKET_ERROR == retVal)
	{
		std::printf("監聽失敗!\n");
		closesocket(Server);    //關閉套接字
		WSACleanup();            //釋放套接字資源;
		return -1;
	}
	for (;;){
		//接受客戶端請求
		sockaddr_in addrClient;
		int addrClientlen = sizeof(addrClient);
		Client = accept(Server, (sockaddr FAR*)&addrClient, &addrClientlen);
		std::printf("accept a socket\n");
		if (INVALID_SOCKET == Client)
		{
			std::printf("接受客戶端請求失敗!\n");
			continue;
		//	closesocket(Server);    //關閉套接字
		//	WSACleanup();            //釋放套接字資源;
		//	return -1;
		}
		//傳送客戶端資料
		while (true){

			WaitForSingleObject(full_sem, INFINITE);//down操作
			//WaitForSingleObject(mutex, INFINITE);//多個消費者需要加互斥訊號量
			Msg message = MsgQueue[head];
			head = (head + 1) % QUEUE_LENGTH;
			//ReleaseSemaphore(mutex, 1, NULL);//up操作
			ReleaseSemaphore(empty_sem, 1, NULL);//up操作
			
			retVal = send(Client, (char*)&message, sizeof(Msg), 0);
			if (SOCKET_ERROR == retVal)
			{
				std::printf("接收客戶端請求失敗!\n");
				//closesocket(Server);    //關閉套接字
				closesocket(Client);    //關閉套接字
				break;
				//return -1;
			}
			Sleep(100);
		}
		//std::printf("%s\n", buf);    //輸出來自Client的字串    
		//退出
		//closesocket(Server);    //關閉套接字
		closesocket(Client);    //關閉套接字
	}
	closesocket(Server);
	WSACleanup();            //釋放套接字資源;
	return 0;
}


說明:

本文由giantpoplar發表於CSDN

文章地址 http://blog.csdn.net/giantpoplar/article/details/47658929

轉載請保留本說明