● NetWork
□ select 함수를 이용한 채팅프로그램 (3회차)
- 종료시 한개의 client가 종료가 되도록 교체
![](https://t1.daumcdn.net/cfile/tistory/256AC34A55DD7E2608)
![](https://t1.daumcdn.net/cfile/tistory/2763B04855DD7E3932)
=====================================================================================
=====================================================================================
코드 짜면서 주석으로 설명 적었습니다.
server에서의 부하를 줄이기 위해, server에서는 정보만 받고, client에서 계산 후 정보를 보낸다.
#ifndef __SMARTSOCK_H__ #define __SMARTSOCK_H__
#include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> // fork 헤더
#define PORT 7773 // server PORT #define IP "192.168.0.122" // server IP //#define IPc "192.168.0.222" // client IP
#define MSG_SIZE (BUFFER_SIZE+1) + NIC_NAME_MSG // Message size #define BUFFER_SIZE 255 // Buffer size #define MSG_END "\x01\x02\x03" // 제어문자 #define MAX_USER 30 // User counter #define NIC_NAME_SIZE 9 // 한글 닉네임 size #define NIC_NAME_MSG (9+2) // 닉네임 Message
#endif // __SMARTSOCK_H__
|
=====================================================================================
=====================================================================================
#include "smartsock.h"
int main() { int isock; // 서버 소켓 생성 int iret; // error 확인 int iMsock; // 제일 큰 소켓 번호저장 int icsock[MAX_USER]; // client 접속 수 unsigned int uiUser; // 접속 유저 수 unsigned int uicnt; unsigned int uiTcnt; struct sockaddr_in st_addr; // 소켓 구조체 char cbuffer[BUFFER_SIZE]; // 버퍼 사이즈 char cMSG[MSG_SIZE]; // 메세지 사이즈 char cNic[MAX_USER][NIC_NAME_SIZE]; // 한글 닉네임, 유저 수 + 9byte fd_set fdRead; // 1024비트 socklen_t uisocklen = sizeof(struct sockaddr); //클라이언트 소켓
isock = socket(AF_INET , SOCK_STREAM , 0); // 서버 소켓 생성, isock에 소켓 입력
if(isock < 0) // 소켓 생성체크 { perror(" isock error"); close(isock); return; }
bzero(&st_addr, sizeof(st_addr)); // st_addr만큼 st_addr를 0으로 초기화 st_addr.sin_family = AF_INET; // IPv$ 인터넷 프론토콜 st_addr.sin_addr.s_addr = inet_addr(IP); // 32 bit IPv4 주소 st_addr.sin_port = htons(PORT); // 사용할 PORT 번호
// bind함수를 이용하여 소켓에 isock에 필요한 정보를 할당, 커널에 등록 iret = bind(isock , (struct sockaddr *)&st_addr , sizeof(st_addr)); // 생성한 소켓을 st_addr로 등록
if (iret < 0) // bind 실행 확인 { perror(" bind error : "); close(isock); return; }
iret = listen(isock, 5); // 클라이언트 접속 요청 확인
if (iret < 0) { perror(" listen error : "); // 대기상태 모드 설정 실패 return; }
uiUser = 0; // 접속 client수 초기화.
while(1) { FD_ZERO(&fdRead); // 0으로 리셋 FD_SET(0, &fdRead); // 소켓 입력 감시 FD_SET(isock ,&fdRead); // 접속 소켓과 select를 감시
iMsock = isock; // 제일 큰 소켓 번호 저장
for(uicnt = 0 ; uicnt < uiUser ; ++uicnt) // clinet 접속자 수 카운터 for문 { FD_SET(icsock[uicnt] ,&fdRead); // client 감시
if( iMsock < icsock[uicnt] ) // 앞의 client가 나가면 맨뒤에 것을 맨앞으로 보낸다 { iMsock = icsock[uicnt]; } }
select( iMsock + 1, &fdRead , 0 , 0 , 0 ); // 제일 높은 저수준 +1 , 읽기 주소
if(0 != FD_ISSET( isock , &fdRead ) ) // client가 들어오는걸 체크 { // 클라이언트 접속 요청에 따라 accept으로 접속을 허락 icsock[uiUser] = accept(isock,(struct sockaddr *)&st_addr,&uisocklen); // 접속자 정보가 들어간다
if (icsock[uiUser] < 0) // 클라이언트 연결 실패 { perror(" Accept error : "); continue; }
read( icsock[uiUser] , cNic[uiUser] , NIC_NAME_SIZE ); // client 닉네임 읽기 printf(" incoming client : [%s]님 입장\n",cNic[uiUser]); // 클라이언트 접속 연결 성공
// inet_ntoa 함수로 st_addr.sin_addr 구조체 안의 문자열을 출력 printf(" Client IP [ %s ]\n", inet_ntoa(st_addr.sin_addr)); // IP write(icsock[uiUser],"[server Connect Ok]\n",sizeof("[server Connect Ok]\n")); // 클라이언트에 문자 보내기
sprintf(cMSG,"[%s]님이 입장하였습니다.",cNic[uiUser]); // client 접속 메세지
for(uiTcnt = 0 ; uiTcnt < uiUser ; ++uiTcnt) // 소켓 메세지 전송 for문 { //if(uicnt != uiTcnt) // 보낸곳 제외 uiUser++ 이 밑에 있으면 없어도됨 write( icsock[uiTcnt] , cMSG , MSG_SIZE ); // 각 메세지를 각 client에 전송 }
uiUser++; // client가 접속하면 접속 유저수 +1 }
if(0 != FD_ISSET( 0 , &fdRead ) ) // 키보드 입력이 들어오는걸 체크 { iret = read( 0 , cbuffer , BUFFER_SIZE ); // 키보드입력을 버퍼에 저장 cbuffer[iret - 1] = 0; // 입력 끝에 Null 삽입 sprintf( cMSG , "공지사항 [%s]" , cbuffer); // server에서 client에 메세지 전달
for(uicnt = 0 ; uicnt < uiUser ; ++uicnt) // client 접속자 수 카운터 for문 { write( icsock[uicnt] , cMSG , MSG_SIZE ); // 접속 client에 입력 글자 전송 } }
for(uicnt = 0 ; uicnt < uiUser ; ++uicnt) // client 접속자 수 카운터 for문 { if(0 != FD_ISSET( icsock[uicnt] , &fdRead ) ) // 각 소켓 정보를 들어오는걸 체크 { read( icsock[uicnt] , cMSG , MSG_SIZE ); // 각 소켓 메세지 읽기
if( 0 == strncmp( MSG_END , cMSG , sizeof(MSG_END) ) ) // 접속 종료 { sprintf(cMSG , "[%s]퇴장하였습니다.",cNic[uicnt]); // client 퇴장 close(icsock[uicnt]); // 한개의 client가 접속을 끊으면 끈긴다 --uiUser; // 접속 유저 수 -1 icsock[uicnt] = icsock[uiUser]; // 나간 client 자리에 제일 뒤 client 유저를 삽입 memcpy( cNic[uicnt] , cNic[uiUser] , sizeof(NIC_NAME_SIZE) ); // 복사 }
for(uiTcnt = 0 ; uiTcnt < uiUser ; ++uiTcnt)// 소켓 메세지 전송 for문 { write( icsock[uiTcnt] , cMSG , MSG_SIZE ); // 각 메세지를 각 client에 전송 } } } }
close(isock); // 소켓을 닫는다
for(uicnt = 0 ; uicnt < uiUser ; ++uicnt) // client 접속자 수 카운터 for문 { close(icsock[uicnt]); // client 수 만큼 차례대로 접속 종료 } return 0; }
==================================================================================== ==================================================================================== |
#include "smartsock.h"
int main() { int ifd; // 클라이언트 소켓 생성 int iLen; int iRet; char cBuff[MSG_SIZE]; // 버퍼 사이즈 char cMSG[MSG_SIZE]; // 메세지 사이즈 char cNic[NIC_NAME_SIZE]; // 닉네임 사이즈 struct sockaddr_in stAddr; // 소켓 구조체 fd_set fdRead;
ifd = socket(AF_INET, SOCK_STREAM,0); // 클라이언트 소켓 생성, isock에 소켓 입력
if(-1 == ifd) { perror(" Sock() Call Error\n"); return 100; }
bzero(&stAddr, sizeof(stAddr)); // stAddr만큼 stAddr를 0으로 초기화
stAddr.sin_family = AF_INET; // IPv$ 인터넷 프론토콜 stAddr.sin_addr.s_addr = inet_addr(IP); // 32 bit IPv4 주소 stAddr.sin_port = htons(PORT); // 사용할 PORT 번호
iLen = sizeof(stAddr);
while(1) { printf(" 닉네임을 입력하세요 : "); fflush(stdout); // 보안 때문에 scanf를 쓰지 않는다 iRet = read( 0 , cNic , NIC_NAME_SIZE ); // 닉네임을 사이즈 만큼 읽는다
if( 2 > iRet ) // 0 [Ctrl+d], 1 [Enter] 감지 { continue; }
cNic[iRet - 1] = 0; // 읽어드린 닉네임 뒤에 Null //fflush(stdin); // 버퍼 남은걸 비운다 break; }
iRet = connect(ifd , (struct sockaddr *) &stAddr , iLen );
if(-1 == iRet) { perror(" Connect() Call Error\n"); close(ifd); return 200; }
write(ifd , cNic , NIC_NAME_SIZE ); // 닉네임을 client에 보낸다
while(1) { FD_ZERO(&fdRead); // 0으로 리셋 FD_SET(0, &fdRead); // 키보드 입력 감시 FD_SET(ifd, &fdRead); // 받는 소켓과 select를 감시 select( ifd+1 , &fdRead , 0 , 0 , 0); // 제일 높은 저수준 +1 , 읽기 주소
if(0 != FD_ISSET( 0 , &fdRead ) ) // 키보드 입력시 { iRet = read(0 , cBuff , MSG_SIZE); // 키보드 입력 받은 만큼 읽기
if(0 == iRet) // 연결 종료 { break; }
cBuff[iRet - 1] = 0; // 키보드 입력 문자 뒤 0 삽입 sprintf(cMSG , "[%s]%s", cNic , cBuff ); // cMsg에 저장 write(ifd , cBuff , MSG_SIZE); // 키보드 입력 문자 보내기 printf(" [%s] MSG : %s \n",cNic , cBuff); // 보낸 문자 printf //if(0 == strncmp( cBuff , MSG_END , strlen(MSG_END) ) ) // 연결 종료 }
if(0 != FD_ISSET( ifd , &fdRead ) ) // ifd 소켓 송신 { read(ifd , cMSG , MSG_SIZE); // 받는 메세지 읽기 printf(" [Server MSG] : %s \n", cMSG); // 받은 메세지 printf } }
write( ifd , MSG_END , sizeof(MSG_END) ); // 세련된 연결종료 close(ifd); return 0; }
|
=====================================================================================
=====================================================================================
<실행 결과>
![](https://t1.daumcdn.net/cfile/tistory/2143854A55DD7E7531)
client가 종료시 밑의 표처럼 정렬된다.
![](https://t1.daumcdn.net/cfile/tistory/2777D64A55DE57E703)
안향진씨 코드 받아서 쓰는게 좋을듯 합니다.