PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/network/client.cpp

https://github.com/drtrev/curses
C++ | 320 lines | 234 code | 60 blank | 26 comment | 50 complexity | cd7dced103d422d8ea7b48ae063a1f55 MD5 | raw file
  1. #include "network/client.h"
  2. //#include <errno.h>
  3. #include <cstring>
  4. #include "network/net.h"
  5. #include "outverbose.h"
  6. #include <netdb.h> // for gethostbyname
  7. #include <sys/socket.h>
  8. #include <sys/types.h>
  9. #include <unistd.h>
  10. Client::Client()
  11. {
  12. connected = false;
  13. sendBuf.allocate(SENDBUF_SIZE);
  14. numSocksReadable = 0;
  15. PORT = 0;
  16. statSent = 0; // produce some stats
  17. statRecvd = 0;
  18. // UNITS defined in network.h
  19. #ifdef _WIN32
  20. winSockInit = false;
  21. #endif
  22. recvSize = -1;
  23. }
  24. void Client::init(Outverbose &o, int p, int f, int u[])
  25. {
  26. out = &o;
  27. PORT = p;
  28. flagsize = f;
  29. std::memcpy(unitsize, u, UNITS*sizeof(int));
  30. int largestUnit = 0;
  31. for (int i = 0; i < UNITS; i++) {
  32. if (unitsize[i] > largestUnit) largestUnit = unitsize[i];
  33. }
  34. recvSize = largestUnit + flagsize;
  35. }
  36. #ifdef _WIN32
  37. bool Client::initWinSock()
  38. {
  39. if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
  40. *out << VERBOSE_LOUD << "WSAStartup failed.\n";
  41. return false;
  42. }
  43. else
  44. {
  45. *out << VERBOSE_NORMAL << "WSAStartup successful\n";
  46. winSockInit = true;
  47. }
  48. return true;
  49. }
  50. #endif
  51. int Client::getBufferSpace()
  52. {
  53. return SENDBUF_SIZE - sendBuf.getLength();
  54. }
  55. bool Client::openConnection(const char* ip)
  56. {
  57. if (connected) {
  58. int closeval = closeConnection();
  59. *out << VERBOSE_QUIET << "Close connection returned: " << closeval << '\n';
  60. }
  61. #ifdef _WIN32
  62. if (!winSockInit) {
  63. if (!initWinSock()) return false;
  64. }
  65. #endif
  66. if (strlen(ip) > 15) {
  67. // if length not including terminating null char is too long
  68. *out << VERBOSE_LOUD << "IP address too long\n";
  69. return false;
  70. }
  71. strncpy(ipAddress, ip, 16); // store in case need to reconnect
  72. if ((host = gethostbyname(ip)) == NULL) {
  73. perror("perror gethostbyname()");
  74. return false;
  75. }
  76. if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  77. perror("perror socket()");
  78. return false;
  79. }
  80. serverAddress.sin_family = AF_INET; // host byte order
  81. serverAddress.sin_port = htons(PORT); // short, network byte order
  82. serverAddress.sin_addr = *((struct in_addr *)host->h_addr);
  83. memset(&(serverAddress.sin_zero), '\0', 8); // zero the rest of the struct
  84. *out << VERBOSE_QUIET << "Connecting...\n";
  85. if (connect(sockfd, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr)) == -1) {
  86. perror("perror connect()");
  87. return false;
  88. }else connected = true;
  89. *out << VERBOSE_QUIET << "Connected\n";
  90. return true; // connected
  91. }
  92. int Client::closeConnection()
  93. {
  94. connected = false;
  95. #ifndef _WIN32
  96. return close(sockfd);
  97. #else
  98. return closesocket(sockfd);
  99. #endif
  100. }
  101. bool Client::getConnected() const
  102. {
  103. return connected;
  104. }
  105. int Client::addData(uint16_t data)
  106. // if calling this then check all your data unit fits on buffer first with getBufferSpace()
  107. // returns bytes written or -1 on error
  108. {
  109. data = htons(data);
  110. return addData((char*) &data, 2);
  111. }
  112. int Client::addData(uint32_t data)
  113. // if calling this then check all your data unit fits on buffer first with getBufferSpace()
  114. // returns bytes written or -1 on error
  115. {
  116. data = htonl(data);
  117. return addData((char*) &data, 4);
  118. }
  119. int Client::addData(const char* data, int amount)
  120. // checks it fits on buffer - simpler than ending up with half a data unit being
  121. // written, with application having to store the rest (i.e. app can just drop whole
  122. // data unit instead)
  123. {
  124. int written = -1; // error
  125. if (amount < getBufferSpace() + 1)
  126. written = sendBuf.write(data, amount);
  127. return written;
  128. }
  129. void Client::doSelect()
  130. {
  131. struct timeval timeout; // timeout for using select()
  132. timeout.tv_sec = 0; // don't halt
  133. timeout.tv_usec = 0;
  134. FD_ZERO(&readSocks);
  135. FD_ZERO(&writeSocks);
  136. FD_ZERO(&exceptionSocks);
  137. FD_SET(sockfd, &readSocks); // check for sockfd being readable
  138. FD_SET(sockfd, &writeSocks); // check for sockfd being writable
  139. FD_SET(sockfd, &exceptionSocks);
  140. // check if socket is readable/writable
  141. if ((numSocksReadable = select(sockfd + 1, &readSocks, &writeSocks, &exceptionSocks, &timeout)) == -1) {
  142. perror("perror select()");
  143. *out << VERBOSE_LOUD << "Error with select()\n";
  144. }
  145. if (FD_ISSET(sockfd, &exceptionSocks)) {
  146. *out << VERBOSE_LOUD << "SELECT EXCEPTION DETECTED\n";
  147. }
  148. }
  149. void Client::sendData()
  150. // requires doSelect to be called by application (doesn't call it here so
  151. // that app can call once for receive and send)
  152. {
  153. int numSent = 0;
  154. if (sendBuf.getLength() > 0 && FD_ISSET(sockfd, &writeSocks)) {
  155. char data[MAX_SEND_DATA];
  156. int size = sendBuf.peek(data, MAX_SEND_DATA);
  157. if (size > 0) {
  158. numSent = send(sockfd, data, size, MSG_NOSIGNAL); // don't send SIGPIPE
  159. if (numSent == -1) {
  160. perror("perror send()");
  161. *out << VERBOSE_LOUD << "Client::sendData(): error with send.\n";
  162. if (closeConnection() == -1) {
  163. perror("closeConnection()");
  164. *out << VERBOSE_LOUD << "Error closing connection\n";
  165. }
  166. }else if (numSent > 0) {
  167. size = sendBuf.read(data, numSent);
  168. if (size != numSent) *out << VERBOSE_LOUD << "Error reading chars from sendBuf\n";
  169. statSent += numSent;
  170. }
  171. }
  172. }
  173. }
  174. int Client::findUnit(char* data, int datasize)
  175. // return the data unit found
  176. {
  177. int unitFound = -1;
  178. if (datasize > flagsize - 1) {
  179. unitFound = (int) ntohs(*(uint16_t*) &data[0]);
  180. }
  181. return unitFound;
  182. }
  183. Unit Client::recvDataUnit(Net &net)
  184. // requires app to call doSelect
  185. // recvSize should be size of largest unit + flagsize
  186. // returns unit found (with flag of -1 if not found)
  187. {
  188. char *data = new char[recvSize];
  189. int numRecv = 0, unitFound = -1;
  190. if (numSocksReadable > 0 && FD_ISSET(sockfd, &readSocks)) {
  191. numRecv = recv(sockfd, data, recvSize, MSG_PEEK);
  192. // error check
  193. if (numRecv == -1) {
  194. perror("perror recv peek");
  195. *out << VERBOSE_LOUD << "Client::recvDataUnit(): error with recv peek.\n";
  196. if (closeConnection() == -1) {
  197. perror("closeConnection()");
  198. *out << VERBOSE_LOUD << "Error closing connection\n";
  199. }
  200. }else if (numRecv > flagsize - 1) {
  201. unitFound = findUnit(data, numRecv);
  202. if (unitFound > -1 && unitFound < UNITS) {
  203. if (numRecv > unitsize[unitFound] + flagsize - 1) {
  204. // read off data unit
  205. numRecv = recv(sockfd, data, unitsize[unitFound] + flagsize, 0);
  206. // error check
  207. if (numRecv == -1) {
  208. perror("perror recv no peek");
  209. *out << VERBOSE_LOUD << "Client::recvDataUnit(): error with recv no peek.\n";
  210. if (closeConnection() == -1) {
  211. perror("closeConnection()");
  212. *out << VERBOSE_LOUD << "Error closing connection\n";
  213. }
  214. unitFound = -1;
  215. }else if (numRecv != unitsize[unitFound] + flagsize) {
  216. *out << VERBOSE_LOUD << "Could not manage to get full data unit so dropping it\n";
  217. unitFound = -1;
  218. }else{
  219. statRecvd += numRecv; // assuming no errors for stats
  220. }
  221. }else{
  222. //*out << VERBOSE_LOUD << "Waiting for more data... do I care?\n"; // TODO remove this
  223. unitFound = -1;
  224. }
  225. }else{
  226. *out << VERBOSE_LOUD << "Received false unit! TODO\n";
  227. unitFound = -1; // nothing
  228. // drop data unit and move on...
  229. // but how do I know when next data unit starts?
  230. // reconnect!
  231. closeConnection();
  232. openConnection(ipAddress);
  233. // TODO when have worked out logon logoff, can I reconnect as same player?
  234. }
  235. }else{
  236. if (numRecv == 0) {
  237. // server closed
  238. if (closeConnection() == -1) {
  239. perror("closeConnection()");
  240. *out << VERBOSE_LOUD << "Error closing connection\n";
  241. }
  242. }else{
  243. *out << VERBOSE_LOUD << "WAITING for more data2... do I care?\n"; // TODO do I care?
  244. }
  245. }
  246. }
  247. Unit unit;
  248. unit.flag = -1;
  249. if (unitFound > -1) unit = net.bytesToUnit(unitFound, data);
  250. delete [] data;
  251. return unit;
  252. }
  253. int Client::getStatSent()
  254. {
  255. return statSent;
  256. }
  257. int Client::getStatRecvd()
  258. {
  259. return statRecvd;
  260. }