티스토리 뷰
사용언어) 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);
}
'서버 공부 > 네트워크' 카테고리의 다른 글
TCP/IP) Overlapped I/O(2) 모델(CallBack) (0) | 2022.07.12 |
---|---|
TCP/IP) Overlapped I/O(1) 모델(Event) (0) | 2022.07.11 |
TCP/IP) WSAEventSelect 모델 통신 (0) | 2022.07.09 |
TCP/IP) Select 모델 서버 (0) | 2022.07.08 |
이상적인 소켓 입출력 모델의 특징 (0) | 2022.07.08 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 지뢰찾기
- 더블버퍼링
- 컨퍼런스
- 시스템보안
- STL
- 자료구조
- 학교
- 보안
- Dreamhack
- 인제대학교
- Select모델
- 정보보안
- 알고리즘
- 링크드 리스트
- 레지스터
- 멀티쓰레드
- c++
- 드림핵
- 워셜알고리즘
- BFS
- 개발
- queue
- 고양이
- 스레드풀
- 백준
- 야경
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
글 보관함