大家好:
    小弟现在在做一个项目,做一个应用程序(windows),可以和一个文件服务器(linux)进行通讯,文件服务器也是公司定制的,应用程序和服务器之间定制了一些通讯接口,如GetDir(获取文件夹),GetFileSize(获取文件大小)等等,这些接口是基于HTTP协议的,每次通讯结束后,服务器就关闭接口,也就是说不是永久连接。
     我是基于阻塞模式的,为了不影响界面的刷新,我把通讯的过程做成了一个thread,数据收到以后通过回调函数在通知界面,界面再做刷新。就是下面的伪码。
    sendrecventry()
    {
        send();
        recv();
        /**/
        postdatatoui();
        return 0;/*线程返回*/
     }
    GetDir()
    {
       int handle = createthread(0,0,sendrecventry,0);
    }    GetFileSize()
    {
       int handle = createthread(0,0,sendrecventry,0);
    }
    请各位兄弟帮忙看一下这样的方法是否合理? 

解决方案 »

  1.   

    不用每次都CreateThread吧。可以固定一个线程干数据交换的活就行。
      

  2.   

    谢谢回复,因为这些接口的调用不是很频繁,如果保持一个线程一直在工作的话,是不是消耗系统资源呀,而且每次和服务器进行http通讯以后,服务器就会关闭连接了。
      

  3.   

    需要IO数据时,创建子线程进行阻塞操作,通过消息机制传递IO结果
    你的方式很标准,是常用架构,没啥问题从服务器提供的接口走HTTP协议来看,服务器支持短连接并发响应
    所以,你没有必要用“固定子线程”来束缚自己不管是多线程,还是异步模式,本质目的都是为了实现业务流的并发操作
    你使用子线程来处理阻塞操作,说明你不愿意主线程被阻塞,你希望主线程能有更高的业务响应能力
    那么,你的主线程的流程逻辑就应该是:它随时可以开子线程去干活
    而不需要再去关心子线程的状态、工作进度,更不应该去参与子线程的调度管理
    否则,你还不如不用子线程,直接在主线程中将IO请求队列化之后使用一个定时器来分时响应IO和UI请求
      

  4.   

    不太合理,当请求频繁时,CPU因为忙于切换线程上下文而使效率下降,界面上也有可能处于假死状态,当然如果这个应用程序的请求不是很频繁,这样设计也可以,合理的做法应该是当没请求时挂起线程,就是只要一个工作线程即可
      

  5.   

    谢谢大家的回复!分数要多给!
    hi,fangle6688,您讲的“你没有必要用“固定子线程”来束缚自己 “,是什么意思呀,能不能再详细的讲一下,谢谢!
    下面是我简单的调用过程:
    我的过程是调用getfilelist(),这个函数里面创建一个线程获取了file list以后,传给ui,线程销毁(return 0),ui把数据刷新,再选择其中一个文件,调用getfilesize(),里面创建一个一个线程获取文件的大小,然后再传递给ui,ui显示出正确的文件大小。我现在已经开始在VC上编码测试了
      

  6.   

    要做到很好的全双工网络通讯用线程是必要的。“这些接口是基于HTTP协议的”这句话楼主能不能先解析清楚还有,普通的API写全双工服务器程序本来就比较复杂,需要把发送单独线程,接受单独线程。建立连接的时候就要建立客户端连接队列的结构,里面需要基本的接受缓冲跟发送缓冲的组合,要做1对多的服务器程序接受线程不能用阻塞(个人其实觉得阻塞简单而且稳定好控制,但本人没成功的做到1对多,只能一对一的实现,希望哪位高手也指教一下)send(); 
    recv();这个地方如果是阻塞时可以的。其实你的程序我觉得不好控制的就是,你GetDir()和GetFileSize()都发东西出去了,但GetDir()返回的时候是否就是在处理GetDir发送的接受,还有就是GetFileSize()也一样计算他们都接收跟处理到他们要的东西,谁先谁后没办法判断。还有就是你不去发的时候,你什么东西都接收不到。个人愚见,欢迎拍砖。
      

  7.   

    基于HTTP协议就是说GetDir()和GetFileSize()里面发送的是HTTP的命令,比如GET,POST等命令。更详细的函数是
        sendrecventry() 
        { 
           SOCKET sd = socket();
            char req[100] = {0};
           char rsp[100] = {0};
           send(sd,req); 
            recv(sd,rep); 
            /**/ 
            postdatatoui(); 
            return 0;/*线程返回*/ 
        } 
    里面的send和recv都是阻塞操作,当然里面会做一些更复杂的判断,比如发送或者接受失败,或者服务器关闭了socket,然后我就会给ui线程返回错误的报告,ui会显示连接失败等提示信息,如果接收数据成功,会把正确的数据发送给UI,UI再显示正确的信息。
      

  8.   

    按你的思路去做吧,你的思路正确“专门一个线程处理数据”那是照本宣科
    用信号量控制一个专门的子线程进行IO,那是针对异步才有意义的
    而你的子线程中使用的是同步IO,就应该保持多个子线程并发当业务繁忙的时候,例如同时有2笔业务要处理用“专门一个线程去处理数据”,你的主线程必须等待子线程完成一笔业务后再告诉它开始下一笔业务而用子线程并发处理的流程呢?
    第一个子线程完成了send,开始recv时,第二个子线程的send函数就已经开始工作了用你自己的方案,你的程序的业务处理能力要比“专门一个线程去处理数据”高出至少100%
    既然服务器有并发处理能力(HTTP接口肯定支持并发),你有什么理由要自我设限“每一笔业务必须等上一笔业务完成后才开始提交”呢???
      

  9.   

    当然,如果你的业务很繁忙(每分钟30笔以上的业务)你首先需要考虑的,也不是使用固定线程来做IO操作
    你首先需要考虑的,是使用异步IO替代同步IO再者,如果你的业务非常繁忙(每分钟1000笔以上的业务)你才需要开始考虑“固定线程”的IOCP架构
      

  10.   

    /**********************************************
    //lock;
    HANDLE m_hMutex=CreateSemaphore(NULL,0,1,NULL); //参数1可以是你要并发的线程数
    DOWRD Lock(){
        return WaitForSingleObject(m_hMutex);
    }
    //UnLock
    bool UnLock(){
        return ReleaseMutex(m_hMutex);
    }
    /**********************************************//Thread Rev() 主体
    while(
        Lock();
        recv();   //阻塞不阻塞都可以。控制方法在Lock上。
         /*按需要的UnLock()*/
    )GetDir() { 
        Send();
        Unlock(); //按需要UnLock
    } GetFileSize() 
    {
        Send();
        Unlock(); //按需要UnLock
     
    } 发送个人感觉没多大关系,线不现成都可以,当然如果你有很好的发送控制的话,是需要线程的配合的。这样你可以在任何地方用 UnLock() 控制你线程的步调,也不用担心不断的创建线程牺牲的CPU跟内存。
      

  11.   

    用CBLOCKINGSOCKET类来写就不会出现你说的情况了
      

  12.   

    阻塞的send、recv还需要互斥锁???
    越来越画蛇添足了
      

  13.   

    sendrecventry()是个可重入的函数,当这个函数作为线程的入口函数的时候,线程之间还需要同步吗?
      

  14.   

    个人感觉,可以这样设计,但要注意    postdatatoui(); 这个函数的实现这个是要跨线程的
      

  15.   

    跑阻塞IO操作的线程还需要“控制步调”???采用阻塞IO操作的优势就在于逻辑简洁明了,适合中小项目
    缺点是并发响应能力有限(不适合30+的并发环境)
      

  16.   

    下面是小弟写的测试代码,请各位老大帮忙看一下,那里有问题
    #include "winsock2.h"
    #include "iostream.h"
    #include "stdio.h"
    #define SEND_LEN 1024
    #define RECV_LEN 1024
    struct LINK {
    char name[15];
    char url[100];
    char host[100];
    char ip[20];
    };
    int FormatRequestHeader(char *req,int reqlen,char *url, char *host)
    {
        int len = 0;
        if(0 == req)
           return -1;
        memset((char*)req,0,reqlen);
        strcpy(req,"GET ");
        strcat(req,url);
        strcat(req," HTTP/1.1");
        strcat(req,"\r\n");
        strcat(req,"Host:");
        strcat(req," ");
        strcat(req,host);
        strcat(req,"\r\n");
        strcat(req,"Accept:*/*");
        strcat(req,"\r\n");
        strcat(req,"User-Agent:Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)");
        strcat(req,"\r\n");
        strcat(req,"Connection:Keep-Alive");
        strcat(req,"\r\n");
        strcat(req,"\r\n");
        len = strlen((char*)req);
        req[len] = '\0';
        return 0;
    }
    DWORD WINAPI ThreadProc (LPVOID pParam)
    {
        char req[SEND_LEN] = {0};
        char rcv[RECV_LEN] = {0};
        SOCKET sd;
        struct sockaddr_in server_addr = {0};
        int ret = 0;
        FILE *fp = 0;
        LINK *p;
        cout <<"线程开始运行\r\n"<<endl;
        p = (LINK*)pParam;
        fp = fopen(p->name,"wb");
        if(fp == 0)
           return -1;
        sd=socket(PF_INET,SOCK_STREAM,0);
        if(sd==INVALID_SOCKET)
        {
          cout <<"socket()函数出错"<<endl;
          return -1;
        }
        memset(&server_addr,0,sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(80);
        server_addr.sin_addr.s_addr = inet_addr(p->ip);
        if(connect(sd,(struct sockaddr*)&server_addr,sizeof(server_addr))!=0)
        {
           closesocket(sd);
           cout<<"connect()函数执行失败!"<<endl;
           return FALSE;
        }
        else
        {
           cout<<"Connect 服务器成功"<<endl;
        }
        FormatRequestHeader((char*)req,SEND_LEN,p->url,p->host);
        ret = send(sd, (char*)req, strlen((char*)req), 0);
        if(ret == -1)
        {
            cout<<"发送数据失败\r\n"<<endl;
            closesocket(sd);
            return -1;
        }
        cout <<"发送了"<<ret<<"字节的数据"<<endl;
        while(1)
        {
           ret = recv(sd,rcv,RECV_LEN,0);
           if(ret > 0)
           {
              cout <<"接收了"<<ret<<"字节的数据"<<endl;
              //cout <<"数据"<<rcv<<endl;
              fwrite((char*)rcv,1,ret,fp);
              memset((char*)rcv,0,RECV_LEN);
          }
          else if (ret == -1)
          {
              break;
          }
          else if (ret == 0)
          {
              break;
          }
        }
        fclose(fp);
        closesocket(sd);
        cout <<"数据接收完毕over\r\n"<<endl;
        return 0;
    }
    int main()
    {
        int err;
        WORD wVersion;
        WSADATA WSAData;    LPDWORD threadid1 = 0;
        LPDWORD threadid2 = 0;
        LPDWORD threadid3 = 0;
        LPDWORD threadid4 = 0;    LINK link1 = {"52rd_1.html","/","www.52rd.com","211.144.68.54"};
        LINK link2 = {"52rd_2.html","/","www.52rd.com","211.144.68.54"};
        LINK link3 = {"52rd_3.html","/","www.52rd.com","211.144.68.54"};
        LINK link4 = {"52rd_4.html","/","www.52rd.com","211.144.68.54"};    HANDLE hThread1;
        HANDLE hThread2;
        HANDLE hThread3;
        HANDLE hThread4;    wVersion=MAKEWORD(2,0);
        err=WSAStartup(wVersion,&WSAData);
        if(err!=0)
        {
           cout<<"无法加载Socket库."<<endl;
           return -1;
        }
        if(LOBYTE( WSAData.wVersion ) != 2)
        {
           cout<<"无法找到合适Socket库."<<endl;
           WSACleanup();
           return -1;
        }
        cout<<"找到了合适的Socket库."<<endl;    hThread1 = CreateThread(NULL,0,ThreadProc,&link1,0,threadid1 );
        hThread2 = CreateThread(NULL,0,ThreadProc,&link2,0,threadid2 );
        hThread3 = CreateThread(NULL,0,ThreadProc,&link3,0,threadid3 );
        hThread4 = CreateThread(NULL,0,ThreadProc,&link4,0,threadid4 );    cout<<"主线程开始运行"<<endl;
        for(int i=0;;i++){}
        return 0;
    }
    //调试程序的时候加上ws2_32.lib这个库
    所遇到的问题
    当只生成hThread1 的时候,52rd_1.html(80kb)生成正确,当生成hThread1 和hThread2的时候,大部分也是正确的,只是偶尔52rd_1.html大小为0,52rd_2.html为160kb,当生成四个线程的时候,经常出现一个文件为0,有一个文件是160kb,另外两个文件分别是80kb。
    我的疑问是:ThreadProc是可重入得函数,写文件的时候也是分别写到各自的文件,怎么会出现把一个文件的内容写到另一个文件里面去。是不是不同同时写文件。请兄弟们帮我看一下,谢谢!
      

  17.   

    如果在每次创建线程后,加上延时,然后就很正确。如果不加延时,会出现下面的情况,我为每个线程都加上ID,会出现下面的打印信息:线程1开始运行
    主线程开始运行
    线程2开始运行
    线程3开始运行
    线程4开始运行线程1 Connect服务器成功
    线程4 Connect服务器成功
    线程4 Connect服务器成功线程1 发送了133个字节到服务器
    线程4 Connect服务器成功
    线程1 发送了133个字节到服务器线程3 Connect服务器成功
    线程4 Connect服务器成功
    线程1 发送了133个字节到服务器线程3 Connect服务器成功
    线程2 Connect服务器成功
    线程4 发送了133个字节到服务器线程3 发送了133个字节到服务器
    线程2 发送了133个字节到服务器接收数据完毕 over
    接收数据完毕 over
    接收数据完毕 over我觉得很奇怪,线程怎么会重复连接服务器和发送命令呢?是不是把参数传递给ThreadProc的时候,里面保存的还是以前的数据呢?按理说这种情况不会发生的呀,因为四个线程共用一个线程入口函数,但是有四个镜像调用呀?各位兄弟有什么想法给小弟说一下,谢谢!
      

  18.   

    原因是CPU和系统在分配执行时间片时引起的
      

  19.   

    是的,我想实现的是开四个线程,四个线程分别从网上下载文件,如果在创建四个线程的时候,不加上延时,经常会出现一个文件为0,一个文件是160kb,正常的大小是80kb。
      

  20.   

    不知道fopen这个函数是否是线程安全的建议将fopen放到主线程中,主线程次序打开4个文件,LINK结构中不要放文件名,放已经打开的文件句柄
      

  21.   

    谢谢大家的帮助,特别是fangle6688,按照他的方法果然ok,我把代码重新上传了,希望对后来的人帮助!
    #include "winsock2.h" 
    #include "iostream.h" 
    #include "stdio.h" 
    #define SEND_LEN 1024 
    #define RECV_LEN 1024 
    struct LINK { 
    char name[15]; 
    char url[100]; 
    char host[100]; 
    char ip[20]; 
    FILE *fp ;
    }; 
    int FormatRequestHeader(char *req,int reqlen,char *url, char *host) 

        int len = 0; 
        if(0 == req) 
          return -1; 
        memset((char*)req,0,reqlen); 
        strcpy(req,"GET "); 
        strcat(req,url); 
        strcat(req," HTTP/1.1"); 
        strcat(req,"\r\n"); 
        strcat(req,"Host:"); 
        strcat(req," "); 
        strcat(req,host); 
        strcat(req,"\r\n"); 
        strcat(req,"Accept:*/*"); 
        strcat(req,"\r\n"); 
        strcat(req,"User-Agent:Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)"); 
        strcat(req,"\r\n"); 
        strcat(req,"Connection:Keep-Alive"); 
        strcat(req,"\r\n"); 
        strcat(req,"\r\n"); 
        len = strlen((char*)req); 
        req[len] = '\0'; 
        return 0; 

    DWORD WINAPI ThreadProc (LPVOID pParam) 

        char req[SEND_LEN] = {0}; 
        char rcv[RECV_LEN] = {0}; 
        SOCKET sd; 
        struct sockaddr_in server_addr = {0}; 
        int ret = 0; 
        FILE *fp = 0; 
        LINK *p; 
        cout <<"线程开始运行\r\n" <<endl; 
        p = (LINK*)pParam; 
        //fp = fopen(p->name,"wb"); 
        //if(fp == 0) 
         // return -1; 
        sd=socket(PF_INET,SOCK_STREAM,0); 
        if(sd==INVALID_SOCKET) 
        { 
          cout <<"socket()函数出错" <<endl; 
          return -1; 
        } 
        memset(&server_addr,0,sizeof(server_addr)); 
        server_addr.sin_family = AF_INET; 
        server_addr.sin_port = htons(80); 
        server_addr.sin_addr.s_addr = inet_addr(p->ip); 
        if(connect(sd,(struct sockaddr*)&server_addr,sizeof(server_addr))!=0) 
        { 
          closesocket(sd); 
          cout <<"connect()函数执行失败!" <<endl; 
          return FALSE; 
        } 
        else 
        { 
          cout <<"Connect 服务器成功" <<endl; 
        } 
        FormatRequestHeader((char*)req,SEND_LEN,p->url,p->host); 
        ret = send(sd, (char*)req, strlen((char*)req), 0); 
        if(ret == -1) 
        { 
            cout <<"发送数据失败\r\n" <<endl; 
            closesocket(sd); 
            return -1; 
        } 
        cout <<"发送了" <<ret <<"字节的数据" <<endl; 
        while(1) 
        { 
          ret = recv(sd,rcv,RECV_LEN,0); 
          if(ret > 0) 
          { 
              cout <<"接收了" <<ret <<"字节的数据" <<endl; 
              //cout <<"数据" <<rcv <<endl; 
              fwrite((char*)rcv,1,ret,p->fp); 
              memset((char*)rcv,0,RECV_LEN); 
          } 
          else if (ret == -1) 
          { 
              break; 
          } 
          else if (ret == 0) 
          { 
              break; 
          } 
        } 
        fclose(p->fp); 
        closesocket(sd); 
        cout <<"数据接收完毕over\r\n" <<endl; 
        return 0; 

    int main() 

        int err; 
        WORD wVersion; 
        WSADATA WSAData;  FILE *fp1 = 0; 
    FILE *fp2 = 0; 
    FILE *fp3 = 0; 
    FILE *fp4 = 0; 
    FILE *fp5 = 0; 
    FILE *fp6 = 0; 
    FILE *fp7 = 0; 
    FILE *fp8 = 0; 
    FILE *fp9 = 0;    LPDWORD threadid1 = 0; 
        LPDWORD threadid2 = 0; 
        LPDWORD threadid3 = 0; 
        LPDWORD threadid4 = 0;  LPDWORD threadid5 = 0; 
        LPDWORD threadid6 = 0; 
        LPDWORD threadid7 = 0; 
        LPDWORD threadid8 = 0; 
        LPDWORD threadid9 = 0; 
        LINK link1 = {"52rd_1.html","/","www.52rd.com","211.144.68.54",0}; 
        //LINK link2 = {"52rd_2.html","/","www.52rd.com","211.144.68.54",0}; 
        LINK link3 = {"52rd_3.html","/","www.52rd.com","211.144.68.54",0}; 
        LINK link4 = {"52rd_4.html","/","www.52rd.com","211.144.68.54",0};  LINK link5 = {"52rd_5.html","/","www.52rd.com","211.144.68.54",0}; 
        //LINK link6 = {"52rd_6.html","/","www.52rd.com","211.144.68.54",0}; 

    //LINK link6 = {"wubai.mp3","http://www.92bbs.net/bbs/attachment/Mon_0611/83_72628_05be619a11f352f.mp3","www.92bbs.net","218.83.175.155",0}; 
        LINK link7 = {"52rd_7.html","/","www.52rd.com","211.144.68.54",0}; 
        LINK link8 = {"52rd_8.html","/","www.52rd.com","211.144.68.54",0}; 
        LINK link2 = {"1.mp3","http://www.honghejt.com/gh/upfile/other/2007720164337336.mp3","www.honghejt.com","218.63.105.86",0};
    LINK link6 = {"1.mp3","http://dream.giggletang.com/content/mp3/lovesong1990.mp3","www.giggletang.com","59.42.244.223",0};
    LINK link9 = {"1.mp3","http://www.honghejt.com/gh/upfile/other/2007720164337336.mp3","www.honghejt.com","218.63.105.86",0};

    fp1 = fopen((char*)"52rd_1.html","wb"); 
        if(fp1 == 0) 
    return -1; 

    fp2  = fopen((char*)"1.mp3","wb"); 
        if(fp2 == 0) 
    return -1; 

    fp3  = fopen((char*)"52rd_3.html","wb"); 
        if(fp3 == 0) 
    return -1; 

    fp4  = fopen((char*)"52rd_4.html","wb"); 
        if(fp4 == 0) 
    return -1;  fp5= fopen((char*)"52rd_5.html","wb"); 
        if(fp5 == 0) 
    return -1; 

    fp6  = fopen((char*)"1990.mp3","wb"); 
        if(fp6 == 0) 
    return -1; 

    fp7  = fopen((char*)"52rd_7.html","wb"); 
        if(fp7 == 0) 
    return -1; 

    fp8  = fopen((char*)"52rd_8.html","wb"); 
        if(fp8 == 0) 
    return -1;
    fp9  = fopen((char*)"2009.mp3","wb"); 
        if(fp9 == 0) 
    return -1; 

        link1.fp = fp1;
     link2.fp = fp2;
      link3.fp = fp3;
       link4.fp = fp4;    link5.fp = fp5;
       link6.fp = fp6;
       link7.fp = fp7;
       link8.fp = fp8;
       link9.fp = fp9;    HANDLE hThread1; 
        HANDLE hThread2; 
        HANDLE hThread3; 
        HANDLE hThread4; 
    HANDLE hThread5; 
        HANDLE hThread6; 
        HANDLE hThread7; 
        HANDLE hThread8; 
    HANDLE hThread9;     wVersion=MAKEWORD(2,0); 
        err=WSAStartup(wVersion,&WSAData); 
        if(err!=0) 
        { 
          cout <<"无法加载Socket库." <<endl; 
          return -1; 
        } 
        if(LOBYTE( WSAData.wVersion ) != 2) 
        { 
          cout <<"无法找到合适Socket库." <<endl; 
          WSACleanup(); 
          return -1; 
        } 
        cout <<"找到了合适的Socket库." <<endl;     hThread1 = CreateThread(NULL,0,ThreadProc,&link1,0,threadid1 ); 
        hThread2 = CreateThread(NULL,0,ThreadProc,&link2,0,threadid2 ); 
        hThread3 = CreateThread(NULL,0,ThreadProc,&link3,0,threadid3 ); 
        hThread4 = CreateThread(NULL,0,ThreadProc,&link4,0,threadid4 );  hThread5 = CreateThread(NULL,0,ThreadProc,&link5,0,threadid5 ); 
        hThread6 = CreateThread(NULL,0,ThreadProc,&link6,0,threadid6 ); 
        hThread7 = CreateThread(NULL,0,ThreadProc,&link7,0,threadid7 ); 
        hThread8 = CreateThread(NULL,0,ThreadProc,&link8,0,threadid8 ); 
        hThread9 = CreateThread(NULL,0,ThreadProc,&link9,0,threadid9 );     cout <<"主线程开始运行" <<endl; 
        for(int i=0;;i++){} 
        return 0; 

    代码写的比较乱,请大家见谅
    fopen,fwirte是不安全的线程函数,最好使用window提供的API函数,请参考一下
    http://topic.csdn.net/t/20040427/11/3012546.html#
      

  22.   

    知道问题所在了:
    fopen等函数是C运行库的函数,是单线程的,所以要在VC工程中做一下设置,Createthread最好用_beginthread函数,新的代码就不贴了,虽然这个工程很简单,希望对后面的人有帮助!