2011-10-15 31 views
5

Gần đây tôi đã hỏi một câu hỏi ở đây về Stack Overflow về cách truyền dữ liệu của tôi từ một số nguyên 16 bit, theo sau là một số lượng không xác định của bộ nhớ * * vào một std: : vector của chars unsigned vì lợi ích của việc sử dụng một thư viện socket gọi là nETLINK trong đó sử dụng một hàm mà chữ ký trông như thế này để gửi dữ liệu thô:Trong C++, về các kiểu chuyển dịch bit và đúc

void rawSend(const vector<unsigned char>* data); 

(để tham khảo, đây là câu hỏi rằng: Casting an unsigned int + a string to an unsigned char vector)

Câu hỏi đã được trả lời thành công và tôi rất biết ơn những người đã trả lời. Mike Desimone đáp lại bằng một ví dụ về một hàm send_message() có thể chuyển đổi dữ liệu sang một định dạng mà NETLINK chấp nhận (một std :: vector), trông như thế này:

void send_message(NLSocket* socket, uint16_t opcode, const void* rawData, size_t rawDataSize) 
{ 
    vector<unsigned char> buffer; 
    buffer.reserve(sizeof(uint16_t) + rawDataSize); 
    buffer.push_back(opcode >> 8); 
    buffer.push_back(opcode & 0xFF); 
    const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData)); 
    buffer.insert(buffer.end(), base, base + rawDataSize); 
    socket->rawSend(&buffer); 
} 

này có vẻ là chính xác những gì tôi cần , và vì vậy tôi đặt ra để viết một hàm receive_message() đi kèm ...

... nhưng tôi xấu hổ khi nói rằng tôi không hoàn toàn hiểu tất cả các thay đổi bit và không có điều gì, vì vậy tôi đã chạy vào một bức tường ở đây. Trong tất cả các mã tôi từng viết trong gần một thập kỷ qua, hầu hết mã của tôi đã ở các ngôn ngữ cấp cao hơn và phần còn lại của mã của tôi chưa bao giờ thực sự được gọi cho các hoạt động bộ nhớ mức thấp hơn.

Quay trở lại chủ đề của văn bản là một chức năng receive_message(), điểm khởi đầu của tôi, như bạn có thể tưởng tượng, là NETLINK của rawRead() chức năng, có chữ ký trông như thế này:

vector<unsigned char>* rawRead(unsigned bufferSize = DEFAULT_BUFFER_SIZE, string* hostFrom = NULL); 

Dường như mã của tôi sẽ bắt đầu một cái gì đó như thế này:

void receive_message(NLSocket* socket, uint16_t* opcode, const void** rawData) 
{ 
    std::vector<unsigned char, std::allocator<unsigned char>>* buffer = socket->rawRead(); 
    std::allocator<unsigned char> allocator = buffer->get_allocator(); // do I even need this allocator? I saw that one is returned as part of the above object, but... 
    // ... 
} 

Sau đó cuộc gọi đầu tiên để rawRead(), nó xuất hiện tôi sẽ cần phải lặp qua các vector, lấy dữ liệu từ nó và đẩy lùi các hoạt động bitshifting, và sau đó trả lại dữ liệu vào * rawData và * opcode. Một lần nữa, tôi không quen thuộc với bithifting (tôi đã làm một số googling để hiểu cú pháp, nhưng tôi không hiểu lý do tại sao trên send_message() mã đòi hỏi phải thay đổi ở tất cả), vì vậy tôi là một mất mát cho tôi bước tiếp theo ở đây.

Ai đó có thể giúp tôi hiểu cách viết hàm receive_message() đi kèm này không? Như một phần thưởng, nếu ai đó có thể giúp giải thích mã gốc để tôi biết cho tương lai nó hoạt động như thế nào (đặc biệt, cách dịch chuyển hoạt động trong trường hợp này và tại sao nó cần thiết), điều đó sẽ giúp tôi hiểu sâu hơn về tương lai.

Cảm ơn trước!

Trả lời

3

Thư viện ’ chữ ký hàm & hellip;

void rawSend(const vector<unsigned char>* data); 

buộc bạn phải xây dựng một dữ liệu của bạn, điều này có nghĩa là nó tạo ra sự thiếu hiệu quả không cần thiết. Không có lợi thế khi yêu cầu mã máy khách để tạo một std::vector. Bất cứ ai thiết kế mà không biết những gì họ ’ làm lại, và nó sẽ là khôn ngoan để không sử dụng phần mềm của họ.

Chữ ký chức năng thư viện & hellip;

vector<unsigned char>* rawRead(unsigned bufferSize = DEFAULT_BUFFER_SIZE, string* hostFrom = NULL); 

là tồi tệ hơn: nó không chỉ không cần thiết đòi hỏi bạn phải xây dựng một std::string nếu bạn muốn chỉ định một “ hostFrom ” (bất cứ điều gì đó thực sự có nghĩa), nhưng nó không cần thiết đòi hỏi bạn phải deallocate kết quả vector. Ít nhất là nếu có bất kỳ ý nghĩa nào để thực hiện kiểu kết quả. Mà, tất nhiên, có thể không có.

Bạn không nên sử dụng thư viện có chữ ký chức năng đáng kinh tởm. Có lẽ bất kỳ thư viện được chọn ngẫu nhiên nào sẽ tốt hơn nhiều. Tức là, dễ sử dụng hơn nhiều.


Cách sử dụng mã hiện tại & hellip;

void send_message(NLSocket* socket, uint16_t opcode, const void* rawData, size_t rawDataSize) 
{ 
    vector<unsigned char> buffer; 
    buffer.reserve(sizeof(uint16_t) + rawDataSize); 
    buffer.push_back(opcode >> 8); 
    buffer.push_back(opcode & 0xFF); 
    const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData)); 
    buffer.insert(buffer.end(), base, base + rawDataSize); 
    socket->rawSend(&buffer); 
} 

công trình:

  • Cuộc gọi reserve là một trường hợp của tối ưu hóa quá sớm. Nó cố gắng để làm cho các vector làm chỉ là một phân bổ đệm duy nhất (thực hiện tại thời điểm này) thay vì có thể hai hoặc nhiều hơn. Một cách chữa trị tốt hơn cho sự thiếu hiệu quả của việc xây dựng một vector, là sử dụng một thư viện sane hơn.

  • buffer.push_back(opcode >> 8) đặt 8 bit cao (giả định) 16 bit số opcode, khi bắt đầu vectơ. Đặt phần cao, phần quan trọng nhất, đầu tiên, được gọi là lớn endian định dạng. Mã đọc của bạn ở đầu bên kia phải giả định định dạng cuối cùng lớn.Và tương tự như vậy, nếu mã gửi này đã sử dụng định dạng nhỏ endian thì mã đọc sẽ phải giả định định dạng cuối cùng nhỏ. Vì vậy, đây chỉ là một quyết định định dạng dữ liệu, nhưng đưa ra quyết định mã ở cả hai đầu phải tuân thủ nó.

  • Các cuộc gọi buffer.push_back(opcode & 0xFF) đặt 8 bit thấp của opcode sau các bit cao, như chính xác cho người lớn cuối.

  • Khai báo const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData)) chỉ cần đặt tên cho con trỏ được nhập phù hợp vào dữ liệu của bạn, gọi số base. Loại const unsigned char* phù hợp vì nó cho phép mức byte số học địa chỉ. Loại đối số chính thức ban đầu const void* không chấp nhận số học địa chỉ.

  • buffer.insert(buffer.end(), base, base + rawDataSize) thêm dữ liệu vào vectơ. Biểu thức base + rawDataSize là số học địa chỉ mà khai báo trước đó được bật.

  • socket->rawSend(&buffer) là cuộc gọi cuối cùng xuống phương thức SillyLibrary ’ s rawSend.


Làm thế nào để quấn một cuộc gọi đến các chức năng SillyLibrary rawRead.

Đầu tiên, xác định một tên cho kiểu dữ liệu byte (luôn luôn là một ý tưởng tốt để đặt tên cho điều):

typedef unsigned char Byte; 
typedef ptrdiff_t Size; 

Tham khảo tài liệu về cách deallocate/hủy/xóa (nếu cần thiết) kết quả chức năng SillyLibrary:

void deleteSillyLibVector(vector<Byte> const* p) 
{ 
    // perhaps just "delete p", but it depends on the SillyLibrary 
} 

Bây giờ, đối với hoạt động gửi có std::vector liên quan chỉ là một nỗi đau. Đối với hoạt động nhận, nó ’ s đối diện. Tạo một mảng động và truyền nó một cách an toàn và hiệu quả như là một kết quả hàm, chỉ là một thứ mà std::vector được thiết kế cho.

Tuy nhiên, thao tác gửi chỉ là một cuộc gọi duy nhất.

Đối với thao tác nhận, có thể, tùy thuộc vào thiết kế của SillyLibrary, bạn cần lặp lại để thực hiện số cuộc gọi nhận cho đến khi bạn nhận được tất cả dữ liệu. Bạn không cung cấp đủ thông tin để thực hiện việc này. Nhưng đoạn code dưới đây cho thấy một lớp dưới cùng đọc rằng mã vòng lặp của bạn có thể gọi điện thoại, tích lũy dữ liệu trong một vector:

Size receive_append(NLSocket& socket, vector<Byte>& data) 
{ 
    vector<Byte> const* const result = socket.raw_read(); 

    if(result == 0) 
    { 
     return 0; 
    } 

    struct ScopeGuard 
    { 
     vector<Byte>* pDoomed; 
     explicit ScopeGuard(vector<Byte>* p): pDoomed(p) {} 
     ~ScopeGuard() { deleteSillyLibVector(pDoomed); } 
    }; 

    Size const nBytesRead = result->size(); 
    ScopeGuard cleanup(result); 

    data.insert(data.end(), result->begin(), result->end()); 
    return nBytesRead; 
} 

Lưu ý việc sử dụng một destructor làm dọn dẹp, mà làm cho ngoại lệ hơn này an toàn. Trong trường hợp cụ thể này, ngoại lệ duy nhất có thể là std::bad_alloc, dù sao cũng khá là chết người. Nhưng kỹ thuật chung của việc sử dụng một destructor để làm sạch, cho sự an toàn ngoại lệ, rất đáng để biết và sử dụng như một vấn đề tất nhiên (thường là không phải định nghĩa bất kỳ lớp mới nào, nhưng khi giao dịch với một SillyLibrary có thể phải làm điều đó).

Cuối cùng, khi mã vòng lặp của bạn đã xác định rằng tất cả dữ liệu nằm trong tầm tay, nó có thể giải thích dữ liệu trong số vector của bạn. Tôi để nó như một bài tập, mặc dù đó là những gì bạn yêu cầu.Và đó là bởi vì tôi đã viết gần giống như toàn bộ một bài báo ở đây.

Tuyên bố từ chối trách nhiệm: mã off-the-cuff.

Chúc mừng & hth.,

+0

Cảm ơn thông tin; Tôi đã học được khá nhiều. Tôi cũng sẽ xem xét một thư viện socket mới như bạn đã đề nghị .. Tôi đồng ý rằng thư viện này trông rất kém hiệu quả. – Josh1billion

0

Để đặt bit-fiddling vào các thuật ngữ không bit, f2ling, opcode >> 8 tương đương với opcode/256opcode & 0xFF tương đương với opcode - ((opcode/256) * 256). Xem ra cho làm tròn/cắt ngắn.

Hãy suy nghĩ của opcode khi được tạo thành từ hai đoạn, ophioplo, mỗi giá trị có giá trị 0,2255. opcode == (ophi * 256) + oplo.

Một số manh mối thêm ...

0xFF == 255 == binary 11111111 == 2^8 - 1 
0x100 == 256 == binary 100000000 == 2^8 

       opcode 
     /   \ 
Binary : 1010101010101010 
     \  /\ /
      ophi oplo 

Lý do cho điều này về cơ bản là một endian-sửa chữa để viết một giá trị mười sáu chút để một dòng dữ liệu byte-khôn ngoan.Luồng mạng có quy tắc riêng, trong đó "đầu lớn" của giá trị phải được gửi trước tiên, độc lập với cách xử lý theo mặc định trên bất kỳ nền tảng cụ thể nào. Send_message đó về cơ bản là giải mã giá trị 16 bit để gửi nó. Bạn sẽ cần phải đọc hai khối trong, sau đó tái tạo lại giá trị bit mười sáu.

Cho dù bạn mã xây dựng lại là opcode = (ophi * 256) + oplo; hoặc là opcode == (ophi << 8) | oplo; chủ yếu là vấn đề về hương vị - người tối ưu hóa sẽ hiểu sự tương đương và tìm ra điều gì hiệu quả nhất.

Ngoài ra, không, tôi không nghĩ bạn không cần người cấp phát. Tôi thậm chí không chắc chắn rằng việc sử dụng vector là một ý tưởng hay, vì bạn đang sử dụng tham số const void** rawData, nhưng có thể là và bạn nên thực hiện reserve trước khi đọc. Sau đó, thêm các khối liên quan (hai byte để xây dựng lại opcode, cộng với nội dung mảng).

Vấn đề lớn tôi thấy - làm thế nào để bạn biết kích thước của dữ liệu thô mà bạn sẽ đọc? Nó không xuất hiện hoặc là một tham số cho receive_message, cũng không được cung cấp bởi chính luồng dữ liệu.

Các vấn đề liên quan