C/C++에서 TCP 소켓을 읽는 올바른 방법은 무엇입니까?
내 코드는 다음과 같습니다.
// Not all headers are relevant to the code snippet.
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
char *buffer;
stringstream readStream;
bool readData = true;
while (readData)
{
cout << "Receiving chunk... ";
// Read a bit at a time, eventually "end" string will be received.
bzero(buffer, BUFFER_SIZE);
int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE);
if (readResult < 0)
{
THROW_VIMRID_EX("Could not read from socket.");
}
// Concatenate the received data to the existing data.
readStream << buffer;
// Continue reading while end is not found.
readData = readStream.str().find("end;") == string::npos;
cout << "Done (length: " << readStream.str().length() << ")" << endl;
}
보시다시피 약간의 C와 C++입니다.BUFFER_SIZE는 256입니다. 그냥 크기를 늘릴까요?만약 그렇다면, 무엇을 해야 합니까?그게 중요한가요?
저는 어떤 이유로든 "끝"이 수신되지 않는다면, 이것은 끝없는 순환이 될 것이라는 것을 알고 있습니다. 그것은 나쁜 일입니다. 그러니 만약 당신이 더 나은 방법을 제안할 수 있다면, 또한 그렇게 해주십시오.
전체 응용 프로그램을 모르면 문제에 접근하는 가장 좋은 방법이 무엇인지 말하기 어렵지만 일반적인 기술은 메시지의 나머지 길이를 나타내는 고정 길이 필드로 시작하는 헤더를 사용하는 것입니다.
헤더가 메시지의 나머지 길이를 나타내는 4바이트 정수로만 구성되어 있다고 가정합니다.그런 다음 간단하게 다음을 수행합니다.
// This assumes buffer is at least x bytes long,
// and that the socket is blocking.
void ReadXBytes(int socket, unsigned int x, void* buffer)
{
int bytesRead = 0;
int result;
while (bytesRead < x)
{
result = read(socket, buffer + bytesRead, x - bytesRead);
if (result < 1 )
{
// Throw your error.
}
bytesRead += result;
}
}
그리고 코드의 후반부에서.
unsigned int length = 0;
char* buffer = 0;
// we assume that sizeof(length) will return 4 here.
ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length));
buffer = new char[length];
ReadXBytes(socketFileDescriptor, length, (void*)buffer);
// Then process the data as needed.
delete [] buffer;
여기에는 몇 가지 가정이 있습니다.
- ints는 송신자와 수신자의 크기가 같습니다.
- Endianess는 송신자와 수신자 모두에서 동일합니다.
- 양쪽에서 프로토콜을 제어할 수 있습니다.
- 메시지를 보낼 때 앞의 길이를 계산할 수 있습니다.
네트워크를 통해 전송하는 정수의 크기를 명시적으로 알고자 하는 것이 일반적이므로 헤더 파일에서 정의하고 다음과 같이 명시적으로 사용합니다.
// These typedefs will vary across different platforms
// such as linux, win32, OS/X etc, but the idea
// is that a Int8 is always 8 bits, and a UInt32 is always
// 32 bits regardless of the platform you are on.
// These vary from compiler to compiler, so you have to
// look them up in the compiler documentation.
typedef char Int8;
typedef short int Int16;
typedef int Int32;
typedef unsigned char UInt8;
typedef unsigned short int UInt16;
typedef unsigned int UInt32;
그러면 위의 내용이 다음과 같이 변경됩니다.
UInt32 length = 0;
char* buffer = 0;
ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length));
buffer = new char[length];
ReadXBytes(socketFileDescriptor, length, (void*)buffer);
// process
delete [] buffer;
이것이 도움이 되길 바랍니다.
여러 포인터:
반환 값 0을 처리해야 하며, 이는 원격 호스트가 소켓을 닫았음을 나타냅니다.
비차단 소켓의 경우 오류 반환 값(-1)을 확인하고 errno가 예상되는 EINPRODUCE가 아닌지 확인해야 합니다.
당신은 확실히 더 나은 오류 처리가 필요합니다. 당신은 잠재적으로 '버퍼'가 가리키는 버퍼를 누설할 수 있습니다.제가 알아차린 것은, 당신은 이 코드 조각의 어느 곳에도 할당하지 않는다는 것입니다.
만약 당신의 read()가 전체 버퍼를 채우면 당신의 버퍼가 어떻게 null로 종료된 C 문자열이 아닌지에 대해 다른 사람이 좋은 지적을 했습니다.그것은 정말로 문제이고, 심각한 문제입니다.
버퍼 크기는 약간 작지만 256바이트 이상을 읽거나 할당한 값을 읽지 않는 한 작동해야 합니다.
원격 호스트가 잘못된 형식의 메시지(서비스 거부 공격 가능성)를 보낼 때 무한 루프에 빠지는 것이 걱정된다면 소켓에서 시간 제한이 있는 select()를 사용하여 가독성을 확인하고 데이터가 사용 가능한 경우에만 읽고 select()가 시간 초과되면 구제해야 합니다.
다음과 같은 방법이 효과가 있을 수 있습니다.
fd_set read_set;
struct timeval timeout;
timeout.tv_sec = 60; // Time out after a minute
timeout.tv_usec = 0;
FD_ZERO(&read_set);
FD_SET(socketFileDescriptor, &read_set);
int r=select(socketFileDescriptor+1, &read_set, NULL, NULL, &timeout);
if( r<0 ) {
// Handle the error
}
if( r==0 ) {
// Timeout - handle that. You could try waiting again, close the socket...
}
if( r>0 ) {
// The socket is ready for reading - call read() on it.
}
수신할 데이터의 양에 따라 "끝" 토큰에 대해 전체 메시지를 반복적으로 검색하는 방법은 매우 비효율적입니다.상태 기계(상태는 'e'->n'->d'->d'->';)를 사용하면 각 수신 문자를 한 번만 볼 수 있습니다.
그리고 진지하게, 여러분은 여러분을 위해 이 모든 것을 할 수 있는 도서관을 찾는 것을 고려해야 합니다.그것을 맞추는 것은 쉽지 않습니다.
dirks 제안에 따라 버퍼를 실제로 생성하는 경우:
int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE);
버퍼를 완전히 채울 수 있으며 문자열 스트림으로 추출할 때 의존하는 종료 0 문자를 덮어쓸 수 있습니다.필요한 항목:
int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE - 1 );
다른 사람들은 버퍼에 일부 메모리 공간을 할당해야 한다고 지적했습니다.N의 작은 값(예: N <= 4096)에 대해서도 스택에 할당할 수 있습니다.
#define BUFFER_SIZE 4096
char buffer[BUFFER_SIZE]
이것은 당신이 당신을 확실히 하는 것에 대한 걱정을 덜어줍니다.delete[]
버퍼는 예외를 던져야 합니다.
그러나 스택은 크기가 유한하므로(더미는 유한하지만 스택은 유한함), 너무 많이 배치하지 마십시오.
-1 반환 코드에서는 단순히 즉시 반환해서는 안 됩니다(예외를 즉시 반환하는 것은 훨씬 더 개략적입니다).코드가 짧은 숙제 이상이 되려면 처리해야 하는 정상적인 조건이 있습니다.예를 들어, 비차단 소켓에서 현재 사용 가능한 데이터가 없는 경우 EAGAIN은 errno로 반환될 수 있습니다.(2)를 읽으려면 man 페이지를 보세요.
메모리를 어디에 할당하고 있습니까?buffer
를 하는 행bzero
버퍼가 올바른 메모리 영역을 가리키지 않으므로 정의되지 않은 동작을 호출합니다.
char *buffer = new char[ BUFFER_SIZE ];
// do processing
// don't forget to release
delete[] buffer;
소켓 작업을 할 때 항상 참고하는 기사입니다.
여기에는 'select()'를 안정적으로 사용하는 방법이 나와 있으며 소켓에 대한 자세한 정보를 위해 하단에 기타 유용한 링크가 포함되어 있습니다.
위의 게시물 중 몇 가지에 추가하기 위해:
read() -- 적어도 내 시스템에서는 -- ssize_t를 반환합니다.size_t와 같습니다. signed만 제외하고요.제 시스템에서는, 그것은 긴 것이지, 인트가 아닙니다.시스템, 컴파일러 및 설정한 경고에 따라 int를 사용하면 컴파일러 경고가 표시될 수 있습니다.
사소한 애플리케이션(즉, 애플리케이션은 길이가 다른 다양한 종류의 메시지를 수신하고 처리해야 함)의 경우 특정 문제에 대한 해결책은 반드시 프로그래밍 솔루션일 뿐만 아니라 일반적인 프로토콜입니다.
전송해야 할 바이트 수를 결정하려면read
프로그램이 수신하는 공통 접두사 또는 헤더를 설정해야 합니다.그런 식으로 소켓에 처음 사용 가능한 읽기가 있을 때 예상되는 항목에 대한 결정을 내릴 수 있습니다.
이진 예제는 다음과 같습니다.
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
enum MessageType {
MESSAGE_FOO,
MESSAGE_BAR,
};
struct MessageHeader {
uint32_t type;
uint32_t length;
};
/**
* Attempts to continue reading a `socket` until `bytes` number
* of bytes are read. Returns truthy on success, falsy on failure.
*
* Similar to @grieve's ReadXBytes.
*/
int readExpected(int socket, void *destination, size_t bytes)
{
/*
* Can't increment a void pointer, as incrementing
* is done by the width of the pointed-to type -
* and void doesn't have a width
*
* You can in GCC but it's not very portable
*/
char *destinationBytes = destination;
while (bytes) {
ssize_t readBytes = read(socket, destinationBytes, bytes);
if (readBytes < 1)
return 0;
destinationBytes += readBytes;
bytes -= readBytes;
}
return 1;
}
int main(int argc, char **argv)
{
int selectedFd;
// use `select` or `poll` to wait on sockets
// received a message on `selectedFd`, start reading
char *fooMessage;
struct {
uint32_t a;
uint32_t b;
} barMessage;
struct MessageHeader received;
if (!readExpected (selectedFd, &received, sizeof(received))) {
// handle error
}
// handle network/host byte order differences maybe
received.type = ntohl(received.type);
received.length = ntohl(received.length);
switch (received.type) {
case MESSAGE_FOO:
// "foo" sends an ASCII string or something
fooMessage = calloc(received.length + 1, 1);
if (readExpected (selectedFd, fooMessage, received.length))
puts(fooMessage);
free(fooMessage);
break;
case MESSAGE_BAR:
// "bar" sends a message of a fixed size
if (readExpected (selectedFd, &barMessage, sizeof(barMessage))) {
barMessage.a = ntohl(barMessage.a);
barMessage.b = ntohl(barMessage.b);
printf("a + b = %d\n", barMessage.a + barMessage.b);
}
break;
default:
puts("Malformed type received");
// kick the client out probably
}
}
이진 형식을 사용할 때의 한 가지 단점을 이미 알 수 있습니다. 즉, 각 속성이 다음보다 클 경우char
당신은 읽습니다, 당신은 그것의 바이트 순서가 정확한지 확인해야 할 것입니다.ntohl
또는ntohs
기능들.
다른 방법은 바이트 순서 문제를 완전히 방지하지만 구문 분석 및 검증을 위해 추가적인 노력이 필요한 단순 ASCII 또는 UTF-8 문자열과 같은 바이트 인코딩 메시지를 사용하는 것입니다.
C의 네트워크 데이터에 대한 두 가지 최종 고려 사항이 있습니다.
첫 번째는 일부 C 유형의 너비가 고정되어 있지 않다는 것입니다.예를 들어, 겸손한 사람들은int
프로세서의 워드 크기로 정의되므로 32비트 프로세서는 32비트를 생성합니다.int
s, 64비트 프로세서는 64비트를 생성합니다.int
좋습니다. 휴대용 코드는 네트워크 데이터가 고정 너비 유형을 사용하도록 해야 합니다.stdint.h
.
두 번째는 구조 패딩입니다.다른 너비의 구성원을 가진 구조체는 메모리 정렬을 유지하기 위해 일부 구성원 사이에 데이터를 추가하여 구조체를 프로그램에서 더 빨리 사용할 수 있게 하지만 때때로 혼란스러운 결과를 생성합니다.
#include <stdio.h>
#include <stdint.h>
int main()
{
struct A {
char a;
uint32_t b;
} A;
printf("sizeof(A): %ld\n", sizeof(A));
}
이 예에서 실제 너비는 1이 아닙니다.char
+ 4uint32_t
5바이트, 8바이트입니다.
mharrison@mharrison-KATANA:~$ gcc -o padding padding.c
mharrison@mharrison-KATANA:~$ ./padding
sizeof(A): 8
그 이유는 다음에 3바이트가 추가되기 때문입니다.char a
확실히uint32_t b
메모리 정렬입니다.
그래서 만약 당신이write
a struct A
그런 다음 a를 읽으려고 시도합니다.char
그리고 auint32_t
반대쪽에서, 당신은 얻을 것입니다.char a
및 auint32_t 여기서 처음 3바이트는 가비지이고 마지막 바이트는 실제 작성한 정수의 첫 번째 바이트입니다.
데이터 형식을 C 구조 유형으로 명시적으로 문서화하거나 포함할 수 있는 패딩 바이트를 문서화하십시오.
언급URL : https://stackoverflow.com/questions/666601/what-is-the-correct-way-of-reading-from-a-tcp-socket-in-c-c
'programing' 카테고리의 다른 글
SQL 서버의 포트 번호 식별 방법 (0) | 2023.07.04 |
---|---|
Firebase 인증된 사용자만 허용하도록 Firebase Cloud Function HTTP 끝점을 보호하는 방법은 무엇입니까? (0) | 2023.07.04 |
운영을 위해 Angular 앱을 번들로 제공하는 방법 (0) | 2023.07.04 |
Git 태그를 체크아웃하면 "HEAD 분리 상태"가 발생합니다. (0) | 2023.06.29 |
excel vba에서 글로벌 변수의 수명은 얼마입니까? (0) | 2023.06.29 |