스마트팩토리부트갬프 2기 채팅방 구현하기(server.cpp)

Chaedonghyun·2023년 5월 13일

앞에 client.cpp를 했기 때문에 server.cpp를 해보겠다. 처음에는 client쪽에서 모든 기능을 구현 할려 했지만 dm기능은 server쪽에서 구 현 하는게 편할거 같아서 server쪽에서 채팅내용 저장과 dm기능을 구현을 했다.
#pragma comment(lib, "ws2_32.lib")

#include <WinSock2.h> //소켓사용을 위한 헤더
#include < string>
#include < iostream>
#include < thread>
#include < vector>
#include < sstream>
#include <mysql/jdbc.h>

#define MAX_SIZE 1024
#define MAX_CLIENT 4

using std::cout;
using std::endl;
using std::string;
using std::cin;
using std::vector;

const string server = "tcp://127.0.0.1:3306";
const string username = "root";
const string password = "abc1234";

sql::mysql::MySQL_Driver driver;
sql::Connection
con;
sql::Statement stmt;
sql::PreparedStatement
pstmt;
sql::ResultSet* result;

struct SOCKET_INFO
{
SOCKET sck;
string user;
};
vector<SOCKET_INFO> sck_list;
SOCKET_INFO server_sock;
int client_count = 0;

void server_init(); //socket, bind,listen 함수가 들어있다
void add_client(); //accept함수
void send_msg(const char msg); //전체메시지 내용을 모든 접속한 인원한테 보내준다.
void recv_msg(int idx); //recv함수
void del_client(int idx); //채팅방을 나가면 clientlist에서 빠지는 역할을 한다.
void send_msg_dm(const char
msg, string recv);
//dm메시지 대상자한테만 보내주는 함수
int main()
{
WSADATA wsa;

int code = WSAStartup(MAKEWORD(2, 2), &wsa);

if (!code)
{
     //sql연결문
    try {
        driver = sql::mysql::get_mysql_driver_instance();
        con = driver->connect(server, username, password);
    }
    catch (sql::SQLException& e) {
        cout << "Could not connect to server. Error message: " << e.what() << endl;
        exit(1);
    }

    con->setSchema("project");
    //한글을 받아도 깨지지않게 해준다
    stmt = con->createStatement();
    stmt->execute("set names euckr");
    if (stmt) { delete stmt; stmt = nullptr; }


    server_init(); 
    std::thread th1[MAX_CLIENT];
    
    for (int i = 0; i < MAX_CLIENT; i++)
    {
        th1[i] = std::thread(add_client);
    }

    while (1)
    { 
        //client에게 send를 받으면 내용을 buf에 받는다.
        string text, msg = "";
        std::getline(cin, text);
        const char* buf = text.c_str();
        msg = server_sock.user + ":" + buf;
        send_msg(msg.c_str());
    }

    for (int i = 0; i < MAX_CLIENT; i++)
    {
        th1[i].join();
    }

}
else
{
    cout << "프로그램 종류. (Error Code:" << code << ")";
}

WSACleanup();

}

void server_init() {

server_sock.sck = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //소켓 생성

SOCKADDR_IN server_addr = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 //소켓의 주소를 할당한다.
bind(server_sock.sck, (sockaddr*)&server_addr, sizeof(server_addr));
//소켓을 활성화하한다.
listen(server_sock.sck, SOMAXCONN);
server_sock.user = "server";
cout << "open" << endl;

}

void add_client() {
SOCKADDR_IN addr = {};
int addrsize = sizeof(addr);
char buf[MAX_SIZE] = { };

ZeroMemory(&addr, addrsize);
SOCKET_INFO new_client = {};
//accept:클라이언트의 연결 요규를 들어올 때까지 대기한다.
new_client.sck = accept(server_sock.sck, (sockaddr*)&addr, &addrsize);
recv(new_client.sck, buf, MAX_SIZE, 0);
new_client.user = string(buf);

string msg = new_client.user + "님이 입장했습니다";
cout << msg << endl;
sck_list.push_back(new_client);

std::thread th(recv_msg, client_count);
client_count++;

cout << "현재 인원수:" << client_count << "명" << endl;
send_msg(msg.c_str());

th.join();

}

//전체 채팅 내용을 모든 유저 한테 보내주는 함수
void send_msg(const char* msg) {

for (int i = 0; i < client_count; i++)
{
    send(sck_list[i].sck, msg, MAX_SIZE, 0);
}

}
//recv를 받아 user들중 똑같은 id를 가지 사람한테만 보내준다.(dm기능)
void send_msg_dm(const char* msg, string recv) {

for (int i = 0; i < client_count; i++)
{
    if (sck_list[i].user == recv) {
        send(sck_list[i].sck, msg, MAX_SIZE, 0);
        break;
    }
}

}

void recv_msg(int idx) {
char buf[MAX_SIZE] = { };
string msg = "";
while (1)
{
ZeroMemory(&buf, MAX_SIZE);
if (recv(sck_list[idx].sck, buf, MAX_SIZE, 0) > 0)
{
cout << sck_list[idx].user + ":" + buf << endl;
string msg1 = buf; //dm기능이 ->로 시작하기 때문에 msg로 하면 앞에 유저이름까지 나오기 때문에 순수 채팅 내용만 필요하기 때문에 만든 변수
msg = sck_list[idx].user + ":" + buf;

        // string ss; 
        string to;
        if (msg1.substr(0, 2) == "->") {
            std::stringstream ss(msg);
            // 공백 기준 두번째 단어 to에 저장, 임시로 msg에 ':' 저장 여기서 to는 받는 사람의 아이디 이다.
            ss >> to >> to ;
            // 나머지 문자열 msg에 저장
            std::getline(ss, msg);

            if (buf != "") {
            //dm메시지를 저장하기 위한 테이블에 내용을 넣는 쿼리문
                pstmt = con->prepareStatement("insert into direct_msg(send_id,recv_id,msg) values(?,?,?)");
                pstmt->setString(1, sck_list[idx].user);
                pstmt->setString(2, to);
                pstmt->setString(3, msg);
                pstmt->execute();
            }
            string dm="";
            dm = "(1:1 msg)" + to + ":" + msg;
            send_msg_dm(dm.c_str(), to);

        }
        else
        { //->가 없으면 일반 chatting테이블에 내용을 저장한다.
            if (buf != "") {
                pstmt = con->prepareStatement("insert into chatting(id, chat) values(?,?)");
                pstmt->setString(1, sck_list[idx].user);
                pstmt->setString(2, buf);
                pstmt->execute();
            }
            send_msg(msg.c_str());
        }


    }
    else
    {
        msg = sck_list[idx].user + "님이 퇴장했습니다";
        cout << msg << endl;
        send_msg(msg.c_str());
        del_client(idx);
        return;
    }

}

}
//유저가 퇴장시 소켓을 닫아주는 역할을 한다.
void del_client(int idx) {
closesocket(sck_list[idx].sck);
client_count--;
}


로그인 하고 채팅을 하면 전체 채팅으로 전체가 볼수 있다. 하지만 -> "상대방id 내용 을 작성 하면 상대 한테만 (1:1 msg)로 메시지가 보인다.

profile
SMF 일기

0개의 댓글