我想做一个小的服务器,功能是与客户端的信息交流,比如:数据传输,文件发送,
而且是多线程的,我刚刚开始学vc++一个月,mfc我还没入手,很多东西都不懂,可是
我要在不到两个月的时间去搞定这个测试项目才不会失业,真的很急,大家帮帮忙我该
怎么去做,要了解什么知识? 能提供资料吗 ?
    如果谁有资料帮忙发到
                                [email protected]
    不要太大,我的邮箱是 5MB的
    谢谢你的帮忙~!~!~

解决方案 »

  1.   

    1、MSDN里就有几个例子;
    2、到www.codeguru.com找;
    3、到www.google.com找,在搜索框里输入:VC++,WinSock,Socket,绝对不回让你失望!
      

  2.   

    基于TCP/IP的局域网多用户通信
    作者:华东船舶工业学院机械系 袁 渊下载本文示例源代码摘要:基于TCP/IP的网络通信技术实现了面向连接的用户与服务器间点对点异步通信,本文在该基础上应用了多线程以及共享数据结构技术,使网络服务器具有了多用户间数据转发的功能,进而解决了局域网多用户间的通信问题。关键词:TCP/IP;多线程;共享;通信;网络
    引言由于因特网的迅速流行,越来越多的应用程序具备了在网上与其它程序通信的能力。从WIN95开始微软把网络功能融进了它的操作系统,使得应用程序网络通信能力更为普及。因此,微软的TCP/IP协议也就成为网络应用程序基于的首选协议。一般采用TCP/IP协议的应用程序只实现了单用户与服务器间点对点的连接,而本文在VC6.0的环境下,运用了了多线程以及共享数据结构技术,不仅实现了多用户与服务器间的连接,而且解决了多用户间信息互发问题----依靠服务器的转发功能。通过本文的阐述,希望能对那些需要编写多用户网络通信程序的读者以启发。 一、技术概述 
    1.1 基于TCP/IP的通信技术基于TCP/IP的通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。下面简要地讲一下设计思路(VC6.0下):第一部分 服务器端
      一、创建服务器套接字(create)。
      二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。
      三、接受来自用户端的连接请求(accept)。
      四、开始数据传输(send/receive)。
      五、关闭套接字(closesocket)。第二部分 用户端
      一、创建用户套接字(create)。
      二、与远程服务器进行连接(connect),如被接受则创建接收进程。
      三、开始数据传输(send/receive)。
      四、关闭套接字(closesocket)。通过以上设计思路,我们可以创建一个简单的面向连接的单用户程序。下面,将介绍多线程技术,以使程序支持多用户。1.2 多线程技术我们可以把线程看成是一个进程(执行程序)中的一个执行点,每个进程在任何给定时刻可能有若干个线程在运行。一个进程中的所有线程共享该进程中同样的地址空间,同样的数据和代码,以及同样的资源。进程中每个线程都有自己独立的栈空间,和其它线程分离,并且不可互相访问。每个线程在本进程所占的CPU时间内,要么以时间片轮换方式,要么以优先级方式运行。如果以时间片轮换方式运行,则每个线程获得同样的时间量;如果以优先级方式运行,则优先级高的线程将得到较多的时间量,而优先级低的线程只能得到较少的时间量。方式的选择主要取决于系统时间调度器的机制以及程序的实时性要求。现在,运用多线程技术就可以实现对多用户的支持。即在服务器端,使接收来自用户端的连接请求(accept)这步无限循环,每接收一个用户请求,产生两个线程(send和receive线程),用来管理服务器与该用户的通信任务。下面,运用共享数据结构技术,就可以实现本问所要解决的关键技术---服务器转发技术。1.3 共享数据结构技术同一进程中的多个线程共存于单一的线性地址空间,因此,在多线程间共享数据结构是非常容易且方便的。但必须注意的是,对数据结构的访问必须是多线程互斥的,否则数据任意更改将导致不可预料的结果。本文所阐述的服务器转发技术也就是通过共享数据结构实现线程间的互相通信。 二、实现方案整体方案的构思图如下:
     
    通过上图,我们可以看到整个系统分为三个相关的程序,即注册/登陆服务器、通信服务器以及用户程序。其中,注册/登陆服务器负责用户的注册、登陆以及数据库管理;通信服务器负责完成数据转发以及共享数据结构的管理;用户端则完成注册、登陆和通信功能。为什么要把服务器分为两部分呢?主要是考虑到服务器的用户容量问题,以及对通信服务器的保护,只有在通过验证后,用户在能与通信服务器连接。由此可见,整个系统通信任务的实现还是很复杂的。用户端首先必须注册自己,等待注册成功;然后根据自己的注册信息进行服务器登陆,登陆成功后才能与通信服务器连接,进行用户间通信。注册/登陆服务器接收到用户端的信息后,首先判断是注册信息还是登陆信息。如果是注册信息,则将该数据按预定的格式写入数据库,然后返回注册成功的消息,期间有任何异常产生,服务器都会返回注册失败消息,提示用户重新注册;如果是登陆信息,则从数据中提取用户名和ID与数据库中的内容进行比较,如果该用户存在,则返回登陆成功消息,反之,返回登陆失败消息。通信服务器所完成的主要功能是数据转发,这是通过与图中的共享数据结构进行交互完成的。服务器接收到用户端发来的消息后,提取消息的一部分与共享数据结构存储的内容进行比较,确定所要转发的对象,最后通过多线程及其通信机制完成数据转发。 下面,我们将分三部分来讨论系统的具体实现过程。 三、具体实施 
    3.1 注册/登陆服务器注册/登陆服务器程序是基于对话框的,该程序使用I/O端口56789与用户端连接。
    首先,在对话框初始化的同时完成网络初始化,即执行Init_net()函数,代码(不完整)如下:BOOL CServerDlg::Init_net()
    {////////////////////////网络初始化///////////////////////////////
        addrLen=sizeof(SOCKADDR_IN);
        status=WSAStartup(MAKEWORD(1, 1), &Data);
        ………
        memset(&serverSockAddr, 0, sizeof(serverSockAddr));/*以下指定一个与某个SOCKET连接本地或远程地址*/    serverSockAddr.sin_port=htons(PORT);
        serverSockAddr.sin_family=AF_INET;
        serverSockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
        serverSocket=socket(AF_INET, SOCK_STREAM, 0);//初始化SOCKET
        ………    status=bind(serverSocket,(LPSOCKADDR)&serverSockAddr,sizeof(serverSockAddr)); //将SOCKET与地址绑定
        ………
        status=listen(serverSocket, 5); //开始监听
        ………
        return true;
    }
    接着按下RUN键开始服务器功能,执行Reg_Load()函数,使服务器始终处于等待连接状态,但这样也使该线程始终阻塞。当有用户连接时,该函数创建一个任务用于处理与用户及数据库的事务。具体任务函数略(详见原始代码文件)。 void CServerDlg::Reg_Load()
    {   
    while(1)
    {
    CWinThread*  hHandle;
    clientSocket=accept(serverSocket,(LPSOCKADDR)&clientSockAddr,&addrLen); //等待连接,阻塞
    hHandle=AfxBeginThread(talkToClient,(LPVOID)clientSocket);//有连接时,创建任务
            ………
    }
    }
    任务函数在接收到消息时,要对数据库进行操作,由于数据库较简单,采用ODBC连接ACCESS数据库(将netuser.mdb在ODBC数据管理器中安装成同名数据源)具体代码略。 
    3.2 通信服务器通信服务器是本程序实现的关键,它运用共享数据结构技术及多线程技术,通过I/O端口56790与用户端连接,实现了数据转发功能。首先,程序初始化网络Init_net(),接着当用户连接到服务器时,创建接收线程和发送线程,这样就可以实现数据转发。最后,当用户断开连接时,服务器关闭与他的连接,并结束相应的线程。下面我们来看一下本程序中的共享数据结构的具体内容与使用方法以及多线程的相关内容与实现。● 共享数据结构本程序的共享数据结构一共有两个,即socket_info和send_info。前者包含了所有登陆用户的一些基本资料,后者则包含了当前服务器接收到的用户端所发送的信息资料。详细内容及注释如下:struct socket_info  
    {
        SOCKET s_client;                    //用户的SOCKET值
        u_long client_addr;             //用户网络地址
        CString pet;                        //用户昵称
        CWinThread* thread;             //为该用户创建的发送线程对象的指针
    };struct send_info
    {
        CString data;                   //用户端发送的数据内容(经过编辑)
        CWinThread* thread;             //需要发送数据的任务指针
    };
    在程序中,定义两个全局变量,用来在线程间共享: send_info info_data; CList<socket_info,socket_info&>s_info;
    每当有用户连接到服务器,服务器就将用户端的一些信息以socket_info结构体的形式存入s_info列表中;而当服务器接收到用户端发送过来的数据时,就将数据格式化后存入结构体info_data,通过与结构体列表比较,确定需要恢复的发送线程(所有发送线程在创建时都被挂起)。这样,服务器就准确地转了发数据。 ●多线程 
    每当服务器上有用户连接成功,服务器都会为其创建两个线程:接收线程(RecvData)和发送线程(SendData),并且接收线程在创建后处于可执行状态,而发送线程则阻塞,等待服务器将其唤醒。这两个线程都执行一个无限循环的过程,只有当通信出现异常或用户端关闭连接时,线程才被自身所结束,并且,这两个线程一定是同时生成,同时结束的。很显然,每个连接产生两个线程,使得数据转发变的简单,但同时又使得服务器的任务加重。因此,用户端的连接数量有所限制,视服务器软、硬件能力而定。同时,由于多线程对结构体info_data都需要操作,所以线程间必须同步。这儿,我定义了互斥量CMutex m_mutex,用它的方法Lock()和Unlock()来完成同步。我们首先来看一下接收线程(RecvData):(不完整代码)UINT RecvData(void* cs)
    {   
    SOCKET clientSocket=(SOCKET)cs;
    while(1)
    {
    numrcv=recv(clientSocket, buffer, MAXBUFLEN, NO_FLAGS_SET);
    buffer[numrcv]=''\0'';
    if(strcmp(buffer,"Close!")!=0)  //不是接收的“Close”数据
    {
    …………
            for(i=0;i<count;i++)
            {
    if(po!=NULL)
    {
    s1=s_info.GetNext(po);
    if(s1.pet.Compare(petname)==0)      //比较昵称是否一样
    {
    m_mutex.Lock();    //互锁
    info_d
      

  3.   

    TCP/IP Winsock编程要点 蒋勇 2002.5.23   利用Winsock编程由同步和异步方式,同步方式逻辑清晰,编程专注于应用,在抢先式的多任务操作系统中(WinNt、Win2K)采用多线程方式效率基本达到异步方式的水平,应此以下为同步方式编程要点。 1、快速通信 Winsock的Nagle算法将降低小数据报的发送速度,而系统默认是使用Nagle算法,使用 int setsockopt(   SOCKET s,                   int level,                   int optname,                const char FAR *optval,    int optlen                );函数关闭它 例子: SOCKET sConnect; sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); int bNodelay = 1; int err; err = setsockopt(         sConnect,         IPPROTO_TCP,         TCP_NODELAY,         (char *)&bNodelay,         sizoeof(bNodelay));//不采用延时算法 if (err != NO_ERROR) TRACE ("setsockopt failed for some reason\n");;   2、SOCKET的SegMentSize和收发缓冲 TCPSegMentSize是发送接受时单个数据报的最大长度,系统默认为1460,收发缓冲大小为8192。 在SOCK_STREAM方式下,如果单次发送数据超过1460,系统将分成多个数据报传送,在对方接受到的将是一个数据流,应用程序需要增加断帧的判断。当然可以采用修改注册表的方式改变1460的大小,但MicrcoSoft认为1460是最佳效率的参数,不建议修改。 在工控系统中,建议关闭Nagle算法,每次发送数据小于1460个字节(推荐1400),这样每次发送的是一个完整的数据报,减少对方对数据流的断帧处理。   3、同步方式中减少断网时connect函数的阻塞时间 同步方式中的断网时connect的阻塞时间为20秒左右,可采用gethostbyaddr事先判断到服务主机的路径是否是通的,或者先ping一下对方主机的IP地址。 A、采用gethostbyaddr阻塞时间不管成功与否为4秒左右。 例子: LONG lPort=3024; struct sockaddr_in ServerHostAddr;//服务主机地址 ServerHostAddr.sin_family=AF_INET; ServerHostAddr.sin_port=::htons(u_short(lPort)); ServerHostAddr.sin_addr.s_addr=::inet_addr("192.168.1.3"); HOSTENT* pResult=gethostbyaddr((const char *) & (ServerHostAddr.sin_addr.s_addr),4,AF_INET);  if(NULL==pResult) { int nErrorCode=WSAGetLastError(); TRACE("gethostbyaddr errorcode=%d",nErrorCode); } else { TRACE("gethostbyaddr %s\n",pResult->h_name);; } B、采用PING方式时间约2秒左右 暂略   4、同步方式中解决recv,send阻塞问题 采用select函数解决,在收发前先检查读写可用状态。 A、读 例子: TIMEVAL tv01 = {0, 1};//1ms钟延迟,实际为0-10毫秒 int nSelectRet; int nErrorCode; FD_SET fdr = {1, sConnect}; nSelectRet=::select(0, &fdr, NULL, NULL, &tv01);//检查可读状态 if(SOCKET_ERROR==nSelectRet) { nErrorCode=WSAGetLastError(); TRACE("select read status errorcode=%d",nErrorCode); ::closesocket(sConnect); goto 重新连接(客户方),或服务线程退出(服务方); } if(nSelectRet==0)//超时发生,无可读数据 { 继续查读状态或向对方主动发送 } else { 读数据 }         B、写 TIMEVAL tv01 = {0, 1};//1ms钟延迟,实际为9-10毫秒 int nSelectRet; int nErrorCode; FD_SET fdw = {1, sConnect}; nSelectRet=::select(0,  NULL, NULL,&fdw, &tv01);//检查可写状态 if(SOCKET_ERROR==nSelectRet) { nErrorCode=WSAGetLastError(); TRACE("select write status errorcode=%d",nErrorCode); ::closesocket(sConnect); //goto 重新连接(客户方),或服务线程退出(服务方); } if(nSelectRet==0)//超时发生,缓冲满或网络忙 { //继续查写状态或查读状态 } else { //发送 } 5、改变TCP收发缓冲区大小 系统默认为8192,利用如下方式可改变。 SOCKET sConnect; sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); int nrcvbuf=1024*20; int err=setsockopt( sConnect,         SOL_SOCKET,        SO_SNDBUF,//写缓冲,读缓冲为SO_RCVBUF (char *)&nrcvbuf, sizeof(nrcvbuf)); if (err != NO_ERROR) { TRACE("setsockopt Error!\n"); } 在设置缓冲时,检查是否真正设置成功用 int getsockopt( SOCKET s,         int level,        int optname,      char FAR *optval, int FAR *optlen  ); 6、服务方同一端口多IP地址的bind和listen 在可靠性要求高的应用中,要求使用双网和多网络通道,再服务方很容易实现,用如下方式可建立客户对本机所有IP地址在端口3024下的请求服务。 SOCKET hServerSocket_DS=INVALID_SOCKET; struct sockaddr_in HostAddr_DS;//服务器主机地址 LONG lPort=3024; HostAddr_DS.sin_family=AF_INET; HostAddr_DS.sin_port=::htons(u_short(lPort)); HostAddr_DS.sin_addr.s_addr=htonl(INADDR_ANY);   hServerSocket_DS=::socket( AF_INET, SOCK_STREAM,IPPROTO_TCP);   if(hServerSocket_DS==INVALID_SOCKET) { AfxMessageBox("建立数据服务器SOCKET 失败!"); return FALSE; }   if(SOCKET_ERROR==::bind(hServerSocket_DS,(struct sockaddr *)(&(HostAddr_DS)),sizeof(SOCKADDR))) { int  nErrorCode=WSAGetLastError (); TRACE("bind error=%d\n",nErrorCode);                AfxMessageBox("Socket Bind 错误!"); return FALSE; } if(SOCKET_ERROR==::listen(hServerSocket_DS,10))//10个客户 { AfxMessageBox("Socket listen 错误!"); return FALSE; } AfxBeginThread(ServerThreadProc,NULL,THREAD_PRIORITY_NORMAL); 在客户方要复杂一些,连接断后,重联不成功则应换下一个IP地址连接。也可采用同时连接好后备用的方式。   7、用TCP/IP Winsock实现变种Client/Server 传统的Client/Server为客户问、服务答,收发是成对出现的。而变种的Client/Server是指在连接时有客户和服务之分,建立好通信连接后,不再有严格的客户和服务之分,任何方都可主动发送,需要或不需要回答看应用而言,这种方式在工控行业很有用,比如RTDB作为I/O Server的客户,但I/O Server也可主动向RTDB发送开关状态变位、随即事件等信息。在很大程度上减少了网络通信负荷、提高了效率。 采用1-6的TCP/IP编程要点,在Client和Server方均已接收优先,适当控制时序就能实现。