使用套接字send()/ recv()(winsock)的Echo服务器/客户端消息截止

时间:2013-05-04 17:21:35

标签: c++ sockets network-programming

echo_server和echo_client的代码发布在下面。我注意到当我在echo_client中输入一定长度的消息时,服务器会截断它的结尾并回显它的一部分。缓冲区大小为1024字节,我输入的消息比该长度短得多。这里发生了什么?我如何解决这个问题,以便服务器回复提供长度限制的完整消息?

服务器代码:

#include "stdafx.h"
#include <iostream>
#include <string>
#include <string.h>


#ifndef UNICODE
#define UNICODE
#endif
#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

using namespace std;


static int MAXPENDING = 5;


int main(int argc, char *argv[])

{

WSADATA wsaData;
int iResult;
int optv = 0;
bool connected = false;
char *optval = (char*)&optv;
int optlen = sizeof(optval);
string Q = "quit";
const char *exit =Q.c_str();




iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
    wprintf(L"WSAStartup function failed with error: %d\n", iResult);
    return 1;
}


if(argc!=2){
    printf("Error: incorrect number of arguments.  ");
    return 1;
}


SOCKET servSock;
servSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(servSock==INVALID_SOCKET){
    printf("Socket function failed with error: %d\n",GetLastError());
    return 1;
}


u_int servPort = atoi(argv[1]);

sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(servPort);

int opt = setsockopt(servSock,SOL_SOCKET,SO_REUSEADDR,optval,sizeof(optval));


if(bind(servSock,(sockaddr*)&servAddr,sizeof(servAddr))<0){
    printf("Bind function failed with error: %d\n", GetLastError());
    return 1;
}



for(;;){


    if(listen(servSock,MAXPENDING) < 0){
    printf("Listen function failed with error: %d/n",GetLastError());
    return 1;
    }else{
        char *str = new char[5];

        printf("Server listening on port %d\n",servPort);
    }

    SOCKET clientSock;
    sockaddr_in clientAddr;
    socklen_t caddrlen = sizeof(clientAddr);



    clientSock = accept(servSock,(sockaddr*)&clientAddr,&caddrlen);

    if(clientSock < 0){
       printf("Accept() function failed with error: %d/n", GetLastError());
       goto QUIT;

    }else if(clientSock >=0){
        connected = true;
    }



    char cName[INET_ADDRSTRLEN];

    if(inet_ntop(AF_INET,&clientAddr.sin_addr,cName,sizeof(cName))!=NULL){
        printf("Handling client %s/%d\n", cName,ntohs(clientAddr.sin_port));
    }else{
        printf("Error: Unable to get client address");
    }


    char buffer[1024];


    while(connected==true){



        long nbytesrcvd = recv(clientSock,buffer,sizeof(buffer),0);




        if(nbytesrcvd==0){
            connected = false;
            cout << endl;
            cout << cName << ": client disconnected" << endl;                   
            cout << endl;
            break;
        }



        if(nbytesrcvd < 0){
            printf("Error: recv() failed");
            cout << endl;
            goto QUIT;
        }


         if(nbytesrcvd > 0){

            long nbytessent = send(clientSock,buffer,nbytesrcvd,0);
            if(nbytessent < 0){
                cout << "Error: send() failed" << endl;
                cout << endl;
                goto QUIT;          

            }else if(nbytessent!=nbytesrcvd){
                cout << "send() error: sent unexpected # of bytes" << endl;
                cout << endl;
                goto QUIT;

            }                       
         }

    }



    QUIT:

        int iResult = closesocket(clientSock);
        if (iResult == SOCKET_ERROR) {
            printf("closesocket function failed with error: %d\n",GetLastError());
        }



    }




}

客户端代码:

#include "stdafx.h"
#include <iostream>
#include <string>
#include <string.h>


#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <Windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>


#define BUFFSIZE 1024

#pragma comment(lib, "ws2_32.lib")

using namespace std;


int main(int argc,char* argv[])
{


WSADATA wsaData;
int result;
bool connected = false;
hostent *rhost;
char buffer[BUFFSIZE];
string Q = "quit";
const char *exit =Q.c_str();






result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != NO_ERROR) {
    printf("WSAStartup function failed with error: %d\n", result);
    return 1;
}




SOCKET connector;
connector = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (connector == INVALID_SOCKET) {
    wprintf(L"socket function failed with error: %ld\n", WSAGetLastError());
    WSACleanup();
    return 1;
}

string hname;
cout << "Enter host name(URL): ";
cin >> hname;
cout << endl;

string portnum;
cout << "Enter the port number you wish to connect on: " ;
cin >> portnum;
cout << endl;

char *hostname = const_cast<char*>(hname.c_str());
char *hostPort = const_cast<char*>(portnum.c_str());



rhost = gethostbyname(hostname);
in_addr addr;
addr.s_addr = *(u_long *)rhost->h_addr_list[0];



sockaddr_in clientserv;
clientserv.sin_family = AF_INET;
clientserv.sin_addr.s_addr = addr.s_addr;
clientserv.sin_port = htons(atoi(hostPort));

if(connect(connector,(sockaddr*)&clientserv,sizeof(clientserv))==SOCKET_ERROR){
    printf("connect function failed with error: %d\n", GetLastError());
    if(closesocket(connector)<0){
        printf("closesocket function failed %d\n", GetLastError());
    }
    return 1;
}else{
    connected = true;
    cout << "Connected to host " << hname << " on port " << portnum << endl;
    cout << "Type 'quit' to exit the program " << endl;
}


while(connected==true){

   int nbr = 0;
   string msg;

   cout << ">";
   getline(cin,msg);
   cout << endl;




   if(msg=="quit"){

       connected = false;
       goto quit;
   }


   int len = sizeof(msg);
   const char *message = msg.c_str();
   strncpy_s(buffer,message,sizeof(msg));         
   long nbs = send(connector,buffer,len,0);
   if(nbs < 0){
        printf("send() failed", GetLastError());
        return 1;
   }

   while(nbr < nbs){
        nbr = recv(connector,buffer,len,0);
        if(nbr < 0){
            printf("recv() failed", GetLastError());
            return 1;

        }else if(nbr==0){
            printf("recv() failed: connection closed prematurely", GetLastError());
            return 1;               
        }else if(nbr > 0){
            string str(buffer);
            cout << ">> " << str << endl;
            cout << endl;
        }

   }



}

quit:

    if (closesocket(connector) == SOCKET_ERROR) {
        printf("closesocket function failed with error: %ld\n", GetLastError());
        WSACleanup();
        return 1;
    }


WSACleanup();

return 0;

}

2 个答案:

答案 0 :(得分:1)

请记住,recv可能无法一次接收所有数据。这意味着服务器中的recv调用可能无法获得完整的消息,但会将其发送回来,而客户端可能只接收从服务器发送的内容的一部分。

这不会有问题,除非您只在客户端中接收一次,然后继续做其他事情。就像你一样。

有两种解决方案:

  1. 制作一个简单的协议,该协议可以使用消息长度预先添加每条消息,或者使用特殊的消息结束标记。在第一种情况下,您将始终知道有多少数据,并且可以在循环中读取所有数据,直到收到所有数据,在第二种情况下,您将在aloop中收到,直到您收到消息结束标记。

  2. 使套接字无阻塞,并循环读取,直到recv返回错误,错误为WSAEWOULDBLOCK。然后没有更多的数据可供阅读。


  3. 客户的错误:

    int len = sizeof(msg);
    

    此行返回字符串 object 的大小,而不是对象中包含的字符串。您应该使用msg.length()来获取长度。你可以在几个地方使用它。

    使用std::string是正常的,如果你正确使用它。 E.g:

    long nbs = send(connector, msg.c_str(), msg.length(), 0);
    

    另一件事:

    nbr = recv(connector, buffer, len, 0);
    

    此处再次使用错误的大小,即之前错误使用sizeof(msg)提供的大小。相反,您应该提供实际缓冲区的大小。由于buffer是一个合适的数组,因此您可以使用例如sizeof(buffer) - 1在这里。 -1是因为您从客户端发送的数据不是以零结尾的字符串,因此您需要在接收的数据中保留一个空格以像字符串一样终止它:

    buffer[nbr] = '\0';
    

答案 1 :(得分:0)

我发现您的代码存在一些问题。

您的服务器正在listen()循环内调用accept()。它不属于这里。在listen()之后只调用一次bind()

send()可以接受比请求的更少的字节。发送缓冲区时,在循环中调用send(),直到整个缓冲区耗尽。您的客户端和服务器代码都受此影响。

您的客户端代码正在将用户的输入复制到缓冲区中,然后发送整个缓冲区,即使输入小于缓冲区也是如此。如果输入大于缓冲区,那么也会截断输入。在这两种情况下,您根本不需要缓冲区。您可以使用字符串的send()c_str()方法将输入字符串直接传递给length()。这样,无论实际长度如何,都会发送整个字符串。

将回送的数据转换为recv()时,您的客户端代码也未考虑string的返回值。数据不会以空值终止,因此您需要将nbr变量作为string构造函数的第二个参数传递。