tcp/ip write/read 함수의 인자중 size 관련 질문

is1472의 이미지

서버쪽 int result

write(clnt_sock, (char*)&result, sizeof(result));

client 쪽 int result 형태
read(sock, &result, RLT_SIZE);

저 부분이 질문 부분인데요 먼저 result는 int형이고 이것을 받는 client쪽에서 버퍼 역시 int형 입니다.

여기서 서버가 result를 char*형으로 변형해서 sizeof(result)값 즉 4바이트를 보내고 있습니다,

사실 저 char*를 지우고 실행해도 정상적으로 작동 합니다 왜냐하면 int 형 4바이트를 보내는데 문제는 없으니까요

여기서 제 질문은 char*형으로 형변환후 이를 1바이트씩 총 4바이트를 보내겠다는 것이 저 코드의 내용인거 같은데
이는 result의 첫주소를 참조하여 여기서부터 차례대로 한바이트씩 총 4바이트를 보내게 되는 것인가요? 또
write 함수가 그렇게 작동하게끔 되는것인가요 ?

Yi Soo An@Google의 이미지

write 함수의 정의를 보시면 buf가 void*로 되어 있습니다. void 타입이 나오기 이전의 C는 void* 대신 char*를 이용하여 버퍼를 받아 처리하곤 했습니다.
컴파일러의 구현에 따라 int의 값은 다르겠으나 ILP32 기준으로 4바이트를 보내는 것이 맞으며 write 함수는 1byte * sizeof 크기(항상 이 크기가 보내진다고 확신할 수 없습니다.)를 file descriptor에 전송하도록 되어 있습니다.

---------------
Happy Hacking!

is1472의 이미지

맞는건가요

bushi의 이미지

memcpy(&A, &B, 4)
라고 했을 때 B 의 내용 중 처음 4 바이트가 A 의 처음부분에 복사되는 "결과" 가 보장됩니다.
하지만 "구현" 을 어떻게 하라고는 어느 누구도 강제하지 않고, 규칙도 없습니다. (그래서 어떤 플랫폼에선 memcpy 가 memmove 와 똑같은 결과를 보이기도하죠)
그러니 그 안 쪽 구현을 지레 짐작할 필요 없고, 짐작한게 맞는지 틀린지 검증하실 필요는 더더욱 없습니다.

write() 나 read() 도 마찬가지 입니다. memcpy 와 같다는 뜻이 아니라 사양서에 써 있는 것만 보시면 된다는 뜻입니다.
소켓에 대한 I/O, 파일에 대한 I/O, 물리 장치에 대한 I/O 등 내부적인 구현은 천차만별로 다르지만, 사양서에 써 있는 규칙을 지키셨다면 사양서에 써 있는 결과물은 항상 보증됩니다.
size 인자는 "희망하는 양",
저장공간 포인터는 "희망하는 양" 과 같거나 보다 큰 용량을 가진 장소를 가리킬 것,
반환 값은 실제 write/read 된 양 혹은 에러 코드.
이 이상도 이하도 없고, 그 내부 구현에서 3 바이트씩 I/O 를 하건 8 비트씩 I/O 를 하건 지들 멋대로이니 뭘로 지레 짐작하셔봤자 시간 낭비입니다.

예를 들어볼까요 ?
block device 라 불리는 것들의 최소 I/O 단위는 block 입니다. 보통은 512 바이트, sector size 죠.
게다가 보통은 mmap 때문에 mm 과 찰떡같이 엮인 page cache 로 인해 그 크기는 page size (보통은 4096 바이트) 로 확장됩니다.
특히 read 의 경우엔 block device read-ahead cache 와 엮여서 rh cache size(보통은 128KiB) 까지도 확장됩니다.
무슨 예기나면, 파일 open() 해서 1 바이트를 read() 했을 뿐인데, low level 에서는 128KiB I/O 가 발생한 주제에 app 으로 1 바이트 memcpy 만 됐을 수 있다는 뜻입니다.
심지어 이것도 중간에 I/O scheduler 가 없을 때의 단순한 예이고,
I/O scheduler 가 중간에 껴서 merge 가 발생할 경우 실제 행해진 I/O 단위가 얼마가 될런지는 예측하는게 시간 낭비일 정도입니다.
겨우 read/write 시스템 콜 호출하는 수준에서 optimal I/O size 를 운운하는, 자칭 최적화 추구자들이 지껄이는 말들이 처음부터 끝까지 완벽하게 헛소리인 이유이기도하죠.
소켓은 뭐 좀 다를 것 같나요 ?
오히려 block device 와 대체로 비슷합니다. 심지어 tcp 의 경우엔 nagle's algorithm 때문에 block device 의 I/O scheduler 에서 발생하는 merge'ed write 와 똑같은 일이 발생합니다.
겨우 시스템 콜 정도가지고 low level 이라 말할 수 있는 정도에서는, 그 시스템 콜의 내부 구현이 어떻게 되는지 궁금해 하실 필요가 딱히 없습니다.
시스템 콜의 내부구현에 대한 고민에 가치가 없다는 말이 아니라,
그걸 고민하는 것 보다 수십배는 더 가치있는 다른 일이 엄청나게 많을 뿐만 아니라,
더 가치있는 다른 일을 못 찾겠다면 그게 오히려 제일 큰 문제니 시스템 콜의 내부 구현말고 다른 고민거릴 찾는게 더 좋다는 뜻입니다.

is1472의 이미지

검증이 아닌 어떤식으로 돌아가는지가 이게 맞나 궁금해서요..
write 로 포인터를 보내면 그첫주소부터 차례대로 1바이트식 보내지는게 맞나싶어서요 예를들면
첫주소 1000 이면
이후 1000 1001 1002 1003 이렇게 총 4바이트가 차례대로 채워지는가 해서요
그리고 그렇기 때문에 보내는게 (char*)로 형변환을 하든 아니면 하지 않고 그대로 int*로 보내도 상관이 없는게 맞나 싶어서 질문 드립니다.

bushi의 이미지

어째 묘하게 제가 댓글 수정하는 시점과 제 댓글에 답글 다는 시점이 꼬인 것 같습니다.
제가 댓글 수정하던 시점에서는 댓글에 대한 답글이 없었는데...

char* 든 int* 든 아무 상관이 없는 이유는 포인터에 대한 공부가 부족해서 모르시는 겁니다.
포인터는 그 자체가 포인터라는 type 입니다.
char * 니 int * 니 해서 (char*) type 과 (int *) type 이 따로 있는 것 처럼 이해하고 계셨다면 오해이시고요,
(char *) 니 (int *) 니 해서 따로 있는 것 처럼 보여주고 처리도 해주는 것은,
1. dereference 때 발생할 수 있는 코딩러의 실수를 줄이기 위한 고육지책
2. 함수 prototype 에 적은 것만으로도 byte align 에 대한 힌트를 주기 위한 트릭.

함수 protptype 에 (void*) 로 argument type 이 적혀있다면,
"이 포인터에 대해선 address align 에 대해 고민할 필요없고, 최적화를 하건 뭘 어찌 지지고 볶건 함수 안에서 자체적으로 해결할테니, 넌 쓸데없는 고민말고 주소값을 그냥 넘기기나 해라"
라는 뜻입니다.
(int *) 로 argument type 이 적혀있다면,
"엔간하면 커널수준에서라도 어떻게 wrapper 에 의해 처리 되겠지만, 그래도 혹시 모르니 address align 은 sizeof(int) 에 좀 맞춰줬을 좋겠다"
라는 뜻이고요.

is1472의 이미지

char*는 주소에대해서 한 바이트만 읽고
int* 는 주소에 대해서 4바이트를 읽지 않나요? 실제로 테스트해보니 결과도 그러하구요..
둘다 같은 pointer형이라는 것은 아는데 구분해둔 이유가 2가지 뿐아니라 읽어들이는 방식에서도 차이가 있고
거기서 부터 시작해서 저부분에 대한 코드를 해석하다가 질문을 드립니다 이게 어떻게 구현되었는가는 전혀 궁금하지 않아요...

bushi의 이미지

함수 파라미터로 쓰이는 (char*) (int*) 등과 access 에 쓰이는 *(int*) *(char*) 등을 헷갈리시는 것 같은데,
둘은 서로 아무 관계도 없습니다.
전자는 프로그래머의 키보드 타이핑 횟수(소스코드 크기)도 줄이고 실수(casting)도 방지할 겸 제공되는 일종의 서비스.
후자는 8bit, 16bit, 32bit, 64bit access 에 대한 개별적인 instrunction 이 있을 경우 그걸 꼭 사용해주길 바란다는, 안되겠으면 컴파일러 니가 wrapping 하는 코드를 삽입하라는 의사표현이죠.

라스코니의 이미지

write()를 하게 되면 network endian인 big endian으로 바꿔서 상대방으로 보내게 되며(즉 char, short, int 에 따라서 짤라서 보내게 됩니다), 받은 상대방은 이것을 잘 붙여서 받아야 합니다.

big, little endian이라는 귀찮은 것 때문에 수신측은 송신측이 어떻게 보냈는지를 참고해서 받아야 하는데 그것을 없애려고 network으로 보내는 데이터는 big endian으로 왔다갔다 하기로 약속되어 있습니다.

(char *)& 로 write()에서 사용하는 것은 한 바이트씩 읽어드려서 보내라는 것입니다. 바이트 내에는 endian이 없으니 문자열같은 것을 보낼때 최상의 방법일 것입니다. 즉 맨 앞에서부터 한 바이트씩 날라간다고 보시면 됩니다. wireshark 같은 패키 분석툴로 UDP/TCP 패킷을 보면 payload에 쯕 데이터 들이 들어있어서 네트웍을 타고 있는 것을 알수 있습니다.

그럼 문자열이 아닌 것을 날릴려면 어떻게 할까요? endian을 잘 살펴서 받아야 합니다.

bushi의 이미지

네트웍의 endian 은 헤더(ethernet, ip, tcp 기타등등)에 적히는 값들에만 해당됩니다.
송수신 측만의 문제가 아니라, 중간에 거쳐가는 각종 장비들도 패킷을 뜯어보고 적절하게 처리를 해야하기 때문에 정해진 공통 규격입니다.
데이타엔 해당되지 않으며, 송신측과 수신측이 약속(protocol)만 한다면 뭘 사용해도 상관없습니다.
비유하자면, ext4fs 의 meta data 는 모두 little-endian 입니다만 파일에 기록하는 내용들까지 모두 little endian 이어야 한다거나 저절로 little endian 으로 바뀌는 마법이 숨어있거나 하지는 않습니다.

is1472의 이미지

이것이 그나마 가장 이해가 되네요 한분은 너무 수준이 하이라서 아직 제가 받아들이기에는 지식이 부족한 것같습니다... 아는 만큼 보인다고 하자나요 ..
그럼 추가로 저기서 (char*) 를 지워도 정상 작동이 되는데 즉 int*로 날려도 정상작동이 되는데 이는 4바이트씩보내고 위 size값도 4바이트라서 그런가요..???

Yi Soo An@Google의 이미지

bushi님의 "어째 묘하게 제가 댓글 수정하는 시점과 제 댓글에" 댓글을 읽어보시면 힌트가 있습니다.

char *, int *를 보지마시고 write의 parameter type을 보세요. void * (dereference시 1byte를 읽어라 라는 정보가 담긴 포인터)입니다.

---------------
Happy Hacking!

Yi Soo An@Google의 이미지

참고로 포인터를 해석하실때는 다음과 같이 하시면 쉽습니다.

void * ptr: ptr is a pointer that compatible with any type objects.
int * ptr: ptr is a pointer to an integer type object.
char * ptr: ptr is a pointer to a character type object.

요점은 integer, char, void type pointer가 아니라 a pointer to/that 입니다.

---------------
Happy Hacking!

is1472의 이미지

다만 void* 라고 있는건 역참조시 무조건 1바이트 씩 읽겠다는 것인가요?
그리고 그때 가리키는 포인터의 타입 즉 int char ..등 타입에따라 읽는 바이트수에 대한 횟수? 그것이 달라지는 것인가요..?

라스코니의 이미지

void *는 모든 포인터 타입을 건네줄 때 (함수 호출시 parameter 등으로) 사용한다면 컴파일러 워닝을 피하면서 type casting을 통해 함수 내에서는 받은 포인터를 뜻대로 조작할 수 있습니다. 어자피 포인터의 사이즈는 4 byte (8 byte in 64 bit OS)이니 char *, int * 등 원하는 대로 캐스팅하여 사용할 수 있죠. 하지만 네트웍을 통해서 데이터를 보낼 때는 소스 포맷이 뭔지 모르니 최종 write() 단에서는 char * 타입으로 한 바이트 씩 보내는 것이 단순하겠죠.

이제 받는 쪽이 문제인데 위에 제가 좀 중간 생략하고 적은 것이 있는데, 이종 기계간에 통신을 하려면 골치 아픈게 endian인데 이게 완벽한 보편성을 가진 통신 모듈을 만들기가 쉽지 않게 만듭니다. 그래서 머리를 쓴게 모든 데이터를 big endian으로 보내는 것입니다. 물론 TCP/UDP 패킷의 payload 자체는 big, little endian을 전혀 모른 상태로 그냥 유저가 보낸대로 보내죠. 이것을 보내는 쪽(sender)이 무조건 big endian으로 보내게 하면 받는 쪽에서 자기가 big endian이면 그냥 받고, 자기가 little endian 머신이면 컨버팅하여 받으면 문제가 해결됩니다. 자신의 endianness는 자기가 제일 잘 아니까요.

수신 측에서 송신측이 보낸 데이터의 구조체 포맷만 알고 있으면 이런 식으로 endianness free로 데이터 송수신이 가능합니다. 물론 그 이전에는 수신 buffer의 완벽한 컨트롤이 동반되어야 하지만요. 왜냐하면 상대방이 1500 byte를 보냈다고 해서 수신측에서 모두 1500 byte를 한번에 받는다는 보장이 없거든요. 최악으로는 1499 byte + 1 byte 이렇게 올수도 있죠. 그래서 받은 데이터를 아주 잘 이어붙여야 합니다. 이게 상당히 힘듭니다. 거기가 (mission critical 또는 security 시스템이라서) CRC 등의 체크섬까지 붙어야 한다면 더 복잡해 지죠.

is1472의 이미지

결국 그럼 size 인자쪽에서 논리적으로 맞게 했으니 char* 와 int* 둘다 될수 있는것이네요
답변 감사합니다 !! 추가적이 지식 또한 감사합니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.