arongnet 阅读(867) 评论(4)
根据前面一节的说明,服务端套接字应该按照如下顺序建立:
  1. 初始化
  2. 创建套接字
  3. 绑定本地地址
  4. 进入侦听状态
  5. 处理接受循环

下面首先创建一个例子来演示服务端套接字的实现,并在以后的各节中优化这个设计。

这个设计实现的功能如下:允许客户端(实际上就是telnet程序)登陆,并对客户端的输入回显。

4.1 准备工程

为了实现这个demo,我打算使用Visual Studio 6.0提供的对话框模板来实现,请按照下述步骤准备工程:

  1. 启动Visual C++ 6.0
  2. 选择File/New/Project
  3. 选择MFC AppWizard(exe)
  4. 在Project name中输入demo1, Next
  5. 选择Dialog Base, Next
  6. 在Windows Sockets前打钩
  7. 其他都保持缺省值,点击Next完成向导
  8. 在工程中加入两个文件:sockutil.h和sockutil.cpp:这两个文件将保存公共的函数,避免以后重复编写。

这样创建的工程会自动加入WinSock2支持,具体的内容包括:

  1. 在stdafx.h中加入#include <afxsock.h>,该头文件包含如下语句载入对应LIB:
    #pragma comment(lib, "wsock32.lib")
  2. 在InitInstance中加入如下代码:
    if(!AfxSocketInit())
    {
          AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
          return FALSE;
    }
    
  3. 在资源中定义字符串资源IDP_SOCKETS_INIT_FAILED:Windows通讯端口初始化失败

    如果大家创建工程时没有加入对应的sock支持,我们可以参照上述列表手工加入。

    对于前文所述的sockutil.h文件,加入如下初始代码:

    #if !defined(__SOCKET_UTILITY_HEADER__)
    #define __SOCKET_UTILITY_HEADER__
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    #endif 
    

    对于sockutil.cpp文件,则加入如下初始代码:

    #include "stdafx.h"
    #include "sockutil.h"
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif
    
    

    4.2 准备错误处理方式

    在编制每个程序之前,我习惯是为错误处理定制一个统一的处理方式。在以后的例子中,我会按照如下的方式处理错误:在一个log文件中记录错误发生的位置(文件、行号),错误号和字符串解释。为了实现这个目的,我在sockutil.cpp定义如下的函数:

    bool ErrorHandle(LPCTSTR expression, bool bFalse, LPCTSTR file, UINT line)
    {
        if(!bFalse)
        {//没有错误,直接返回
         return false;
        }
        FILE * fp;
        fp = fopen("demo.log","at");
        if(NULL == fp)
        {//如果文件打开失败,放弃记录
         return true;
        }
        //获得错误码
        DWORD ErrorCode = GetLastError();
        //格式化成字符串格式
        LPVOID lpMsgBuf;
        FormatMessage( 
            FORMAT_MESSAGE_ALLOCATE_BUFFER | 
            FORMAT_MESSAGE_FROM_SYSTEM | 
            FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            ErrorCode ,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
            (LPTSTR) &lpMsgBuf,
            0,
            NULL 
         );
        //输出到文件
        fprintf(fp,_T("file=%s,line=%u\n%d:%s\n"),file,line,ErrorCode, (LPCTSTR)lpMsgBuf);
        // 释放空间,该空间由FormatMessage分配
        LocalFree( lpMsgBuf );
        //关闭文件
        fclose(fp);
     
        return true;
    }
    

    在sockutil.h中,添加如下代码:

    bool ErrorHandle(LPCTSTR expression, bool bFalse, LPCTSTR file, UINT line);
    #define ERRORHANDLE(expression) ErrorHandle(#expression,(expression),__FILE__,__LINE__)
    

    4.3 核心代码

    根据前文,设计该服务端套接字主函数如下:

    void sockmain(LPCTSTR ip, UINT port)
    {
    
       SOCKET hSocket;
       hSocket = socket(AF_INET,SOCK_STREAM,0);
       if(ERRORHANDLE(hSocket == INVALID_SOCKET))
       {
          return;
       }
       sockaddr_in addr;
       InitializeAddress(inet_addr(ip), port, addr);
       if(ERRORHANDLE(SOCKET_ERROR == bind(hSocket, (const sockaddr*) & addr, sizeof(addr))))
       {
          closesocket(hSocket);
          return;
       }
       if(ERRORHANDLE(SOCKET_ERROR == listen(hSocket,5)))
       {
          closesocket(hSocket);
          return;
       }
       SOCKET hClient;
       int size;
       char buffer[2048];
       int length;
       size = sizeof(addr);
       while(INVALID_SOCKET != (hClient = accept(hSocket,(sockaddr*)&addr, & size)))
       {
         size = sizeof(addr);
         while((length = recv(hClient, buffer, sizeof(buffer),0)) > 0)
         {
           SendData(hClient,buffer, length);
         }
         closesocket(hClient);
       }
       closesocket(hSocket);
    
       return;
    }
    

    其中,InitializeAddress定义如下:

    void InitializeAddress(DWORD ip, UINT port, sockaddr_in & addr)
    {
       memset(&addr,0,sizeof(addr));
       addr.sin_family  = AF_INET;
       addr.sin_addr.s_addr= ip;
       addr.sin_port       = htons(port);
    }
    
    SendData定义如下:
    int SendData(SOCKET hSocket, const char * data, int length)
    {
       int result;
       int pos = 0;
       while(pos < length)
       {
          result = send(hSocket, data + pos, length - pos , 0);
          if(result > 0 )
         {
            pos += result;
         }else{
            return result;
         }
       }
     return length;
    }
    

    4.4 启动该任务

    为了启动服务端套接字,我们可以在对话框资源的OK按钮上双击,然后在OnOK中添加如下代码

       sockmain("0.0.0.0",2000);
    

    当我们启动该工程,并点击OK按钮,就可以通过telnet来测试是否有回显功能了。


    评论列表
    fliex
    re: socket 程序设计(4) - 服务端套接字demo1
    为什么我照着你的做,telnet不能打开连接呢??
    馨荣家园
    re: socket 程序设计(4) - 服务端套接字demo1
    哪就不知道逆怎么搞得了
    babazhang
    re: socket 程序设计(4) - 服务端套接字demo1
     memset(&addr,0,sizeof(addr)); 
    这里的地址不需要初始化。
    arong
    re: socket 程序设计(4) - 服务端套接字demo1
    如果你不这样初始化,你就需要初始化后面8字节。我的经验是:如果不初始化这个,有时有问题

    发表评论
    切换编辑模式