본문으로 바로가기

1.

▶ IP주소설정하기

 


  


내 네트워크 환경 우클릭 > 속성 > 로컬영역연결 > 속성 위에 그림 나옴 . 순서대로 쓰면됨.

IP주소는 현재 192.168.10.(출석번호+10) 로 지정해주었다.

기본 게이트 웨이 주소는 HUB 주소로 나갈때 주소를 한번에 다 관리한다.

기본 설정 DNS 서버 주소는 KT 를 사용하므로, KT DNS 서버 IP 주소를 적어주면된다.




 

            : 현재 실습중인 와우_리눅스에서만 가능함. setup 이라 치면됨.

             IP 주소

             Netmask 저절로 적힘.

             gateway 주소 (HUB주소) 192.168.10.1 정해져있음.

             KT DNSS 주소 : 168.126.63.1 



                          : 와우리눅스의 현재 네트워크 상태를 항상 켜져있는 상태로 체크

                      

< 윈도우에서 바뀐 IP주소 >


< 리눅스에서 바뀐 IP주소 >


             : telnet google.com 80 으로 테스트 해보면 연결이 되있는걸 확인할 수 있음.

             참고 : Domain 주소창에 google.com/80

                                              daum.net/80  

                                              naver.com/80   .. 처럼 80을 포트번호로 지정해놨음. 일반적으로 다 똑같아서 생략을 해놓는다.





2.-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

▶ TCP/IP_Socket설정하기_ 개념 설명.


: IP주소(4byte) + PORT(2byte) = 6byte 차지함. 

: Server 는 항상 고정적, Client 는 항상 유동적,




DNS Server 란 Domain name service Server 를 뜻함. 이름으로 검색하면 DNS Server에 접속하여 IP주소로 변환시켜서 반환하고, 주소를 보내면 이름으로 반환해줌.

심볼테이블 개념이라고 보면 되겠다. 







▶ server.c

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define  MAXPENDING 5 // 서버에서 받을 허용 인원.

int main()
{
 int servSock; // 서버소켓 
 int clntSock; // 클라이언트소켓
 int iRet;  // 저장할 변수
 unsigned char uc_buff[500];  // 채팅시에 문자열을 받는 버퍼.
 
 struct sockaddr_in echoServAddr; // 신구조체의 서버IP를 가지는 변수.
 struct sockaddr_in echoClntAddr; // 신구조체의 클라이언트IP를 가지는 변수

 unsigned short echoServPort;  // Port 번호를 가지는 변수. 2byte 크기.
 unsigned int clntLen;    // 클라이언트
  
 echoServPort = 9999; // Port 번호 0~65535 범위를 가짐.
 
 // Socket 생성 
 //프로토콜패밀리결정, 소켓의형태(데이타보내는방식,SOCK_STREAM or SOCK_DGRAM)설정,
 //STREAM인지DGRAM인지에 따라서 IPPROTO_TCP , IPPROTO_UDP 로 정해줌. 
 //신뢰성 : STREAM (데이터의 송수신값을 확인하며 보냄),ex)일반파일
 //비신뢰성 : DGRAM(데이터을 확인없이 한번에 보냄),ex)MP3..
 servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
 if( 0 > servSock ) // Error 코드
 {
  printf("socket() failed\n");
  return 0;
 }
   
 // 메모리 크기를 특정값으로 초기화 여기서는 0 으로 만들어줌.
 memset(&echoServAddr, 0, sizeof(echoServAddr));
 // #define AF_INET PF_INET 2 로 define 되어있음.  패밀리선택, 우리는 PF_INET 을 쓴다.
 echoServAddr.sin_family = AF_INET; 
 // Host to Network long 으로 host의 크기를 Long형(4byte)만큼 
 // 지정하고 little-Endian -> Big-Endian방식으로 보내줌.
 // 0 을 넣어주면 Host에서 주소를 랜덤으로 줌, 고정적으로 사용하고 싶을때는 직접 주소값을 넣어줌.
 echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
 // Host to Network short 으로 host의 크기를 short형(2byte)만큼 
 // 지정하고 little-Endian -> Big-Endian방식으로 보내줌.
 // 0 을 넣어주면 Host에서 주소를 랜덤으로 줌, 고정적으로 사용하고 싶을때는 직접 주소값을 넣어줌.
 echoServAddr.sin_port = htons(echoServPort); // Port는 정해져있으므로 정해줌. 

 // 구조체 안에 설정한것을  소켓에다가 넣어줌. bind()
 iRet = bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr));

 // if조건문을 위해서 iRet 변수를 지정.
 if( 0 > iRet )
 {
  printf("bind() failed\n");
  close(servSock); // 서버소켓 닫기.
  return 0;
 }
 
 // listen(서버소켓, 받을 클라이언트 수) 
 // if조건문을 위해서 iRet 변수를 지정.
 iRet = listen(servSock,MAXPENDING); // Client 연결할때마다 하나씩 새로운 소켓을 얻는데 사용.
 if( 0 > iRet)
 {
  printf("listen() failed\n");
  close(servSock);
  return 0;
 }
 
 // 서버로 들어오는 클라이언트연결에대해서 accept()를 호출하면서 소켓을 만든다.
 clntLen = sizeof(echoClntAddr); // clntLen로 Client 크기를 받고, clntLen의 주소를 인자로 넘김.
 // 대기중에 있다가 서버에 접속허용을 받으면 Client 소켓을 만듬. 강제로 대기(블로킹함수)
 clntSock = accept(servSock, (struct sockaddr *)&echoClntAddr,&clntLen);
 // 소켓은 2가지가 있는데 랑데뷰소켓, 커뮤니케이션소켓이 있다.
 // accept 함수에 의해서 랑데뷰소켓이 생성됨.(랑데뷰소켓은 Clinet 의 주소를 들고있음)
 
 if( 0 > clntSock ) // 클라이언트소켓 생성 확인 구문 
 {
  printf("accept() failed\n");
  close(servSock);
  return 0;
 }

 // Network to ASCII, 클라이언트IP를 문자열[(ex)192.211.10.20]로 바꿔서 출력해줌.
 printf("Handling client IP %s\n", inet_ntoa(echoClntAddr.sin_addr)); 
 // Port 번호를 Network to Host short형 크기로 출력해줌.
 printf("Handling client PORT  %d\n", ntohs(echoClntAddr.sin_port));
 
 while(1)
 {
  iRet = read(clntSock,uc_buff,sizeof(uc_buff)-1); // read함수로 상대방말을 읽어옴.
  uc_buff[iRet] = 0;      
  write(1,uc_buff,iRet);
  if( 'q' == uc_buff[0] )
  {
   break; // 'q' 를 눌러서 종료.
  }
 }

 close(servSock); // 서버소켓을 닫기.
 close(clntSock); // 클라이언트소켓을 닫기.

 return 0;
}






3.-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 ▶ TCP/IP select 함수 활용.

 

멀티 플렉싱을 구현하기위해서 Select 함수가 가장 많이 사용하고 있다.

 

멀티플렉싱이란?

하나의 전송로를 여러 사용자가 동시에 사용해서 효율성을 극대화하는 것

 

I/O 멀티플렉싱이란?

  • 클라이언트와 입/출력하는 프로세스를 하나로 묶어버리는 형식
  • 프로세스가 고속의 전송로에 해당합니다.

아래와 같은 형식을

입/출력 프로세스를 하나로 묶어버립니다.

멀티 프로세스 VS 멀티플렉싱

  1. 멀티 프로세스 기반의 서버 ( Thread )
  • 클라이언트와 서버간의 송수신 데이터의 용량이 큰 경우 적합
  • 송수신이 쉬지않고 연속적으로 발생하는 경우 적합
  1. 멀티플렉싱 기반의 서버 ( Select )
  • 클라이언트와 서버간의 송수신 데이터 용량이 작은 경우 적합
  • 송수신이 연속적이지 않은 경우에 적합
  • 멀티 프로세스 기반에 비해 많은 수의 클라이언트 처리에 적합

 ▶ Select() 함수에 대한 설명.

파일 디스크립터의 변화를 확인하는 함수
기본적으로 blocking 함수(확인할 파일 디스크립터에 변화가 생길 때까지 무한 대기)

멀티플렉싱 서버를 구현하기 위한 방법으로 select 함수가 가장 많이 사용되는 방법이고 윈도우즈 시스템에서도 동일한 이름으로 동일한

기능을 하는 함수를 제공하고 있으니 이식성에서도 높은 점수를 줄 수 있다

select 함수를 사용하게 되면, 한 곳에 모아놓은 여러 개의 파일 디스크립터를 동시에 관찰할 수 있다.
수신할 데이터를 지니고 있는 파일 디스크립터가 어떤 것들인지, 데이터를 전송할 경우 블로킹되지 않고 바로 전달 가능한 파일디스크립터는

어떤 것들인지, 그리고 예외가 발생한 파일 디스크립터는 어떤 것들인지 정도가 관찰 내용이 된다.

select 함수의 기능과 호출 순서

-- select 함수 사용 순서 --
1. 디스크립터 설정
2. 검사 범위 설정
3. 타임 아웃 설정
4. select 함수 호출
5. 결과 확인
-------------------------



===== 디스크립터 설정 =====

1) 파일 디스크립터 설정
  - 변화를 확인할 파일 디스크립터들을 한 묶음으로 모아둔다.
  - 파일 디스크립터를 모아두는 비트단위 자료형 fd_set 이용
  - 3가지 변화(수신 데이터 존재유무, 데이터 송신 가능여부, 소켓의 예외상황 발생여부)별로 파일 디스크립터들을 구분지어 모아둠



fd_set 자료형 관련 함수
 FD_ZERO(fd_set * fdset); //fd_set 초기화 함수
 FD_SET(int fd, fd_set * fdset); //해당 파일디스크립터 fd를 1로 셋
 FD_CLR(int fd, fd_set * fdset); //해당 파일디스크립터 fd를 0으로 셋
 FD_ISSET(int fd, fd_set * fdset); //해당 파일디스크립터 fd가 1인지 확인


===== 검사범위 설정 =====

2) 검사할 파일 디스크립터의 범위 지정
  - 검사해야할 파일 디스크립터의 개수를 전달.
  - 가장 큰 파일 디스크립터 값에 1을 더함(파일 디스크립터 값이 0부터 시작하므로)


===== 타임아웃 설정 =====

3) 타임 아웃 설정
  - select 함수가 blocking되는 것을 피하기 위해 타임 아웃을 설정함. 

===== select 함수의 원형 ===== 

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int n, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval * timeout);

   리 턴 값   의    미
   -1   오류 발생
    0   타임 아웃
    0보다 큰 수   변화발생 파일 디스크립터 수


- n : 검새 대상이 되는 파일 디스크립터의 수

- readfds : 이 리스트에 있는 식별자들은 시스템에 의해 즉시 입력이 가능한지 확인된다. 즉, 입력 가능으로 인해 반환된 식별자에 대해 recv()는 블로킹되지 않는다.

- writefds : 이 리스트에 있는 식별자들은 시스템에 의해 즉시 출력이 가능한지 확인된다, 즉 출력 가능으로 인해 반환된 식별자에 대해 send()는 블로킹되지 않는다.

- excepfds : 이 리스트에 있는 식별자들은 시스템에 의해 예상되는 예외 사항이나 에러가 발생했는지 확인된다. TCP소켓에서 발생할 수 있는 이러한 예상되는 예외 사항의 한 예로 상대방이 데이터 전송 중에 TCP연결을 끊었을 때가 있다. 이런 경우, 다음에 이어지는 읽기 또는 쓰기는 실패하고 ECONNRESET에러를 나타낸다.

- timeout : 함수 호출 후, 무한 대기 상태에 빠지지 않도록 타임-아웃(time-out)을 설정하기 위해
 인자를 전달한다.

※ select 함수 호출시 전달되는 파일 디스크립터의 정보를 소켓뿐 아니라 파일을 나타내는 경우에도
   전달 가능한다.


===== 결과 확인 =====


fd_set자료형에 파일디스크립터 0(stdin), 3의 변화를 확인하기 위해 설정




Select() 호출하면 변화가 생긴 파일디스크립터는 1로 세팅됨(그림에서 파일디스크립터 3이 변화가 발생한 것을 확인할 수 있음)

- 변화가 확인된 해당 파일디스크립터와 데이터 통신을 진행하면 된다.


[ 파일 디스크립터 범위 설정하기 ]

select 함수는 여러 파일 디스크립터를 검사하고, 그 결과를 전달해 준다. 
select 함수는 여러 개의 파일 디스크립터를 확인해야 하는데, 이왕이면 확인해야 하는 파일 디스크립터의 범위를 제한해 주면, 보다 효율적으로 수행할 수 있다.
그래서 select 함수의 첫 번째 인자로 검사해야 하는 총 디스크립터의 개수를 넘겨주게 된다.
그러나 일반적으로 디스크립터는 생성될 때마다 값이 1씩 증가하기 때문에 가장 큰 파일 디스크립터 값에 1을 더해서 인자로 전달하면 된다.
1을 더하는 이유는 디스크립터 값이 0부터 시작하기 때문이다. 따라서 인자로 n이라는 값을 넘겨주게 되면 select 함수는 검사하게 되는 파일 디스크립터의 범위를 부터 n-1로 설정된다.
따라서 반드시 1을 더해줘야 한다.


[ 타임아웃 (time out) 설정하기 ]

타임아웃을 설정하기 위한 timeval 구조체

struct timeval
{
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
}
예를 들어서 tv_sec가 3이고 tv_usec가 500000 이면 타임아웃은 3.5초로 설정된다. 
이렇게 설정한 timeval 
구조체 변수의 포인터를 select 함수의 마지막 인자(timeout 인자)로 넘겨주게 되면 파일 디스크립터에 아무런 변화가 없더라도 3.5초가 지나면 무좋건 리턴하게 된다. 만약에 타임 아웃을 설정해 주지 않을 경우, NULL 포인터를 인자로 전달하면 된다.


[ select 함수 호출 이후 결과 확인 ]
select 함수가 리턴되고 나서 무엇보다도 중요한 것은 결과를 얻는 것이다. 일단 함수 호출이 정상적으로 리턴했다는 것은 파일 디스크립터에 변화가 있엇거나, 아니면 타임아웃이 발생했거나 둘 중에 하나이다. 

리턴 값이 -1인 경우는 오류발생을 의미한다. 또한 0이 리턴 된 경우에는 타임아웃에 의해 리턴되었음을 의미한다. 즉 0이 리턴된 경우 파일 디스크립터에 아무런 변화도 발생하지 않았다는 의미가 된다. 
그러나 리턴된 값이 0보다 큰 경우에는, 변화가 발생한 파일 디스크립터의 수를 의미하게 된다.
예를 들어 수신할 데이터가 존재하는 파일 디스크립터가 두 개 발생했다면, 2가 리턴될 것이다.

-------- select 호출 전 / 후 --------

==select 호출 전 ==
fd0  fd1  fd2  fd3  fd4
| 1 | 0 | 0 | 1 | 0 | 0 |...........................

==select 호출 후==
fd0  fd1  fd2  fd3  fd4
| 0 | 0 | 0 | 1 | 0 | 0 |...........................

 

------------------------------------------------- 예 제 소 스 ----------------------------------------------------

 

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
 unsigned char uc_key[500];
 int iRet;
 
 fd_set test; // fd_set 자료형이다. 구조체임 
 printf("[%d]\n", sizeof(test));
 FD_ZERO(&test);  // 구조체의 주소. test의 메모리 모두 0 으로 초기화.
 FD_SET(0,&test); // 메모리의 제일 앞에(파일디스크립터0) 1을 넣음.     키보드 자리, stdin
 FD_SET(1,&test); // 메모리의 두번째(파일디스크립터1) 자리에 1을 넣음. 모니터 자리, stdout
 
 select(2,&test,&test,0,0); // stdin, stdout 두개를 감지, 읽는거/쓰는거 감지, 예외안씀, 타임아웃안씀
 iRet = read(0,uc_key,sizeof(uc_key)); // 키보드 입력값 받음.
 write(1,uc_key,iRet); // 버퍼에 값을 다시 화면에 출력함.
 if( 1 == FD_ISSET(0,&test) && 1 == FD_ISSET(1,&test) ) // 변화확인.
 {
  printf("키보드감지\n");
  printf("모니터출력감지\n");
 }
 
 return 0;
}

 

         ▶ 예제 소스

 

: 결과 출력. fd_set 의 크기 ( 128byte ) 와 FD_SET 으로 모니터 , 키보드의 변화를 감지하면 if문을 통해서 감지되었음을 확인 할 수 있다.

















 기본이론정리

 .반이중 통신
양쪽 방향으로 신호의 전송이 가능하기는 하지만 경우에 따라 반드시 한쪽 방향으로만 전송이 이루어지게 한 방식을 말합니다.

주컴퓨터와 단말기가 반이중 방식으로 통신할 경우, 주컴퓨터가 단말기에 데이터를 보내는 동안은 단말기에서 데이터를 입력할 수 없으며, 반대로 단말기에서 데이터가 입력되고 있는 동안은 주컴퓨터가 단말기로 데이터를 보낼 수 없습니다

.전이중 통신
송신을 하면서 동시에 수신도 할 수 있는 방식을 말한다.

.server의 랑데뷰 소켓
accept를 하게되면 서버에 랑데뷰 소켓을 1차적으로 사용하게 됩니다 

랑데뷰 소켓이란 ?
 클라이언트에서 보내져서 큐상에서 대기하고 있는 정보(서버와 통신을 원하는 클라이언트의 정보)를 두번째 인자에 연결한 다음 클라이언트소켓과 연결된 새로운 소켓을 반환합니다
앞으로 그 클라이언트와의 통신은 반환된 소켓 디스크립터를 사용하게됩니다
반환값-- 성공시 communication 소켓 디스크립터, 실패시 -1을 반환
이 communicatoin 소켓을 이용하여 통신을 합니다

이러한 랑데뷰소켓은 4번부터 지정이되고 

 Ierr 랑데뷰  


서버로 접속하는 클라이언트는 5번부터 접속이됩니다

 Ierr 랑데뷰  클라이언트



.멀티플렉싱을 활용한 서버
:멀티플렉싱이란 값을 하나하나 대입하여 찾는 풀링방식과 특정 값을 호출하기까지 대기하는 인터럽트방식을 결합한 방식을 뜻합니다 간단하게 구조를 그린다면 

 while(무한반복)
 검사 및 세팅
 select
 accept
 read & write


의 구조로 되어 있습니다 여기서  select가 인터럽트 방식에 해당되고 read write accept처리에는 풀링방식이 사용됩니다

 

 

 

Sever.c 소스

 

 #include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define  MAXPENDING 5 // 서버에서 받을 허용 인원.
#define  MAXUSER 2  // 소켓으로 받을 사람수.

int main()
{
 int servSock; // 서버소켓 
 int clntSock[MAXUSER]; // 클라이언트소켓 2개를 받음.
 unsigned int ui_User; // 접속자 수
 int iMaxSock; // select함수의 첫번째 인자.
 int tempSock; // 임시 소켓.
 int iRet;  // 저장할 변수
 unsigned char uc_buff[500];  // 채팅시에 문자열을 받는 버퍼.
 
 struct sockaddr_in echoServAddr; // 신구조체의 서버IP를 가지는 변수.
 struct sockaddr_in echoClntAddr; // 신구조체의 클라이언트IP를 가지는 변수

 unsigned short echoServPort;  // Port 번호를 가지는 변수. 2byte 크기.
 unsigned int clntLen;    // 클라이언트 변수
 unsigned int iCnt;       // for 문 돌리기위함.
 unsigned int iCnt2;     // for 문 돌리기위함.
 fd_set fsStatus;         // fd_set 구조체, select 함수를 쓰기위해서 
  
 echoServPort = 9999; // Port 번호 0~65535 범위를 가짐.
 
 // Socket 생성 
 //프로토콜패밀리결정, 소켓의형태(데이타보내는방식,SOCK_STREAM or SOCK_DGRAM)설정,
 //STREAM인지DGRAM인지에 따라서 IPPROTO_TCP , IPPROTO_UDP 로 정해줌. 
 //신뢰성 : STREAM (데이터의 송수신값을 확인하며 보냄),ex)일반파일
 //비신뢰성 : DGRAM(데이터을 확인없이 한번에 보냄),ex)MP3..
 servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
 if( 0 > servSock ) // Error 코드
 {
  printf("socket() failed\n");
  return 0;
 }
   
 // 메모리 크기를 특정값으로 초기화 여기서는 0 으로 만들어줌.
 memset(&echoServAddr, 0, sizeof(echoServAddr));
 // #define AF_INET PF_INET 2 로 define 되어있음.  패밀리선택, PF_INET 을 쓴다.
 echoServAddr.sin_family = AF_INET; 
 // Host to Network long 으로 host의 크기를 Long형(4byte)만큼 
 // 지정하고 little-Endian -> Big-Endian방식으로 보내줌.
 // 0 을 넣어주면 Host에서 주소를 랜덤으로 줌, 고정적으로 사용하고 싶을때는 직접 주소값을 넣어줌.
 echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
 // Host to Network short 으로 host의 크기를 short형(2byte)만큼 
 // 지정하고 little-Endian -> Big-Endian방식으로 보내줌.
 // 0 을 넣어주면 Host에서 주소를 랜덤으로 줌, 고정적으로 사용하고 싶을때는 직접 주소값을 넣어줌.
 echoServAddr.sin_port = htons(echoServPort); // Port는 정해져있으므로 정해줌. 

 // 구조체 안에 설정한것을  소켓에다가 넣어줌. bind()
 iRet = bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr));

 // if조건문을 위해서 iRet 변수를 지정.
 if( 0 > iRet )
 {
  printf("bind() failed\n");
  close(servSock); // 서버소켓 닫기.
  return 0;
 }
 
 // listen(서버소켓, 받을 클라이언트 수) 
 // if조건문을 위해서 iRet 변수를 지정.
 iRet = listen(servSock,MAXPENDING); // Client 연결할때마다 하나씩 새로운 소켓을 얻는데 사용.
 if( 0 > iRet)
 {
  printf("listen() failed\n");
  close(servSock);
 
  return 0;
 }
 
 // 서버로 들어오는 클라이언트연결에대해서 accept()를 호출하면서 소켓을 만든다.
 clntLen = sizeof(echoClntAddr); // clntLen로 Client 크기를 받고, clntLen의 주소를 인자로 넘김.
 // 대기중에 있다가 서버에 접속허용을 받으면 Client 소켓을 만듬. 강제로 대기(블로킹함수)

 iMaxSock = servSock+1; // Sock의 제일 큰번호를 의미 , 제일큰번호 + 1
 ui_User = 0;   // 접속자 수.
 
 while(1)
 { 
  FD_ZERO(&fsStatus);  // 파일디스크립트 모두 초기화.
  FD_SET(0,&fsStatus); // 키보드(stdin:0번)입력 비트를 상태확인 위해 SET으로 바꿈. 
  FD_SET(servSock,&fsStatus); // 서버소켓상태 확인을 위해 SET으로 바꿈.
  
  for(iCnt2=ui_User; iCnt2 > 0; --iCnt2)  // ui_User 접속상태일때 동작.
  {
   FD_SET(clntSock[iCnt2-1],&fsStatus); // 클라이언트소켓상태 확인을 위해 SET으로 바꿈.
   if(iMaxSock <= clntSock[iCnt2-1])
   {
    iMaxSock = clntSock[iCnt2-1] + 1;    
   }
  }
  
  select(iMaxSock,&fsStatus,0,0,0); // 소켓과키보드,읽어오는상태확인,
  if( 1 == FD_ISSET(servSock,&fsStatus)) // 랑데뷰소켓감지.
  {
   tempSock = accept(servSock, (struct sockaddr *)&echoClntAddr,&clntLen);
   // 소켓은 2가지가 있는데 랑데뷰소켓, 커뮤니케이션소켓이 있다.
   // accept 함수에 의해서 랑데뷰소켓이 생성됨.(랑데뷰소켓은 Clinet 의 주소를 들고있음)
   // 랑데뷰소켓이 Client의 정보를 들고 있다가 
   if( 0 > tempSock ) // 클라이언트소켓 생성 확인 구문 
   {
    printf("accept() failed\n");
    continue;
   }
  
   // Network to ASCII, 클라이언트IP를 문자열[(ex)192.211.10.20]로 바꿔서 출력해줌.
   printf("Handling client IP %s\n", inet_ntoa(echoClntAddr.sin_addr)); 
   // Port 번호를 Network to Host short형 크기로 출력해줌.
   printf("Handling client PORT  %d\n", ntohs(echoClntAddr.sin_port)); 
   
   if(ui_User >= MAXUSER) // 접속자수 제한. 접속자가 5명이 넘어가면 끊고 새로받음.
   {
    close(tempSock);
    continue;
   }
   clntSock[ui_User] = tempSock;
   ++ui_User;
   printf("현재 접속자수는 [%d]명 입니다.\n", ui_User);
  }
  else if( 1 == FD_ISSET(0,&fsStatus) ) // 키보드 입력을 받으면 if 문 실행.
  {
   iRet = read(0, &uc_buff, 500); // 키보드로 버퍼에 저장.
   for(iCnt=ui_User;iCnt > 0; --iCnt)
   {           // 반복문을 통해서 
    write(clntSock[iCnt-1],&uc_buff, iRet); // 접속한 사람한테 모두 다 날림.
   } 
  } 
  else
  {
   for(iCnt=ui_User;iCnt > 0; --iCnt) // 접속자 모두에게 순서대로 메시지를 보냄.
   { // 여러명중에 특정 Client가 보낸 메시지를 각각 화면에 출력함.
    if( 1 == FD_ISSET(clntSock[iCnt-1],&fsStatus) ) // 커뮤니케이션소켓상태 변화가 생기면 if 문 실행.
    {// read함수로 접속한 client로 부터 글자를 받음.
     iRet = read(clntSock[iCnt-1],uc_buff,sizeof(uc_buff)-1); 
     printf("[Client%d: ", iCnt-1);//보낸이 확인.
     fflush(stdout);  // '\n' 비워줌.
    // uc_buff[iRet] = 0; // '\n'문자를 '\0' 으로 변환
     write(1,uc_buff,iRet-1); // 화면에 Client로 부터 온 글자를 출력.
     printf("]\n");// '\n' 출력, 
     // 여기까지는 모두 서버에 출력됨.

     for(iCnt2=ui_User; iCnt2 > 0; --iCnt2)
     {
      write(clntSock[iCnt2-1],&uc_buff, iRet); // 접속한 사람한테 모두 다 날림.
     }
     
    // write(clntSock,uc_buff,iRet); // 클라이언트소켓에 uc_buff에 있는 글자를 출력.
    
    }
   }
   
   if( 'q' == uc_buff[0] ) // q 가 uc_buff에 차면 종료.
   {
    break; // 'q' 를 눌러서 종료.
   }
   
  }
 }

 for(iCnt2=ui_User; iCnt2 > 0; --iCnt2)
 {
  close(clntSock[iCnt-2]); // 클라이언트소켓을 닫기.
 }
 close(servSock); // 서버소켓을 닫기.
 return 0;
}


 

 

 

 Client.c

 #include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define RCVBUFSIZE 500

void my_bzero(void *vp, unsigned int ui_size);
void my_memset(void *vp, unsigned char uc_pad, unsigned int ui_size);

int main(int argc, char *argv[])
{
 int sock;
 struct sockaddr_in echoservaddr;
 unsigned short echoservport;
 char *servIP; // IP 주소.
 char *echoString;
 char Buffer[RCVBUFSIZE];
 unsigned int echoStringLen;
 int bytesRcvd;
 int totalBytesRcvd;
 int iRet;
 fd_set fsStatus;
 argc = 1;

 servIP = argv[1]; // IP 주소 변수.

 sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 소켓 생성.

 if( 0 > sock )
 {
  printf("socket() failed\n");
  return 0;
 }
 // 구조체 설정.
    my_memset(&echoservaddr, 0, sizeof(echoservaddr));  // 메모리 초기화.
 echoservaddr.sin_family  = AF_INET;    // 프로토콜 설정. 
 echoservaddr.sin_addr.s_addr= inet_addr(servIP); // 주소 지정.
 echoservaddr.sin_port  = htons(9999); //  short형으로 host>network 크기지정.

 // 고객이 은행을 찾아가는 것. 서버의 accept() 함수랑 비슷.
 iRet = connect(sock, (struct sockaddr *)&echoservaddr, sizeof(echoservaddr));

 if( 0 > iRet )
 {
  printf("connect() failed\n");
  close(sock);
  return 0;
 }

 
 while(1)
 {
  FD_ZERO(&fsStatus); // 파일디스크립트 모두를 초기화(메모리 초기화)
  FD_SET(0,&fsStatus); // 키보드출력(stdin) 0번 비트를 지정 1로 변경, 상태를 알기위함.
  FD_SET(sock,&fsStatus); // 소켓을 지정, 저수준입/출력 변화 상태를 알기위함. 
        // 여기선 Client에서 보내온 문자.
        
  // 소켓과키보드(변수갯수), 문자읽기상태확인(변수의주소), 
  select(sock+1,&fsStatus,0,0,0); // 문자쓰기상태확인(변수의주소),예외처리(변수주소),select가 반환전까지 시간.
  
  if( 1 == FD_ISSET(0,&fsStatus) ) // 키보드의상태가 변화가있으면 1임. 없으면 0.
  {         // 변화가 있다면 if 문 실행.
   iRet = read(0,Buffer,sizeof(Buffer)); // 키보드로 받은 문자를 버퍼에 저장.
   //Buffer[iRet-1] = '\0';     // '\n' 문자를 '\0' 으로 받기위함.
   write(sock,Buffer,iRet); // iRet로 받은갯수만큼, Buffer에 저장후 글자를 출력.
   //putchar('\n');   // 비교를 위해 엔터를 쳐줌. 
   //iRet = read(sock,Buffer,500); // 위의 글자를 다시 버퍼에 저장. 
   //Buffer[iRet-1] = '\0';    // '\n' 문자를 '\0' 으로 받기위함.

   //printf("[%s]\n", Buffer);  // Buffer 에 있는 글자를 화면에 출력.
  }
  else if( 1 == FD_ISSET(sock,&fsStatus) ) // Server에서 보낸 문자가 생기면 sock에 변화가 생김.
  {          // 변화가 있다면 if 문 실행.
   iRet = read(sock,Buffer,500); // Server에서 보낸 문자 버퍼에서 읽어옴.
   printf("[Server : "); // 보낸이 확인.
   fflush(stdout);   // '\n' 비워줌.
   write(1, Buffer, iRet-1);// 서버 문자를 화면에 출력. 개행문자 제거.
   printf("]\n"); // 출력후 '\n'
  }
  if( 'q' == Buffer[0] )  // q 눌르면 종료.
  {
   break;
  }
 }
  
 close(sock); // 소켓 해제.
 return 0;
}

// memset 은 메모리에 원하는 값으로 채워줌.
void my_memset(void *vp, unsigned char uc_pad, unsigned int ui_size)
{
 while( 0 !=  ui_size )  // 메모리에 uc_pad의 값이 다 채워지면 반복문을 나옴.
 {       // ui_size가 0이 되면 나옴.
  *(unsigned char *)vp = uc_pad;
  --ui_size;
  vp = (unsigned char *)vp+1;  // void* 타입을 unsigned char 형으로 타입으로 바꿔서 
 }         // 옮길 바이트(unsigned char)만큼을 지정해줬음.
}      

void my_bzero(void *vp, unsigned int ui_size)
{
 my_memset(vp, 0x00,ui_size); 
}