/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
#include <arpa/inet.h>
#include <errno.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include "Log.h"
#include "ClientSocket.h"
ClientSocket::ClientSocket()
: mSocket(-1),
mTimeoutEnabled(false)
{
}
ClientSocket::~ClientSocket()
{
release();
}
bool ClientSocket::init(const char* hostIp, int port, bool enableTimeout)
{
LOGD("ClientSocket::init");
mSocket = socket(AF_INET, SOCK_STREAM, 0);
if (mSocket < 0) {
LOGE("cannot open socket %d", errno);
return false;
}
int reuse = 1;
if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
LOGE("setsockopt error %d", errno);
release();
return false;
}
struct sockaddr_in serverAddr;
bzero((char*)&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
if (inet_pton(AF_INET, hostIp, &serverAddr.sin_addr) != 1) {
release();
LOGE("inet_pton failed %d", errno);
return false;
}
if (connect(mSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
release();
LOGE("cannot connect socket %d", errno);
return false;
}
mTimeoutEnabled = enableTimeout;
return true;
}
const int ZERO_RW_SLEEP_TIME_US = 10;
// make non-blocking mode only during read. This allows supporting time-out for read
bool ClientSocket::readData(char* data, int len, int timeoutInMs)
{
bool useTimeout = (mTimeoutEnabled && (timeoutInMs > 0));
int flOriginal = 0;
int timeInSec = 0;
int timeInUs = 0;
if (useTimeout) {
flOriginal = fcntl(mSocket, F_GETFL,0);
if (flOriginal == -1) {
LOGE("fcntl error %d", errno);
return false;
}
if (fcntl(mSocket, F_SETFL, flOriginal | O_NONBLOCK) == -1) {
LOGE("fcntl error %d", errno);
return false;
}
timeInSec = timeoutInMs / 1000;
timeInUs = (timeoutInMs % 1000) * 1000;
}
bool result = true;
int read;
int toRead = len;
while (toRead > 0) {
if (useTimeout) {
fd_set rfds;
struct timeval tv;
tv.tv_sec = timeInSec;
tv.tv_usec = timeInUs;
FD_ZERO(&rfds);
FD_SET(mSocket, &rfds);
if (select(mSocket + 1, &rfds, NULL, NULL, &tv) == -1) {
LOGE("select failed");
result = false;
break;
}
if (!FD_ISSET(mSocket, &rfds)) {
LOGE("socket read timeout");
result = false;
break;
}
}
read = recv(mSocket, (void*)data, toRead, 0);
if (read > 0) {
toRead -= read;
data += read;
} else if (read == 0) {
// in blocking mode, zero read mean's peer closed.
// in non-blocking mode, select said that there is data. so it should not happen
LOGE("zero read, peer closed or what?, nonblocking: %d", useTimeout);
result = false;
break;
} else {
LOGE("recv returned %d", read);
result = false;
break;
}
}
if (useTimeout) {
fcntl(mSocket, F_SETFL, flOriginal); // now blocking again
}
return result;
}
bool ClientSocket::sendData(const char* data, int len)
{
int sent;
int toSend = len;
while (toSend > 0) {
sent = send(mSocket, (void*)data, (size_t)toSend, 0);
if (sent > 0) {
toSend -= sent;
data += sent;
} else if (sent == 0) { // no more buffer?
usleep(ZERO_RW_SLEEP_TIME_US); // just wait
} else {
LOGE("send returned %d, error %d", sent, errno);
return false;
}
}
return true;
}
void ClientSocket::release()
{
if (mSocket != -1) {
close(mSocket);
mSocket = -1;
}
}