服务器接收数据:
//开启服务 按钮响应函数
void CTcpServerDlg::OnButtonListen() 
{
// TODO: Add your control notification handler code here
if(SOCKET_ERROR == listen(sockSrv, 5))
{
MessageBox("侦听失败!", "提示", MB_ICONERROR | MB_OKCANCEL);
return;
}

while(TRUE)
{
m_sockClient = accept(sockSrv, (SOCKADDR*)&addrClient, &m_nAddLength);
if(m_sockClient == INVALID_SOCKET)
{
return ;
}
HANDLE hRecvThread;
hRecvThread = CreateThread(NULL, 0, recvProc, (LPVOID)this, 0, NULL);
SetDlgItemText(IDC_EDIT_SEND, m_strRecv);
CloseHandle(hRecvThread);
}
}//线程函数
DWORD WINAPI CTcpServerDlg::recvProc(LPVOID lpParameter)
{
SOCKET sockClient = ((CTcpServerDlg*)lpParameter)->m_sockClient;
SOCKET sock = ((CTcpServerDlg*)lpParameter)->sockSrv;
int len = sizeof(SOCKADDR); char recvbuff[1024] = {0};
CString strRecv = "";
int recvError = 0; 
recvError = recv(sockClient, recvbuff, sizeof(recvbuff), 0);
if(recvError == SOCKET_ERROR)
{
return 0;
}
strRecv += recvbuff;
send(sockClient, (char*)"Server received data successfully", sizeof("Server received data successfully"), 0);
((CTcpServerDlg*)lpParameter)->m_strRecv = strRecv;
return 1;
}为什么实现不了啊,哪里有毛病,麻烦给指正一下,谢谢了。

解决方案 »

  1.   

    DWORD WINAPI CTcpServerDlg::recvProc(LPVOID lpParameter)
    DWORD WINAPI recvProc(LPVOID lpParameter)没调试,不知道你具体问题在哪里,不过从你这个框架上看,按钮按下去了界面也就死了
      

  2.   

    还有个问题,为什么我在程序里不能定义应用程序类和窗口类的析构函数啊Linking...
    TcpServer.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall CTcpServerApp::~CTcpServerApp(void)" (??1CTcpServerApp@@UAE@XZ)
    Debug/TcpServer.exe : fatal error LNK1120: 1 unresolved externals什么原因?
      

  3.   

    OnButtonListen是windows消息处理回调的函数,你在这个函数里面死循环,windows消息回调无法执行下去了,当然会出现窗口死掉的现象。
    建议在线程中调用accept的循环等待或者用异步通讯(select方式)
      

  4.   

    m_sockClient = accept(sockSrv, (SOCKADDR*)&addrClient, &m_nAddLength);hRecvThread = CreateThread(NULL, 0, recvProc, (LPVOID)this, 0, NULL);中应该把m_sockClient 当作参数传近来把,
    recvError = recv(sockClient , recvbuff, sizeof(recvbuff), 0);
    可能要这样用recvError = recv(m_sockClient, recvbuff, sizeof(recvbuff), 0);
      

  5.   

    把this指针传过去m_sockClient就也传过去了吧。
      

  6.   

    CloseHandle(hRecvThread)是不是太早了
      

  7.   

    你这里面基本的通讯都没处理好,比如Recv的返回值为0
      

  8.   

    那你知道Recv返回0不处理意味着什么?意味着你永远在执行一个死特环
      

  9.   


    在 MFC 中,一般用 AfxBeginThread 函数创建并运行线程。
      

  10.   

    AfxBeginThread只是多createthread做了一个封装吧,原理应该还是一样的吧。
    简单问一下吧,服务器那个“开启服务”按钮的响应函数该怎么写啊?
      

  11.   

    把代码放到线程当中,因为那是一个while accept死循环,如果处于accept阻塞状态下,当然也就阻塞了整个线程的执行。
      

  12.   

    是把accept放到线程里面么?
      

  13.   

    1,按钮响应函数里创建线程不会阻塞界面响应吧?有什么应该注意的没?
    2,accept()和recerve()放在一个循环里面么?
    谢谢了。
      

  14.   

    1,按钮响应函数里创建线程不会阻塞界面响应吧?有什么应该注意的没?
    >>当线程创建过程的代码执行完之后,相关代码就执行完成了,不会有等待。
    2,accept()和recerve()放在一个循环里面么?
    >>accept是在一个listening Socket当中accept连接,receive是接收accepted scoket当中的数据,两两不相关系,如果你在把accept和receive放同一线程当中,则你accept到一个连接之后就进入到receive当中去了,此时没有再次调用accept,新的连接是连不上来的。当然也可以使用多多个线程如此去各自accept一个连接,然后进行receive/send.
      

  15.   

    线程中可以创建线程么?如果每一个accept后都开一个线程去接收数据,那么如何写这个线程呢?难道只写一个线程系统就会为每个accept socket分配一个线程么?
    谢谢你这么耐心
      

  16.   

    线程中当然可以创建线程,而且你也应该为每个accept到的连接创建一个recv线程来接收数据
    大致看了一下你的代码,accept循环是过不去的,除非你closesocket(sockSrv),如果你要帧听多个连接,那你就得另开一个线程进行accept,否则,当你accept到一个有效的连接后,你就得break掉accept的循环进行下面的工作,你不防用工具看看你的线程是否创建成功了,任务管理器中把线程计数打开,你会发现你的recv线程是没开启的
    另外,MFC中不要用CreateThread创建线程,看看MSDN吧,CreateThread在MFC中有memory leak,用_beginthread 或者_beginthreadex,但记得要_endthread或者_endthreadex
      

  17.   

    谢谢了,accept()和createthread()都放在同一个循环里,难道也不可以创建出线程么?
    谢谢你这么宝贵的意见。
      

  18.   

    当然可以,但问题是你的OnButtonListen函数什么时候返回呢?如果OnButtonListen()是按钮的Click事件,那你的用户界面就死了...
      

  19.   

    while(TRUE)
    {
    m_sockClient = accept(sockSrv, (SOCKADDR*)&addrClient, &m_nAddLength);
    if(m_sockClient == INVALID_SOCKET)
    {
    return ;
    }
    HANDLE hRecvThread;
    hRecvThread = CreateThread(NULL, 0, recvProc, (LPVOID)this, 0, NULL);
    SetDlgItemText(IDC_EDIT_SEND, m_strRecv);
    CloseHandle(hRecvThread);
    }
    这段代码也得放到一个线程中去,否则accpet的时候主线程就阻塞了,你的程序界面自然就没反应了,其余的地方没仔细看,估计问题也都不大,注意处理recv返回0的情况,返回0是表示socket已经关闭了。
      

  20.   

    谢谢了,OnButtonListen()是按钮的Click事件响应函数,就是说在这里创建一个accept()线程,然后在accept里在创建一个recv()线程,是这样的么?
    SOCKET应该是在程序关闭的时候才关闭吧,如果不处理返回0的情况,会发生什么后果呢?
      

  21.   

    没错,另开一线程进行accept()
    返回0是对方已经关闭socket,所以你再对此socket进行IO操作都是惘然
      

  22.   

    可是如果第一个SOCKET的recv()还没有结束,又有一个SOCKET连接过来,可是一共就一个recv()线程函数,这个时候是怎么回事呢?
      

  23.   

    accept线程只需一个,而每accept到一个连接都需要为其开一个recv线程,所有你有多少个有效的连接就会有多少个recv线程,如果连接较多或者无效连接较频繁,建议使用线程池,那样可以减少线程创建与释放的开销,如果连接数量相当多,那就得使用完成端口了,注意两点,一个是进程的线程数量,缺省情况下2000个线程就是上限了,除非更改线程的堆栈,但太多线程并不可取,因为系统在巨量线程切换中的开销也是非常可怕的,再者就是连接数,系统的端口数量是有限的,端口类型为u_short,也就是说理论上系统最多支持65535个端口,还得除去系统保留以及其它程序占用,当然,你的程序应该不会要求这么多吧,如果确实用户数量过大,那加增加服务器,接下来就是服务器之间的协调与调度了
      

  24.   

    accept线程只需一个,而每accept到一个连接都需要为其开一个recv线程,所有你有多少个有效的连接就会有多少个recv线程一个线程函数会被那么多线程使用么?一般都是一个线程对应一个函数,可是现在有那么多连接,怎么去创建这么多个线程呢?谢谢了。
      

  25.   

    还是基本知道没明白,找本书看看吧,按你的意思一个函数被调用100次不是得有100个函数体了?
    如果是在做项目,建议你在继续工作之间先巩固一下基础知道,再把多线程以及线程安全方面的知识吃透了再继续,否则你写出来的的程序得不到任何保障
    如果纯粹是学习,而且对程序开发感兴趣,那就更应该把基本功打扎实!recv线程的回调函数跟你创建线程的数量没有关系,不管你是1个线程还是1000个线程,只是每次创建线程时,Caller会从你程序的代码段中找此回调函数并交给新创建的线程,你的程序中有很多地方需要修改,比方说你的m_sockClient,如果你接收了第二个连接,那第一连接的套接字描述符就被覆盖了,你就没办法再对其进行任何操作了
    你应该为每个连接创建一个客户端对象,然后把套接字描述符保存在此客房端对象中
    class CClient
    {
    public:
        CClient(SOCKET sock) {m_sock = sock;}
        ~CClient();    // ...
        SOCKET m_sock;
    };
    // listening (in accept thread)
    while(Listening)
    {
        SOCKET sock = accept(...);
        
        // validity checking
        if (sock == INVALID_SOCKET || sock == SOCKET_ERROR) continue;    CClient *pClient = new CClient(sock);
        // add the client to the client array
        // ...    // Create worker thread (Attention: pClient object used here)
        HANDLE hWorker = CreateThread(NULL, 0, recvProc, (LPVOID)pClient, 0, NULL);
        CloseHandle(hWorker);
    }// 线程函数(此处你把此函数作为类CTcpServerDlg的成员函数,不行吧,如果确实要这样使用,那你在头文件中需要将其定义成static)
    DWORD WINAPI CTcpServerDlg::recvProc(LPVOID lpParameter)
    {
        CClient *pClient = (CClient *)lpParameter;    // 进行你的操作: recv()/send()
        // 其套接字描述符为pClient->m_sock
        // ...
        return 0;
    }
    // 正是因为用到了lpParameter,所以recvProc()才知道自己要干什么,具体操作哪个socket对象,如果你再不明白,那我真没办法了
      

  26.   

    明白了,太感谢了。
    可以结帖了,还想问一下,我想把recvProc线程里收到的字串显示在主窗口的EDIT控件里,请问该怎么做?
      

  27.   

    你传给线程的参数中能够直接或者间接访问那个EDIT控件就行,用SetWindowText
      

  28.   

    如果我把控件指针p传给线程,那么在线程中可以用这个指针
    p->SetWindowText()吗?线程里这样用,UI上会对这个控件更新显示么?
      

  29.   

    当然会更新了,跟你在哪里调用都没有关系,但这样做有很多问题需要注意,如果你多个线程都操作同一个Edit,那就必须考虑线程安全,当然我不知道你的Edit控件是否是线程安全的,如果不是,那必须自行加入线程安机制,不然程序莫名其妙的崩溃了还不知道怎么回事...
      

  30.   

    没想到你还来关注
    线程安全机制是不是就是使用同步呢?
    然后再请教一个问题吧:
    对话框中点击一个“连接”按钮,弹出一个对话框,设置服务器IP,PORT,
    然后我打算开一个线程去连接这个服务器,请问这个线程写在“连接”的按钮事件中呢,还是模式对话框的OnOK()中呢。我是写在了“连接”中,当在对话框中点CANCEL按钮的时候,本来是没填IP什么的,可是 int nRet = connect(*psock, (SOCKADDR*)&addr, sizeof(SOCKADDR));
    if(SOCKET_ERROR == nRet)
    {
    AfxMessageBox("连接超时!");
    return 0;
    }
    这个对话框还是弹出来了,是不是我线程开的地方不对,或者是有什么办法解决吗?谢谢。
      

  31.   

    连接过程为什么要另开线程去做呢?这是个用户主动发起的行为,即使等待几秒或者更长一些时间也是允许的
    把线程去掉,直接调用你的代码即可,在弹出的对话框中加一个标志,用于保存用户是点击OK还是Cancel,如果是OK则执行连接线程安全是一个挺烦琐的东西,不等同于同步,简单地说吧,比如你开了一块内存空间,LPBYTE pBuf = new BYTE[32]; 在多个线程中都有这块内存的指针,当其中一个线程正往pBuf所指向的内存空间写数据时另一线程也发起对pBuf的写操作,这时就会出现内存错误,程序基本上崩溃,所以你一定要加一个保护锁,在有线程操作(写)pBuf时,其它有写操作的线程都挂起排队,直到获得前一线程写完数据的通知才继续,如果有多个线程在等待,那一次也只能放一个线程,多线程同时读操作不会导致内存错误,但数据的完成性不能够保证,比如一线程在写pBuf时,另一线程开始读操作,内存的读写操作是遵循一定顺序进行的,而且读操作比写操作要快,此时就可能出现你读出的数据一半(比例不一定)是之前的旧数据,一半是写线程刚写的新数据,所以读写操作也是要进行保护的,保护的方法很多,临界区、信号灯、Event、Mutex等都可以进行多线程保护以达到线程安全
    以上希望对你有所帮助...
      

  32.   

    太谢谢了。
    E-MAIL:[email protected]
      

  33.   

    把接收到的字符显示出来:
    在你的EDIT控件里用向导加个成员变量m_csOutPut,然后把接收到的BUFF赋给m_csOutPut, 即可显示.
    问题,你的多线程做完了吗?
    如果问题解决了,写一下你解决的思路吧
    我也遇到同样的问题了
      

  34.   

    你把这个帖子好好看看,就有思路了。
    Sky_huang(Sky)说地很清楚了。
    还是自己动手写一下吧。
      

  35.   

    我已经写好了单线程下的客户端与服务端通信的程序,但多个客户端连接的时候,服务端只能与最后一个与之连上的通信,因为没有创建多个客户端SOCKET,
    我的程序必须做成多线程或是异步处理,不然不能多客户端与服务器通时通信
    问题:
    1.在服务端accept的时候用多线程吗?
    2.在连接时给不同的客户端如何创建不同的SOKECT?
    提供一下思路吧
    这个贴子我研究了两天了.
      

  36.   

    算了,我还是贴代码吧://////////////////*CNewSocket.h*//////////////////////
    #define WM_FD_ACCEPT WM_USER+1
    #define WM_FD_RECEIVE WM_USER+2
    #define WM_FD_CLOSE WM_USER+3#include "stdafx.h"
    #include "afxsock.h"class CNewSocket : public CSocket
    {
    // Attributes
    public:// Operations
    public:
        CNewSocket(HWND hwnd);
        virtual ~CNewSocket();// Overrides
    public:    // ClassWizard generated virtual function overrides
        //{{AFX_VIRTUAL(CNewSocket)
        //}}AFX_VIRTUAL    virtual void OnClose(int nErrorCode);
        virtual void OnReceive(int nErrorCode);
        virtual void OnAccept(int nErrorCode);    // Generated message map functions
        //{{AFX_MSG(CNewSocket)
            // NOTE - the ClassWizard will add and remove member functions here.
        //}}AFX_MSG
    private:
        HWND m_hWnd;// Implementation
    protected:
    };//////////////////*CNewSocket.cpp*//////////////////////
    #include "stdafx.h"
    #include "CNewSocket.h"CNewSocket::CNewSocket(HWND hwnd)
    {
        m_hWnd = hwnd;
    }CNewSocket::~CNewSocket()
    {
    }void CNewSocket::OnAccept(int nErrorCode)
    {
    AfxMessageBox(_T("Sure to Connect ?"));
        PostMessage(m_hWnd, WM_FD_ACCEPT, 0, 0);
        CSocket::OnAccept( nErrorCode );
    }void CNewSocket::OnReceive(int nErrorCode)
    {
    // AfxMessageBox(_T("Dates will send to the server!"));
        PostMessage(m_hWnd, WM_FD_RECEIVE, 0, 0);
        CSocket::OnReceive( nErrorCode );
    }void CNewSocket::OnClose(int nErrorCode)
    {
        PostMessage(m_hWnd, WM_FD_CLOSE, 0, 0);
        CSocket::OnClose( nErrorCode );
    }
      

  37.   

    ............................
    // CCSmodeDlg message handlersBOOL CCSmodeDlg::OnInitDialog()
    {
    CDialog::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
    CString strAboutMenu;
    strAboutMenu.LoadString(IDS_ABOUTBOX);
    if (!strAboutMenu.IsEmpty())
    {
    pSysMenu->AppendMenu(MF_SEPARATOR);
    pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
    }
    } // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE); // Set big icon
    SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here
    m_nStatus = -1;
        m_pServerSocket = NULL;
        m_pClientSocket = NULL;
        m_csConnect = "";
        m_IpAddress = "127.0.0.1";
        m_Port = 7897;
    //GetDlgItem(IDC_BtnReceiveClear)->EnableWindow(FALSE);
    //GetDlgItem(IDC_BtnSendClear)->EnableWindow(FALSE);

    return TRUE;  // return TRUE  unless you set the focus to a control
    }void CCSmodeDlg::OnSysCommand(UINT nID, LPARAM lParam)
    {
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
    CAboutDlg dlgAbout;
    dlgAbout.DoModal();
    }
    else
    {
    CDialog::OnSysCommand(nID, lParam);
    }
    }// If you add a minimize button to your dialog, you will need the code below
    //  to draw the icon.  For MFC applications using the document/view model,
    //  this is automatically done for you by the framework.void CCSmodeDlg::OnPaint()
    {
    if (IsIconic())
    {
    CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // Center icon in client rectangle
    int cxIcon = GetSystemMetrics(SM_CXICON);
    int cyIcon = GetSystemMetrics(SM_CYICON);
    CRect rect;
    GetClientRect(&rect);
    int x = (rect.Width() - cxIcon + 1) / 2;
    int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon
    dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {   
    CDialog::OnPaint();
    }
    }// The system calls this function to obtain the cursor to display while the user drags
    //  the minimized window.
    HCURSOR CCSmodeDlg::OnQueryDragIcon()
    {
    return static_cast<HCURSOR>(m_hIcon);
    }LRESULT CCSmodeDlg::OnReceive(WPARAM wParam,LPARAM lParam)
    { TCHAR *buff = new TCHAR[1024 * 1024];
        memset(buff, 0, 1024 * 1024);
        int n = m_pClientSocket->Receive(buff , 1024* 1024); 
    m_csInPut = buff;
    delete []buff; 
    //m_pServerSocket->Socket()RemoteAddress
        UpdateData(FALSE);
    return S_OK;
    }
     
    LRESULT CCSmodeDlg::OnAccept(WPARAM wParam,LPARAM lParam)
    {
    //m_csConnect += "OK!";
    m_csConnect = "Already Conntected"; 
        UpdateData(FALSE);
        m_pClientSocket = new CNewSocket(this->m_hWnd);
        m_pServerSocket->Accept(*m_pClientSocket);
    return NULL;
    }LRESULT CCSmodeDlg::OnClose(WPARAM wParam, LPARAM lParam)
    {
    if (1 == m_nStatus)
        {
            m_csConnect = "Client";
        }
        else
        {
            m_csConnect = "Server"; 
        }
        m_csConnect += " has disconnected";
        UpdateData(FALSE);
    return NULL;
    }void CCSmodeDlg::SocketReset()
    {
        if (m_pServerSocket != NULL)
        {
            delete m_pServerSocket;
            m_pServerSocket = NULL;
        }    if (m_pClientSocket != NULL)
        {
            delete m_pClientSocket;
            m_pClientSocket = NULL;
        }    m_csConnect = "";
        UpdateData(FALSE);
    }void CCSmodeDlg::OnBnClickedBtnstart()
    {
    // TODO: Add your control notification handler code here
    m_nStatus = 1;
    if (m_pServerSocket != NULL)     //服务器Socket是否已经创建
            {
                m_csConnect = "Please disconnect!";
                UpdateData(FALSE);
            }
            else
            {
                m_csConnect = "Wait for client...";
                UpdateData(FALSE);
                
                if (!AfxSocketInit())   //Socket初始化
                {
                    MessageBox(_T("WindowSocket initial failed!", "Send", MB_ICONSTOP));
                    return;
                }
                //将Socket与窗口关联
                m_pServerSocket = new CNewSocket(this->m_hWnd);
                
                //如果创建Socket失败则提示,成功则开始监听
                if (!m_pServerSocket->Create(m_Port))
                {
                    MessageBox(_T("SendSocket Create failed!", "Send", MB_ICONSTOP));
                }
                else
                {
                    m_pServerSocket->Listen();
                }
            }
    }void CCSmodeDlg::OnBnClickedBtnconnect()
    {
    // TODO: Add your control notification handler code here
    m_nStatus = 0;
    if (m_pClientSocket != NULL) //判断客户端Socket是否已经创建
            {
                m_csConnect = "Please disconnect!";
                UpdateData(FALSE);
            }
            else
            {
                m_csConnect = "Connect to the server...";
                UpdateData(FALSE);
                if (!AfxSocketInit())
                {
                    MessageBox(_T("WindowSocket initial failed!", "Receive", MB_ICONSTOP));
                    return;
                }
                m_pClientSocket = new CNewSocket(this->m_hWnd);
                if(!m_pClientSocket->Create())
                { 
                    MessageBox(_T("ReceiveSocket create failed!","Receive",MB_ICONSTOP)); 
                    return; 
                }
                else
                {
                    if (!m_pClientSocket->Connect(m_IpAddress, m_Port))
                    {
                        CString strTmp = m_csConnect;
                        SocketReset();
                        m_csConnect = strTmp;
                        m_csConnect += "Error!";
                        UpdateData(FALSE);
                    }
                    else
                    {
                        m_csConnect += "OK!";
                    }
                    UpdateData(FALSE);
                }
            }
    }void CCSmodeDlg::OnBnClickedBtnsend()
    {
    // TODO: Add your control notification handler code here
    UpdateData(TRUE);
    if(m_pClientSocket != NULL)
    this->m_pClientSocket->Send(m_csOutPut.GetBuffer(0), m_csOutPut.GetLength() * sizeof(TCHAR));
    else
    MessageBox(_T("The connection hasn't built yet!"));
    }void CCSmodeDlg::OnBnClickedBtnsendclear()
    {
    // TODO: Add your control notification handler code here
     m_csOutPut = "";
        UpdateData(FALSE);
    }void CCSmodeDlg::OnBnClickedBtnreceiveclear()
    {
    // TODO: Add your control notification handler code here
    m_csInPut = "";
        UpdateData(FALSE);
    }void CCSmodeDlg::OnBnClickedBtndisconnect()
    {
    // TODO: Add your control notification handler code here
    SocketReset();
        m_csConnect = "Disconnect!";
        UpdateData(FALSE);
    }void CCSmodeDlg::OnBnClickedBtnend()
    {
    // TODO: Add your control notification handler code here
    SocketReset();
        m_csConnect = "Server";
        m_csConnect += " has disconnected";
        UpdateData(FALSE);
    }界面没法贴了.我想在此基础上做出如下改进:
    A与B连接后C又与A连接,A能同时与BC直接互通消息,且B不必先断开后再连接
    求思路方法!
    另祝中秋快乐