blob: 55f512e1cad0bcf02b8be6bc5685ac5e85c37097 [file] [log] [blame]
Mark Sleee8540632006-05-30 09:24:40 +00001#include <sys/socket.h>
2#include <arpa/inet.h>
3#include <netinet/in.h>
4#include <netinet/tcp.h>
5#include <netdb.h>
6#include <unistd.h>
7#include <errno.h>
8
9#include "transport/TSocket.h"
Mark Slee8d7e1f62006-06-07 06:48:56 +000010#include "transport/TTransportException.h"
Mark Sleee8540632006-05-30 09:24:40 +000011
12using namespace std;
13
Mark Slee8d7e1f62006-06-07 06:48:56 +000014uint32_t g_socket_syscalls = 0;
15
16/**
17 * TSocket implementation.
18 *
19 * @author Mark Slee <mcslee@facebook.com>
20 */
21
Mark Sleee8540632006-05-30 09:24:40 +000022// Mutex to protect syscalls to netdb
23pthread_mutex_t g_netdb_mutex = PTHREAD_MUTEX_INITIALIZER;
24
25// TODO(mcslee): Make this an option to the socket class
26#define MAX_RECV_RETRIES 20
27
28TSocket::TSocket(string host, int port) :
29 host_(host), port_(port), socket_(0) {}
30
31TSocket::TSocket(int socket) {
32 socket_ = socket;
33}
34
35TSocket::~TSocket() {
36 close();
37}
38
Mark Slee8d7e1f62006-06-07 06:48:56 +000039bool TSocket::isOpen() {
40 return (socket_ > 0);
41}
42
43void TSocket::open() {
Mark Sleee8540632006-05-30 09:24:40 +000044 // Create socket
45 socket_ = socket(AF_INET, SOCK_STREAM, 0);
46 if (socket_ == -1) {
Mark Slee8d7e1f62006-06-07 06:48:56 +000047 perror("TSocket::open() socket");
48 close();
49 throw TTransportException(TTX_NOT_OPEN, "socket() ERROR:" + errno);
Mark Sleee8540632006-05-30 09:24:40 +000050 }
51
Mark Slee8d7e1f62006-06-07 06:48:56 +000052 // Lookup the hostname
Mark Sleee8540632006-05-30 09:24:40 +000053 struct sockaddr_in addr;
54 addr.sin_family = AF_INET;
55 addr.sin_port = htons(port_);
56
57 /*
58 if (inet_pton(AF_INET, host_.c_str(), &addr.sin_addr) < 0) {
59 perror("TSocket::open() inet_pton");
60 }
61 */
62
63 {
64 // TODO(mcslee): Fix scope-locking here to protect hostname lookups
65 // scopelock sl(&netdb_mutex);
66 struct hostent *host_entry = gethostbyname(host_.c_str());
67
68 if (host_entry == NULL) {
69 // perror("dns error: failed call to gethostbyname.\n");
70 close();
Mark Slee8d7e1f62006-06-07 06:48:56 +000071 throw TTransportException(TTX_NOT_OPEN, "gethostbyname() failed");
Mark Sleee8540632006-05-30 09:24:40 +000072 }
73
74 addr.sin_port = htons(port_);
75 memcpy(&addr.sin_addr.s_addr,
76 host_entry->h_addr_list[0],
77 host_entry->h_length);
78 }
79
80 // Connect the socket
81 int ret = connect(socket_, (struct sockaddr *)&addr, sizeof(addr));
82
83 // Connect failed
84 if (ret < 0) {
85 perror("TSocket::open() connect");
86 close();
Mark Slee8d7e1f62006-06-07 06:48:56 +000087 throw TTransportException(TTX_NOT_OPEN, "open() ERROR: " + errno);
Mark Sleee8540632006-05-30 09:24:40 +000088 }
89
Mark Slee8d7e1f62006-06-07 06:48:56 +000090 // Connection was successful
Mark Sleee8540632006-05-30 09:24:40 +000091}
92
93void TSocket::close() {
94 if (socket_ > 0) {
95 shutdown(socket_, SHUT_RDWR);
96 ::close(socket_);
97 }
98 socket_ = 0;
99}
100
Mark Slee8d7e1f62006-06-07 06:48:56 +0000101uint32_t TSocket::read(uint8_t* buf, uint32_t len) {
102 if (socket_ <= 0) {
103 throw TTransportException(TTX_NOT_OPEN, "Called read on non-open socket");
104 }
Mark Sleee8540632006-05-30 09:24:40 +0000105
Mark Sleee8540632006-05-30 09:24:40 +0000106 uint32_t retries = 0;
Mark Slee8d7e1f62006-06-07 06:48:56 +0000107
108 try_again:
109 // Read from the socket
110 int got = recv(socket_, buf, len, 0);
111 ++g_socket_syscalls;
112
113 // Check for error on read
114 if (got < 0) {
115 perror("TSocket::read()");
116
117 // If temporarily out of resources, sleep a bit and try again
118 if (errno == EAGAIN && retries++ < MAX_RECV_RETRIES) {
119 usleep(50);
120 goto try_again;
Mark Sleee8540632006-05-30 09:24:40 +0000121 }
122
Mark Slee8d7e1f62006-06-07 06:48:56 +0000123 // If interrupted, try again
124 if (errno == EINTR && retries++ < MAX_RECV_RETRIES) {
125 goto try_again;
Mark Sleee8540632006-05-30 09:24:40 +0000126 }
127
Mark Slee8d7e1f62006-06-07 06:48:56 +0000128 // If we disconnect with no linger time
129 if (errno == ECONNRESET) {
130 throw TTransportException(TTX_NOT_OPEN, "ECONNRESET");
131 }
132
133 // This ish isn't open
134 if (errno == ENOTCONN) {
135 throw TTransportException(TTX_NOT_OPEN, "ENOTCONN");
136 }
137
138 // Timed out!
139 if (errno == ETIMEDOUT) {
140 throw TTransportException(TTX_TIMED_OUT, "ETIMEDOUT");
141 }
142
143 // Some other error, whatevz
144 throw TTransportException(TTX_UNKNOWN, "ERROR:" + errno);
145 }
146
147 // The remote host has closed the socket
148 if (got == 0) {
149 close();
150 return 0;
Mark Sleee8540632006-05-30 09:24:40 +0000151 }
152
153 // Pack data into string
Mark Slee8d7e1f62006-06-07 06:48:56 +0000154 return got;
Mark Sleee8540632006-05-30 09:24:40 +0000155}
156
Mark Slee8d7e1f62006-06-07 06:48:56 +0000157void TSocket::write(const uint8_t* buf, uint32_t len) {
158 if (socket_ <= 0) {
159 throw TTransportException(TTX_NOT_OPEN, "Called write on non-open socket");
160 }
161
Mark Sleee8540632006-05-30 09:24:40 +0000162 uint32_t sent = 0;
163
Mark Slee8d7e1f62006-06-07 06:48:56 +0000164 while (sent < len) {
Marc Slemko9d4a3e22006-07-21 19:53:48 +0000165
166 int flags = 0;
167
168 #if HAVE_BITS_SOCKET_H
Mark Slee8d7e1f62006-06-07 06:48:56 +0000169 // Note the use of MSG_NOSIGNAL to suppress SIGPIPE errors, instead we
170 // check for the EPIPE return condition and close the socket in that case
Marc Slemko9d4a3e22006-07-21 19:53:48 +0000171 flags |= MSG_NOSIGNAL;
172 #endif // HAVE_BITS_SOCKET_H
173
174 int b = send(socket_, buf + sent, len - sent, flags);
Mark Slee8d7e1f62006-06-07 06:48:56 +0000175 ++g_socket_syscalls;
176
Mark Sleee8540632006-05-30 09:24:40 +0000177 // Fail on a send error
178 if (b < 0) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000179 if (errno == EPIPE) {
180 close();
181 throw TTransportException(TTX_NOT_OPEN, "EPIPE");
182 }
183
184 if (errno == ECONNRESET) {
185 close();
186 throw TTransportException(TTX_NOT_OPEN, "ECONNRESET");
187 }
188
189 if (errno == ENOTCONN) {
190 close();
191 throw TTransportException(TTX_NOT_OPEN, "ENOTCONN");
192 }
193
194 perror("TSocket::write() send < 0");
195 throw TTransportException(TTX_UNKNOWN, "ERROR:" + errno);
Mark Sleee8540632006-05-30 09:24:40 +0000196 }
197
198 // Fail on blocked send
199 if (b == 0) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000200 throw TTransportException(TTX_NOT_OPEN, "Socket send returned 0.");
Mark Sleee8540632006-05-30 09:24:40 +0000201 }
Mark Sleee8540632006-05-30 09:24:40 +0000202 sent += b;
203 }
204}
205
Mark Slee8d7e1f62006-06-07 06:48:56 +0000206void TSocket::setLinger(bool on, int linger) {
207 // TODO(mcslee): Store these options so they can be set pre-connect
208 if (socket_ <= 0) {
209 return;
210 }
211
Mark Sleee8540632006-05-30 09:24:40 +0000212 struct linger ling = {(on ? 1 : 0), linger};
213 if (-1 == setsockopt(socket_, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling))) {
214 close();
215 perror("TSocket::setLinger()");
Mark Sleee8540632006-05-30 09:24:40 +0000216 }
Mark Sleee8540632006-05-30 09:24:40 +0000217}
218
Mark Slee8d7e1f62006-06-07 06:48:56 +0000219void TSocket::setNoDelay(bool noDelay) {
220 // TODO(mcslee): Store these options so they can be set pre-connect
221 if (socket_ <= 0) {
222 return;
223 }
224
Mark Sleee8540632006-05-30 09:24:40 +0000225 // Set socket to NODELAY
226 int val = (noDelay ? 1 : 0);
227 if (-1 == setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val))) {
228 close();
229 perror("TSocket::setNoDelay()");
Mark Sleee8540632006-05-30 09:24:40 +0000230 }
Mark Sleee8540632006-05-30 09:24:40 +0000231}