리눅스 환경에서 c언어로 소켓 프로그래밍하는데 막혀서요...

jjoship의 이미지

c언어 초보라서 뭐가 문제인지 잘 모르겠어서 질문좀 올리려구 합니다.

서버측에서 Hello World! 라는 문자열을 한칸씩 왼쪽으로 이동하여

변형된 문자열을 클라이언트측에게 전달을 하고 싶습니다.

ex)
Hello World!
ello World!H
llo World!He
lo World!Hel
o World!Hell

**********서버측 소스**********

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
void error_handling(char*);
void str_left(char*);
 
int main(int argc, char *argv[]) {
 
	int i;
 
	int serv_sock;
	int clnt_sock;
 
	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
 
	socklen_t clnt_addr_size;
 
	char message[] = "Hello World!";
 
	printf("argc : %d\n", argc);
	if (argc != 2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
 
	// socket 생성
	if ((serv_sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
		error_handling("socket() error");
 
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(atoi(argv[1]));
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 
	// ip와 port를 할당한다
	if (bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
		error_handling("bind() error");
 
	// 연결요청 가능상태로 변경
	if (listen(serv_sock, 5) == -1)
		error_handling("listen() error");
 
	clnt_addr_size = sizeof(clnt_addr);
	if ((clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size)) == -1)
		error_handling("accept() error");
 
	for (i=0; i<100; i++) {	
		str_left(message);
		printf("%s\n", message);
		write(clnt_sock, message, sizeof(message));
	}
 
	close(clnt_sock);
	close(serv_sock);
 
	return 0;
 
}
 
 
 
void error_handling(char *message) {
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
 
 
 
// 문자열을 한칸씩 왼쪽으로 이동!!
void str_left(char *str) {
	int i;
	int max;
	char *temp = (char*) malloc(strlen(str)+1);
	strcpy(temp, str);
	max = strlen(str);
 
	for (i=max; i>=0; i--) {
		str[i-1] = temp[i];
	}
 
	str[max-1] = temp[0];
}

서버측 문제인가 하고 printf 로 찍어보았으나 정상적으로

Hello World!
ello World!H
llo World!He
lo World!Hel
o World!Hell

와 같이 출력되는것을 확인하였습니다.

**********클라이언트 소스**********

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
void error_handling(char *);
 
int main(int argc, char *argv[]) {
 
	int sock, read_result, read_index = 0;
	struct sockaddr_in serv_addr;
	char message[30];
 
	if (argc != 3) {
		printf("ip, port 를 넘겨주세요");
		exit(1);
	}
 
	if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
		error_handling("socket() error");
 
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(atoi(argv[2]));
	serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
 
	if (connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
		error_handling("connect() error");
 
	// 서버로부터 데이터 읽기
	printf("server message : \n");
	while (1) {
		if (read(sock, message, sizeof(message)) > 0) {
			printf("%s\n", message);
			memset(message, 0, sizeof(message));
		} else {
			break;
		}
	}
 
	close(sock);
	return 0;
}
 
 
 
void error_handling(char *message) {
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

서버에서 출력하였을 때는 정상적으로 잘 찍히던 문자가 클라이언트로 접속을 해서, 서버로부터 데이터를 받아보니
ello World!H
orld!Hel
ello

ello Worl
orld!

이런 형식으로 서버로부터 데이터를 깨져서 가지고 옵니다.

C를 처음 배우다보니 뭐가 문제인지 잘 모르겠네요.. 어느부분이 잘못되었을까요?

yukariko의 이미지

클라이언트 부분의 소스에 문제가 있습니다.
read 함수는 데이터를 문자열 단위로 받아오지 않습니다.
따라서 message 배열엔 한번에 2개 이상의 문자열이 들어가거나, 1개의 문자열이 잘려서 들어올 수도 있습니다.

배열안에 문자열이 2개있더라도 printf는 맨앞 하나만을 출력하겠죠. 그래서 결과가 잘려서 보이는 것입니다.

잘 확인해보시면 read로 읽어오는 데이터 자체는 문제가 없을것입니다.

jjoship의 이미지

이해가 잘 안갑니다 ㅠㅠ..

read 함수를 쓰면 읽어들인 바이트 수를 리턴한다고 들었는데 이거를 이용해서 다른 변수에 메모리를 할당해서 거기에다가 read 해야되는건가요

yukariko의 이미지

그렇게 할수도 있죠.
아니면 배열의 크기를 충분히 크게 잡은 다음
리턴값을 이용해서 배열에 모두 집어놓고 문자열을 하나하나 살펴봐도 되겠지요.

익명 사용자의 이미지

데이터 보낼때 혹은 받을때
운영체제 송신 혹은 수신 버퍼에 담겨 보내집니다

운영체제는 io 효율 위해 버퍼 방식을 선호 하지요

그렇기 때문에 수신측 받은 데이터는
송신할때 1번에 보낸 데이터 전체가 아닌 쪼개진 데이터 입니다. 물론 운이 좋으면 전체 데이터 수신 될수 있습니다.

제가 아는 방법은 대락 2가지 입니다

크기정보를 갖는 고정크기 헤더와 바디(데이터)가 첫번째요

Half close가 두번째입니다

저는 여기까지요 누구 가르치기에 내공이 약해서요

익명 사용자의 이미지

맞는 예기 입니다.

그래서 보통 Socket programming 을 하면 Application Protocol 을 별도로 개발합니다.

Receive 하는 측에서는 Buffer 에 데이터를 계속 넣고 그때 마다 Protocol 로 Parsing 해서 원하는 데이터가 다 들어왔는지 확인하고 데이터를 취합니다..

XML 을 써도 되고 JSON, HTML 을 써도 되고요. 그니깐 원하는 RAW data를 xml 형식에 넣으면 됩니다. 이렇게 하는 이유는

그래야 데이터가 끝까지 다 들어왔다는걸 확인 할 수 있기 때문입니다. 예를들어 로 시작되면 까지는 들어와야

데이터 처리가 가능해지겠죠 ?

이런게 싫으시다면 SSL 같은 거를 써도 되는데 Block Encryption 이 내부 적으로 Buffer 없이 동작 할 수 없기 때문에

Send 로 보낸게 Recv로 한번에 읽혀 집니다. 물론 그 이면에는 위와 동일한 Buffer 처리 부분이 있는 거고요.

twinwings의 이미지

read의 경우 메세지 보다 작은 바이트를 리턴 할 수 있습니다. 나머지는 아직 커널 버퍼에 존재 할 수 있지요.

하지만 recv의 경우 flag에 옵션을 주면 길이를 보장 받을 수 있지요. (예외적인 경우 제외. 밑에 긁어온 man 참조)

man recv

NAME
       recv, recvfrom, recvmsg - receive a message from a socket
 
SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>
 
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 
       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
 
       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
 
DESCRIPTION
       .... 중략 ......
 
       The  flags  argument to a recv() call is formed by ORing one or more of the following val‐
       ues:
 
       MSG_WAITALL (since Linux 2.2)
              This flag requests that the operation block until the full  request  is  satisfied.
              However,  the call may still return less data than requested if a signal is caught,
              an error or disconnect occurs, or the next data to be received is  of  a  different
              type than that returned.

해당 플래그 이용하시면 길이 보장 받아서 클라이언트 측에서 "이미" 길이를 알 수 있다면 그다지 신경 쓸게 없게 됩니다.

물론 실제 통신에서는 위에서 다른분이 언급하신 것 처럼 별도의 어플리케이션 프로토콜이 필요하구요.

댓글 달기

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