티스토리 뷰

사용언어) C, C++

 

2번) 명령행 인자로 숫자를 입력받아 해당 숫자만큼 서버에 연결하는 TCP 클라이언트를 MultiConnectTCPClient 이름으로 작성하시오.

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#define BUFSIZE 512
#define THREADSIZE 1024

short PORTNUM = 0;
char IP[10];
int total = 0;
CRITICAL_SECTION cs;
void err_quit(const char* msg);
DWORD WINAPI SendServer(LPVOID arg);
int recvn(SOCKET s, char* buf, int len, int flag);

int main()
{
    int n = 0;
    InitializeCriticalSection(&cs);
    printf("MultiConnectTCPClient ");
    scanf("%s %hd %d", IP, &PORTNUM, &n);
    getchar();
    HANDLE* hThread = new HANDLE[n];
    total = n;
    for (int i = 0; i < n; i++) {
        hThread[i] = CreateThread(NULL, 0, SendServer, (LPVOID)(i+1), 0, NULL);
        if (hThread[i] == NULL) return 1;
    }
    while (1) {
        if (total == 0)
            break;
    }
    printf("프로그램 종료\n");
    delete[] hThread;
    DeleteCriticalSection(&cs);
    return 0;
}

void err_quit(const char* msg) {
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
    printf("[%s] %s", msg, (char*)lpMsgBuf);
    LocalFree(lpMsgBuf);
}

int recvn(SOCKET s, char* buf, int len, int flag) {
    int received;
    char* ptr = buf;
    int left = len;

    while (left > 0) {
        received = recv(s, ptr, left, flag);
        if (received == SOCKET_ERROR)
            return SOCKET_ERROR;
        else if (received == 0)
            break;
        left -= received;
        ptr += received;
    }
    return (len - left);
}

DWORD WINAPI SendServer(LPVOID arg) {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("dlload error\n");
        return 1;
    }

    SOCKET sockfd;
    char buf[BUFSIZE];
    struct sockaddr_in sockaddr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == INVALID_SOCKET)
    {
        printf("socket create error\n");
        return 1;
    }

    ZeroMemory(&sockaddr, sizeof(sockaddr));
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(PORTNUM);
    sockaddr.sin_addr.s_addr = inet_addr(IP);

    if (::connect(sockfd, (SOCKADDR*)&sockaddr, sizeof(sockaddr))) err_quit("Connet");

    while(1){
        //데이터 보내기(고정길이)
        fgets(buf, BUFSIZE, stdin);

        int retVal = send(sockfd, buf, strlen(buf), 0);
        if (retVal == SOCKET_ERROR) {
            err_quit("Send");
            break;
        }
        printf("[TCP 클라이언트] %d바이트를 보냈습니다.\n", retVal);

        retVal = recvn(sockfd, buf, retVal, 0);
        if (retVal == SOCKET_ERROR) {
            err_quit("Recv");
            break;
        }
        if (retVal == 0)
            break;
        buf[retVal] = '\0';
        printf("입력 받은 데이터 : %s[%d바이트]\n", buf, retVal);
    }
    printf("%d번 쓰레드 종료됨\n", (int)arg);
    closesocket(sockfd);
    WSACleanup();
    //임계영역
    EnterCriticalSection(&cs);
    total -= 1;
    LeaveCriticalSection(&cs);
    return 0;
}

 원래는 WaitForMultipleObject를 쓸려고 했지만, nCount가 64개 이상이 안되서 그냥 전역변수로 체크하였습니다.


3장 FD_SETSIZE 재정의

단순히 FD_SETSIZE의 숫자만 

//WinSock2.h

#ifndef FD_SETSIZE
#define FD_SETSIZE      128

 

4번) 무수히 많은 소켓을 처리할 수 있는 SelectTCPServer를 만들어라(쓰레드 풀), 한 스레드랑 SETSIZE 만큼 소켓을 소환할 수 있다.

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")
#include<stdio.h>
#include<Winsock2.h>
#include<stdlib.h>


#define PORT	9000
#define BUFSIZE 512
#define THREADSIZE 10

//소켓 정보 저장을 위한 구조체와 변수
struct SOCKETINFO {
    SOCKET sock;
    char buf[BUFSIZE + 1];
    int recvBytes;
    int sendBytes;
};

//멀티쓰레드 관리 변수들
int FULL = FALSE;
int Thread_total = 0;
HANDLE ThreadPool[THREADSIZE];
//각 스레드가 가지고 있는 스레드 갯수
int nTotalSockets[THREADSIZE] = { 0, };
//각 스레드는 FD_SETSIZE(64)개씩 스레드를 저장함.
SOCKETINFO* SocketInfoArray[THREADSIZE][FD_SETSIZE];

//Listen SOCKET
SOCKET listen_s;

CRITICAL_SECTION cs;
//소켓 관리 함수
BOOL AddSocketInfo(SOCKET sock, int len);
void RemoveSocketInfo(int nIndex, int len);

//쓰레드 프로시저
DWORD WINAPI ClientThread(LPVOID arg);
//오류 출력 함수
void err_quit(const char* msg);

int main()
{
    WSADATA wsaData;
    SOCKADDR_IN sock_addr;

    //윈속 초기화
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        err_quit("WSAStartup");
    InitializeCriticalSection(&cs);

    //소켓 생성
    listen_s = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_s == INVALID_SOCKET)
        err_quit("socket");

    ZeroMemory(&sock_addr, sizeof(struct sockaddr_in));
    sock_addr.sin_family = PF_INET;
    sock_addr.sin_port = htons(PORT);
    sock_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    for (int i = 0; i < THREADSIZE; i++) {
        ThreadPool[i] = CreateThread(NULL, 0, ClientThread, (LPVOID)i, CREATE_SUSPENDED, NULL);
        if (ThreadPool[i] == NULL) return 1;
    }
    if (bind(listen_s, (struct sockaddr*)&sock_addr, sizeof(struct sockaddr_in)) == SOCKET_ERROR) err_quit("Bind");
    if (listen(listen_s, SOMAXCONN) == SOCKET_ERROR) err_quit("listen");

    //비동기 소켓으로 전환 하는 과정
    u_long on = 1;
    int retVal = ioctlsocket(listen_s, FIONBIO, &on);
    if (retVal == SOCKET_ERROR) err_quit("ioctlsocket()");

    FD_SET rset;
    SOCKET clientsock;
    SOCKADDR_IN clientaddr;
    int addrlen;

    while (1) {
        //소켓 초기화
        FD_ZERO(&rset);
        FD_SET(listen_s, &rset);

        //select()
        retVal = select(0, &rset, nullptr, nullptr, nullptr);
        if (retVal == SOCKET_ERROR) err_quit("select()");

        //소켓 셋 검사 : 클라이언트 접속 수용
        if (FD_ISSET(listen_s, &rset)) {
            addrlen = sizeof(clientaddr);
            clientsock = accept(listen_s, (SOCKADDR*)&clientaddr, &addrlen);
            if (clientsock == INVALID_SOCKET) {
                err_quit("accept()");
            }
            else {
                //accept로 생성한 clientsock을 자리가 비어있는 thread에게 전달하는 과정
                for (int i = 0; i < THREADSIZE; i++) {
                    //만약 64개가 아니라면 아직 비어있는 쓰레드이기 때문에 작동중인 thread에게 전달
                    if (nTotalSockets[i] != 64 && nTotalSockets[i] != 0) {
                        AddSocketInfo(clientsock, i);
                        printf("[TCP서버 %d] 클라이언트 접속 IP(%3d) : %s %d\r\n", i, nTotalSockets[i],
                            inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
                        break;
                    }
                    //새로운 쓰레드 작동하여 추가
                    else if (nTotalSockets[i] == 0) {
                        printf("%d번 쓰레드 재시작", i);
                        AddSocketInfo(clientsock, i);
                        ResumeThread(ThreadPool[i]);
                        Thread_total++;
                        printf("[TCP서버 %d] 클라이언트 접속 IP(%3d) : %s %d\r\n", i, nTotalSockets[i],
                            inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
                        break;
                    }
                }
            }
        }
    }
    printf("프로그램을 종료합니다.\n");
    DeleteCriticalSection(&cs);
    closesocket(listen_s);
    return 0;
}

DWORD WINAPI ClientThread(LPVOID arg) {

    WSADATA wsaData;
    int len = (int)arg;
    //윈속 초기화
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        err_quit("WSAStartup");
    int retVal;
    printf("%d번 쓰레드 작동\n", len);
    //데이터 통신에 사용할 변수
    FD_SET rset, wset;
    SOCKADDR_IN clientaddr;
    int addrlen, i;
    bool type = TRUE;
    while (1) {
        //소켓 초기화
        FD_ZERO(&rset);
        FD_ZERO(&wset);
        FD_SET(listen_s, &rset);
        for (int i = 0; i < nTotalSockets[len]; i++) {
            if (SocketInfoArray[len][i]->recvBytes > SocketInfoArray[len][i]->sendBytes)
                FD_SET(SocketInfoArray[len][i]->sock, &wset);
            else
                FD_SET(SocketInfoArray[len][i]->sock, &rset);
        }

        //select()
        retVal = select(0, &rset, &wset, nullptr, nullptr);
        if (retVal == SOCKET_ERROR) err_quit("select()");

        //소켓 셋 검사 : 데이터 통신
        for (i = 0; i < nTotalSockets[len]; i++) {
            SOCKETINFO* ptr = SocketInfoArray[len][i];
            if (FD_ISSET(ptr->sock, &rset)) {
                //데이터 받기
                retVal = recv(ptr->sock, ptr->buf, BUFSIZE, 0);
                if (retVal == SOCKET_ERROR) {
                    err_quit("Recv()");
                    RemoveSocketInfo(i, len);
                    continue;
                }
                else if (retVal == 0) {
                    RemoveSocketInfo(i, len);
                    continue;
                }
                else if (retVal == 1) {
                    type = FALSE;
                }
                ptr->recvBytes = retVal;
                addrlen = sizeof(clientaddr);
                getpeername(ptr->sock, (SOCKADDR*)&clientaddr, &addrlen);
                ptr->buf[retVal] = '\0';
                printf("[TCP(%d)/%s : %d] %s\r\n", len, inet_ntoa(clientaddr.sin_addr),
                    ntohs(clientaddr.sin_port), ptr->buf);
            }
            if (FD_ISSET(ptr->sock, &wset)) {
                //데이터 전달
                retVal = send(ptr->sock, ptr->buf + ptr->sendBytes, ptr->recvBytes - ptr->sendBytes, 0);
                if (retVal == SOCKET_ERROR) {
                    err_quit("send()");
                    RemoveSocketInfo(i, len);
                    continue;
                }
                ptr->sendBytes += retVal;
                if (ptr->recvBytes == ptr->sendBytes) {
                    ptr->recvBytes = ptr->sendBytes = 0;
                }
            }
            if (!type) {
                RemoveSocketInfo(i, len);
                type = TRUE;
            }
        }
        if (nTotalSockets[len] == 0) {
            //임계영역으로 토탈갯수 줄임
            EnterCriticalSection(&cs);
            Thread_total--;
            LeaveCriticalSection(&cs);
            printf("%d번(%d) 쓰레드 잠듬\n", len, nTotalSockets[len]);
            SuspendThread(ThreadPool[len]);
        }
    }
    WSACleanup();
    printf("%d번(%d) 쓰레드 종료\n", len, nTotalSockets[len]);
    
    return 0;
}

//소켓 정보 추가
BOOL AddSocketInfo(SOCKET sock, int len) {
    if (nTotalSockets[len] >= FD_SETSIZE) {
        printf("[오류] 소켓 정보를 추가할 수 없습니다. %d\n", nTotalSockets[len]);
        return FALSE;
    }

    SOCKETINFO* ptr = new SOCKETINFO;
    if (ptr == NULL) {
        printf("[오류] 메모리가 부족합니다.\n");
        return FALSE;
    }

    ptr->sock = sock;
    ptr->recvBytes = 0;
    ptr->sendBytes = 0;
    SocketInfoArray[len][nTotalSockets[len]++] = ptr;

    return TRUE;
}

//소켓 정보 삭제
void RemoveSocketInfo(int nIndex, int len) {
    SOCKETINFO* ptr = SocketInfoArray[len][nIndex];

    //클라 정보 얻기
    SOCKADDR_IN clientaddr;
    int addrlen = sizeof(clientaddr);
    getpeername(ptr->sock, (SOCKADDR*)&clientaddr, &addrlen);

    printf("[TCP 서버] 클라이언트 종료 : IP  주소 = %s, 포트번호 = %d\r\n",
        inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

    closesocket(ptr->sock);
    delete ptr;

    if (nIndex != (nTotalSockets[len] - 1))
        SocketInfoArray[len][nIndex] = SocketInfoArray[len][nTotalSockets[len] - 1];

    --nTotalSockets[len];
}
void err_quit(const char* msg) {
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
    printf("[%s] %s\n", msg, (char*)lpMsgBuf);
    LocalFree(lpMsgBuf);
    exit(1);
}
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함