안녕하세요. recv send 함수에 대한 질문드립니다.

K.I.D Neon (NK)@Google의 이미지

제가 pthread를 사용해서 간단한 채팅 프로그램 소스를 짰습니다.

그런데, 리눅스에서 send recv 함수를 사용해 쓰레드로 동기화 시켜 다음과 같이 구현했는데요.

Server Code

// Override
int User::run()
{
    char buf[User::MAXSTRLEN];
 
    while(true)
    {
        try
        {
            recvMessage(buf);
            stringstream oss;
            oss << "(" << getIP() << ":" << getPort() << ") : " << buf; // 익셉션 메시지 로그,,
            sendMessageAll(oss.str().c_str());  // 모든 사용자에게 전달,,
            cout << "(" << getIP() << ":" << getPort() << ") : " << buf << endl;
 
        }
        catch(ChatException ex)
        {
            stringstream oss;
            oss << "(" << getIP() << ":" << getPort() << ") : " << "유저가 나갔습니다. ";
            sendMessageAll(oss.str().c_str());
            printLeaveUser(*this);
            break;  // 오류가 있으면, 서버가 종료.
        }
    }
    pthread_mutex_lock(&App::pMutex);
    int len = App::userList.size();
    for(int i = 0; i < len; i++)
    {
        User *user = App::userList.at(i);   // 사용자 목록에서 한 사용자를 가져옵니다,,
        if(user->getSocket() == this->getSocket())
        {
            App::userList.erase(App::userList.begin() + i); // 사용자 리스트에서 끊어진 사용자 제거,,
            break;
        }
    }
    pthread_mutex_unlock(&App::pMutex);
    delete this;
    return 0;
}
 
void User::recvMessage(char *buf)
{
    Message msg;
    int len = 0;
    memset(&msg, 0, sizeof(Message));
 
    if(recv(this->recv_socket, (char*)&msg, sizeof(Message), 0) <= 0)
    {
        throw ChatException(SESSION_ERROR);
    }
    len = strnlen(msg.data, User::MAXSTRLEN);
    strncpy(buf, msg.data, strnlen(msg.data, User::MAXSTRLEN));
    buf[len] = 0;
}
 
void User::sendMessageAll(const char *buf)
{
    int len = App::userList.size();
    for(int i = 0; i < len; i++)
    {
        User *user = App::userList.at(i);
        try
        {
            sendMessage(user->getSocket(), buf);
        }
        catch(ChatException ex)
        {
            throw ChatException(SEND_ERROR);
        }
    }
}
 
void User::sendMessage(int socket, const char *buf)
{
    Message msg;
    memset(&msg, 0, sizeof(Message));
 
    if(buf != nullptr)
    {
        int len = strnlen(buf, User::MAXSTRLEN);
        strncpy(msg.data, buf, len);
        msg.data[len] = 0;
    }
 
    pthread_mutex_lock(&App::pMutex);
    if(send(socket, (const char*)&msg, sizeof(Message), 0) <= 0)
    {
        pthread_mutex_unlock(&App::pMutex);
        throw ChatException(SESSION_ERROR);
    }
    pthread_mutex_unlock(&App::pMutex);
}

Client Code

int ChatClient::run()
{
    // 보내고 받는 스레드를 각각 만들어줌,,
    sendThread = new SendThread(this->client_socket);
    recvThread = new RecvThread(this->client_socket);
 
    connectServer();    // 서버 접속 시도...
 
    sendThread->start();
    recvThread->start();
 
    sendThread->join(); // sendThread가 종료될 때까지 계속 대기..
    recvThread->join();
 
    return sendThread->getExitCode();   // 혹여 서버가 죽을 경우, 반환할 값 (join 함수에서 받아야 함)
}
 
void sendRecvInterface::recvMessage(int socket, char *buf)
{
    Message msg;
    int len = 0;
    memset(&msg, 0, sizeof(Message));
 
    if(recv(socket, (char*)&msg, sizeof(Message), 0) <= 0)
    {
        throw ChatException(1100);
    }
    len = strnlen(msg.data, ChatClient::MAXSTRLEN);
    strncpy(buf, msg.data, len);
    buf[len] = 0;
}
 
void sendRecvInterface::sendMessage(int socket, const char* buf)
{
    Message msg;
    memset(&msg, 0, sizeof(Message));
 
    if(buf != nullptr)
    {
        int len = strnlen(buf, ChatClient::MAXSTRLEN);
        strncpy(msg.data, buf, len);
        msg.data[len] = 0;
    }
 
    pthread_mutex_lock(&App::pMutex);
    if(send(socket, (const char*)&msg, sizeof(Message), 0) <= 0)
    {
        pthread_mutex_unlock(&App::pMutex);
        throw ChatException(1100);
    }
    pthread_mutex_unlock(&App::pMutex);
}
 
void sendRecvInterface::gotoxy(int x, int y)
{
    fprintf(stdout, "\033[%dd\033[%dG", y, x);
}
 
SendThread::SendThread(int cs) : client_socket(cs) {}
 
int SendThread::run()
{
    int result = -1;
    char buf[ChatClient::MAXSTRLEN];
    while(true)
    {
        try
        {
            cin >> buf;
            cin.clear();
 
            if(exitUser(buf))
            {
                result = 0;
                throw ChatException(2100);
            }
            sendMessage(this->client_socket, buf);
        }
        catch(ChatException ex)
        {
            close(this->client_socket);
            break;
        }
    }
    return result;
}
 
bool SendThread::exitUser(const char *buf)
{
    if(strcmp(buf, UserCommand::EXIT) == 0)
    {
        return true;
    }
    return false;
}
 
 
RecvThread::RecvThread(int cs) {}
 
int RecvThread::run()
{
    char buf[ChatClient::MAXSTRLEN];
    while(true)
    {
        try
        {
            recvMessage(this->client_socket, buf);
            cout << buf << endl;
        }
        catch (ChatException ex)
        {
            close(this->client_socket);
            break;
        }
    }
    return 0;
}

이렇게 구현을해서, 클라이언트에서 cin >> buf 쪽에서 입력을 기다리고, 메시지를 입력하면, 서버로 보내는 간단한 채팅 프로그램입니다. 그런데, 메시지를 입력하고, 엔터를 누르면 한 개의 메시지만 가는게 아니라 해당 메시지가 여러 번 송신이 되는데, 왜 그런걸까요? 고수님들의 도움을 부탁드립니다.

댓글 달기

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