티스토리 뷰

 지난 기말 과제로 작성했던 프로그램을 흔적을 남기기위해 남겨둡니다. 몇가지 제약과 문제점을 가지고 있기 때문에 완벽한 코드는 절대 아닙니다. 가장 기초적인 코드 작성을 해두었기 때문에 불필요한 부분이 많이 있을 것이라고 생각됩니다. 단순 참고용으로 활용하시고, 이상한 부분은 지적 부탁드립니다.


3가지로 분리하고, 서버와 클라이언트 정의, 서버 소스코드, 클라이언트 소스코드로 분리하여 올려둡니다.

  1. C언어 노트 프로그램 서버와 클라이언트 정의

      http://thdev.net/248

  2.  C언어 노트 프로그램 서버

  3. C언어 노트프로그램 클라이언트


작성한 프로그램은?

 기말 과제로 나온건 에버노트를 참고하여 프로그램을 작성하는 문제였습니다.

 에버노트를 간단히 정리하면 클라이언트 프로그램이 존재하며, 클라이언트는 동시에 여러개의 PC에서 사용이 가능합니다. 여러개의 PC가 실시간 동기화 처리되지는 않으며, 특정 시간을 정해두고, 해당 시간이 되면 서버와 클라이언트의 동기화가 진행됩니다. 그리고 가장 기본적으로 메모 작성, 수정, 태그 기능이 있고, 1개의 노트북에는 여러개의 노트가 올 수 있고, 한명의 사용자는 여러개의 노트북을 둘 수 있습니다. 태그를 이용하여 분리하기도 하지만 해당 프로그램에는 제외했습니다.

 그렇기 때문에 사용자가 직접 동기화를 요청하거나, 시간이 되어야 동기화가 처리됩니다. 제가 작성한 노트에는 푸쉬형태의 동기화는 처리되지 않고, 새로운 파일을 생성하고, 특정 메뉴를 호출 할 경우에 서버에 전송 및 서버 DB에 저장을 하게 됩니다. 그리고 1명의 여러개의 클라이언트를 가질 수 있고, 모두 다른 세션으로 존재합니다. 사용자는 노트북을 생성하고, 새로운 노트를 수정 삭제 추가 할 수 있습니다.


작성한 서버 코드

make 파일

 - 간단하게 make파일을 정의하여 프로그램을 컴파일 했습니다.

//make
//start ./server port Number
all: server

server: startServer.c fileInOut.h serverTCP.h common.h
	gcc -g -Wall startServer.c fileInOut.c serverTCP.c -o server -pthread

clean:
	rm server

run_server: server
	./server


common.h - 구조체를 저장

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef struct { //사용자 정보를 저장하는 구조체
	int userID;
	char ID[12];
	int createTime;
	int updateTime;
}userinfo;

typedef struct { //노트북 정보를 저장하는 구조체
	int notebooksID;
	int userID;
	char notebookTitle[32];
	int createTime;
	int updateTime;	
}notebooks;

typedef struct { //노트를 저장하는 구조체
	int noteID;
	int userID;
	int notebooksID;
	char noteContent[1024];
}notes;

typedef struct { //사용자, 노트북, 노트를 한번에 관리하기 위한 구조체
	userinfo userInfo[30];
	notebooks notebook[100];
	notes note[100];
}serverData;

typedef struct { //클라이언트에 전송하기 위한 구조체
	notebooks notebook[100];
	notes note[100];
}clientData;

//클라이언트와 통신에 사용하기 위한 구조체
//Data Edit, Submit
typedef struct {
	int dataInputA;
	int dataInputB;
	char content[2048];
}dataSubmit;

//사용자 세션을 저장하기 위한 구조체
typedef struct {
	int session;
	int userID;
	int type;
}userSession;


fileInOut.h - 파일 입출력 부분

#include <stdio.h>
#include <stdlib.h>

void setFile(void *arg, size_t size); //파일을 저장하는 부분
void *getFile(void *arg, size_t size); //파일을 불러 오는 부분


fileInOut.c

#include "fileInOut.h"

void *getFile(void *arg, size_t size) {
	FILE *f;

	if((f = fopen("user.dat", "r")) == NULL) {
		printf("connot open file for reding\n");
		exit(1);
	}
	fread(arg, size, 1, f);
	fclose(f);
	return arg;
}

void setFile(void *arg, size_t size) {
	FILE *f;
	
	if((f = fopen("user.dat", "w")) == NULL) {
		printf("connot open ffile for writting.\n");
		exit(1);
	}
	fwrite(arg, size, 1, f);
	fclose(f);
}


serverTCP.h - 서버 생성 부분

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
#include "common.h"

#define MAXLINE 2048 //buf size
#define LISTENQ 100 //ListenQ
#define THREADNUM 30 //Client connect Num

//cleint 요청 값
#define CLIID 0 //ID
#define CLIWRITE 1 //입력
#define CLIEDIT 2 //수정
#define CLIDELETE 3 //삭제
#define CLIBOOKSWRITE 4 //books 생성
#define CLIBOOKSDELTE 5 //books 삭제
#define CLIEXIT 99

extern serverData sData;
userSession session[30];

void noteServer(char *argv[]); //서버 시작
void *thrfunc(void *arg); //Thread

//노트북 관련 함수
void notebookCtrl(dataSubmit *d, userSession *s, int type); //노트북 Ctrl
void createNotebooks(char *str, time_t timer, int user); //새로운 노트북 추가
void deleteNotebooks(dataSubmit *d, userSession *s); //노트북 삭제

//Session Check
void initSession(); //세션 초기화
userSession userCreateAndCheck(char *str); //사용자 체크
int userSessionCheck(userSession *s); //사용자 Session Check

//노트 관련 함수
void noteCtrl(dataSubmit *d, userSession *s, int type); //노트 Ctrl
void createNote(dataSubmit *d, userSession *s); //새로운 노트 추가
void updateNote(dataSubmit *d, userSession *s); //노트 내용 수정
void deleteNote(dataSubmit *d, userSession *s, int type); //노트 삭제

//Client Data
void submitClient(userSession *s); //동일한 세션이 있을 경우 데이터 전송
clientData getClientData(userSession *s); //Client Data 복사


serverTCP.c

 - 서버에서 데이터를 전송하고, 받아오는 부분을 read와 write를 사용하였습니다. 하지만 구조체를 전송할 경우에는 문제가 발생합니다. 구조체가 모두 전송이 되기전에 read문을 빠져나가서 정상적인 데이터가 전송이 되지 않는 문제였습니다. 해당 부분을 해결하기 위해서 구조체를 전송할 때에는 send() 함수를 사용하였습니다.

send 함수의 원형 (참고 사이트 주소 : http://bit.thdev.net/MvakeC)

  int send(int s, const void *msg, size_t len, int flags);

  int s : sock 디스크립트

  const void * msg : 전송할 데이트 void 형태

  size_t len : 데이터의 byte 단위 길이

  int flages : MSG_DONTWAIT : 전송 준비전 대기상태가 필요할 경우 -1을 반환하고 복귀

                      MSG_NOSIGNAL : recv와의 연결이 끊어지면 시그널을 받지 않도록 처리


 Return 값

  0보다 큰 경우 실제 전송한 데이터 크기이고, -1의 경우 실패

#include "serverTCP.h"
#include "fileInOut.h"

//mutex
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int sessionCnt = 0; //session count

void noteServer(char *argv[]) { //서버를 생성하는 부분
	struct sockaddr_in servaddr, cliaddr;
	int listen_sock, accp_sock, status;
	socklen_t addrlen = sizeof(servaddr);
	pthread_t tid;

	//session 초기화
	initSession();

	if((listen_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket Fail");
		exit(0);
	}
	
	memset(&servaddr, 0, sizeof(servaddr)); //0으로 초기화
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(atoi(argv[1]));

	//bind 호출
	if(bind(listen_sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
		perror("bind Fail");
		exit(0);
	}

	while(1) {
		listen(listen_sock, LISTENQ);

		accp_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen);
		if(accp_sock < 0) {
			perror("accept fail");
			exit(0);
		}

                /*클라이언트 마다 새로운 쓰레드를 생성한다.
                    해당 부분에서는 새로운 쓰레드를 생성하는 코드만 작성하고,  종료 코드를 적용하지 않는다.
                    만약 기존 예제 코드에 있던 쓰레드 종료 대기코드를 작성하게 되면
                    1명의 사용자 쓰레드가 종료되기 전까지는 새로운 사용자가 쓰레드로 접근하여 
                    작업을 진행 할 수 없다. 그렇기에 self 쓰레드 종료를 사용 함 */
		if((status = pthread_create(&tid, NULL, &thrfunc, (void *)&accp_sock)) != 0) {
			printf("thread create error: %s\n", strerror(status));
			exit(0);
		}함
	}
}

void *thrfunc(void *arg) { //클라이언트마다 각각의 쓰레드 처리
	int accp_sock = (int) *((int*)arg);
	char buf[2048];
	int nbyte;
	userSession uSession; //세션을 보내고 받기해 생성
	clientData cData; //Client와 데이터를 주고 받기 위한 구조체

       //쓰레드를 스스로 종료하기 위해서 아래 코드를 작성
	pthread_detach(pthread_self());

	//사용자 계정 입력 대기
	puts("ID 입력 대기");
	if((nbyte = read(accp_sock, &buf, sizeof(buf))) < 0) {
		perror("read fail");
		exit(0);
	}
	buf[nbyte] = 0;
	printf("Client connect id : %s\n", buf);
	uSession = userCreateAndCheck(buf);

	write(accp_sock, &uSession, sizeof(userSession));
	printf("session:%d, userID:%d\n", uSession.session, uSession.userID);
	read(accp_sock, &buf, sizeof(buf));

	cData = getClientData(&uSession);
	send(accp_sock, &cData, sizeof(clientData), 0);

	//명령 실행
	while(1) {
	buf[0] = '\0';
	puts("Client Command Wait...");
	read(accp_sock, &uSession, sizeof(uSession));
	printf("%d User Select Type : %d\n", uSession.userID, uSession.type);

	int t = userSessionCheck(&uSession);
	write(accp_sock, &t, sizeof(int));
	if(t == -1) {
		puts("Client Session Error.....");
		close(accp_sock);
		break;
	}

	dataSubmit dSub;
	switch(uSession.type) {
	case CLIWRITE:
		printf("%d User Note Input\n", uSession.userID);
		read(accp_sock, &dSub, sizeof(dSub));
		printf("Note Messages %s\n", dSub.content);
		noteCtrl(&dSub, &uSession, 0);
		break;

	case CLIEDIT:
		printf("%d User Note Edit\n", uSession.userID);
		read(accp_sock, &dSub, sizeof(dSub));
		printf("Note Message %s\n", dSub.content);
		noteCtrl(&dSub, &uSession, 1);
		break;

	case CLIDELETE:
		printf("%d User Note Delete\n", uSession.userID);
		read(accp_sock, &dSub, sizeof(dSub));
		printf("Delete Note Num : %d\n", dSub.dataInputB);
		noteCtrl(&dSub, &uSession, 2);
		break;

	case CLIBOOKSWRITE:
		printf("%d User Note Book Create\n", uSession.userID);
		read(accp_sock, &dSub, sizeof(dSub));
		printf("Create NoteBook Name %s\n", dSub.content);
		notebookCtrl(&dSub, &uSession, 0);
		break;

	case CLIBOOKSDELTE:
		printf("%d User Note Book Delete\n", uSession.userID);
		read(accp_sock, &dSub, sizeof(dSub));
		notebookCtrl(&dSub, &uSession, 1);
		break;

	case CLIEXIT:
		puts("Client exit");
		strcpy(buf, "exit");
		write(accp_sock, &buf, sizeof(buf));
		close(accp_sock); //클라이언트가 닫혔기 때문에 종료한다
		pthread_exit((void*)NULL); //쓰레드를 종료한다.
		break;
	}
	cData = getClientData(&uSession);
	send(accp_sock, &cData, sizeof(clientData), 0);
	}
	
	return ((void*)NULL);
}

//노트북 Ctrl
void notebookCtrl(dataSubmit *d, userSession *s, int type) {
	time_t timer = time(NULL);
	
	pthread_mutex_lock(&lock);
	sData.userInfo[s->userID].updateTime = timer;
	switch(type) {
		case 0: //insert
			createNotebooks(d->content, timer, s->userID);
			break;
		case 1: //delete
			deleteNotebooks(d, s);
			deleteNote(d, s, 1);
			break;
	}
	setFile((void*)&sData, sizeof(sData)); //Data 저장
	pthread_mutex_unlock(&lock);
}

//새로운 노트북 생성 코드
void createNotebooks(char *str, time_t timer, int user) {
	int i;

	for(i=0;i<100;i++) {
		if(strcmp(sData.notebook[i].notebookTitle, "0") == 0) {
			puts("new Notebook Create");
			sData.notebook[i].notebooksID = (i+1) * 100;
			sData.notebook[i].userID = user;
			strcpy(sData.notebook[i].notebookTitle, str);
			sData.notebook[i].createTime = timer;
			sData.notebook[i].updateTime = timer;
			break;
		}
	}
}

//노트북 삭제
void deleteNotebooks(dataSubmit *d, userSession *s) {
	int i;
	for(i=0;i<100;i++) {
		if(sData.notebook[i].notebooksID == d->dataInputA && sData.notebook[i].userID == s->userID) {
			puts("NoteBook Delete");
			sData.notebook[i].notebooksID = -1;
			sData.notebook[i].userID = -1;
			strcpy(sData.notebook[i].notebookTitle, "0");
			sData.notebook[i].createTime = -1;
			sData.notebook[i].updateTime = -1;
			break;
		}
	}
}

//세션을 초기화 하기 위한 코드
void initSession() {
	int i;
	for(i=0;i<30;i++) {
		session[i].session = -1;
		session[i].userID = -1;
		session[i].type = -1;
	}
}

//새로운 사용자 인지 기존 사용자인지 세션을 체크
userSession userCreateAndCheck(char *str) {
	userSession tmpSession;
	int i;
	time_t timer;
	
	for(i=0; i < 30; i++) {
		if((strcmp(sData.userInfo[i].ID, str) == 0) && strcmp(sData.userInfo[i].ID, "0") != 0) {
			puts("new Session Create");
			pthread_mutex_lock(&lock);
			session[sessionCnt].session = sessionCnt;
			session[sessionCnt].userID = sData.userInfo[i].userID;
			session[sessionCnt].type = 0;
			tmpSession = session[sessionCnt];
			sessionCnt++;
			pthread_mutex_unlock(&lock);
			break;

		} else if(strcmp(sData.userInfo[i].ID, "0") == 0) {
			puts("new User & new Session Create");
			pthread_mutex_lock(&lock);
			timer = time(NULL);
			sData.userInfo[i].userID = ((i+1) * 10);
			strcpy(sData.userInfo[i].ID, str);
			sData.userInfo[i].createTime = timer;
			sData.userInfo[i].updateTime = timer;
			session[sessionCnt].session = sessionCnt;
			session[sessionCnt].userID = sData.userInfo[i].userID;
			session[sessionCnt].type = 0;
			createNotebooks("New Note", timer, session[sessionCnt].userID);
			tmpSe크ssion = session[sessionCnt];
			sessionCnt++;

			setFile((void *)&sData, sizeof(sData)); //Data Save
			pthread_mutex_unlock(&lock);
			break;
		}
	}
	return tmpSession;
}

//세션이 정상인지 체크
int userSessionCheck(userSession *s) {
	int i;
	for(i=0;i<30;i++) {
		if(session[i].session == s->session && session[i].userID == s->userID && session[i].session != -1) {
			return i;
		} else if(session[i].session == -1 && session[i].userID == -1)
			return -1;
	}
	return 0;
}

//노트 삭제, 추가, 수정을 위한 Ctrl
void noteCtrl(dataSubmit *d, userSession *s, int type) {
	time_t timer = time(NULL);
	
	pthread_mutex_lock(&lock);
	sData.userInfo[s->userID].updateTime = timer;
	sData.notebook[d->dataInputA].updateTime = timer;
	switch(type) {
		case 0: //insert
			createNote(d, s);
			break;
		case 1: //update
			updateNote(d, s);
			break;
		case 2: //delete
			deleteNote(d, s, 0);
			break;
	}
	setFile((void*)&sData, sizeof(sData)); //Data 저장
	pthread_mutex_unlock(&lock);
}

//새로운 노트 추가
void createNote(dataSubmit *d, userSession *s) {
	int i;
	for(i=0; i<100;i++) {
		if(sData.note[i].noteID ==  -1) {
			puts("new Note Create");
			sData.note[i].noteID = (i+1) * 100;
			sData.note[i].userID = s->userID;
			sData.note[i].notebooksID = d->dataInputA;
			strcpy(sData.note[i].noteContent, d->content);
			break;
		}
	}
}

//노트 수정
void updateNote(dataSubmit *d, userSession *s) {
	int i;
	for(i=0;i<100;i++) {
		if(sData.note[i].noteID == d->dataInputB && sData.note[i].notebooksID == d->dataInputA && sData.note[i].userID == s->userID) {
			puts("Edit Note");
			strcpy(sData.note[i].noteContent, d->content);
			break;
		}
	}
}

//노트 삭제
void deleteNote(dataSubmit *d, userSession *s, int type) {
	int i;
	switch(type) {
	case 0:
		for(i=0;i<100;i++) {
		if(sData.note[i].noteID == d->dataInputB && sData.note[i].notebooksID == d->dataInputA && sData.note[i].userID == s->userID) {
			puts("Delete Note");
			sData.note[i].noteID = -1;
			sData.note[i].userID = -1;
			sData.note[i].notebooksID = -1;
			strcpy(sData.note[i].noteContent, "0");
			break;
		}
		}
		break;
	case 1:
		for(i=0;i<100;i+사+) {
		if(sData.note[i].notebooksID == d->dataInputA && sData.note[i].userID == s->userID) {
			puts("Delete Note");
			sData.note[i].noteID = -1;
			sData.note[i].userID = -1;
			sData.note[i].notebooksID = -1;
			strcpy(sData.note[i].noteContent, "0");
		}
		}
		break;
	}
}

//Client에서 요청을 할 경우 해당 클라이언트의 데이터를 복사
clientData getClientData(userSession *s) {
	clientData cData;
	int i, k;
	for(i=0;i<100;i++) {
		cData.notebook[i].notebooksID = -1;
		cData.notebook[i].userID = -1;
		strcpy(cData.notebook[i].notebookTitle, "0");
		cData.notebook[i].createTime = -1;
		cData.notebook[i].updateTime = -1;

		cData.note[i].noteID = -1;
		cData.note[i].userID = -1;
		cData.note[i].notebooksID = -1;
		strcpy(cData.note[i].noteContent, "0");
	}
	k = 0;
	for(i=0;i<100;i++) {
		if(s->userID == sData.notebook[i].userID && sData.notebook[i].userID != -1) {
			cData.notebook[k].notebooksID = sData.notebook[i].notebooksID;
			cData.notebook[k].userID = sData.notebook[i].userID;
			strcpy(cData.notebook[k].notebookTitle, sData.notebook[i].notebookTitle);
			cData.notebook[k].createTime = sData.notebook[i].createTime;
			cData.notebook[k].updateTime = sData.notebook[i].updateTime;
			printf("%d, %s\n", cData.notebook[k].userID, cData.notebook[k].notebookTitle);
			k++;
		}
	}

	k = 0;
	for(i=0;i<100;i++) {
		if(s->userID == sData.note[i].userID && sData.note[i].userID != -1) {
			cData.note[k].noteID = sData.note[i].noteID;
			cData.note[k].userID = sData.note[i].userID;
			cData.note[k].notebooksID = sData.note[i].notebooksID;
			strcpy(cData.note[k].noteContent, sData.note[i].noteContent);
			printf("%d, %s\n", cData.note[k].noteID, cData.note[k].noteContent);
			k++;
		}
	}
	return cData;
}


startServer.c - 서버프로그램 main 문 포함 부분

#include "fileInOut.h"
#include "serverTCP.h"

#define NOTEDATA 100

serverData sData;

void createStruct();
int main(int argc, char *argv[]) {
	if((fopen("user.dat", "r")) == NULL) {
               //저장한 파일이 없을 경우 처리
		createStruct(); //struct 초기화
		setFile((void *)&sData, sizeof(sData));
	}
        //저장된 데이터를 불러온다.
	sData = (serverData) *((serverData*)getFile((void *)&sData, sizeof(sData)));
	//server Start
	noteServer(argv);

	return 0;
}

void createStruct() {
	int i;

	//user info
	for(i = 0; i<30;i++) {
		sData.userInfo[i].userID = -1;
		strcpy(sData.userInfo[i].ID, "0");
		sData.userInfo[i].createTime = -1;
		sData.userInfo[i].updateTime = -1;
	}
	
	//나머지 부분 초기화
	for(i = 0; i<100;i++) {
		sData.notebook[i].notebooksID = -1;
		sData.notebook[i].userID = -1;
		strcpy(sData.notebook[i].notebookTitle, "0");
		sData.notebook[i].createTime = -1;
		sData.notebook[i].updateTime = -1;

		sData.note[i].noteID = -1;
		sData.note[i].userID = -1;
		sData.note[i].notebooksID = -1;
		strcpy(sData.note[i].noteContent, "0");
	}
}

마무리

 위와 같은 서버 코드를 작성하였습니다. 배열을 초기화 하기위한 코드가 상당히 많이 존재합니다. 불필요한 부분도 있고. 이렇게 처리하지 않아도 되는 부분도 많을 겁니다. 지적해주셔도 좋습니다!

 이어서 Client 코드부분을 추가하겠습니다.





댓글