सी

2011-11-03 5 views
7

में टीसीपी (SOCK_STREAM) सॉकेट पर एक स्ट्रक्चर पास कर रहा है मेरे पास एक छोटा क्लाइंट सर्वर एप्लिकेशन है जिसमें मैं सी ++ में टीसीपी सॉकेट पर एक संपूर्ण संरचना भेजना चाहता हूं। struct मान लें निम्नलिखित हो:सी

struct something{ 
int a; 
char b[64]; 
float c; 
} 

मैं पाया है कई पदों कह मैं pragma पैक का उपयोग करने के लिए या भेजने और recieveing ​​से पहले डेटा को क्रमानुसार करने की आवश्यकता है।

मेरा सवाल है, क्या यह जस्ट प्रगा पैक या केवल सीरियलज़ेशन का उपयोग करने के लिए पर्याप्त है? या मुझे दोनों का उपयोग करने की ज़रूरत है?

चूंकि सीरियलाज़ेशन प्रोसेसर गहन प्रक्रिया है, इसलिए यह आपके प्रदर्शन में काफी गिरावट आती है, इसलिए बाहरी पुस्तकालय का उपयोग किए बिना संरचना को क्रमबद्ध करने का सबसे अच्छा तरीका क्या है (मुझे नमूना कोड/अलगो पसंद है)?

उत्तर

13

आप portably struct के भेजने के लिए नेटवर्क पर निम्नलिखित की जरूरत है:

  • पैक संरचना। जीसीसी और संगत कंपाइलर्स के लिए, __attribute__((packed)) के साथ ऐसा करें।

  • निश्चित आकार के हस्ताक्षरित पूर्णांक के अलावा किसी अन्य सदस्य का उपयोग न करें, इन आवश्यकताओं को पूरा करने वाले अन्य पैक किए गए ढांचे, या पूर्व में से किसी के सरणी। हस्ताक्षरित पूर्णांक भी ठीक हैं, जब तक कि आपकी मशीन दो पूरक पूरक का उपयोग न करे।

  • यह तय करें कि आपका प्रोटोकॉल पूर्णांक के छोटे-बड़े-एंडियन एन्कोडिंग का उपयोग करेगा या नहीं। उन पूर्णांक को पढ़ने और लिखते समय रूपांतरण करें।

  • इसके अलावा, आकार 1 या अन्य नेस्टेड पैक किए गए ढांचे वाले लोगों को छोड़कर पैक की गई संरचना के सदस्यों के पॉइंटर्स न लें। this answer देखें।

एन्कोडिंग और डिकोडिंग का एक सरल उदाहरण निम्नानुसार है।यह मानता है कि बाइट ऑर्डर रूपांतरण फ़ंक्शन hton8(), ntoh8(), hton32(), और ntoh32() उपलब्ध हैं (पूर्व दो नो-ऑप हैं, लेकिन स्थिरता के लिए)।

#include <stdint.h> 
#include <inttypes.h> 
#include <stdlib.h> 
#include <stdio.h> 

// get byte order conversion functions 
#include "byteorder.h" 

struct packet { 
    uint8_t x; 
    uint32_t y; 
} __attribute__((packed)); 

static void decode_packet (uint8_t *recv_data, size_t recv_len) 
{ 
    // check size 
    if (recv_len < sizeof(struct packet)) { 
     fprintf(stderr, "received too little!"); 
     return; 
    } 

    // make pointer 
    struct packet *recv_packet = (struct packet *)recv_data; 

    // fix byte order 
    uint8_t x = ntoh8(recv_packet->x); 
    uint32_t y = ntoh32(recv_packet->y); 

    printf("Decoded: x=%"PRIu8" y=%"PRIu32"\n", x, y); 
} 

int main (int argc, char *argv[]) 
{ 
    // build packet 
    struct packet p; 
    p.x = hton8(17); 
    p.y = hton32(2924); 

    // send packet over link.... 
    // on the other end, get some data (recv_data, recv_len) to decode: 
    uint8_t *recv_data = (uint8_t *)&p; 
    size_t recv_len = sizeof(p); 

    // now decode 
    decode_packet(recv_data, recv_len); 

    return 0; 
} 

जहां तक ​​बाइट क्रम रूपांतरण कार्यों का संबंध है, आपके सिस्टम के htons()/ntohs() और htonl()/ntohl() 16- और 32-बिट पूर्णांकों, क्रमशः के लिए इस्तेमाल किया जा सकता, करने के लिए/परिवर्तित करने के लिए बड़े endian से। हालांकि, मुझे 64-बिट पूर्णांक के लिए किसी भी मानक फ़ंक्शन से अवगत नहीं है, या छोटे एंडियन से/में कनवर्ट करने के लिए। आप my byte order conversion functions का उपयोग कर सकते हैं; यदि आप ऐसा करते हैं, तो आपको BADVPN_LITTLE_ENDIAN या BADVPN_BIG_ENDIAN परिभाषित करके अपनी मशीन के बाइट ऑर्डर को बताना होगा।

जहां तक ​​हस्ताक्षरित पूर्णांक का संबंध है, रूपांतरण कार्यों को उसी तरह से कार्यान्वित किया जा सकता है जैसा मैंने लिखा और लिंक किया है (सीधे बाइट्स को स्वैप करना); हस्ताक्षरित हस्ताक्षर करने के लिए बस हस्ताक्षर करें।

अद्यतन: यदि आप एक कुशल द्विआधारी प्रोटोकॉल चाहते हैं, लेकिन बाइट्स के साथ नगण्य पसंद नहीं है, आप की तरह Protocol Buffers (C implementation) कुछ कोशिश कर सकते हैं। यह आपको अलग-अलग फ़ाइलों में अपने संदेशों के प्रारूप का वर्णन करने की अनुमति देता है, और आपके द्वारा निर्दिष्ट प्रारूप के संदेशों को एन्कोड और डीकोड करने के लिए उपयोग किए जाने वाले स्रोत कोड उत्पन्न करता है। मैंने कुछ भी खुद को लागू किया, लेकिन बहुत सरल बनाया; my BProto generator और some examples देखें (उपयोग उदाहरण के लिए .bproto फ़ाइलें, और addr.h देखें)।

+1

मैं इस विधि को आज़माउंगा, मैं सिर्फ यह पूछना चाहता हूं कि क्या मैं सिर्फ स्प्रिंटफ का उपयोग करता हूं और संरचना के तत्वों को अलग करने और सॉकेट को भेजने के लिए एक डेलीमीटर का उपयोग करके स्ट्रिंग में सभी डेटा लिखता हूं और फिर प्रत्येक तत्व को निकालने के लिए स्ट्रोक का उपयोग करता हूं दूसरी ओर ? क्या यह एक व्यवहार्य समाधान भी होगा? – user434885

+0

हां, sprintf काम करेगा, लेकिन * केवल * पूर्णांक के लिए; यदि आप इस विधि का उपयोग करते हुए एक स्ट्रिंग (यानी कच्चे बाइट्स सरणी) भेजना चाहते हैं, तो आपको उन्हें बाइट्स की सरणी के रूप में देखना होगा और प्रत्येक बाइट को एक पूर्णांक में परिवर्तित करना होगा, बीच में रिक्त स्थान डालना होगा। उदाहरण के लिए, "एबीसी" को "9 7 98 99" के रूप में भेजा जाएगा। यह बेहतर हो सकता है क्योंकि डीबगिंग के दौरान विश्लेषण करना किसी भी तरह से आसान है, लेकिन यह एन्कोड/डीकोड करने के लिए बेकार है, खासकर यदि आप डिकोडिंग करते समय पूर्ण त्रुटि जांचना चाहते हैं। –

+0

आपके दूसरे बुलेट बिंदु के पीछे प्रेरणा क्या है - केवल हस्ताक्षरित पूर्णांक का उपयोग करना। अक्षरों, बाइट्स या तार भेजने के लिए संरचना (या चार सरणी) में वर्णों का उपयोग क्यों नहीं किया जा सकता था? – aaronsnoswell

1

आप संरचना आप भेजना चाहते हैं और एक सरणी के साथ एक union इस्तेमाल कर सकते हैं:

union SendSomething { 
    char arr[sizeof(struct something)]; 
    struct something smth; 
}; 

इस तरह से आप भेजने के लिए और सिर्फ आगमन प्राप्त कर सकते हैं। बेशक, आपको अंतहीन मुद्दों के बारे में ख्याल रखना होगा और sizeof(struct something) मशीनों में भिन्न हो सकता है (लेकिन आप #pragma pack के साथ आसानी से इसे दूर कर सकते हैं)।

2

इससे पहले कि आप किसी टीसीपी कनेक्शन पर कोई डेटा भेज लें, प्रोटोकॉल विनिर्देशन करें। तकनीकी शब्दावली से भरा एक बहु पृष्ठ दस्तावेज होना आवश्यक नहीं है। लेकिन यह निर्दिष्ट करना होगा कि बाइट स्तर पर सभी संदेशों को कब और कब निर्दिष्ट करना होगा। यह निर्दिष्ट करना चाहिए कि संदेश के सिरों की स्थापना कैसे की जाती है, चाहे कोई टाइमआउट हो और जो उन्हें लगाए, और इसी तरह।

विनिर्देश के बिना, उन प्रश्नों को पूछना आसान है जो उत्तर देने में असंभव हैं। अगर कुछ गलत हो जाता है, तो कौन सा अंत गलती है? एक विनिर्देश के साथ, अंत जो विनिर्देश का पालन नहीं करता है वह गलती है। (और यदि दोनों सिरों विनिर्देश का पालन करते हैं और यह अभी भी काम नहीं करता है, तो विनिर्देश गलती पर है।)

एक बार आपके पास विनिर्देश होने के बाद, प्रश्नों का उत्तर देना कितना आसान होता है कि एक अंत या दूसरे को कैसे डिजाइन किया जाना चाहिए।

मैं दृढ़ता से को अपने हार्डवेयर के विनिर्देशों के आसपास नेटवर्क प्रोटोकॉल को डिजाइन करने की सलाह देता हूं। कम से कम, एक सिद्ध प्रदर्शन मुद्दे के बिना नहीं।

1

Message Pack जैसे अच्छे और तेज़ सीरियलाइजेशन लाइब्रेरी होने पर आप ऐसा क्यों करेंगे, जो आपके लिए कड़ी मेहनत करते हैं, और बोनस के रूप में वे आपको आपके सॉकेट प्रोटोकॉल की क्रॉस-भाषा संगतता प्रदान करते हैं?

ऐसा करने के लिए संदेश पैक या कुछ अन्य धारावाहिक पुस्तकालय का उपयोग करें।

+0

मुझे किसी भी बाहरी पुस्तकालयों का उपयोग करने की अनुमति नहीं है। :/ – user434885

0

प्रज्ञा पैक का उपयोग किसी अन्य छोर पर आपके बाइनरी संगतता के लिए किया जाता है। क्योंकि सर्वर या क्लाइंट जिसे आप स्ट्रक्चर भेजते हैं उसे किसी अन्य भाषा पर लिखा जा सकता है या अन्य सी कंपाइलर या अन्य सी कंपाइलर विकल्पों के साथ बनाया जा सकता है।

जैसा कि मैं समझता हूं, सीरियलाइजेशन, आपके द्वारा संरचना से बाइट्स स्ट्रीम कर रहा है। जब आप सॉकेट में संरचना लिखते हैं तो आप धारावाहिक बनाते हैं।

2

यह इस बात पर निर्भर करता है कि आप यह सुनिश्चित कर सकते हैं कि कनेक्शन के किसी भी अंत में आपके सिस्टम सजातीय हैं या नहीं। यदि आप निश्चित हैं, हर समय (जो हम में से अधिकांश नहीं हो सकते हैं) के लिए, तो आप कुछ शॉर्टकट ले सकते हैं - लेकिन आपको अवगत होना चाहिए कि वे शॉर्टकट हैं।

struct something some; 
... 
if ((nbytes = write(sockfd, &some, sizeof(some)) != sizeof(some)) 
    ...short write or erroneous write... 

और समान read()

हालांकि, यदि कोई मौका है कि सिस्टम अलग हो सकते हैं, तो आपको यह निर्धारित करने की आवश्यकता है कि डेटा औपचारिक रूप से कैसे स्थानांतरित किया जाएगा। आप डेटा को अच्छी तरह से रैखिकरण (क्रमबद्ध) कर सकते हैं - संभावित रूप से एएसएन .1 जैसी किसी चीज़ के साथ या संभवतः एक प्रारूप के साथ अधिक आसानी से जिसे आसानी से पढ़ा जा सकता है। इसके लिए, पाठ अक्सर फायदेमंद होता है - जब आप देख सकते हैं कि क्या गलत हो रहा है तो डीबग करना आसान है। विफल होने पर, आपको बाइट ऑर्डर को परिभाषित करने की आवश्यकता है जिसमें int स्थानांतरित हो जाता है और यह सुनिश्चित कर लें कि स्थानांतरण उस क्रम का पालन करता है, और स्ट्रिंग को संभवतः बाइट गिनती मिलती है जिसके बाद डेटा की उचित मात्रा होती है (इस पर विचार करें कि टर्मिनल नल को स्थानांतरित करना है या नहीं) नहीं), और फिर फ्लोट के कुछ प्रतिनिधित्व। यह अधिक स्पष्ट रूप से है। फ़ॉर्मेटिंग को संभालने के लिए क्रमबद्धता और deserialization कार्यों को लिखना मुश्किल नहीं है। मुश्किल हिस्सा प्रोटोकॉल को डिजाइन (निर्णय ले रहा है) है।

+0

यह कुछ मामलों में काम करेगा, लेकिन अच्छी संभावना है कि मेरा सर्वर और ग्राहक 32 और 64 बिट मशीनें होंगी, इसलिए आकार (संरचना) फ़ंक्शन किसी भी आकार पर अलग-अलग मान वापस कर देगा क्योंकि int के आकार 4 से बढ़ेगा 8 बाइट्स तक बाइट्स। – user434885

1

आमतौर पर, क्रमबद्धता उदाहरण पर कई लाभ लाती है उदा। तार पर संरचना के बिट्स भेजना (उदाहरण के लिए fwrite)।

  1. यह प्रत्येक गैर-समग्र परमाणु डेटा (उदा। Int) के लिए व्यक्तिगत रूप से होता है।
  2. यह तार
  3. पर भेजे गए सीरियल डेटा प्रारूप को परिभाषित करता है, इसलिए यह विषम वास्तुकला से संबंधित है: मशीनों को भेजने और प्राप्त करने के लिए अलग-अलग शब्द की लंबाई और अंतहीनता हो सकती है।
  4. जब टाइप थोड़ा सा बदलता है तो यह कम भंगुर हो सकता है। इसलिए यदि एक मशीन में आपके कोड का पुराना संस्करण चल रहा है, तो यह एक मशीन के साथ हालिया संस्करण के साथ बात करने में सक्षम हो सकता है, उदा। एक एक char b[80];char b[64]; के बजाय
  5. यह और अधिक जटिल डेटा संरचनाओं -variable आकार वैक्टर, या यहाँ तक कि एक तार्किक ढंग से हैश tables- के साथ सौदा कर सकते हैं (हैश तालिका के लिए, संघ संचारित, ..)
  6. होने

अक्सर, क्रमबद्धता दिनचर्या उत्पन्न होती है। यहां तक ​​कि 20 साल पहले, आरपीसीएक्सडीआर पहले से ही उस उद्देश्य के लिए अस्तित्व में था, और एक्सडीआर क्रमबद्धीकरण प्राइमेटिव अभी भी कई libc में हैं।

0

यदि आपको पोर्टेबिलिटी की आवश्यकता है तो आपको अंतहीनता और संरचना पैडिंग के कारण व्यक्तिगत रूप से प्रत्येक सदस्य को क्रमबद्ध करना होगा।

binn *obj; 

    // create a new object 
    obj = binn_object(); 

    // add values to it 
    binn_object_set_int32(obj, "id", 123); 
    binn_object_set_str(obj, "name", "Samsung Galaxy Charger"); 
    binn_object_set_double(obj, "price", 12.50); 
    binn_object_set_blob(obj, "picture", picptr, piclen); 

    // send over the network 
    send(sock, binn_ptr(obj), binn_size(obj)); 

    // release the buffer 
    binn_free(obj); 

यह सिर्फ 2 फ़ाइलें (binn.c और binn.h) है, इसलिए यह बजाय परियोजना के साथ संकलित किया जा सकता किसी साझा लाइब्रेरी के रूप में इस्तेमाल:

यहाँ एक उदाहरण Binn का उपयोग कर रहा है।

शायद आपको सॉकेट स्ट्रीम में संदेश फ़्रेमिंग (जिसे लम्बाई-उपसर्ग फ़्रेमिंग भी कहा जाता है) का उपयोग करना चाहिए।