Select()를 이용해 채팅 서버-클라이언트(Standalone방식?) 프로그래밍 중 질문드립니다.

theageha의 이미지

/*
Student ID : 20031943
Name : 박성진
*/
 
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define MAXSOCK 100
#define MAXLINE 1000
 
int tcpSockets[MAXSOCK];	// List of sockets
int connCount=0;	// connection count
 
void removeClient(int i);
int getmax(int k);
void error_handling(char *command);
 
int main(int argc, char **argv)
{
	int tcpServ_sock;	// a TCP server Socket
	int udpServ_sock;	// a UDP server Socket
	int tcpRcv_sock;
	int udpCnt_sock;
	int tcpCnt_sock;
 
	fd_set reads, temps;
	int fd_max, fd, tcpReq;
 
	char msg[MAXLINE];
	char rcvmsg[MAXLINE];
	char command[MAXLINE];
	char udpReq_msg[MAXLINE];
	char udpRcv_msg[MAXLINE];
	char tcpReq_msg[MAXLINE];
	char closemsg[MAXLINE];
 
	char *comm, *reqhost, *udpPort, *tcpPort, *host;
 
	struct sockaddr_in tcpServ_addr;
	struct sockaddr_in udpServ_addr;
	struct sockaddr_in tcpReq_addr;
	struct sockaddr_in udpReq_addr;
 
	int tcp_byte_read, udpReq_addr_size, udp_byte_read, tcpReq_addr_size, close_byte_read, rcv_byte_read;
	int i,j,k, testn;
 
	if(argc != 4){
		printf("Usage : %s <tcpport> <udpport> <userid>\n", argv[0]);
		exit(1);
	}
 
	printf("Student ID :20031943\n");
	printf("Name : Park Sung-jin\n");
 
	if((tcpServ_sock=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))<0)
		error_handling("TCP server socket opening is failed!\n");
 
	if((udpServ_sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP))<0)
		error_handling("UDP server socket opening is failed!\n");
 
	memset(&tcpServ_addr, 0, sizeof(tcpServ_addr));
	tcpServ_addr.sin_family=AF_INET;
	tcpServ_addr.sin_addr.s_addr=htonl(INADDR_ANY);  // need modifying !!!!!!!!!!!
	tcpServ_addr.sin_port = htons(atoi(argv[1]));
 
	if(bind(tcpServ_sock, (struct sockaddr*)&tcpServ_addr, sizeof(tcpServ_addr))<0)
		error_handling("TCP server binding is failed!\n");
 
	if(listen(tcpServ_sock, MAXLINE)<0)
		error_handling("listening is failed!\n");
 
 
	FD_ZERO(&reads);
	FD_SET(fileno(stdin), &reads);
	FD_SET(tcpServ_sock, &reads);
	FD_SET(udpServ_sock, &reads);
	fd_max = udpServ_sock;
 
	printf("%s>\n", argv[3]);
 
	while(1){
			int nfound;
			temps = reads;
 
			<span>fd_max=getmax(tcpServ_sock);</span>
			if(nfound = select(fd_max+1, &temps, 0, 0, NULL)<0)
				error_handling("Select is failed!\n");
 
			for(i=0; i<connCount; i++)         //check;                                                     
				FD_SET(tcpSockets[i], &temps);					
 
			if(FD_ISSET(fileno(stdin), &temps)) {
				FD_CLR(fileno(stdin), &temps);
 
				fgets(command, MAXLINE, stdin);
				comm=strtok(command, " ");
				reqhost=strtok(NULL, " ");
				udpPort=strtok(NULL, "\n");
 
				if(strncmp(comm, "@c", 2)==0){
					if((udpCnt_sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP))<0)
							error_handling("UDP Client socket opening is failed!\n");
					sprintf(udpReq_msg, "Connect %s", argv[1]); //creat udp message
					sendto(udpCnt_sock, udpReq_msg, strlen(udpReq_msg), 0,(struct sockaddr*)&udpServ_sock, sizeof(udpServ_addr));
					close(udpCnt_sock);
				}/*
				else if(strncmp(comm, "@q", 2)==0){
					if((udpCnt_sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP))<0)
						error_handling("UDP Client socket opening is failed!\n");
 
					memset(&tcpServ_addr, 0, sizeof(tcpServ_addr));
					tcpServ_addr.sin_family=AF_INET;
					tcpServ_addr.sin_addr.s_addr=htonl(INADDR_ANY);  // need modifying !!!!!!!!!!!
					tcpServ_addr.sin_port = htons(atoi(argv[2]));
 
					sprintf(udpReq_msg, "@q"); //creat udp message
					sendto(udpCnt_sock, udpReq_msg, strlen(udpReq_msg), 0,(struct sockaddr*)&tcpServ_sock, sizeof(tcpServ_addr));
					close(udpCnt_sock);
					exit(1);
				}
					*/
				printf("%s>", argv[3]);
			}
 
			else if(FD_ISSET(udpServ_sock, &temps)){
				FD_CLR(udpServ_sock, &temps);
				printf("udpServ check");
 
				memset(&udpServ_addr, 0, sizeof(udpServ_addr));
				udpServ_addr.sin_family=AF_INET;
				udpServ_addr.sin_addr.s_addr=htonl(INADDR_ANY);  // need modifying !!!!!!!!!!!
				udpServ_addr.sin_port = htons(atoi(argv[2]));
 
				if(bind(udpServ_sock, (struct sockaddr*)&udpServ_addr, sizeof(udpServ_addr))<0)
					error_handling("UDP server binding is failed!\n");
 
				// NEED TO IMPLEMENT 
				// Other user sent a connection request via UDP message 
				udpReq_addr_size=sizeof(udpReq_addr);
				udp_byte_read=recvfrom(udpServ_sock, udpRcv_msg, MAXLINE, 0, (struct sockaddr*)&udpReq_addr, &udpReq_addr_size);
 
				strtok(udpRcv_msg, " ");
				host=strtok(NULL, " ");
				tcpPort=strtok(NULL, "\n");
 
				memset(&tcpServ_addr, 0, sizeof(tcpServ_addr));
				tcpServ_addr.sin_family=AF_INET;
				tcpServ_addr.sin_addr.s_addr=htonl(atoi(host));  // need modifying !!!!!!!!!!!
				tcpServ_addr.sin_port = htons(atoi(tcpPort));
 
				if((tcpCnt_sock=socket(PF_INET, SOCK_STREAM,0))<0)
						error_handling("TCP client socket opening is failed!\n");
 
				if(connect(tcpCnt_sock, (struct sockaddr*)&tcpServ_addr, sizeof(tcpServ_addr))<0)
						error_handling("TCP connection is failed\n");		
			}
 
			else if(FD_ISSET(tcpServ_sock, &temps))	{
				FD_CLR(tcpServ_sock, &temps);
				tcpReq_addr_size=sizeof(tcpReq_addr);
 
				if(tcpRcv_sock=accept(tcpServ_sock, (struct sockaddr*)&tcpReq_addr, &tcpReq_addr_size)<0)
					error_handling("Accept is failed\n");		
 
				tcpSockets[connCount]=tcpRcv_sock;
				connCount++;
				// NEED TO IMPLEMENT 
				// Other user requested a TCP connection 
			}
			// for messages from other users
			else if(FD_ISSET(tcpCnt_sock, &temps)){
				FD_CLR(tcpCnt_sock, &temps);
 
				if((rcv_byte_read = recv(tcpCnt_sock,rcvmsg,MAXLINE, 0 ))<0)
					error_handling("recieve error\n");
				rcvmsg[rcv_byte_read] = '\0';
				printf("%s : %s\n", argv[3], rcvmsg);
			}
 
			<span>for (fd = 0; fd <= fd_max ; fd++) {</span>				/* look for other sockets that have data available */
				if (FD_ISSET(fd, &temps)) {
					FD_CLR(fd, &temps);
					// NEED TO IMPLEMENT 
					// read the message from the socket fd
					if((tcp_byte_read = recv(tcpSockets[fd],msg,MAXLINE, 0 )) <= 0) {
						removeClient(i);
						continue;
					}
					if(strstr(msg,"@q") != NULL) {
						sprintf(closemsg,"Connection Closed %d\n", fd);
						for(k=0;k<connCount;k++);
							send(tcpSockets[k], closemsg, strlen(closemsg), 0);
						removeClient(i);
						continue;
					}
					/* 모든 채팅 참가자에게 메시지 방송 */
					msg[tcp_byte_read] = '\0';
					for(j = 0; j < connCount; j++)
						send(tcpSockets[j],msg,tcp_byte_read,0);
							printf("%s\n",msg);
					// write the message to other neighbors
					// print the message on the screen
				}
			}
  }//while End
  close(tcpServ_sock);
  close(udpServ_sock);	
}//main End
 
int getmax(int k)
{
	int max = k;
	int r;
	for(r = 0; r < connCount ; r++)                                                              /* 채팅 참가자 수만큼 소켓 배열 탐색 */
		if(tcpSockets[r] > max)
			max = tcpSockets[r];
	return max;
}
 
void error_handling(char *command){
	fputs(command, stderr);
	fputc('\n', stderr);
		exit(1);
}
void removeClient(int i)                                                                        /* i번째 참가자 탈퇴 */
{
	close(tcpSockets[i]);                                                                         /* 해당 소켓 닫기 */
	if(i != connCount-1)
		tcpSockets[i] = tcpSockets[connCount -1];                                                    /*  i번째 참가자와 맨 마지말 참가장의 위치 switch*/
	connCount --;                                                                                /* 채팅 참가자 수 1명 줄이기 */
}

여기저기서 짜집기하구, 급하게 만들어서 보시기 좀 불편하실 수도 있겠네요;;

1. 중간에 fd_max 저렇게 해도 되나요? max=udpServ_sock; 이후에 fd_max=getMax(tcpServ_sock) 하게 되면 값이 바뀌지 않나요?

2. main문 맨 아래 for 루프에서 왜 fd=0 부터인지 모르겠습니다. 파일디스크립터가 3번부터 부여되는 것으로 알고 있는데..
게다가 이미 3번tcpServer, 4번udpServer 로 부여가 되었을텐데, 그럼 5번부터 돌아야하는거 아닌가요?
그래서 5번-최초의 tcpClient 사용자가 되겠죠-에 출력할 내용이 있으면 출력!;;

3.실제로 컴파일 해보면 udp클라이언트에서 udp 서버로 요청하는 부분부터 안넘어가집니다.
토큰으로 명령어 다 짤라내서 요청 메세지를 만들어 내는데, 그 메세지가 udpServer쪽으로 넘어갔는지 확인할 방법이 없네요.ㅠ
제가 코딩을 잘못한 것일까요?ㅠ

kgykingdom의 이미지

1. getmax 함수가 어떻게 되어 있는지 모르겠지만 현재 설정된 파일 디스크립트 보다 큰 값이
들어올 경우에 업데이트 해주는거 같으니 상관없을 거 같네요. 당연히 connection이 추가 되서
파일 디스크립트가 추가 되면 fd_max 도 증가 시켜줘야 겠지요..

2. fd 가 0번 부터 되는건 코드 보시면 아실수 있으실 텐데요.

	FD_ZERO(&reads);
	<span>FD_SET(fileno(stdin), &reads);</span>
	FD_SET(tcpServ_sock, &reads);
	FD_SET(udpServ_sock, &reads);

보시면 0번도 세팅합니다. (0: input, 1: output, 2: error)
자세힌 모르겠으나 사용자가 입력하는 명령어에 대해서도 처리를 해줄려고 저리 했나 보네요.

3. udp는 connectionless 였던가 아무튼 tcp 랑 다르게 보내면 땡입니다. 수신측에서 잘 받았는지
안받았는지.. 신경 안쓰죠..

허접한 답변이지만 도움이 됬으면 하네요.. 그럼 수고하십시오.^^;;

theageha의 이미지

설명해주신 부분은 이해가 되었습니다.
그래서 sendto가 리턴하는 것을 이용해서 메세지가 날라갔는지 확인해보니 -1, 즉 실패하더라구요;;
왜그런지 모르겠습니다.ㅠㅠ 혹시 알고 계시면 답변 부탁드립니다.
혹시 몰라서 주소 구조체 셋팅을 다시 했습니다. 그런데도 안돼네요..

if(strncmp(comm, "@c", 2)==0){
	if((udpCnt_sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP))<0)
	   error_handling("UDP Client socket opening is failed!\n");
	sprintf(udpReq_msg, "Connect %s", argv[1]); //creat udp message
 
        memset(&udpServ_addr, 0, sizeof(udpServ_addr));
	udpServ_addr.sin_family=AF_INET;
	udpServ_addr.sin_addr.s_addr=htonl(atoi(reqhost));
	udpServ_addr.sin_port = htons(atoi(udpPort));
 
        if((testn=sendto(udpCnt_sock, udpReq_msg, strlen(udpReq_msg), 0,(struct sockaddr*)&udpServ_addr
           , sizeof(udpServ_addr)))<0);
	   error_handling("UDP message sending error");
	close(udpCnt_sock);
}

----------------------------------------------------------------------------
삼인행 필유아사언(三人行 必有我師焉) - 세 사람이 길을 가면 그 중에 반드시 스승이 있다.

----------------------------------------------------------------------------
삼인행 필유아사언(三人行 必有我師焉) - 세 사람이 길을 가면 그 중에 반드시 스승이 있다.

theageha의 이미지

진짜...이 부분 왜 안되는지도저히 모르겠어요..ㅠㅠ

윗쪽에서 이미 udpServ_addr에 대한 주소 구조체를 초기화 시켜줘서 그런걸까요?

혹시 몰라서 udpServ2_addr을 따로 만들고, 서버소켓도 하나 더 만들어서 테스트 했는데도 안돼요..

계속 sending error 메시지만 나오구.....udp는 서버안키고 그냥 전송해도 전송까지는 되어야 되는거 아닌가요?

도착하는걸 확인안한다고 했으니...전송까지라도 되야되는데..ㅠ

----------------------------------------------------------------------------
삼인행 필유아사언(三人行 必有我師焉) - 세 사람이 길을 가면 그 중에 반드시 스승이 있다.

----------------------------------------------------------------------------
삼인행 필유아사언(三人行 必有我師焉) - 세 사람이 길을 가면 그 중에 반드시 스승이 있다.

댓글 달기

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