PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/realtime/src/buffer/matlab/buffer.c

https://bitbucket.org/palday/fieldtrip
C | 450 lines | 306 code | 68 blank | 76 comment | 99 complexity | 802d304a3d83b5cf6c1912788698f3f6 MD5 | raw file
  1. /*
  2. * Copyright (C) 2008, Robert Oostenveld
  3. * F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
  4. * Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
  5. *
  6. */
  7. #include <string.h>
  8. #include "mex.h"
  9. #include "matrix.h"
  10. #include "buffer.h"
  11. #include <pthread.h>
  12. #include "extern.h"
  13. #define DEFAULT_HOST "localhost"
  14. #define DEFAULT_PORT 1972
  15. #ifdef WIN32
  16. # ifndef COMPILER_LCC
  17. # define sleep(x) Sleep((x)*1000)
  18. # endif
  19. #endif
  20. /* This struct is used for keeping a linked list of hostnames / ports / sockets.
  21. The list is initially empty and added to whenever a new connection is opened.
  22. sock>0 indicates an open TCP connection.
  23. sock=0 corresponds to a local fieldtrip buffer spawned within this MEX file.
  24. The list is kept until the MEX-file is unloaded and automatically cleaned up
  25. inside the MEX-file exit routine. TCP communication errors on a specific socket
  26. will trigger closing the connection and removing the corresponding list item.
  27. */
  28. typedef struct host_port_sock_list_item {
  29. char *hostname;
  30. int port;
  31. int sock;
  32. struct host_port_sock_list_item *next; /* NULL if last elemenent */
  33. } host_port_sock_list_item_t;
  34. /* This is the head of the list */
  35. host_port_sock_list_item_t *firstHostPortSock = NULL;
  36. /* This keeps track of the running thread */
  37. pthread_t tcpserverThread;
  38. /* a pointer to this is passed on to the thread, we only need it once
  39. and therefore use a static variable */
  40. host_t host_for_server;
  41. /* these subfunctions are used in the switch-yard below
  42. ** SK: they now return integer error codes like the following
  43. ** <0 : some low-level communication error occured (especially -3 from the tcprequest)
  44. ** 0 : no error
  45. ** >0 : an error on the buffer level occured (as returned from the buffer
  46. ** To keep track of connection failures (for closing the sockets),
  47. ** mexErrMsgTxt should only be used to report pure Matlab errors (e.g., invalid
  48. ** arguments).
  49. */
  50. int buffer_gethdr(int, mxArray **, const mxArray **);
  51. int buffer_getdat(int, mxArray **, const mxArray **);
  52. int buffer_getevt(int, mxArray **, const mxArray **);
  53. int buffer_getprp(int, mxArray **, const mxArray **);
  54. int buffer_puthdr(int, mxArray **, const mxArray **);
  55. int buffer_putdat(int, mxArray **, const mxArray **);
  56. int buffer_putevt(int, mxArray **, const mxArray **);
  57. int buffer_putprp(int, mxArray **, const mxArray **);
  58. int buffer_flushhdr(int, mxArray **, const mxArray **);
  59. int buffer_flushdat(int, mxArray **, const mxArray **);
  60. int buffer_flushevt(int, mxArray **, const mxArray **);
  61. int buffer_waitdat(int, mxArray **, const mxArray **);
  62. /* this function is called upon unloading of the mex-file */
  63. void exitFun(void) {
  64. int verbose = 1;
  65. int rc;
  66. if (verbose) {
  67. printf("Entering exitFun() routine\n");
  68. }
  69. /* tell the tcpserver thread to stop */
  70. pthread_mutex_lock(&mutexstatus);
  71. if (tcpserverStatus) {
  72. pthread_mutex_unlock(&mutexstatus);
  73. mexPrintf("requesting cancelation of tcpserver thread\n");
  74. pthread_cancel(tcpserverThread);
  75. pthread_join(tcpserverThread, NULL);
  76. }
  77. else {
  78. pthread_mutex_unlock(&mutexstatus);
  79. }
  80. /* free the memory that is used for the header, data and events */
  81. free_event();
  82. free_data();
  83. free_header();
  84. /* clean up host/address/socket list and close open sockets */
  85. while (firstHostPortSock != NULL) {
  86. host_port_sock_list_item_t *hpsli = firstHostPortSock;
  87. if (hpsli->sock > 0) {
  88. if (verbose) {
  89. printf("Closing socket and ");
  90. }
  91. close_connection(hpsli->sock);
  92. }
  93. if (verbose) {
  94. printf("cleaning up list entry %s:%i\n",hpsli->hostname, hpsli->port);
  95. }
  96. FREE(hpsli->hostname);
  97. firstHostPortSock = hpsli->next;
  98. free(hpsli);
  99. }
  100. return;
  101. }
  102. /** This function searches through the linked list to retrieve the socket number
  103. of the first (and only) item with matching hostname and port.
  104. Returns -1 if no item matched.
  105. */
  106. int lookup_hps_item(const char *hostname, int port) {
  107. host_port_sock_list_item_t *hpsli = firstHostPortSock;
  108. while (hpsli != NULL) {
  109. if ((strcmp(hpsli->hostname, hostname)==0) && (hpsli->port == port)) {
  110. return hpsli->sock;
  111. }
  112. hpsli = hpsli->next;
  113. }
  114. return -1;
  115. }
  116. /* This function adds a new item to the host/port/socket list without checking if
  117. the same element is already there. Also registers the cleanup routine.
  118. */
  119. int add_hps_item(const char *hostname, int port, int sock) {
  120. host_port_sock_list_item_t *hpsli;
  121. int n;
  122. if (hostname == NULL) return 0;
  123. n = strlen(hostname);
  124. hpsli = (host_port_sock_list_item_t *) malloc(sizeof(host_port_sock_list_item_t));
  125. if (hpsli == NULL) return 0; /* out of memory - probably never */
  126. hpsli->hostname = (char *) malloc(n+1);
  127. if (hpsli->hostname == NULL) {
  128. /* out of memory - probably never */
  129. free(hpsli);
  130. return 0;
  131. }
  132. memcpy(hpsli->hostname, hostname, n+1);
  133. hpsli->port = port;
  134. hpsli->sock = sock;
  135. hpsli->next = firstHostPortSock;
  136. firstHostPortSock = hpsli;
  137. mexAtExit(exitFun); /* register cleanup routine so the list get's properly destroyed */
  138. return 1;
  139. }
  140. /* This function searches the item with matching socket number and removes it from the list
  141. Note: The socket itself is not closed here.
  142. */
  143. void remove_hps_item(int sock) {
  144. host_port_sock_list_item_t *hpsli = firstHostPortSock;
  145. /* the "next" pointer of the previous list item, or initially a pointer to the head of the list */
  146. host_port_sock_list_item_t **prev_next = &firstHostPortSock;
  147. while (hpsli != NULL) {
  148. if (hpsli->sock == sock) {
  149. *prev_next = hpsli->next;
  150. free(hpsli->hostname);
  151. free(hpsli);
  152. return;
  153. }
  154. prev_next = &(hpsli->next);
  155. hpsli = hpsli->next;
  156. }
  157. }
  158. /** This is a MEX-specific wrapper for the open_connection call.
  159. If the requested host+port combination is already in the linked list,
  160. it means that the connection is still open (at least from this side), and
  161. we can just return the corresponding socket.
  162. Otherwise, we try to connect using the normal open_connection, and
  163. in case of success, add the new host/port/sock triple to the list.
  164. */
  165. int open_connection_with_list(const char *hostname, int port) {
  166. int verbose = 0;
  167. int sock;
  168. /* first perform lookup of already known hostnames and connections */
  169. if (verbose > 0)
  170. printf("Looking for list item...\n");
  171. sock = lookup_hps_item(hostname, port);
  172. /* hostname/port combination found? just return the socket */
  173. if (sock == 0) {
  174. /* dma connection to internal buffer */
  175. if (verbose > 0)
  176. printf("Found (dma) connection to internal buffer\n");
  177. return sock;
  178. }
  179. if (sock > 0) {
  180. /* First check whether that socket can be read from, indicating a
  181. closed connection from the remote side.
  182. */
  183. fd_set readSet;
  184. struct timeval tv;
  185. int sel;
  186. tv.tv_sec = 0;
  187. tv.tv_usec = 0;
  188. FD_ZERO(&readSet);
  189. FD_SET(sock, &readSet);
  190. sel = select(sock + 1, &readSet, NULL, NULL, &tv);
  191. if (sel == 1) {
  192. mexWarnMsgTxt("Existing connection seems to have been closed from remote side - trying to re-open...\n");
  193. remove_hps_item(sock);
  194. /* go on further down with opening new connection */
  195. } else {
  196. /* reading not possible means that connection is still open */
  197. if (verbose > 0)
  198. printf("Found connection on %i\n",sock);
  199. return sock;
  200. }
  201. }
  202. if (verbose>0)
  203. printf("Trying to open new connection...\n");
  204. if (port == 0) {
  205. sock = open_unix_connection(hostname);
  206. } else {
  207. sock = open_connection(hostname, port);
  208. }
  209. if (sock>0) {
  210. if (verbose>0)
  211. printf("open_connection: connected to %s:%d on socket %d\n", hostname, port, sock);
  212. if (!add_hps_item(hostname, port, sock)) {
  213. /* out of memory for creating a list entry - IF this ever happens, we better close the socket right away */
  214. close_connection(sock);
  215. /* return with an error further down */
  216. } else {
  217. return sock;
  218. }
  219. }
  220. mexErrMsgTxt("ERROR: failed to create socket (1)");
  221. return -1;
  222. }
  223. /* for debugging */
  224. void dump_hps_list() {
  225. host_port_sock_list_item_t *hpsli = firstHostPortSock;
  226. printf("----- Open connections -----\n");
  227. while (hpsli != NULL) {
  228. printf("%s:%i -> %i\n",hpsli->hostname, hpsli->port, hpsli->sock);
  229. hpsli = hpsli->next;
  230. }
  231. printf("----------------------------\n");
  232. }
  233. void mexFunction (int nlhs, mxArray * plhs[], int nrhs, const mxArray * prhs[])
  234. {
  235. int port;
  236. char *command = NULL, *argument = NULL, *hostname = NULL;
  237. int num;
  238. int rc, t;
  239. int server, errorCode = 0;
  240. if (nrhs <2)
  241. mexErrMsgTxt ("Invalid number of input arguments");
  242. if (nrhs>0) {
  243. /* first argument is command, e.g. GET_HDR or TCPSERVER */
  244. command = mxArrayToString(prhs[0]);
  245. if (command==NULL)
  246. mexErrMsgTxt ("invalid input argument #1");
  247. }
  248. if (nrhs>1) {
  249. /* second argument depends on the command */
  250. /* this will be processed below */
  251. }
  252. if (nrhs>2) {
  253. /* third argument is the hostname (optional) */
  254. hostname = mxArrayToString(prhs[2]);
  255. if (hostname==NULL)
  256. mexErrMsgTxt ("invalid input argument #3");
  257. }
  258. else {
  259. /* use the default value */
  260. hostname = (char *) mxMalloc(strlen(DEFAULT_HOST)+1);
  261. strncpy(hostname, DEFAULT_HOST, strlen(DEFAULT_HOST)+1);
  262. }
  263. if (nrhs>3) {
  264. /* fourth argument is the port number (optional) */
  265. if (mxIsEmpty(prhs[3]) || !mxIsNumeric(prhs[3]))
  266. mexErrMsgTxt ("invalid input argument #4");
  267. else
  268. port = (int)mxGetScalar(prhs[3]);
  269. }
  270. else {
  271. /* use the default value */
  272. port = DEFAULT_PORT;
  273. }
  274. if (strcasecmp(command, "tcpserver")==0) {
  275. argument = mxArrayToString(prhs[1]);
  276. if (argument==NULL)
  277. mexErrMsgTxt ("invalid input argument #2");
  278. if (strcasecmp(argument, "init")==0) {
  279. if (tcpserverStatus)
  280. mexErrMsgTxt("thread is already running");
  281. mexPrintf("In main: spawning tcpserver thread\n");
  282. strncpy(host_for_server.name, hostname, 256); /* the 256 is a limit inside the host_t structure */
  283. host_for_server.port = port;
  284. rc = pthread_create(&tcpserverThread, NULL, tcpserver, (void *)&host_for_server);
  285. if (rc)
  286. mexErrMsgTxt("problem with return code from pthread_create()");
  287. /* put this as a "connection" with sock=0 into the list */
  288. add_hps_item(hostname, port, 0);
  289. }
  290. else if (strcasecmp(argument, "exit")==0) {
  291. if (!tcpserverStatus)
  292. mexErrMsgTxt("thread is not running");
  293. mexPrintf("In main: requesting cancelation of tcpserver thread\n");
  294. rc = pthread_cancel(tcpserverThread);
  295. if (rc)
  296. mexErrMsgTxt("problem with return code from pthread_cancel()");
  297. /* remove sock=0 connection from host/port/socket list */
  298. remove_hps_item(0);
  299. }
  300. else if (strcasecmp(argument, "status")==0) {
  301. plhs[0] = mxCreateDoubleScalar(tcpserverStatus);
  302. }
  303. }
  304. else if (strcasecmp(command, "get_hdr")==0) {
  305. server = open_connection_with_list(hostname, port);
  306. errorCode = buffer_gethdr(server, &(plhs[0]), &(prhs[1]));
  307. }
  308. else if (strcasecmp(command, "get_dat")==0) {
  309. server = open_connection_with_list(hostname, port);
  310. errorCode = buffer_getdat(server, &(plhs[0]), &(prhs[1]));
  311. }
  312. else if (strcasecmp(command, "get_evt")==0) {
  313. server = open_connection_with_list(hostname, port);
  314. errorCode = buffer_getevt(server, &(plhs[0]), &(prhs[1]));
  315. }
  316. else if (strcasecmp(command, "put_hdr")==0) {
  317. server = open_connection_with_list(hostname, port);
  318. errorCode = buffer_puthdr(server, &(plhs[0]), &(prhs[1]));
  319. }
  320. else if (strcasecmp(command, "put_dat")==0) {
  321. server = open_connection_with_list(hostname, port);
  322. errorCode = buffer_putdat(server, &(plhs[0]), &(prhs[1]));
  323. }
  324. else if (strcasecmp(command, "put_evt")==0) {
  325. server = open_connection_with_list(hostname, port);
  326. errorCode = buffer_putevt(server, &(plhs[0]), &(prhs[1]));
  327. }
  328. else if (strcasecmp(command, "flush_hdr")==0) {
  329. server = open_connection_with_list(hostname, port);
  330. errorCode = buffer_flushhdr(server, &(plhs[0]), &(prhs[1]));
  331. }
  332. else if (strcasecmp(command, "flush_dat")==0) {
  333. server = open_connection_with_list(hostname, port);
  334. errorCode = buffer_flushdat(server, &(plhs[0]), &(prhs[1]));
  335. }
  336. else if (strcasecmp(command, "flush_evt")==0) {
  337. server = open_connection_with_list(hostname, port);
  338. errorCode = buffer_flushevt(server, &(plhs[0]), &(prhs[1]));
  339. }
  340. else if (strcasecmp(command, "wait_dat")==0) {
  341. server = open_connection_with_list(hostname, port);
  342. errorCode = buffer_waitdat(server, &(plhs[0]), &(prhs[1]));
  343. }
  344. else if (strcasecmp(command, "list_connections")==0) {
  345. dump_hps_list();
  346. }
  347. else if (strcasecmp(command, "lookup_connection")==0) {
  348. plhs[0] = mxCreateDoubleScalar(lookup_hps_item(hostname, port));
  349. }
  350. else if (strcasecmp(command, "close_connection")==0) {
  351. int sock = lookup_hps_item(hostname, port);
  352. /* Note that we ignore request to close the dma "connection" */
  353. if (sock > 0) {
  354. close_connection(sock);
  355. remove_hps_item(sock);
  356. }
  357. dump_hps_list();
  358. }
  359. else {
  360. mexErrMsgTxt ("unknown command");
  361. }
  362. /* These are either allocated using mxMalloc'ed or mxArrayToString.
  363. Deallocating them manually is not strictly necessary, but cleaner (says Mathworks)
  364. */
  365. if (argument) mxFree(argument);
  366. if (command) mxFree(command);
  367. if (hostname) mxFree(hostname);
  368. if (errorCode > 0) {
  369. char msg[512];
  370. /* valid connection, but invalid request */
  371. sprintf(msg, "ERROR: the buffer returned an error (%d)\n", errorCode);
  372. mexErrMsgTxt(msg);
  373. }
  374. else if (errorCode == -3) {
  375. /* valid connection, but invalid request, we'll close the socket and remove the list item */
  376. printf("Trying to close socket %i\n", server);
  377. close_connection(server);
  378. remove_hps_item(server);
  379. mexErrMsgTxt("ERROR: tcp connection to buffer failed.");
  380. }
  381. return;
  382. }