blob: 2161697d8b4c7c83a83216174c5b2ace27f7cba3 [file] [log] [blame]
Marc Slemkoe03da182006-07-21 21:32:36 +00001#include <config.h>
Mark Sleee8540632006-05-30 09:24:40 +00002#include <sys/socket.h>
3#include <arpa/inet.h>
4#include <netinet/in.h>
5#include <netinet/tcp.h>
6#include <netdb.h>
7#include <unistd.h>
8#include <errno.h>
9
10#include "transport/TSocket.h"
Mark Slee8d7e1f62006-06-07 06:48:56 +000011#include "transport/TTransportException.h"
Mark Sleee8540632006-05-30 09:24:40 +000012
13using namespace std;
14
Mark Slee8d7e1f62006-06-07 06:48:56 +000015uint32_t g_socket_syscalls = 0;
16
17/**
18 * TSocket implementation.
19 *
20 * @author Mark Slee <mcslee@facebook.com>
21 */
22
Mark Sleee8540632006-05-30 09:24:40 +000023// Mutex to protect syscalls to netdb
24pthread_mutex_t g_netdb_mutex = PTHREAD_MUTEX_INITIALIZER;
25
26// TODO(mcslee): Make this an option to the socket class
27#define MAX_RECV_RETRIES 20
28
29TSocket::TSocket(string host, int port) :
30 host_(host), port_(port), socket_(0) {}
31
32TSocket::TSocket(int socket) {
33 socket_ = socket;
34}
35
36TSocket::~TSocket() {
37 close();
38}
39
Mark Slee8d7e1f62006-06-07 06:48:56 +000040bool TSocket::isOpen() {
41 return (socket_ > 0);
42}
43
44void TSocket::open() {
Mark Sleee8540632006-05-30 09:24:40 +000045 // Create socket
46 socket_ = socket(AF_INET, SOCK_STREAM, 0);
47 if (socket_ == -1) {
Mark Slee8d7e1f62006-06-07 06:48:56 +000048 perror("TSocket::open() socket");
49 close();
50 throw TTransportException(TTX_NOT_OPEN, "socket() ERROR:" + errno);
Mark Sleee8540632006-05-30 09:24:40 +000051 }
52
Mark Slee8d7e1f62006-06-07 06:48:56 +000053 // Lookup the hostname
Mark Sleee8540632006-05-30 09:24:40 +000054 struct sockaddr_in addr;
55 addr.sin_family = AF_INET;
56 addr.sin_port = htons(port_);
57
58 /*
59 if (inet_pton(AF_INET, host_.c_str(), &addr.sin_addr) < 0) {
60 perror("TSocket::open() inet_pton");
61 }
62 */
63
64 {
65 // TODO(mcslee): Fix scope-locking here to protect hostname lookups
66 // scopelock sl(&netdb_mutex);
67 struct hostent *host_entry = gethostbyname(host_.c_str());
68
69 if (host_entry == NULL) {
70 // perror("dns error: failed call to gethostbyname.\n");
71 close();
Mark Slee8d7e1f62006-06-07 06:48:56 +000072 throw TTransportException(TTX_NOT_OPEN, "gethostbyname() failed");
Mark Sleee8540632006-05-30 09:24:40 +000073 }
74
75 addr.sin_port = htons(port_);
76 memcpy(&addr.sin_addr.s_addr,
77 host_entry->h_addr_list[0],
78 host_entry->h_length);
79 }
80
81 // Connect the socket
82 int ret = connect(socket_, (struct sockaddr *)&addr, sizeof(addr));
83
84 // Connect failed
85 if (ret < 0) {
86 perror("TSocket::open() connect");
87 close();
Mark Slee8d7e1f62006-06-07 06:48:56 +000088 throw TTransportException(TTX_NOT_OPEN, "open() ERROR: " + errno);
Mark Sleee8540632006-05-30 09:24:40 +000089 }
90
Mark Slee8d7e1f62006-06-07 06:48:56 +000091 // Connection was successful
Mark Sleee8540632006-05-30 09:24:40 +000092}
93
94void TSocket::close() {
95 if (socket_ > 0) {
96 shutdown(socket_, SHUT_RDWR);
97 ::close(socket_);
98 }
99 socket_ = 0;
100}
101
Mark Slee8d7e1f62006-06-07 06:48:56 +0000102uint32_t TSocket::read(uint8_t* buf, uint32_t len) {
103 if (socket_ <= 0) {
104 throw TTransportException(TTX_NOT_OPEN, "Called read on non-open socket");
105 }
Mark Sleee8540632006-05-30 09:24:40 +0000106
Mark Sleee8540632006-05-30 09:24:40 +0000107 uint32_t retries = 0;
Mark Slee8d7e1f62006-06-07 06:48:56 +0000108
109 try_again:
110 // Read from the socket
111 int got = recv(socket_, buf, len, 0);
112 ++g_socket_syscalls;
113
114 // Check for error on read
115 if (got < 0) {
116 perror("TSocket::read()");
117
118 // If temporarily out of resources, sleep a bit and try again
119 if (errno == EAGAIN && retries++ < MAX_RECV_RETRIES) {
120 usleep(50);
121 goto try_again;
Mark Sleee8540632006-05-30 09:24:40 +0000122 }
123
Mark Slee8d7e1f62006-06-07 06:48:56 +0000124 // If interrupted, try again
125 if (errno == EINTR && retries++ < MAX_RECV_RETRIES) {
126 goto try_again;
Mark Sleee8540632006-05-30 09:24:40 +0000127 }
128
Mark Slee8d7e1f62006-06-07 06:48:56 +0000129 // If we disconnect with no linger time
130 if (errno == ECONNRESET) {
131 throw TTransportException(TTX_NOT_OPEN, "ECONNRESET");
132 }
133
134 // This ish isn't open
135 if (errno == ENOTCONN) {
136 throw TTransportException(TTX_NOT_OPEN, "ENOTCONN");
137 }
138
139 // Timed out!
140 if (errno == ETIMEDOUT) {
141 throw TTransportException(TTX_TIMED_OUT, "ETIMEDOUT");
142 }
143
144 // Some other error, whatevz
145 throw TTransportException(TTX_UNKNOWN, "ERROR:" + errno);
146 }
147
148 // The remote host has closed the socket
149 if (got == 0) {
150 close();
151 return 0;
Mark Sleee8540632006-05-30 09:24:40 +0000152 }
153
154 // Pack data into string
Mark Slee8d7e1f62006-06-07 06:48:56 +0000155 return got;
Mark Sleee8540632006-05-30 09:24:40 +0000156}
157
Mark Slee8d7e1f62006-06-07 06:48:56 +0000158void TSocket::write(const uint8_t* buf, uint32_t len) {
159 if (socket_ <= 0) {
160 throw TTransportException(TTX_NOT_OPEN, "Called write on non-open socket");
161 }
162
Mark Sleee8540632006-05-30 09:24:40 +0000163 uint32_t sent = 0;
164
Mark Slee8d7e1f62006-06-07 06:48:56 +0000165 while (sent < len) {
Marc Slemko9d4a3e22006-07-21 19:53:48 +0000166
167 int flags = 0;
168
Marc Slemkoe03da182006-07-21 21:32:36 +0000169 #if defined(MSG_NOSIGNAL)
Mark Slee8d7e1f62006-06-07 06:48:56 +0000170 // Note the use of MSG_NOSIGNAL to suppress SIGPIPE errors, instead we
171 // check for the EPIPE return condition and close the socket in that case
Marc Slemko9d4a3e22006-07-21 19:53:48 +0000172 flags |= MSG_NOSIGNAL;
Marc Slemkoe03da182006-07-21 21:32:36 +0000173 #endif // defined(MSG_NOSIGNAL)
Marc Slemko9d4a3e22006-07-21 19:53:48 +0000174
175 int b = send(socket_, buf + sent, len - sent, flags);
Mark Slee8d7e1f62006-06-07 06:48:56 +0000176 ++g_socket_syscalls;
177
Mark Sleee8540632006-05-30 09:24:40 +0000178 // Fail on a send error
179 if (b < 0) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000180 if (errno == EPIPE) {
181 close();
182 throw TTransportException(TTX_NOT_OPEN, "EPIPE");
183 }
184
185 if (errno == ECONNRESET) {
186 close();
187 throw TTransportException(TTX_NOT_OPEN, "ECONNRESET");
188 }
189
190 if (errno == ENOTCONN) {
191 close();
192 throw TTransportException(TTX_NOT_OPEN, "ENOTCONN");
193 }
194
195 perror("TSocket::write() send < 0");
196 throw TTransportException(TTX_UNKNOWN, "ERROR:" + errno);
Mark Sleee8540632006-05-30 09:24:40 +0000197 }
198
199 // Fail on blocked send
200 if (b == 0) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000201 throw TTransportException(TTX_NOT_OPEN, "Socket send returned 0.");
Mark Sleee8540632006-05-30 09:24:40 +0000202 }
Mark Sleee8540632006-05-30 09:24:40 +0000203 sent += b;
204 }
205}
206
Mark Slee8d7e1f62006-06-07 06:48:56 +0000207void TSocket::setLinger(bool on, int linger) {
208 // TODO(mcslee): Store these options so they can be set pre-connect
209 if (socket_ <= 0) {
210 return;
211 }
212
Mark Sleee8540632006-05-30 09:24:40 +0000213 struct linger ling = {(on ? 1 : 0), linger};
214 if (-1 == setsockopt(socket_, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling))) {
215 close();
216 perror("TSocket::setLinger()");
Mark Sleee8540632006-05-30 09:24:40 +0000217 }
Mark Sleee8540632006-05-30 09:24:40 +0000218}
219
Mark Slee8d7e1f62006-06-07 06:48:56 +0000220void TSocket::setNoDelay(bool noDelay) {
221 // TODO(mcslee): Store these options so they can be set pre-connect
222 if (socket_ <= 0) {
223 return;
224 }
225
Mark Sleee8540632006-05-30 09:24:40 +0000226 // Set socket to NODELAY
227 int val = (noDelay ? 1 : 0);
228 if (-1 == setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val))) {
229 close();
230 perror("TSocket::setNoDelay()");
Mark Sleee8540632006-05-30 09:24:40 +0000231 }
Mark Sleee8540632006-05-30 09:24:40 +0000232}