SOCKET控制台双线程聊天程序

    今天算是终于拿下了最简单的socket(tcp模式)+线程的聊天程序,嘿嘿,还记得前些天写这个程序的狼狈情景,这下总算能扬眉吐气了哈。

    对于一般的socket编程入门例子,不外乎讲下如何用套接字发送一个信息,最终得到的应用程序和我们的QQ这种聊天程序比起来,实在是不能算东西。看孙鑫的视频时,我就想写个自己的聊天程序,功能是能够聊天和发送文件。

    在后来实现时发现要想在发送信息的同时,能够接收信息,就需要在主线程外有另一个线程帮忙,乖乖,只好再去学会线程的知识。

    经过几天的学习(其实每天还要上课,玩游戏…),咱家也算是有了点心得,一口气就搞定了这个简单的聊天程序。功能:只能聊天,且是单聊,断线需重启,哈哈。

    稍微有点特色的功能是对字符串的处理,使聊天看起来有几分界面的效果。

 

    好,不多说,来说下程序的具体实现。


客户端代码如下:

#pragma comment(lib,"ws2_32.lib")
#include<winsock2.h>
#include<iostream>
#include<string>
using namespace std;
int follow=0;
string show="";
void recvProc(SOCKET sockConnect)
{
    char msgRcv[100]={0};
    while(true)
    {
        if(SOCKET_ERROR==recv(sockConnect,msgRcv,sizeof(msgRcv),0))
        {
            cout<<"/n对方已经下线了";
            return;
        }
        if(msgRcv[0]!='/0')
        {
            show.erase(show.end()-7,show.end());
            show+="对方说: ";
            show+=msgRcv;
            show+='/n';
            show+="input: ";
            system("cls");
            cout<<show;
        }
    }
}

int main(int argc, char* argv[])
{
    //Load socket and its version
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD( 1, 1 );//MAKEWORD macro
    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
        return 1;
    }
    if ( LOBYTE( wsaData.wVersion ) != 1|| 
        HIBYTE( wsaData.wVersion ) != 1) {
        WSACleanup( );//low(main) and high(deputy)
        return 1; 
    }
    //create a socket and set it
    SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    sockaddr_in addrSrv;
    memset(&addrSrv,0,sizeof(addrSrv));
    addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
    addrSrv.sin_family=AF_INET;
    addrSrv.sin_port=htons(6000);
    //bind
    if(bind(sockSrv,(sockaddr*)&addrSrv,sizeof(sockaddr))==SOCKET_ERROR)
    {
        cout<<"bind error"<<endl;
    }
    
    //listen 
    if(listen(sockSrv,5)==SOCKET_ERROR)
    {
        cout<<"listen error"<<endl;
    }
    
    //accept
    SOCKADDR_IN addrClient;
    int len=sizeof(sockaddr);//caution:len should be initial as sizeof(sockaddr),    //or accepting will be failed
    while(true)
    {
        cout<<"等待客户建立连接...";
        SOCKET sockConnect=accept(sockSrv,(sockaddr*)&addrClient,&len);
        if(sockConnect==INVALID_SOCKET)
        {
            cout<<"invalid socket"<<endl;
            return 0;
        }

        //recv Thread
        CreateThread(NULL,0,
            (LPTHREAD_START_ROUTINE)recvProc,(void*)sockConnect,
            0,NULL);

        //send
        while(true)
        {
            char buf[100]={0};
            show+="input: ";
            system("cls");
            cout<<show;
            cin>>buf;
            show.erase(show.end()-7,show.end());
            show+="我说:";
            show+=buf;
            show+='/n';

            send(sockConnect,buf,sizeof(buf),0);
        }
        closesocket(sockConnect);
    }
    //end winsock dll
    WSACleanup();
    return 0;
}

如果你已经开始学socket编程,和我差不多水平,就可以直接跳到

while(true)
{
cout<<"等待客户建立连接...";
……

处,前面我不说什么,说接下来的。


      Accept函数返回一个新的SOCKET后,进程开始通信了,由于需要将发送与接收2个操作分离开来,且应经有了一个main主线程,因此我们只需再创建一个线程就可以了,这个线程可以是send,也可以是recv,我这里选的是recv,主线程负责发送信息。

    注意这个recvProc的参数(唯一一个)是SOCKET,而这个SOCKET就是刚刚Accept函数返回的哈,这样就建立了与主线程的关系。(这个SOCKET像一个句柄,提供访问)

 

    RecvProc干自己的事,接收信息。由于是阻塞式模式,因此即使是while循环,也不会耗费CPU资源,阻塞式模式下recv会等到有信息来到才允许往下运行,否则就挡在那,挂着。

主线程中的while循环也不会浪费CPU资源,cin是和recv一样的家伙,等待着使用者输入。

 

    那么运行的情况(界面)是怎么样的呢?

    可以这样来理解,主线程和子线程共同使用I/O设备输出信息,大家各自输出自己的东西,各不干涉。但是这样容易出现紊乱,一下主线程输出,一下子线程输出,主线程还要输入。防止紊乱发生的方法想了几个,但都不大理想,最后想到用一个标准库库的string对象来保存界面上所有的信息,嘿嘿,这下就好看了。大家可以看下string对象show的变化,有2处地方,一是recv处,一是send处,有点绕的,呵呵。

 

    至于客户端,只在最后发下代码,道理都一样。

    有一个问题就是string对象show太大了怎么办,我想如果show的’/n’的个数达到控制台界面的行数时,如果再增加字符串,就将多余的部分保存到文件中,然后从show中删除,就可以了。

当然,程序还有许多地方有问题,如没有释放子线程的资源,等等,呵呵…


附:客户端源代码:

#pragma comment(lib,"ws2_32.lib")
#include<winsock2.h>
#include<iostream>
#include<string>
using namespace std;
//CLIENT CPP
string show="";
void recvProc(SOCKET sockClient)
{
    char msgRcv[100]={0};
    while(true)
    {
        if(SOCKET_ERROR==recv(sockClient,msgRcv,sizeof(msgRcv),0))
        {
            cout<<"/n对方已经下线了";
            return;
        }
        if(msgRcv[0]!='/0')
        {
            show.erase(show.end()-7,show.end());
            show+="对方说: ";
            show+=msgRcv;
            show+='/n';
            show+="input: ";
            system("cls");
            cout<<show;
        }
    }
}
int main(int argc, char* argv[])
{
    //Load socket and its version
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD( 1, 1 );//MAKEWORD macro
    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
        return 1;
    }
    if ( LOBYTE( wsaData.wVersion ) != 1|| 
        HIBYTE( wsaData.wVersion ) != 1) {
        WSACleanup( );//low(main) and high(deputy)
        return 1; 
    }
    //create a client socket
    SOCKET sockClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);    //set the address of the server
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr=inet_addr("192.168.1.100");
    addrSrv.sin_family=AF_INET;
    addrSrv.sin_port=htons(6000);

    //connect
    connect(sockClient,(sockaddr*)&addrSrv,sizeof(sockaddr));

    //recieve msg Thread
    CreateThread(NULL,0,
        (LPTHREAD_START_ROUTINE)recvProc,(void*)sockClient,
        0,NULL);
    
    //send msg
    while(true)
    {
        char buf[100]={0};
        show+="input: ";
        system("cls");
        cout<<show;
        cin>>buf;
        show.erase(show.end()-7,show.end());
        show+="我说:";
        show+=buf;
        show+='/n';

        send(sockClient,buf,sizeof(buf),0);
    }

    //close socket to release the resource
    closesocket(sockClient);
    WSACleanup();
    return 0;
}