socket

전병욱@Facebook의 이미지

socket echo server, client인 데 소스 봐주시면 감사하겠습니다.

메시지를 보내면 서버에서 받을 때 grabage 값이 있고

버퍼 사이즈보다 많은 데이터 전송하고 수신 시 값이 잘리는 오류가 있습니다.

이럴 때 어떻게 해결하면 되는지 봐주시면 감사하겠습니다.

echoserver.c

#include "libDefine.h"
#include

int main(int argc, char *argv[])
{
/* signal 처리 */
signal(SIGINT, signalHandler);

int nServerSock = -1;
int nClientSock = -1;
int nPort = -1; /* server socket, client socket */
char pMsg[SIZEBUFFER]; /* 수신용 버퍼 */
int nSocketLen = 1;
struct sockaddr_in stClientAddr;
unsigned int uClientAddrSize = sizeof(struct sockaddr_in);

if (argc != 2)
{
printf("Usage : %s \n", argv[0]);
exit(1);
}

nPort = atoi(argv[1]);
printf("서버 연결 중입니다.\n");
nServerSock = serversSocket(nPort);

if (nServerSock == -1)
{
printf("error = %s\n", strerror(errno));
errHandle("socket() error");
}

//uClientAddrSize = sizeof(stClientAddr); /* 클라이언트 주소 구조체의 크기 */

while (1)
{
nClientSock = accept(nServerSock, (struct sockaddr*) &stClientAddr, &uClientAddrSize);

if (nClientSock == -1)
{
printf("error = %s\n", strerror(errno));
errHandle((char*) "accept() 서버로부터 연결 요청 수락이 되지 않았습니다.");
close(nServerSock);
}
else
{
printf("접속한 클라이언트 IP : %s\n", inet_ntoa(stClientAddr.sin_addr));
printf("accept() 클라이언트의 연결 요청 수락되었습니다.\n");
}

while ((nSocketLen = read(nClientSock, pMsg, SIZEBUFFER)) != 0)
{
/*
if ((nSocketLen = read(nClientSock, pMsg, SIZEBUFFER)) == -1)
{
errHandle("read() 되지 않았습니다.");
printf("error = %s\n", strerror(errno));
}
*/
printf("클라이언트로 받은 메시지: %s\n", pMsg);
write(nClientSock, pMsg, nSocketLen);

if(nSocketLen == -1)
{
errHandle("read 되지 않았습니다.");
}

}

//close(nClientSock);
printf("연결이 끊어졌습니다.\n");
}

close(nClientSock);
return 0;
}

echoclient

#include "libDefine.h"

/*
jmp_buf x;

void jmp()
{
longjmp(x, 5);
}
*/

int main(int argc, char *argv[])
{
signal(SIGINT, signalHandler);

int nClientSock = -1;
int nPort = 0;
char* pAddr;
char pMsg[SIZEBUFFER + 0x01]; /* 서버에 보낼 메시지를 저자알 문자열 버퍼 */
int nStrLen = 0; /* 송수신 메시지의 문자열 길이 */

/* port */
if (argc != 3)
{
printf("usage : %s \n", argv[0]);
exit(1);
}

pAddr = argv[1];
nPort = atoi(argv[2]);

nClientSock = clientSocket(nPort, pAddr);

if(nClientSock == -1)
{
printf("error = %s\n", strerror(errno));
errHandle("socket() error");
}

while (1)
{
//pMsg = NULL;

fputs("입력 메시지(Q는 종료) : ", stdout);
fgets(pMsg, SIZEBUFFER, stdin);

if (!strcasecmp(pMsg, "q\n"))
{
break;
}

write(nClientSock, pMsg, strlen(pMsg));

/*
if(write(nClientSock, pMsg, strlen(pMsg)) != 0)
{
//errHandle("Write() error");
write(nClientSock, pMsg, strlen(pMsg));
}
else
{
errHandle("Write() error");
//write(nClientSock, pMsg, strlen(pMsg));
}
*/

/*
if((read(nClientSock, pMsg, SIZEBUFFER)) != 0)
{
nStrLen = read(nClientSock, pMsg, SIZEBUFFER);
errHandle("read 되지 않았습니다.");
printf("error = %s\n", strerror(errno));
}
else if((read(nClientSock, pMsg, SIZEBUFFER)) == -1)
{
errHandle("read 되지 않았습니다.");
}
*/

nStrLen = read(nClientSock, pMsg, SIZEBUFFER - 1);

/*
if((iStrLen = setjmp(x)) <= 1025)
{
jmp();
}
else
{
switch(iStrLen >= 1025)
{
default:
fprintf(stdout, "error code = %d\n", iStrLen);
break;
}
}
*/

pMsg[nStrLen] = 0;
printf("서버로 받은 메시지 : %s\n", pMsg);
}
close(nClientSock);
return 0;
}

libDefine 헤더, 함수있는 파일

#include "libDefine.h"

int serversSocket(int port)
{
int nServerSock = -1; /* server socket */
struct sockaddr_in stServerAddr; /* 서버용 소켓(accept용) */
int nOptions = 1;

/* TCP 통신용 서버 소켓 생성 */
nServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

if (nServerSock == -1)
{
printf("error = %s\n", strerror(errno));
errHandle((char*) "socket() error");
}
else
{
printf("socket() TCP 통신용 서버 소켓이 생성되었습니다.\n");
}

memset(&stServerAddr, 0, sizeof(stServerAddr));
stServerAddr.sin_family = AF_INET;
stServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
stServerAddr.sin_port = htons(port);

if(stServerAddr.sin_port == -1)
{
errHandle("port가 없습니다.");
}

/* bind */
if (bind(nServerSock, (struct sockaddr*) &stServerAddr, sizeof(stServerAddr)) == -1)
{
printf("error = %s\n", strerror(errno));
errHandle("bind() error");
}
else
{
printf("bind() - socket에 주소와 IP 할당되었습니다.\n");
}

/* 소켓을 서버용으로 사용할 수가 있게 한다. */
if (listen(nServerSock, 5) == -1)
{
printf("error = %s\n", strerror(errno));
errHandle((char*) "listen() error");
}
else
{
printf("listen() 연결 요청 대기하는 중입니다.\n");
}

return nServerSock;
}

int clientSocket(int port, char *addr)
{
int nClientSock;
struct sockaddr_in stServerAddr; /* 접속할 서버의 주소 */

nClientSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

if (nClientSock == -1)
{
printf("error = %s\n", strerror(errno));
errHandle("socket() error");
}

/* 서버 쪽 주소 구조체 0 초기화 */
memset(&stServerAddr, 0, sizeof(stServerAddr));
stServerAddr.sin_family = AF_INET; /* 인터넷 주소체계 사용 */
stServerAddr.sin_addr.s_addr = inet_addr(addr); /* 서버 IP 구조체에 저장 */
stServerAddr.sin_port = htons(port); /* 서버 Port 구조체에 저장 */

/* connect */
if (connect(nClientSock, (struct sockaddr*) &stServerAddr, sizeof(stServerAddr)) == -1)
{
printf("error = %s\n", strerror(errno));
errHandle("connect() error");
}
else
{
printf("연결되었습니다.\n");
}

return nClientSock;
}

/* error 처리 handler */
void errHandle(char *pMsg)
{
fputs(pMsg, stderr);
fputc('\n', stderr);
exit(1);
}

void signalHandler(int iSignals)
{
char chMessage[SIZEBUFFER];
if (iSignals == SIGINT)
{
fputs("종료하시겠습니까?(y 입력) : ", stdout);
fgets(chMessage, SIZEBUFFER, stdin);
if (!strcasecmp(chMessage, "y\n"))
{
exit(0);
}
}
}

libDefine.h

#ifndef LIBDEFINE_H_
#define LIBDEFINE_H_

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define SIZEBUFFER 1024

// 서버 socket
int serversSocket(int port);

// 클라이언트
int clientSocket(int port, char *addr);

// 에러 예외 처리
void errHandle(char* msg);

// 시그널 처리
void signalHandler(int signals);

#endif

jick의 이미지

TCP/IP는 원래 n바이트를 한번에 보낸다고 받는 쪽에서 n바이트를 받는다는 보장이 없습니다. 소켓통신에 대해 설명하는 아무 책에서나 다 나올 텐데요.

write를 하는 쪽에서도 리턴값을 체크해서 실제로 몇 바이트를 보냈는지 검사한 다음 아직 다 못 보냈으면 다시 write를 불러야 하고, read하는 쪽에서도 자기가 몇 바이트를 읽어야 하는지 계산한 다음 계속 리턴값을 체크하면서 그만큼 읽어야 합니다.

전병욱@Facebook의 이미지

답변 감사드립니다.

구체적으로 저기서 어떻게 추가하는 것이 좋을까요?

버퍼는 1024인데 1025자를 보낼 때 데이터가 잘려서 나올 때가 있습니다.

그리고 빈 값 넣을 때 garbage 값을 해결하는 것은 어떻게 추가하면 될까요?

전병욱@Facebook의 이미지

어떻게 추가하면 될까요?

익명 사용자의 이미지

뛰어난 학생이라면, read와 write 함수의 명세를 읽고, 필요한 기능을 구현하기 위하여 이들 함수를 어떻게 호출해야 하는지 스스로 구상해 볼 수 있을 겁니다.

하지만 설령 그렇게 하지 못하는 경우에도, 좋은 텍스트북이 있지요.

CSAPP 3e(https://csapp.cs.cmu.edu/) 책을 사서 처음부터 찬찬히 공부해 보시기 바랍니다.
"10.5 Robust Reading and Writing with the Rio Package"쯤까지 공부하시면 원하시는 내용이 있을 겁니다.

전병욱@Facebook의 이미지

감사합니다.

제 코드에서 수정할 것이 있을까요?

익명 사용자의 이미지

전병욱@Facebook의 이미지

감사합니다.

근데 어떤 부분을 수정하면 될까요?

익명 사용자의 이미지

만약 1025를 보낸다면
1024로 한번 보내고
나머지 1을 보내서 총 2번을 write 한다

전병욱@Facebook의 이미지

감사합니다.

근데 1025를 보내면 1024를 받고 그 다음 패킷에서 1을 받습니다.

여기서 1024만 받고 나머지 데이터가 잘려서 나오는 것을 1을 안 받고 하는 방법이나 그런 것이 있을까요?

익명 사용자의 이미지

좀더 좋게 하자고 하면, 본인만의 프로토콜을 만드는 방법입니다.

예들면,

헤더는 정적사이즈로 64byte로 정의 하세요

64byte의 헤더 내용에는 다음 보낼 데이터의 정보를 넣어 보내줍니다.
(데이터 사이즈 /세그먼트 /체크섬 등등 기타 필요한 정보.)

헤더는 64byte 고정이기에 handshake 이후 클라이언트가 먼저 헤더를 보내던지 아니면 서버쪽에서 먼저 헤더르 보내던지 정의 한후에,

64byte만 보내고 받고 한후 이후 데이터는 헤더 내용이 있는 사이즈만큼 보내고 받으면 됩니다.

jick의 이미지

일반적으로 1024 바이트를 한번에 write했을 때 1024 바이트가 한번에 간다는 보장이 없습니다.

마르고 닳도록 말하지만 loop를 돌려서 몇 바이트 보냈는지 세어서 다 보낼 때까지 남은 바이트를 write해야 합니다.

전병욱@Facebook의 이미지

그럼 남은 바이트는 1024만큼 받고 다음 패킷에서 받으면 되지 않나요?

jick의 이미지

묘하게 인터넷에서 제대로 된 예제를 찾기가 힘드네요. 요즘엔 아무도 소켓 안쓰나... -_-

아무튼 아래의 예제는 소켓프로그래밍의 경전이라 할 수 있는 Stevens의 UNIX Network Programming에서 베낀 겁니다. 이런 식으로 하시면 됩니다.

해당 파일은 http://www.kohala.com/start/unpv12e.html 여기서 "source code"를 클릭하면 다운받을 수 있습니다.

ssize_t						/* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
	size_t		nleft;
	ssize_t		nwritten;
	const char	*ptr;
 
	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (errno == EINTR)
				nwritten = 0;		/* and call write() again */
			else
				return(-1);			/* error */
		}
 
		nleft -= nwritten;
		ptr   += nwritten;
	}
	return(n);
}

그리고 마르고 닳도록 말하지만 정말로 "이 코드를 수행하면 무슨 무슨 패킷이 왔다갔다 하는지 리눅스 커널의 TCP/IP 구현방법을 알고 싶다!" 같은 게 아니라면 "이번 패킷, 다음 패킷" 같은 개념은 그냥 잊어버리세요. TCP는 스트림 프로토콜입니다. 내부적으로 패킷이 몇 개 왔다갔다하는지는 사용자 입장에서 전혀 모르는 일이고, 그런 걸 가정하고 코드를 짰으면 십중팔구 틀린 코드라고 생각하면 됩니다.

전병욱@Facebook의 이미지

감사합니다.

written 함수에서 어떤 것을 하고 있나요?

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.