65.9K
CodeProject 正在变化。 阅读更多。
Home

一个简单但有效的 Windows 套接字程序

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.29/5 (9投票s)

2006年6月23日

CPOL

4分钟阅读

viewsIcon

59673

downloadIcon

562

这个Windows服务器套接字类封装了Winsock函数,让您无需修改代码的复杂性,就能编写出健壮的代码。

引言

由于需要通过互联网从运行在Unix平台下的一个盒子接收命令,并将响应发送回它,我需要创建一个套接字服务器应用程序。我需要一个套接字类,而我找到的每一个教程或示例代码要么没有制作出足够健壮的套接字类,要么使代码难以使用(通过使其过于特定于它们的使用)。所以,我决定自己编写代码。它允许您有一个覆盖代码来控制套接字服务器,从某种意义上说,您有一个执行类来启动您的套接字,告诉它何时发送和接收等等。

class server_socket
{
    ///<summary> constructor and destructor</summary>
public:
    //constructs a well known socket structure
    server_socket(int user_port_number);
    //constructs a accepted socket structure
    server_socket();
    //destructor
    ~server_socket();
    ///<summary>Operations</summary>///
public:
    //creates the socket connection
    bool create_socket();
    //accepts a client connection
    bool socket_accept();
    //gives you the socket address
    sockaddr* socket_address();
    //socket send 
    bool socket_send(const char* data_buffer,int data_size);

    //socket receive
    bool socket_receive(char* data_buffer, int data_size);
    //bool socket_receive(char* data_buffer, int data_size);
    //shutdown the socket
    bool socket_shutdown();
protected:
    short port_number; //this is the port number
    struct sockaddr_in socket_structure; //provides the low-level TCP/IP socket structure
    SOCKET    theClient;
};

首先,我们将要监听的端口号作为参数传递给构造函数。然后,我们继续将套接字结构清零。我们使用适当的系列分配套接字地址系列;在我们的例子中,这是AF_INET。我们有地址,在我们的例子中是INADDR_ANY;这告诉服务器监听通过指定端口的任何类型的IP地址。当然,我们使用在构造函数中作为参数传递的端口来分配套接字端口。

server_socket::server_socket(int user_port_number)
:port_number(user_port_number)
{
    memset(&socket_structure, 0, sizeof(struct sockaddr_in));
    socket_structure.sin_family=AF_INET;
    socket_structure.sin_addr.s_addr=INADDR_ANY;
    socket_structure.sin_port=htons(port_number);
    created_from_accept=false;
}

在通常情况下,您希望您的套接字代码能够创建一个连接(它将处理监听和绑定到特定的端口和IP地址(尽管让您的服务器套接字代码监听通过指定端口的任何类型的IP地址是可取的)。

bool server_socket::create_socket()
{
    if (created_from_accept)
    {
        return false;
    }//end if (false....
    version=MAKEWORD(2,0); //creates a word which in our case
    // is the version of winsock that we are using  winsock2 to be precise
    nret=WSAStartup(version,&wsaData);
    //this starts up the winsock

    if (nret!=0)
    {
        cout<<"\nCreate_Socket::: Error occured could not create socket\n";
        return false;
    }//end if(nret
    
    listeningSocket=socket(AF_INET,
                           SOCK_STREAM,
                           IPPROTO_TCP);
    if(INVALID_SOCKET==listeningSocket)
    {
        cout<<"\n Create_Socket:::Error: "<<WSAGetLastError()
            <<" occurred, could not create socket\n";
        WSACleanup();
        return false;
    }//end if(invalid_error.....
    nret=bind(listeningSocket,(LPSOCKADDR)&socket_structure,
              sizeof(socket_structure));
    if (SOCKET_ERROR==nret)
    {
        cout<<"\n Create_Socket:::Binding Error: "<<WSAGetLastError()
            <<" occurred, could not create socket\n";
        WSACleanup();
        return false;
    }//end if(socketerror....
    //up to 10 connections may be waiting at any one time to be accepted
    nret=listen(listeningSocket,10);
    if (SOCKET_ERROR==nret)
    {
        cout<<"\n Create_Socket:::Listening Error: "<<WSAGetLastError()
            <<" occurred, could not create socket\n";
        WSACleanup();
        return false;
    }//end if (socket_error.....
        cout<<"socket created\n";
    return true;
}

查看代码,我们首先要做的是确定我们计划使用的Winsock版本,然后使用WSAStartup调用启动Winsock。确保启动调用有效是关键,因为如果它不成功,则不应采取任何进一步的Winsock操作。接下来,我们使用socket函数创建监听套接字。套接字操作接受地址系列规范、套接字类型(在我们的例子中,它是一个套接字流)以及协议作为参数。一旦创建了套接字,您将其绑定到在构造函数中指定的地址。我们现在准备好监听连接了。

在代码中,我一次最多可以监听10个连接。但需要注意的关键是,由于我使用的是同步套接字实现,我们一次只能接受一个连接。监听将保持所有10个连接,但accept函数将一次只接受一个连接。一旦我们听到传入的连接,我们就会调用accept函数来做它的事情。

int socket_struct_size=sizeof(socket_structure);
    theClient=accept(listeningSocket,
                     NULL, //new_socket->socket_address(),
                     NULL);//&socket_struct_size);
    if(INVALID_SOCKET==theClient)
    {
        cout<<"\n Accept_Socket:::Listening Error: "<<WSAGetLastError()
            <<" occurred, could not create socket\n";
        WSACleanup();
        return false;
    }//end if(INVALID_SOCKET....

    //listeningSocket=theClient;  //this is wrong when you do 
    //this you servere the connection after your first disconnect
    cout<<"socket_accepted\n";
    return true;

accept函数接受连接并返回新连接的描述符。我将其命名为theClient;这使我们能够读取和写入连接的客户端。注释掉的listening Socket = theClient 只是为了确保人们不会犯我在编写这个类时犯的同样的错误。在查看MSDN时,他们将监听套接字等同于等同的已接受描述符的值。他们这样做的原因是因为他们只想要一个连接并结束应用程序。在我们的情况下,我们不想将此控制权留给服务器套接字类。

bool server_socket::socket_send(const char *data_buffer, int data_size)
{
    int number_of_bytes_transferred=0;
    bool return_value=true;
    number_of_bytes_transferred=send(theClient,
                                     data_buffer,
                                     data_size,
                                     0);
    if (SOCKET_ERROR==number_of_bytes_transferred)
    {
        return_value=false;
    }
    else
    {
        if(number_of_bytes_transferred==data_size)
        {
            return_value=true;
        }
        else
        {
            return_value=false;
        }//end if (number_of_bytes_ transferred
    }//end if(socket_error....
    return true;
}

一旦我们接受了一个连接,我们就可以发送或接收。上面的代码向您展示了如何发送数据。send命令接受已接受的连接描述符、要传输的数据以及要传输的数据的大小作为参数,并返回已传输的字节数。

bool server_socket::socket_receive(char* data_buffer,int data_size)
{
    bool return_value=true;
    int socket_return_value;
    int total_received=0;
    while((return_value !=0)&&(total_received<data_size))
    {
        socket_return_value=recv(theClient,
                                 (data_buffer)+total_received,  //const_cast<char*>
                                 (data_size-total_received),
                                 0);
        if((socket_return_value<=0)||(socket_return_value==WSAECONNRESET))
        {
            return_value = false;
            break;
        }
        else
        {
            total_received=total_received+socket_return_value;
        }//end if((socket_return_value.....
        
    }//end while((return_value......
    if(data_size==total_received)
    {
        return_value=true;
    }
    else
    {
        return_value=false;
    }
    return return_value;

接收就像发送一样,不同之处在于我们将接收命令包含在一个while循环中,以确保我们接收到发送给我们的所有内容。在我的实现中,我有一个固定的标头,我接收到它告诉了我其他内容的大小。因此,我检查以确保总接收到的内容等于作为参数发送的数据大小。

bool server_socket::socket_shutdown()
{
    closesocket(theClient);
    
    return true;
}

一旦我们完成了已接受的连接,我们使用closesocket函数关闭套接字。我们仍然能够监听连接,因为监听套接字仍然存在。

server_socket::~server_socket()
{
    closesocket(listeningSocket);
    WSACleanup();
}

最后,一旦我们完成监听,我们关闭监听套接字,就完成了。这只是使用Winsock的一个简单实现。并且,为了我需要它做的事情,它确实做到了。作为如何使用套接字服务器示例的一个例子,我创建了一个函数,该函数处理设置套接字服务器,另一个函数处理发送和接收。

void run_server()
{
    server_socket* the_Server_socket;
    bool continue_flag = true;
    the_Server_server = new server_socket(port_number);
    if (the_Server_socket->create_socket() == false)
    {
        cout<<"executive::run():  ERROR:  Call to create_socket failed. " 
              "Constructor failed.\n"<<flush;
        return false;
    }
    // Accept new connect requests and spawn threads to process them
    while (continue_flag)
    {
        if (the_Server_socket->socket_accept() == false)
        {
            cout<<"executive::run():  ERROR:  Accept error occurred.\n"<<flush;
            //return false;
        }
        else
        {
            num_clients_so_far++;
            cout << "Accepted new connection:  client " << dec 
                 << num_clients_so_far <<"\n" << flush;    
            // Receive and process and commands. 
               receive_loop(the_Server_socket);
        }
    } // while (true)                
    itsServer_socket->socket_shutdown();
}

receive_loop(server_socket *new_Server)
{
   bool return_status =true;
   char input_buffer [10];
  while ( (return_status = new_Server->socket_receive( input_buffer, 10)!=false)) 
  {
      //put your code here to deal with the received data
  }
}
© . All rights reserved.