Created
December 25, 2016 11:11
-
-
Save anonymous/558d625b8be4d8cf7a913381c851fec6 to your computer and use it in GitHub Desktop.
Vim as a echo server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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