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;
}
答案 0 :(得分:1)
请记住,recv
可能无法一次接收所有数据。这意味着服务器中的recv
调用可能无法获得完整的消息,但会将其发送回来,而客户端可能只接收从服务器发送的内容的一部分。
这不会有问题,除非您只在客户端中接收一次,然后继续做其他事情。就像你一样。
有两种解决方案:
制作一个简单的协议,该协议可以使用消息长度预先添加每条消息,或者使用特殊的消息结束标记。在第一种情况下,您将始终知道有多少数据,并且可以在循环中读取所有数据,直到收到所有数据,在第二种情况下,您将在aloop中收到,直到您收到消息结束标记。
使套接字无阻塞,并循环读取,直到recv
返回错误,错误为WSAEWOULDBLOCK
。然后没有更多的数据可供阅读。
客户的错误:
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
构造函数的第二个参数传递。