Skip to content

Instantly share code, notes, and snippets.

Created December 25, 2016 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/558d625b8be4d8cf7a913381c851fec6 to your computer and use it in GitHub Desktop.
Save anonymous/558d625b8be4d8cf7a913381c851fec6 to your computer and use it in GitHub Desktop.
Vim as a echo server
diff --git a/src/channel.c b/src/channel.c
index 32f3527f7..3a9e6c7e4 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -2739,6 +2739,322 @@ channel_status(channel_T *channel, int req_part)
return "closed";
}
+/*
+ * Bind a socket channel to "hostname":"port".
+ * "waittime" is the time in msec to wait for the connection.
+ * When negative wait forever.
+ * Returns the channel for success.
+ * Returns NULL for failure.
+ */
+ channel_T *
+channel_listen(
+ char *hostname,
+ int port_in,
+ int waittime,
+ void (*nb_close_cb)(void))
+{
+ int sd = -1;
+ struct sockaddr_in server;
+ struct hostent *host;
+ int port = port_in;
+ channel_T *channel;
+ int ret;
+
+ channel = add_channel();
+ if (channel == NULL)
+ {
+ ch_error(NULL, "Cannot allocate channel.");
+ return NULL;
+ }
+
+ /* Get the server internet address and put into addr structure */
+ /* fill in the socket address structure and connect to server */
+ vim_memset((char *)&server, 0, sizeof(server));
+ server.sin_family = AF_INET;
+ server.sin_port = htons(port);
+ if ((host = gethostbyname(hostname)) == NULL)
+ {
+ ch_error(channel, "in gethostbyname() in channel_open()");
+ PERROR(_("E901: gethostbyname() in channel_open()"));
+ channel_free(channel);
+ return NULL;
+ }
+ memcpy((char *)&server.sin_addr, host->h_addr, host->h_length);
+
+ /* On Mac and Solaris a zero timeout almost never works. At least wait
+ * one millisecond. Let's do it for all systems, because we don't know why
+ * this is needed. */
+ if (waittime == 0)
+ waittime = 1;
+
+ /*
+ * For Unix we need to call connect() again after connect() failed.
+ * On Win32 one time is sufficient.
+ */
+ while (TRUE)
+ {
+ long elapsed_msec = 0;
+ int waitnow;
+
+ if (sd >= 0)
+ sock_close(sd);
+ sd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sd == -1)
+ {
+ ch_error(channel, "in socket() in channel_open().");
+ PERROR(_("E898: socket() in channel_open()"));
+ channel_free(channel);
+ return NULL;
+ }
+
+ if (waittime >= 0)
+ {
+ /* Make connect() non-blocking. */
+ if (
+ fcntl(sd, F_SETFL, O_NONBLOCK) < 0
+ )
+ {
+ SOCK_ERRNO;
+ ch_errorn(channel,
+ "channel_open: Connect failed with errno %d", errno);
+ sock_close(sd);
+ channel_free(channel);
+ return NULL;
+ }
+ }
+
+ /* Try binding as the server. */
+ ch_logsn(channel, "Binding to %s port %d", hostname, port);
+ ret = bind(sd, (struct sockaddr *)&server, sizeof(server));
+
+ if (ret == -1)
+ {
+ /* The connection couldn't be established. */
+ if (waittime < 0 || (errno != EWOULDBLOCK
+ && errno != ECONNREFUSED
+ ))
+ {
+ ch_errorn(channel,
+ "channel_open: Connect failed with errno %d", errno);
+ PERROR(_(e_cannot_connect));
+ sock_close(sd);
+ channel_free(channel);
+ return NULL;
+ }
+ }
+
+ ch_logn(channel, "Start listening a socket %d", sd);
+ ret = listen(sd, SOMAXCONN);
+
+ if (ret == -1)
+ {
+ /* The connection couldn't be established. */
+ if (waittime < 0 || (errno != EWOULDBLOCK
+ && errno != ECONNREFUSED
+ ))
+ {
+ ch_errorn(channel,
+ "channel_open: Connect failed with errno %d", errno);
+ PERROR(_(e_cannot_connect));
+ sock_close(sd);
+ channel_free(channel);
+ return NULL;
+ }
+ }
+
+ ret = accept(sd, NULL, 0);
+
+ if (ret >= 0)
+ {
+ // XXX: めちゃ無理矢理
+ sd = ret;
+ break;
+ }
+
+ /* Limit the waittime to 50 msec. If it doesn't work within this
+ * time we close the socket and try creating it again. */
+ waitnow = waittime > 50 ? 50 : waittime;
+
+ /* If connect() didn't finish then try using select() to wait for the
+ * connection to be made. For Win32 always use select() to wait. */
+ if (errno != ECONNREFUSED)
+ {
+ struct timeval tv;
+ fd_set rfds;
+ fd_set wfds;
+ int so_error = 0;
+ socklen_t so_error_len = sizeof(so_error);
+ struct timeval start_tv;
+ struct timeval end_tv;
+ FD_ZERO(&rfds);
+ FD_SET(sd, &rfds);
+ FD_ZERO(&wfds);
+ FD_SET(sd, &wfds);
+
+ tv.tv_sec = waitnow / 1000;
+ tv.tv_usec = (waitnow % 1000) * 1000;
+ gettimeofday(&start_tv, NULL);
+ ch_logn(channel, "Waiting for connection (waiting %d msec)...", waitnow);
+ ret = select((int)sd + 1, &rfds, &wfds, NULL, &tv);
+
+ if (ret < 0)
+ {
+ SOCK_ERRNO;
+ ch_errorn(channel,
+ "channel_open: Connect failed with errno %d", errno);
+ PERROR(_(e_cannot_connect));
+ sock_close(sd);
+ channel_free(channel);
+ return NULL;
+ }
+
+ /* On Linux-like systems: See socket(7) for the behavior
+ * After putting the socket in non-blocking mode, connect() will
+ * return EINPROGRESS, select() will not wait (as if writing is
+ * possible), need to use getsockopt() to check if the socket is
+ * actually able to connect.
+ * We detect a failure to connect when either read and write fds
+ * are set. Use getsockopt() to find out what kind of failure. */
+ if (FD_ISSET(sd, &rfds) || FD_ISSET(sd, &wfds))
+ {
+ ret = getsockopt(sd,
+ SOL_SOCKET, SO_ERROR, &so_error, &so_error_len);
+ if (ret < 0 || (so_error != 0
+ && so_error != EWOULDBLOCK
+ && so_error != ECONNREFUSED
+ ))
+ {
+ ch_errorn(channel,
+ "channel_open: Connect failed with errno %d",
+ so_error);
+ PERROR(_(e_cannot_connect));
+ sock_close(sd);
+ channel_free(channel);
+ return NULL;
+ }
+ }
+
+ if (FD_ISSET(sd, &wfds) && so_error == 0)
+ /* Did not detect an error, connection is established. */
+ break;
+
+ gettimeofday(&end_tv, NULL);
+ elapsed_msec = (end_tv.tv_sec - start_tv.tv_sec) * 1000
+ + (end_tv.tv_usec - start_tv.tv_usec) / 1000;
+ }
+
+ if (waittime > 1 && elapsed_msec < waittime)
+ {
+ /* The port isn't ready but we also didn't get an error.
+ * This happens when the server didn't open the socket
+ * yet. Select() may return early, wait until the remaining
+ * "waitnow" and try again. */
+ waitnow -= elapsed_msec;
+ waittime -= elapsed_msec;
+ if (waitnow > 0)
+ {
+ mch_delay((long)waitnow, TRUE);
+ ui_breakcheck();
+ waittime -= waitnow;
+ }
+ if (!got_int)
+ {
+ if (waittime <= 0)
+ /* give it one more try */
+ waittime = 1;
+ continue;
+ }
+ /* we were interrupted, behave as if timed out */
+ }
+
+ /* We timed out. */
+ ch_error(channel, "Connection timed out");
+ sock_close(sd);
+ channel_free(channel);
+ return NULL;
+ }
+
+ ch_log(channel, "Binding made");
+
+ if (waittime >= 0)
+ {
+ (void)fcntl(sd, F_SETFL, 0);
+ }
+
+ channel->CH_SOCK_FD = (sock_T)sd;
+ channel->ch_nb_close_cb = nb_close_cb;
+ channel->ch_hostname = (char *)vim_strsave((char_u *)hostname);
+ channel->ch_port = port_in;
+ channel->ch_to_be_closed |= (1 << PART_SOCK);
+
+#ifdef FEAT_GUI
+ channel_gui_register_one(channel, PART_SOCK);
+#endif
+
+ return channel;
+}
+
+/*
+ * Implements ch_listen().
+ */
+ channel_T *
+channel_listen_func(typval_T *argvars)
+{
+ char_u *address;
+ char_u *p;
+ char *rest;
+ int port;
+ jobopt_T opt;
+ channel_T *channel = NULL;
+
+ address = get_tv_string(&argvars[0]);
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL))
+ {
+ EMSG(_(e_invarg));
+ return NULL;
+ }
+
+ /* parse address */
+ p = vim_strchr(address, ':');
+ if (p == NULL)
+ {
+ EMSG2(_(e_invarg2), address);
+ return NULL;
+ }
+ *p++ = NUL;
+ port = strtol((char *)p, &rest, 10);
+ if (*address == NUL || port <= 0 || *rest != NUL)
+ {
+ p[-1] = ':';
+ EMSG2(_(e_invarg2), address);
+ return NULL;
+ }
+
+ /* parse options */
+ clear_job_options(&opt);
+ opt.jo_mode = MODE_JSON;
+ opt.jo_timeout = 2000;
+ if (get_job_options(&argvars[1], &opt,
+ JO_MODE_ALL + JO_CB_ALL + JO_WAITTIME + JO_TIMEOUT_ALL) == FAIL)
+ goto theend;
+ if (opt.jo_timeout < 0)
+ {
+ EMSG(_(e_invarg));
+ goto theend;
+ }
+
+ channel = channel_listen((char *)address, port, opt.jo_waittime, NULL);
+ if (channel != NULL)
+ {
+ opt.jo_set = JO_ALL;
+ channel_set_options(channel, &opt);
+ }
+theend:
+ free_job_options(&opt);
+ return channel;
+}
+
static void
channel_part_info(channel_T *channel, dict_T *dict, char *name, ch_part_T part)
{
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 08be12bb1..a7f8b7472 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -93,6 +93,7 @@ static void f_ch_sendexpr(typval_T *argvars, typval_T *rettv);
static void f_ch_sendraw(typval_T *argvars, typval_T *rettv);
static void f_ch_setoptions(typval_T *argvars, typval_T *rettv);
static void f_ch_status(typval_T *argvars, typval_T *rettv);
+static void f_ch_listen(typval_T *argvars, typval_T *rettv);
#endif
static void f_changenr(typval_T *argvars, typval_T *rettv);
static void f_char2nr(typval_T *argvars, typval_T *rettv);
@@ -508,6 +509,7 @@ static struct fst
{"ch_getbufnr", 2, 2, f_ch_getbufnr},
{"ch_getjob", 1, 1, f_ch_getjob},
{"ch_info", 1, 1, f_ch_info},
+ {"ch_listen", 1, 2, f_ch_listen},
{"ch_log", 1, 2, f_ch_log},
{"ch_logfile", 1, 2, f_ch_logfile},
{"ch_open", 1, 2, f_ch_open},
@@ -2021,6 +2023,19 @@ f_ch_status(typval_T *argvars, typval_T *rettv)
rettv->vval.v_string = vim_strsave((char_u *)channel_status(channel, part));
}
+
+/*
+ * "ch_listen()" function
+ */
+ static void
+f_ch_listen(typval_T *argvars, typval_T *rettv)
+{
+ rettv->v_type = VAR_CHANNEL;
+ if (check_restricted() || check_secure())
+ return;
+ rettv->vval.v_channel = channel_listen_func(argvars);
+}
+
#endif
/*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment