在按下 Enter 之前线程函数不会终止
thread function doesn't terminate until Enter is pressed
以下代码(最后)表示从远程客户端接收 ls 命令并将当前工作目录发送到该客户端的线程函数。
发送成功但有一个问题:
当它完全停止发送时,我希望它重新开始收听。在行:
printf("Enter 1 if you want to exit or 0 if you don't: ");
fgets(exit_status,MAX_SIZE,stdin);
当我按 Enter
时卡住并终止(并启动另一个线程)
我不明白为什么?在调试时,我看到上面的打印语句在按 Enter 后执行,尽管调试器处于函数末尾(意味着它通过了此打印语句)。
我想让它在发送完数据后自动重新开始监听。
如果有人想在这里查看我的完整代码是 link:https://pastebin.com/9UmTkPge
void *server_socket_ls(void *arg) {
int* exit_status = (int*)malloc(sizeof(int));
*exit_status = 0;
while (*exit_status == 0) {
//socket code is here
//code for ls
char buffer[BUFFSIZE];
int received = -1;
char data[MAX];
memset(data,0,MAX);
// this will make server wait for another command to run until it receives exit
data[0] = '[=11=]';
if((received = recv(new_socket, buffer,BUFFSIZE,0))<0){
perror("Failed");
}
buffer[received] = '[=11=]';
strcat (data, buffer);
if (strcmp(data, "exit")==0) // this will force the code to exit
exit(0);
puts (data);
char *args[100];
setup(data,args,0);
int pipefd[2],lenght;
if(pipe(pipefd))
perror("Failed to create pipe");
pid_t pid = fork();
char path[MAX];
if(pid==0)
{
close(1); // close the original stdout
dup2(pipefd[1],1); // duplicate pipfd[1] to stdout
close(pipefd[0]); // close the readonly side of the pipe
close(pipefd[1]); // close the original write side of the pipe
execvp(args[0],args); // finally execute the command
}
else
if(pid>0)
{
close(pipefd[1]);
memset(path,0,MAX);
while(lenght=read(pipefd[0],path,MAX-1)){
printf("Data read so far %s\n", path);
if(send(new_socket,path,strlen(path),0) != strlen(path) ){
perror("Failed");
}
//fflush(NULL);
printf("Data sent so far %s\n", path);
memset(path,0,MAX);
}
close(pipefd[0]);
//removed so server will not terminate
}
else
{
printf("Error !\n");
exit(0);
}
printf("Enter 1 if you want to exit or 0 if you don't: ");
fgets(exit_status,MAX_SIZE,stdin);
}
}
有许多个错误:
- 在
terminal_thread
中,input_command
在每次循环迭代时分配——内存泄漏
- 去除换行符的代码已损坏
- 使用
.l
, 不 指定 IP 地址会导致段错误,因为 token
是 NULL
terminal_thread
中.l
的端口号是5126,不匹配相应服务器代码 中的9191
- 连接后,
server_socket_file
不做任何事情。
- 在
server_socket_ls
中,它 在 socket
、bind
、listen
和 accept
上循环 。循环应该在 listen
之后 开始(即只在循环中执行 accept
并且 重用 监听套接字)。
- 代码中标记的其他错误
我不得不重构代码并添加一些调试。它用错误注释。我使用 cpp
条件来表示旧代码与新代码:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
这是代码。我得到了 minimal .l
(远程 ls)工作:
编辑: 由于下面的更新 运行 超出了 SO space 限制,我删除了我在此处发布的第一个代码块。
这是 debug.txt
输出:
term term: PROMPT
term term: FGETS
ls ls: ENTER
ls ls: SOCKET
file file: ENTER
ls ls: BIND prtNum=9191
file file: BIND portNum=6123
ls ls: LISTEN
term term: COMMAND '.l'
term term: port=9191
ls ls: ACCEPTED
term term: PROMPT
This program is exiting as soon as its stops sending data at exit(0) and so doesn't ask for exit_status. Is there a way somehow to make it not stop and instead the terminal prompt reappears along with servers listening at the back? –
Dragut
因为我感觉到了紧迫性,所以我犯了错误,现在部分解决方案比为时已晚的完美解决方案更好。
我可能在 ls 服务器父进程(现已修复)中引入了一个带有无关 exit
调用的错误。
但是,还有其他问题...
主要问题是服务器(对于ls)提示用户是否继续(在stdout/stdin
).这不太管用。
应该提示用户的是客户端(即terminal_thread
)。或者,正如我所做的那样,客户端将在命令提示符处看到 exit
,然后 发送 一个带有“exit”的数据包到服务器,然后终止。然后,服务器将看到此命令并终止。
我在没有完全重做所有内容的情况下尽可能多地进行了重构。
我将一些代码拆分成函数。一些 can/could 被重用来实现“文件”服务器。
但是,我会将两个 函数放入单个服务器线程中。我会让服务器查看它获取的“命令”,并根据命令执行任一操作。由于在“文件”服务器中没有实际执行某些操作的代码[还],因此很难返工。
有一件事要解决[我没有时间]:
.l
命令的格式为:.l [ip_address]
。 ip_address
的默认值为 127.0.0.1
。但是,这应该是 split 成两个命令(例如):
- 附加 [ip_address]
- ls [ls 参数]
无论如何,这是更新后的代码。我不得不移动得有点快,所以它没有我想要的那么干净。
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdarg.h>
#if 1
#include <time.h>
#endif
#define BACKLOG 10
#define MAX_SIZE 200
#define BACKLOG 10
#define BUFFSIZE 2048
#define MAXPENDING 5
#define MAX 2048
__thread char *tid;
__thread char dbgstrbuf[1000];
FILE *xfdbg;
double tsczero = 0.0;
typedef struct server_arg {
int portNum;
} server_arg;
typedef struct server_arg1 {
int portNum;
} server_arg1;
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tsczero;
return sec;
}
void
dbgprt(const char *fmt,...)
{
va_list ap;
char msg[1000];
char *bp = msg;
bp += sprintf(bp,"[%.9f/%4s] ",tscgetf(),tid);
va_start(ap,fmt);
bp += vsprintf(bp,fmt,ap);
va_end(ap);
fputs(msg,xfdbg);
}
const char *
dbgstr(const char *str,int len)
{
char *bp = dbgstrbuf;
if (len < 0)
len = strlen(str);
bp += sprintf(bp,"'");
for (int i = 0; i < len; ++i) {
int chr = str[i];
if ((chr > 0x20) && (chr <= 0x7E))
bp += sprintf(bp,"%c",chr);
else
bp += sprintf(bp,"{%2.2X}",chr);
}
bp += sprintf(bp,"'");
return dbgstrbuf;
}
void
setup(char inputBuffer[], char *args[], int *background)
{
const char s[4] = " \t\n";
char *token;
token = strtok(inputBuffer, s);
int i = 0;
while (token != NULL) {
args[i] = token;
i++;
// printf("%s\n", token);
token = strtok(NULL, s);
}
args[i] = NULL;
}
int
open_remote(const char *ip,unsigned short port)
{
int sock;
struct sockaddr_in echoserver;
dbgprt("open_remote: ENTER ip=%s port=%u\n",dbgstr(ip,-1),port);
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("Failed to create socket");
exit(1);
}
int enable = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable,
sizeof(int)) < 0) {
perror("error");
}
memset(&echoserver, 0, sizeof(echoserver));
echoserver.sin_family = AF_INET;
echoserver.sin_addr.s_addr = inet_addr(ip);
// NOTE/BUG: this port number does _not_ match any server port
#if 0
echoserver.sin_port = htons(5126);
#else
dbgprt("term: port=%u\n",port);
echoserver.sin_port = htons(port);
#endif
if (connect(sock, (struct sockaddr *) &echoserver,
sizeof(echoserver)) < 0) {
perror("Failed to connect with server");
exit(1);
}
dbgprt("open_remote: EXIT sock=%d\n",sock);
return sock;
}
void *
terminal_thread(void *arg)
{
// NOTE/FIX: do this _once_
#if 1
char *input_command = malloc(MAX_SIZE);
#endif
tid = "term";
char buffer[BUFFSIZE];
int sock_ls = -1;
while (1) {
dbgprt("term: PROMPT\n");
printf(">> ");
//memset(input_command,0,strlen(str));
// NOTE/BUG: this is a memory leak
#if 0
char *input_command = malloc(MAX_SIZE);
#endif
dbgprt("term: FGETS\n");
fgets(input_command, MAX_SIZE, stdin);
// NOTE/BUG: code is broken to strip newline
#if 0
if ((strlen(input_command) > 0) &&
(input_command[strlen(input_command) - 1] == '\n'))
input_command[strlen(input_command) - 1] = '[=12=]';
#else
input_command[strcspn(input_command,"\n")] = 0;
#endif
dbgprt("term: COMMAND %s\n",dbgstr(input_command,-1));
char list[] = "ls";
char cp[] = "cp";
#if 0
char s[100];
printf("%s\n", getcwd(s,100));
chdir("Desktop");
printf("%s\n", getcwd(s,100));
#endif
// exit program (and exit server)
if (strcmp(input_command,"exit") == 0) {
if (sock_ls >= 0) {
dbgprt("term: SENDEXIT\n");
if (send(sock_ls,"exit",4,0) < 0) {
perror("send/exit");
exit(1);
}
break;
}
}
if (strcmp(input_command, list) == 0) {
// ls code will run here
}
if ((input_command[0] == '.') && (input_command[1] == 'l')) {
printf("remote ls\n");
char ip[20];
const char c[2] = " ";
// strcpy(str,input_command);
char *token;
// get the first token
token = strtok(input_command, c);
// walk through other tokens
int i = 0;
while (token != NULL && i != -1) {
token = strtok(NULL, c);
i--;
}
#if 1
if (token == NULL) {
token = "127.0.0.1";
printf("no IP address found -- using %s\n",token);
}
#endif
if (sock_ls < 0)
sock_ls = open_remote(token,9191);
char s[100];
strcpy(s, "ls");
// NOTE/BUG: this blows away the "s" in "ls" because s is _set_ with strcpy
#if 0
s[strlen(s) - 1] = '[=12=]'; // fgets doesn't automatically discard '\n'
#endif
unsigned int echolen;
echolen = strlen(s);
int received = 0;
/* send() from client; */
if (send(sock_ls, s, echolen, 0) != echolen) {
perror("Mismatch in number of sent bytes");
}
fprintf(stdout, "Message from server: ");
int bytes = 0;
/* recv() from server; */
if ((bytes = recv(sock_ls, buffer, echolen, 0)) < 1) {
perror("Failed to receive bytes from server");
}
received += bytes;
buffer[bytes] = '[=12=]';
/* Assure null terminated string */
fprintf(stdout, buffer);
bytes = 0;
// this d {...} while block will receive the buffer sent by server
do {
buffer[bytes] = '[=12=]';
printf("%s\n", buffer);
} while ((bytes = recv(sock_ls, buffer, BUFFSIZE - 1, 0)) >= BUFFSIZE - 1);
buffer[bytes] = '[=12=]';
printf("%s\n", buffer);
printf("\n");
continue;
}
}
dbgprt("term: EXIT\n");
return (void *) 0;
}
int
ls_loop(int new_socket)
{
dbgprt("ls_loop: ENTER new_socket=%d\n",new_socket);
//code for ls
char buffer[BUFFSIZE];
int received = -1;
char data[MAX];
int stop = 0;
while (1) {
memset(data, 0, MAX);
// this will make server wait for another command to run until it
// receives exit
data[0] = '[=12=]';
if ((received = recv(new_socket, buffer, BUFFSIZE, 0)) < 0) {
perror("Failed");
}
buffer[received] = '[=12=]';
strcpy(data, buffer);
dbgprt("ls_loop: COMMAND %s\n",dbgstr(data,-1));
// this will force the code to exit
#if 0
if (strcmp(data, "exit") == 0)
exit(0);
puts(data);
#else
if (strncmp(data, "exit", 4) == 0) {
dbgprt("ls_loop: EXIT/COMMAND\n");
stop = 1;
break;
}
#endif
char *args[100];
setup(data, args, 0);
int pipefd[2], length;
if (pipe(pipefd))
perror("Failed to create pipe");
pid_t pid = fork();
char path[MAX];
if (pid == 0) {
// NOTE/BUG: no need to close before dup2
#if 0
close(1); // close the original stdout
#endif
dup2(pipefd[1], 1); // duplicate pipfd[1] to stdout
close(pipefd[0]); // close the readonly side of the pipe
close(pipefd[1]); // close the original write side of the pipe
execvp(args[0], args); // finally execute the command
exit(1);
}
if (pid < 0) {
perror("fork");
exit(1);
}
dbgprt("ls_loop: PARENT\n");
close(pipefd[1]);
while (length = read(pipefd[0], path, MAX - 1)) {
dbgprt("ls_loop: DATAREAD %s\n",dbgstr(path,length));
if (send(new_socket, path, length, 0) != length) {
perror("Failed");
}
memset(path, 0, MAX);
}
close(pipefd[0]);
}
dbgprt("ls_loop: EXIT stop=%d\n",stop);
}
void *
server_socket_ls(void *arg)
{
tid = "ls";
dbgprt("lsmain: ENTER\n");
do {
server_arg *s = (server_arg *) arg;
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
dbgprt("lsmain: SOCKET\n");
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable,
sizeof(int)) < 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(s->portNum);
dbgprt("lsmain: BIND prtNum=%u\n",s->portNum);
if (bind(server_fd, (struct sockaddr *) &address, sizeof(address))
< 0) {
perror("bind failed");
}
dbgprt("lsmain: LISTEN\n");
if (listen(server_fd, 3) < 0) {
perror("listen");
}
while (1) {
if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
(socklen_t *) & addrlen)) < 0) {
perror("accept");
}
dbgprt("lsmain: ACCEPTED\n");
int stop = ls_loop(new_socket);
close(new_socket);
if (stop) {
dbgprt("lsmain: STOP\n");
break;
}
}
} while (0);
dbgprt("lsmain: EXIT\n");
return (void *) 0;
}
void *
server_socket_file(void *arg)
{
tid = "file";
dbgprt("file: ENTER\n");
server_arg1 *s1 = (server_arg1 *) arg;
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int))
< 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(s1->portNum);
dbgprt("file: BIND portNum=%u\n",s1->portNum);
if (bind(server_fd, (struct sockaddr *) &address, sizeof(address)) < 0) {
perror("bind failed");
}
if (listen(server_fd, 3) < 0) {
perror("listen");
}
if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
(socklen_t *) & addrlen)) < 0) {
perror("accept");
}
printf("Server Connected\n");
}
int
main(int argc, char const *argv[])
{
tid = "main";
tsczero = tscgetf();
server_arg *s = (server_arg *) malloc(sizeof(server_arg));
server_arg1 *s1 = (server_arg1 *) malloc(sizeof(server_arg1));
pthread_t id_1;
pthread_t id_2;
pthread_t id_3;
xfdbg = fopen("debug.txt","w");
setlinebuf(xfdbg);
if (pthread_create(&id_3, NULL, terminal_thread, NULL) != 0) {
perror("pthread_create");
}
// NOTE/BUG: this port (or the one below) doesn't match the client code
// port of 5126
s->portNum = 9191;
pthread_create(&id_1, NULL, server_socket_ls, s);
s1->portNum = 6123;
if (0)
pthread_create(&id_2, NULL, server_socket_file, s1);
pthread_join(id_1, NULL);
if (0)
pthread_join(id_2, NULL);
pthread_join(id_3, NULL);
// NOTE/BUG: pthread_exit in main thread is wrong
#if 0
pthread_exit(0);
#else
fclose(xfdbg);
return 0;
#endif
}
更新:
Feedback 2: the program does make terminal thread to reappear, but it doesn't listen anymore. When I tried to send ls command again from remote pc, it just blocks (and debugging shows it is because it gets stuck at blocking receive function). –
Dragut
我试图避免过多的重构,但现在,我添加了更多更改。这个版本几乎是一个完整的重构:
pthread_create
在测试时没问题,但如果服务器在 不同的 系统上,则不够通用。
- 通常,客户端和服务器是独立的程序(例如,我们在不同的window或从
systemd
启动服务器)。
- 服务器通常会创建一个subprocess/subthread来传输请求(下面,我做了一个
fork
但是服务器可以做pthread_create
).
- 此子进程处理
accept
之后的所有内容,因此服务器主进程可以自由循环 accept
并同时拥有多个客户端。
- 因为我们正在使用 stream 套接字(例如 TCP),每一方都需要知道何时 停止 读取。通常是创建一个
struct
,它是要遵循的数据的描述符(例如下面的 xmsg_t
),它具有“类型”和“有效负载长度”。
- sent/received 的有效载荷数据的每一位都以这样的描述符为前缀。
- 换句话说,我们需要一个简单的“协议”
现在,我们需要两个 windows(它们可以在不同的系统上):
- 启动服务器:
./myprogram -s
- 启动客户端:
./myprogram
这是重构后的代码。注释为:
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdarg.h>
#if 1
#include <errno.h>
#include <time.h>
#include <sys/file.h>
#include <sys/wait.h>
#endif
#define MAXBUFF 2048 // max buffer size
#define MAXPENDING 5 // max number of connections (listen)
#define MAXARG 100 // max number of args
#define PORTNO 9191 // default port number
#if 0
#define STOP_SIGNO SIGTERM // stop signal to use
#else
#define STOP_SIGNO SIGHUP // stop signal to use
#endif
#define CLOSEME(_fd) \
do { \
dbgprt("CLOSEME fd=%d (" #_fd ")\n",_fd); \
if (_fd >= 0) \
close(_fd); \
_fd = -1; \
} while (0)
int opt_h; // 1=send HELO message
int opt_s; // 1=doserver, 0=doclient
int opt_n; // 1=run server command in foreground
char ipaddr[100] = { "127.0.0.1" };
unsigned short portno = PORTNO;
pid_t server_pid; // pid of server main process
volatile int server_signo; // signal received by server main
__thread char *tid;
__thread char dbgstrbuf[MAXBUFF + 1];
int dbgfd = -1;
double tsczero = 0.0;
typedef struct {
int xmsg_type;
int xmsg_paylen;
} xmsg_t;
enum {
XMSG_NOP,
XMSG_CMD,
XMSG_DATA,
XMSG_EOF,
};
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tsczero;
return sec;
}
#if _USE_ZPRT_
#ifndef DEBUG
#define DEBUG 1
#endif
#endif
#if DEBUG
#define dbgprt(_fmt...) \
xdbgprt(__FUNCTION__,_fmt)
#else
#define dbgprt(_fmt...) \
do { } while (0)
#endif
void
xdbgprt(const char *fnc,const char *fmt,...)
{
va_list ap;
char msg[MAXBUFF * 4];
char *bp = msg;
int sverr = errno;
bp += sprintf(bp,"[%.9f/%4s] %s: ",tscgetf(),tid,fnc);
va_start(ap,fmt);
bp += vsprintf(bp,fmt,ap);
va_end(ap);
// when doing forks, we have to lock the stream to guarantee atomic,
// non-interspersed messages that are sequential
flock(dbgfd,LOCK_EX);
lseek(dbgfd,0,2);
ssize_t remlen = bp - msg;
ssize_t curlen;
for (bp = msg; remlen > 0; remlen -= curlen, bp += curlen) {
curlen = write(dbgfd,bp,remlen);
if (curlen < 0) {
perror("xdbgprt");
break;
}
}
flock(dbgfd,LOCK_UN);
errno = sverr;
}
const char *
dbgstr(const char *str,int len)
{
char *bp = dbgstrbuf;
if (len < 0)
len = strlen(str);
bp += sprintf(bp,"'");
for (int i = 0; i < len; ++i) {
int chr = str[i];
if ((chr > 0x20) && (chr <= 0x7E))
bp += sprintf(bp,"%c",chr);
else
bp += sprintf(bp,"{%2.2X}",chr);
}
bp += sprintf(bp,"'");
return dbgstrbuf;
}
// tokenize -- convert buffer to tokens
int
tokenize(char **argv,const char *cmdbuf)
{
static char tokbuf[MAXBUFF];
char **av = argv;
strcpy(tokbuf,cmdbuf);
char *token = strtok(tokbuf," ");
while (token != NULL) {
*av++ = token;
token = strtok(NULL," ");
}
*av = NULL;
return (av - argv);
}
// xsend -- send buffer (guaranteed delivery)
ssize_t
xsend(int sock,const void *vp,size_t buflen,int flags)
{
const char *buf = vp;
ssize_t curlen;
ssize_t totlen = 0;
dbgprt("ENTER buflen=%zu flags=%8.8X\n",buflen,flags);
for (; totlen < buflen; totlen += curlen) {
dbgprt("LOOP totlen=%zd\n",totlen);
curlen = send(sock,&buf[totlen],buflen - totlen,flags);
if (curlen <= 0)
break;
}
dbgprt("EXIT totlen=%zd\n",totlen);
return totlen;
}
// xrecv -- receive buffer (guaranteed delivery)
ssize_t
xrecv(int sock,void *vp,size_t buflen,int flags)
{
char *buf = vp;
ssize_t curlen;
ssize_t totlen = 0;
dbgprt("ENTER buflen=%zu flags=%8.8X\n",buflen,flags);
for (; totlen < buflen; totlen += curlen) {
dbgprt("LOOP totlen=%zu\n",totlen);
curlen = recv(sock,&buf[totlen],buflen - totlen,flags);
if (curlen <= 0)
break;
}
dbgprt("EXIT totlen=%zd\n",totlen);
return totlen;
}
// open_remote -- client open connection to server
int
open_remote(const char *ip,unsigned short port)
{
int sock;
struct sockaddr_in echoserver;
dbgprt("ENTER ip=%s port=%u\n",dbgstr(ip,-1),port);
if ((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) {
perror("Failed to create socket");
exit(1);
}
// NOTE/BUG: only server (who does bind) needs to do this
#if 0
int enable = 1;
if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(enable)) < 0) {
perror("error");
}
#endif
memset(&echoserver,0,sizeof(echoserver));
echoserver.sin_family = AF_INET;
echoserver.sin_addr.s_addr = inet_addr(ip);
echoserver.sin_port = htons(port);
if (connect(sock,(struct sockaddr *) &echoserver,sizeof(echoserver)) < 0) {
perror("Failed to connect with server");
exit(1);
}
dbgprt("EXIT sock=%d\n",sock);
return sock;
}
// send_cmd -- client send command to server and process reply
void
send_cmd(int type,const char *cmd,int paylen)
{
int sock;
xmsg_t xmsg;
char buffer[MAXBUFF];
dbgprt("ENTER type=%d\n",type);
// open socket to remote server
sock = open_remote(ipaddr,portno);
// send command descriptor
xmsg.xmsg_type = type;
if (paylen < 0)
paylen = strlen(cmd);
xmsg.xmsg_paylen = paylen;
xsend(sock,&xmsg,sizeof(xmsg),0);
// send command payload
xsend(sock,cmd,xmsg.xmsg_paylen,0);
fprintf(stdout,"Message from server:\n");
int received = 0;
int bytes;
// get all data that the server sends back
while (1) {
dbgprt("LOOP\n");
// get descriptor for next chunk
xrecv(sock,&xmsg,sizeof(xmsg),0);
// handle EOF from server
if (xmsg.xmsg_paylen <= 0)
break;
// get payload
bytes = recv(sock,buffer,xmsg.xmsg_paylen,0);
dbgprt("RCVD bytes=%d\n",bytes);
#if 0
if (bytes == 0)
break;
#endif
/* recv() from server; */
if (bytes < 0) {
perror("Failed to receive bytes from server");
break;
}
received += bytes;
dbgprt("PAYLOAD %s\n",dbgstr(buffer,bytes));
// send payload to terminal
fwrite(buffer,1,bytes,stdout);
}
close(sock);
dbgprt("EXIT\n");
}
void
doclient(void)
{
char cmdbuf[MAXBUFF];
char *argv[MAXARG];
tid = "clnt";
while (1) {
dbgprt("PROMPT\n");
printf(">> ");
fflush(stdout);
dbgprt("FGETS\n");
fgets(cmdbuf,sizeof(cmdbuf),stdin);
cmdbuf[strcspn(cmdbuf,"\n")] = 0;
dbgprt("COMMAND %s\n",dbgstr(cmdbuf,-1));
// tokenize the line
int argc = tokenize(argv,cmdbuf);
if (argc <= 0)
continue;
// set/display remote server IP address
if (strcmp(argv[0],"remote") == 0) {
if (argc >= 2)
strcpy(ipaddr,argv[1]);
if (ipaddr[0] != 0)
printf("REMOTE: %s\n",ipaddr);
continue;
}
// stop server
if (strcmp(argv[0],"stop") == 0) {
if (ipaddr[0] != 0) {
dbgprt("STOP/SERVER\n");
send_cmd(XMSG_CMD,cmdbuf,-1);
}
ipaddr[0] = 0;
continue;
}
// exit client program
if (strcmp(argv[0],"exit") == 0) {
dbgprt("STOP/CLIENT\n");
break;
}
// send command and echo response to terminal
send_cmd(XMSG_CMD,cmdbuf,-1);
}
dbgprt("EXIT\n");
}
// server_cmd -- process command on server
void
server_cmd(int new_socket)
{
xmsg_t xmsg;
char cmdbuf[MAXBUFF];
char *argv[MAXARG];
dbgprt("ENTER new_socket=%d\n",new_socket);
do {
// get command descriptor
xrecv(new_socket,&xmsg,sizeof(xmsg),0);
// get command text
xrecv(new_socket,cmdbuf,xmsg.xmsg_paylen,0);
cmdbuf[xmsg.xmsg_paylen] = 0;
dbgprt("COMMAND %s\n",dbgstr(cmdbuf,-1));
// tokenize the command
int argc = tokenize(argv,cmdbuf);
if (argc <= 0)
break;
// stop the server
if (strcmp(argv[0],"stop") == 0) {
dbgprt("KILL server_pid=%d\n",server_pid);
// FIXME -- we could send a "stopping server" message here
// send EOF to client
xmsg.xmsg_type = XMSG_EOF;
xmsg.xmsg_paylen = 0;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
// signal the server main process to stop (cleanly)
if (opt_s)
server_signo = STOP_SIGNO;
else
kill(server_pid,STOP_SIGNO);
break;
}
int pipefd[2];
int length;
if (pipe(pipefd))
perror("Failed to create pipe");
pid_t pid = fork();
dbgprt("FORK pid=%d\n",pid);
// invoke the target program (under a pipe)
if (pid == 0) {
tid = "exec";
dbgprt("DUP2\n");
fflush(stdout);
int err = dup2(pipefd[1],1); // duplicate pipefd[1] to stdout
if (err < 0)
perror("dup2");
CLOSEME(pipefd[0]); // close the readonly side of the pipe
CLOSEME(pipefd[1]); // close the write side of the pipe
dbgprt("EXECVP\n");
CLOSEME(dbgfd);
if (opt_h) {
int len = sprintf(cmdbuf,"HELO\n");
write(1,cmdbuf,len);
}
execvp(argv[0],argv); // finally execute the command
perror("execvp");
exit(1);
}
// fork error
if (pid < 0) {
perror("fork");
exit(1);
}
dbgprt("PARENT\n");
CLOSEME(pipefd[1]);
// grab all output from the target program and send in packets to
// client
while (1) {
dbgprt("READBEG\n");
length = read(pipefd[0],cmdbuf,sizeof(cmdbuf));
dbgprt("READEND length=%d\n",length);
if (length < 0) {
perror("readpipe");
break;
}
if (length == 0)
break;
dbgprt("READBUF %s\n",dbgstr(cmdbuf,length));
// send descriptor for this chunk
xmsg.xmsg_type = XMSG_DATA;
xmsg.xmsg_paylen = length;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
// send the payload
if (xsend(new_socket,cmdbuf,length,0) != length) {
perror("Failed");
}
}
CLOSEME(pipefd[0]);
// tell client we have no more data
xmsg.xmsg_paylen = 0;
xmsg.xmsg_type = XMSG_EOF;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
} while (0);
CLOSEME(new_socket);
dbgprt("EXIT\n");
}
void
sighdr(int signo)
{
server_signo = signo;
}
void
doserver(void)
{
int server_fd,new_socket;
struct sockaddr_in address;
pid_t pid;
tid = "serv";
dbgprt("ENTER\n");
server_pid = getpid();
#if 0
signal(STOP_SIGNO,(void *) sighdr);
#else
struct sigaction act;
sigaction(STOP_SIGNO,NULL,&act);
act.sa_sigaction = (void *) sighdr;
sigaction(STOP_SIGNO,&act,NULL);
sigset_t set;
sigemptyset(&set);
sigaddset(&set,STOP_SIGNO);
sigprocmask(SIG_UNBLOCK,&set,NULL);
#endif
#if 0
int addrlen = sizeof(address);
#else
socklen_t addrlen = sizeof(address);
#endif
dbgprt("SOCKET\n");
// Creating socket file descriptor
if ((server_fd = socket(AF_INET,SOCK_STREAM,0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(int)) < 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(portno);
dbgprt("BIND portno=%u\n",portno);
if (bind(server_fd,(struct sockaddr *) &address,sizeof(address)) < 0) {
perror("bind failed");
}
dbgprt("LISTEN\n");
if (listen(server_fd,MAXPENDING) < 0) {
perror("listen");
}
int pending = 0;
int status;
while (1) {
dbgprt("LOOP\n");
// reap all finished children
while (1) {
pid = waitpid(-1,&status,WNOHANG);
if (pid <= 0)
break;
dbgprt("REAP pid=%d pending=%d\n",pid,pending);
--pending;
}
// one of the children was given a stop command and it signaled us
if (server_signo) {
dbgprt("SIGNO server_signo=%d\n",server_signo);
break;
}
// wait for new connection from a client
// FIXME -- sending us a signal to stop cleanly is _broken_ because
// we do _not_ get an early return here (e.g. EINTR) -- we may need
// select with timeout
dbgprt("WAITACCEPT\n");
new_socket = accept(server_fd,(struct sockaddr *) &address,
(socklen_t *) &addrlen);
// stop cleanly
if (server_signo) {
dbgprt("SIGNO server_signo=%d\n",server_signo);
break;
}
if (new_socket < 0) {
if (errno == EINTR)
break;
perror("accept");
}
dbgprt("ACCEPTED\n");
// do command execution in main process (i.e. debug)
if (opt_n) {
server_cmd(new_socket);
continue;
}
pid = fork();
if (pid < 0) {
CLOSEME(new_socket);
continue;
}
// process the command in the child
if (pid == 0) {
server_cmd(new_socket);
exit(0);
}
++pending;
dbgprt("CHILD pid=%d\n",pid);
// server main doesn't need this after fork
#if 1
CLOSEME(new_socket);
#endif
}
// reap all children
while (pending > 0) {
pid = waitpid(-1,&status,0);
if (pid <= 0)
break;
dbgprt("REAP pid=%d pending=%d\n",pid,pending);
--pending;
}
dbgprt("EXIT\n");
}
int
main(int argc,char **argv)
{
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'h':
opt_h = ! opt_h;
break;
case 'n': // do _not_ fork server
opt_n = ! opt_n;
break;
case 'p':
portno = (*cp != 0) ? atoi(cp) : PORTNO;
break;
case 's': // invoke server
opt_s = ! opt_s;
break;
}
}
tsczero = tscgetf();
#if DEBUG
int flags = O_WRONLY | O_APPEND;
if (opt_s)
flags |= O_TRUNC | O_CREAT;
dbgfd = open("debug.txt",flags,0644);
if (dbgfd < 0) {
perror("debug.txt");
exit(1);
}
#endif
if (opt_s)
doserver();
else
doclient();
#if DEBUG
if (dbgfd >= 0)
close(dbgfd);
#endif
return 0;
}
以下代码(最后)表示从远程客户端接收 ls 命令并将当前工作目录发送到该客户端的线程函数。
发送成功但有一个问题: 当它完全停止发送时,我希望它重新开始收听。在行:
printf("Enter 1 if you want to exit or 0 if you don't: ");
fgets(exit_status,MAX_SIZE,stdin);
当我按 Enter
时卡住并终止(并启动另一个线程)我不明白为什么?在调试时,我看到上面的打印语句在按 Enter 后执行,尽管调试器处于函数末尾(意味着它通过了此打印语句)。
我想让它在发送完数据后自动重新开始监听。
如果有人想在这里查看我的完整代码是 link:https://pastebin.com/9UmTkPge
void *server_socket_ls(void *arg) {
int* exit_status = (int*)malloc(sizeof(int));
*exit_status = 0;
while (*exit_status == 0) {
//socket code is here
//code for ls
char buffer[BUFFSIZE];
int received = -1;
char data[MAX];
memset(data,0,MAX);
// this will make server wait for another command to run until it receives exit
data[0] = '[=11=]';
if((received = recv(new_socket, buffer,BUFFSIZE,0))<0){
perror("Failed");
}
buffer[received] = '[=11=]';
strcat (data, buffer);
if (strcmp(data, "exit")==0) // this will force the code to exit
exit(0);
puts (data);
char *args[100];
setup(data,args,0);
int pipefd[2],lenght;
if(pipe(pipefd))
perror("Failed to create pipe");
pid_t pid = fork();
char path[MAX];
if(pid==0)
{
close(1); // close the original stdout
dup2(pipefd[1],1); // duplicate pipfd[1] to stdout
close(pipefd[0]); // close the readonly side of the pipe
close(pipefd[1]); // close the original write side of the pipe
execvp(args[0],args); // finally execute the command
}
else
if(pid>0)
{
close(pipefd[1]);
memset(path,0,MAX);
while(lenght=read(pipefd[0],path,MAX-1)){
printf("Data read so far %s\n", path);
if(send(new_socket,path,strlen(path),0) != strlen(path) ){
perror("Failed");
}
//fflush(NULL);
printf("Data sent so far %s\n", path);
memset(path,0,MAX);
}
close(pipefd[0]);
//removed so server will not terminate
}
else
{
printf("Error !\n");
exit(0);
}
printf("Enter 1 if you want to exit or 0 if you don't: ");
fgets(exit_status,MAX_SIZE,stdin);
}
}
有许多个错误:
- 在
terminal_thread
中,input_command
在每次循环迭代时分配——内存泄漏 - 去除换行符的代码已损坏
- 使用
.l
, 不 指定 IP 地址会导致段错误,因为token
是NULL
terminal_thread
中.l
的端口号是5126,不匹配相应服务器代码 中的9191
- 连接后,
server_socket_file
不做任何事情。 - 在
server_socket_ls
中,它 在socket
、bind
、listen
和accept
上循环 。循环应该在listen
之后 开始(即只在循环中执行accept
并且 重用 监听套接字)。 - 代码中标记的其他错误
我不得不重构代码并添加一些调试。它用错误注释。我使用 cpp
条件来表示旧代码与新代码:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
这是代码。我得到了 minimal .l
(远程 ls)工作:
编辑: 由于下面的更新 运行 超出了 SO space 限制,我删除了我在此处发布的第一个代码块。
这是 debug.txt
输出:
term term: PROMPT
term term: FGETS
ls ls: ENTER
ls ls: SOCKET
file file: ENTER
ls ls: BIND prtNum=9191
file file: BIND portNum=6123
ls ls: LISTEN
term term: COMMAND '.l'
term term: port=9191
ls ls: ACCEPTED
term term: PROMPT
This program is exiting as soon as its stops sending data at exit(0) and so doesn't ask for exit_status. Is there a way somehow to make it not stop and instead the terminal prompt reappears along with servers listening at the back? – Dragut
因为我感觉到了紧迫性,所以我犯了错误,现在部分解决方案比为时已晚的完美解决方案更好。
我可能在 ls 服务器父进程(现已修复)中引入了一个带有无关 exit
调用的错误。
但是,还有其他问题...
主要问题是服务器(对于ls)提示用户是否继续(在stdout/stdin
).这不太管用。
应该提示用户的是客户端(即terminal_thread
)。或者,正如我所做的那样,客户端将在命令提示符处看到 exit
,然后 发送 一个带有“exit”的数据包到服务器,然后终止。然后,服务器将看到此命令并终止。
我在没有完全重做所有内容的情况下尽可能多地进行了重构。
我将一些代码拆分成函数。一些 can/could 被重用来实现“文件”服务器。
但是,我会将两个 函数放入单个服务器线程中。我会让服务器查看它获取的“命令”,并根据命令执行任一操作。由于在“文件”服务器中没有实际执行某些操作的代码[还],因此很难返工。
有一件事要解决[我没有时间]:
.l
命令的格式为:.l [ip_address]
。 ip_address
的默认值为 127.0.0.1
。但是,这应该是 split 成两个命令(例如):
- 附加 [ip_address]
- ls [ls 参数]
无论如何,这是更新后的代码。我不得不移动得有点快,所以它没有我想要的那么干净。
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdarg.h>
#if 1
#include <time.h>
#endif
#define BACKLOG 10
#define MAX_SIZE 200
#define BACKLOG 10
#define BUFFSIZE 2048
#define MAXPENDING 5
#define MAX 2048
__thread char *tid;
__thread char dbgstrbuf[1000];
FILE *xfdbg;
double tsczero = 0.0;
typedef struct server_arg {
int portNum;
} server_arg;
typedef struct server_arg1 {
int portNum;
} server_arg1;
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tsczero;
return sec;
}
void
dbgprt(const char *fmt,...)
{
va_list ap;
char msg[1000];
char *bp = msg;
bp += sprintf(bp,"[%.9f/%4s] ",tscgetf(),tid);
va_start(ap,fmt);
bp += vsprintf(bp,fmt,ap);
va_end(ap);
fputs(msg,xfdbg);
}
const char *
dbgstr(const char *str,int len)
{
char *bp = dbgstrbuf;
if (len < 0)
len = strlen(str);
bp += sprintf(bp,"'");
for (int i = 0; i < len; ++i) {
int chr = str[i];
if ((chr > 0x20) && (chr <= 0x7E))
bp += sprintf(bp,"%c",chr);
else
bp += sprintf(bp,"{%2.2X}",chr);
}
bp += sprintf(bp,"'");
return dbgstrbuf;
}
void
setup(char inputBuffer[], char *args[], int *background)
{
const char s[4] = " \t\n";
char *token;
token = strtok(inputBuffer, s);
int i = 0;
while (token != NULL) {
args[i] = token;
i++;
// printf("%s\n", token);
token = strtok(NULL, s);
}
args[i] = NULL;
}
int
open_remote(const char *ip,unsigned short port)
{
int sock;
struct sockaddr_in echoserver;
dbgprt("open_remote: ENTER ip=%s port=%u\n",dbgstr(ip,-1),port);
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("Failed to create socket");
exit(1);
}
int enable = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable,
sizeof(int)) < 0) {
perror("error");
}
memset(&echoserver, 0, sizeof(echoserver));
echoserver.sin_family = AF_INET;
echoserver.sin_addr.s_addr = inet_addr(ip);
// NOTE/BUG: this port number does _not_ match any server port
#if 0
echoserver.sin_port = htons(5126);
#else
dbgprt("term: port=%u\n",port);
echoserver.sin_port = htons(port);
#endif
if (connect(sock, (struct sockaddr *) &echoserver,
sizeof(echoserver)) < 0) {
perror("Failed to connect with server");
exit(1);
}
dbgprt("open_remote: EXIT sock=%d\n",sock);
return sock;
}
void *
terminal_thread(void *arg)
{
// NOTE/FIX: do this _once_
#if 1
char *input_command = malloc(MAX_SIZE);
#endif
tid = "term";
char buffer[BUFFSIZE];
int sock_ls = -1;
while (1) {
dbgprt("term: PROMPT\n");
printf(">> ");
//memset(input_command,0,strlen(str));
// NOTE/BUG: this is a memory leak
#if 0
char *input_command = malloc(MAX_SIZE);
#endif
dbgprt("term: FGETS\n");
fgets(input_command, MAX_SIZE, stdin);
// NOTE/BUG: code is broken to strip newline
#if 0
if ((strlen(input_command) > 0) &&
(input_command[strlen(input_command) - 1] == '\n'))
input_command[strlen(input_command) - 1] = '[=12=]';
#else
input_command[strcspn(input_command,"\n")] = 0;
#endif
dbgprt("term: COMMAND %s\n",dbgstr(input_command,-1));
char list[] = "ls";
char cp[] = "cp";
#if 0
char s[100];
printf("%s\n", getcwd(s,100));
chdir("Desktop");
printf("%s\n", getcwd(s,100));
#endif
// exit program (and exit server)
if (strcmp(input_command,"exit") == 0) {
if (sock_ls >= 0) {
dbgprt("term: SENDEXIT\n");
if (send(sock_ls,"exit",4,0) < 0) {
perror("send/exit");
exit(1);
}
break;
}
}
if (strcmp(input_command, list) == 0) {
// ls code will run here
}
if ((input_command[0] == '.') && (input_command[1] == 'l')) {
printf("remote ls\n");
char ip[20];
const char c[2] = " ";
// strcpy(str,input_command);
char *token;
// get the first token
token = strtok(input_command, c);
// walk through other tokens
int i = 0;
while (token != NULL && i != -1) {
token = strtok(NULL, c);
i--;
}
#if 1
if (token == NULL) {
token = "127.0.0.1";
printf("no IP address found -- using %s\n",token);
}
#endif
if (sock_ls < 0)
sock_ls = open_remote(token,9191);
char s[100];
strcpy(s, "ls");
// NOTE/BUG: this blows away the "s" in "ls" because s is _set_ with strcpy
#if 0
s[strlen(s) - 1] = '[=12=]'; // fgets doesn't automatically discard '\n'
#endif
unsigned int echolen;
echolen = strlen(s);
int received = 0;
/* send() from client; */
if (send(sock_ls, s, echolen, 0) != echolen) {
perror("Mismatch in number of sent bytes");
}
fprintf(stdout, "Message from server: ");
int bytes = 0;
/* recv() from server; */
if ((bytes = recv(sock_ls, buffer, echolen, 0)) < 1) {
perror("Failed to receive bytes from server");
}
received += bytes;
buffer[bytes] = '[=12=]';
/* Assure null terminated string */
fprintf(stdout, buffer);
bytes = 0;
// this d {...} while block will receive the buffer sent by server
do {
buffer[bytes] = '[=12=]';
printf("%s\n", buffer);
} while ((bytes = recv(sock_ls, buffer, BUFFSIZE - 1, 0)) >= BUFFSIZE - 1);
buffer[bytes] = '[=12=]';
printf("%s\n", buffer);
printf("\n");
continue;
}
}
dbgprt("term: EXIT\n");
return (void *) 0;
}
int
ls_loop(int new_socket)
{
dbgprt("ls_loop: ENTER new_socket=%d\n",new_socket);
//code for ls
char buffer[BUFFSIZE];
int received = -1;
char data[MAX];
int stop = 0;
while (1) {
memset(data, 0, MAX);
// this will make server wait for another command to run until it
// receives exit
data[0] = '[=12=]';
if ((received = recv(new_socket, buffer, BUFFSIZE, 0)) < 0) {
perror("Failed");
}
buffer[received] = '[=12=]';
strcpy(data, buffer);
dbgprt("ls_loop: COMMAND %s\n",dbgstr(data,-1));
// this will force the code to exit
#if 0
if (strcmp(data, "exit") == 0)
exit(0);
puts(data);
#else
if (strncmp(data, "exit", 4) == 0) {
dbgprt("ls_loop: EXIT/COMMAND\n");
stop = 1;
break;
}
#endif
char *args[100];
setup(data, args, 0);
int pipefd[2], length;
if (pipe(pipefd))
perror("Failed to create pipe");
pid_t pid = fork();
char path[MAX];
if (pid == 0) {
// NOTE/BUG: no need to close before dup2
#if 0
close(1); // close the original stdout
#endif
dup2(pipefd[1], 1); // duplicate pipfd[1] to stdout
close(pipefd[0]); // close the readonly side of the pipe
close(pipefd[1]); // close the original write side of the pipe
execvp(args[0], args); // finally execute the command
exit(1);
}
if (pid < 0) {
perror("fork");
exit(1);
}
dbgprt("ls_loop: PARENT\n");
close(pipefd[1]);
while (length = read(pipefd[0], path, MAX - 1)) {
dbgprt("ls_loop: DATAREAD %s\n",dbgstr(path,length));
if (send(new_socket, path, length, 0) != length) {
perror("Failed");
}
memset(path, 0, MAX);
}
close(pipefd[0]);
}
dbgprt("ls_loop: EXIT stop=%d\n",stop);
}
void *
server_socket_ls(void *arg)
{
tid = "ls";
dbgprt("lsmain: ENTER\n");
do {
server_arg *s = (server_arg *) arg;
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
dbgprt("lsmain: SOCKET\n");
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable,
sizeof(int)) < 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(s->portNum);
dbgprt("lsmain: BIND prtNum=%u\n",s->portNum);
if (bind(server_fd, (struct sockaddr *) &address, sizeof(address))
< 0) {
perror("bind failed");
}
dbgprt("lsmain: LISTEN\n");
if (listen(server_fd, 3) < 0) {
perror("listen");
}
while (1) {
if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
(socklen_t *) & addrlen)) < 0) {
perror("accept");
}
dbgprt("lsmain: ACCEPTED\n");
int stop = ls_loop(new_socket);
close(new_socket);
if (stop) {
dbgprt("lsmain: STOP\n");
break;
}
}
} while (0);
dbgprt("lsmain: EXIT\n");
return (void *) 0;
}
void *
server_socket_file(void *arg)
{
tid = "file";
dbgprt("file: ENTER\n");
server_arg1 *s1 = (server_arg1 *) arg;
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int))
< 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(s1->portNum);
dbgprt("file: BIND portNum=%u\n",s1->portNum);
if (bind(server_fd, (struct sockaddr *) &address, sizeof(address)) < 0) {
perror("bind failed");
}
if (listen(server_fd, 3) < 0) {
perror("listen");
}
if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
(socklen_t *) & addrlen)) < 0) {
perror("accept");
}
printf("Server Connected\n");
}
int
main(int argc, char const *argv[])
{
tid = "main";
tsczero = tscgetf();
server_arg *s = (server_arg *) malloc(sizeof(server_arg));
server_arg1 *s1 = (server_arg1 *) malloc(sizeof(server_arg1));
pthread_t id_1;
pthread_t id_2;
pthread_t id_3;
xfdbg = fopen("debug.txt","w");
setlinebuf(xfdbg);
if (pthread_create(&id_3, NULL, terminal_thread, NULL) != 0) {
perror("pthread_create");
}
// NOTE/BUG: this port (or the one below) doesn't match the client code
// port of 5126
s->portNum = 9191;
pthread_create(&id_1, NULL, server_socket_ls, s);
s1->portNum = 6123;
if (0)
pthread_create(&id_2, NULL, server_socket_file, s1);
pthread_join(id_1, NULL);
if (0)
pthread_join(id_2, NULL);
pthread_join(id_3, NULL);
// NOTE/BUG: pthread_exit in main thread is wrong
#if 0
pthread_exit(0);
#else
fclose(xfdbg);
return 0;
#endif
}
更新:
Feedback 2: the program does make terminal thread to reappear, but it doesn't listen anymore. When I tried to send ls command again from remote pc, it just blocks (and debugging shows it is because it gets stuck at blocking receive function). – Dragut
我试图避免过多的重构,但现在,我添加了更多更改。这个版本几乎是一个完整的重构:
pthread_create
在测试时没问题,但如果服务器在 不同的 系统上,则不够通用。- 通常,客户端和服务器是独立的程序(例如,我们在不同的window或从
systemd
启动服务器)。 - 服务器通常会创建一个subprocess/subthread来传输请求(下面,我做了一个
fork
但是服务器可以做pthread_create
). - 此子进程处理
accept
之后的所有内容,因此服务器主进程可以自由循环accept
并同时拥有多个客户端。 - 因为我们正在使用 stream 套接字(例如 TCP),每一方都需要知道何时 停止 读取。通常是创建一个
struct
,它是要遵循的数据的描述符(例如下面的xmsg_t
),它具有“类型”和“有效负载长度”。 - sent/received 的有效载荷数据的每一位都以这样的描述符为前缀。
- 换句话说,我们需要一个简单的“协议”
现在,我们需要两个 windows(它们可以在不同的系统上):
- 启动服务器:
./myprogram -s
- 启动客户端:
./myprogram
这是重构后的代码。注释为:
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdarg.h>
#if 1
#include <errno.h>
#include <time.h>
#include <sys/file.h>
#include <sys/wait.h>
#endif
#define MAXBUFF 2048 // max buffer size
#define MAXPENDING 5 // max number of connections (listen)
#define MAXARG 100 // max number of args
#define PORTNO 9191 // default port number
#if 0
#define STOP_SIGNO SIGTERM // stop signal to use
#else
#define STOP_SIGNO SIGHUP // stop signal to use
#endif
#define CLOSEME(_fd) \
do { \
dbgprt("CLOSEME fd=%d (" #_fd ")\n",_fd); \
if (_fd >= 0) \
close(_fd); \
_fd = -1; \
} while (0)
int opt_h; // 1=send HELO message
int opt_s; // 1=doserver, 0=doclient
int opt_n; // 1=run server command in foreground
char ipaddr[100] = { "127.0.0.1" };
unsigned short portno = PORTNO;
pid_t server_pid; // pid of server main process
volatile int server_signo; // signal received by server main
__thread char *tid;
__thread char dbgstrbuf[MAXBUFF + 1];
int dbgfd = -1;
double tsczero = 0.0;
typedef struct {
int xmsg_type;
int xmsg_paylen;
} xmsg_t;
enum {
XMSG_NOP,
XMSG_CMD,
XMSG_DATA,
XMSG_EOF,
};
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tsczero;
return sec;
}
#if _USE_ZPRT_
#ifndef DEBUG
#define DEBUG 1
#endif
#endif
#if DEBUG
#define dbgprt(_fmt...) \
xdbgprt(__FUNCTION__,_fmt)
#else
#define dbgprt(_fmt...) \
do { } while (0)
#endif
void
xdbgprt(const char *fnc,const char *fmt,...)
{
va_list ap;
char msg[MAXBUFF * 4];
char *bp = msg;
int sverr = errno;
bp += sprintf(bp,"[%.9f/%4s] %s: ",tscgetf(),tid,fnc);
va_start(ap,fmt);
bp += vsprintf(bp,fmt,ap);
va_end(ap);
// when doing forks, we have to lock the stream to guarantee atomic,
// non-interspersed messages that are sequential
flock(dbgfd,LOCK_EX);
lseek(dbgfd,0,2);
ssize_t remlen = bp - msg;
ssize_t curlen;
for (bp = msg; remlen > 0; remlen -= curlen, bp += curlen) {
curlen = write(dbgfd,bp,remlen);
if (curlen < 0) {
perror("xdbgprt");
break;
}
}
flock(dbgfd,LOCK_UN);
errno = sverr;
}
const char *
dbgstr(const char *str,int len)
{
char *bp = dbgstrbuf;
if (len < 0)
len = strlen(str);
bp += sprintf(bp,"'");
for (int i = 0; i < len; ++i) {
int chr = str[i];
if ((chr > 0x20) && (chr <= 0x7E))
bp += sprintf(bp,"%c",chr);
else
bp += sprintf(bp,"{%2.2X}",chr);
}
bp += sprintf(bp,"'");
return dbgstrbuf;
}
// tokenize -- convert buffer to tokens
int
tokenize(char **argv,const char *cmdbuf)
{
static char tokbuf[MAXBUFF];
char **av = argv;
strcpy(tokbuf,cmdbuf);
char *token = strtok(tokbuf," ");
while (token != NULL) {
*av++ = token;
token = strtok(NULL," ");
}
*av = NULL;
return (av - argv);
}
// xsend -- send buffer (guaranteed delivery)
ssize_t
xsend(int sock,const void *vp,size_t buflen,int flags)
{
const char *buf = vp;
ssize_t curlen;
ssize_t totlen = 0;
dbgprt("ENTER buflen=%zu flags=%8.8X\n",buflen,flags);
for (; totlen < buflen; totlen += curlen) {
dbgprt("LOOP totlen=%zd\n",totlen);
curlen = send(sock,&buf[totlen],buflen - totlen,flags);
if (curlen <= 0)
break;
}
dbgprt("EXIT totlen=%zd\n",totlen);
return totlen;
}
// xrecv -- receive buffer (guaranteed delivery)
ssize_t
xrecv(int sock,void *vp,size_t buflen,int flags)
{
char *buf = vp;
ssize_t curlen;
ssize_t totlen = 0;
dbgprt("ENTER buflen=%zu flags=%8.8X\n",buflen,flags);
for (; totlen < buflen; totlen += curlen) {
dbgprt("LOOP totlen=%zu\n",totlen);
curlen = recv(sock,&buf[totlen],buflen - totlen,flags);
if (curlen <= 0)
break;
}
dbgprt("EXIT totlen=%zd\n",totlen);
return totlen;
}
// open_remote -- client open connection to server
int
open_remote(const char *ip,unsigned short port)
{
int sock;
struct sockaddr_in echoserver;
dbgprt("ENTER ip=%s port=%u\n",dbgstr(ip,-1),port);
if ((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) {
perror("Failed to create socket");
exit(1);
}
// NOTE/BUG: only server (who does bind) needs to do this
#if 0
int enable = 1;
if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(enable)) < 0) {
perror("error");
}
#endif
memset(&echoserver,0,sizeof(echoserver));
echoserver.sin_family = AF_INET;
echoserver.sin_addr.s_addr = inet_addr(ip);
echoserver.sin_port = htons(port);
if (connect(sock,(struct sockaddr *) &echoserver,sizeof(echoserver)) < 0) {
perror("Failed to connect with server");
exit(1);
}
dbgprt("EXIT sock=%d\n",sock);
return sock;
}
// send_cmd -- client send command to server and process reply
void
send_cmd(int type,const char *cmd,int paylen)
{
int sock;
xmsg_t xmsg;
char buffer[MAXBUFF];
dbgprt("ENTER type=%d\n",type);
// open socket to remote server
sock = open_remote(ipaddr,portno);
// send command descriptor
xmsg.xmsg_type = type;
if (paylen < 0)
paylen = strlen(cmd);
xmsg.xmsg_paylen = paylen;
xsend(sock,&xmsg,sizeof(xmsg),0);
// send command payload
xsend(sock,cmd,xmsg.xmsg_paylen,0);
fprintf(stdout,"Message from server:\n");
int received = 0;
int bytes;
// get all data that the server sends back
while (1) {
dbgprt("LOOP\n");
// get descriptor for next chunk
xrecv(sock,&xmsg,sizeof(xmsg),0);
// handle EOF from server
if (xmsg.xmsg_paylen <= 0)
break;
// get payload
bytes = recv(sock,buffer,xmsg.xmsg_paylen,0);
dbgprt("RCVD bytes=%d\n",bytes);
#if 0
if (bytes == 0)
break;
#endif
/* recv() from server; */
if (bytes < 0) {
perror("Failed to receive bytes from server");
break;
}
received += bytes;
dbgprt("PAYLOAD %s\n",dbgstr(buffer,bytes));
// send payload to terminal
fwrite(buffer,1,bytes,stdout);
}
close(sock);
dbgprt("EXIT\n");
}
void
doclient(void)
{
char cmdbuf[MAXBUFF];
char *argv[MAXARG];
tid = "clnt";
while (1) {
dbgprt("PROMPT\n");
printf(">> ");
fflush(stdout);
dbgprt("FGETS\n");
fgets(cmdbuf,sizeof(cmdbuf),stdin);
cmdbuf[strcspn(cmdbuf,"\n")] = 0;
dbgprt("COMMAND %s\n",dbgstr(cmdbuf,-1));
// tokenize the line
int argc = tokenize(argv,cmdbuf);
if (argc <= 0)
continue;
// set/display remote server IP address
if (strcmp(argv[0],"remote") == 0) {
if (argc >= 2)
strcpy(ipaddr,argv[1]);
if (ipaddr[0] != 0)
printf("REMOTE: %s\n",ipaddr);
continue;
}
// stop server
if (strcmp(argv[0],"stop") == 0) {
if (ipaddr[0] != 0) {
dbgprt("STOP/SERVER\n");
send_cmd(XMSG_CMD,cmdbuf,-1);
}
ipaddr[0] = 0;
continue;
}
// exit client program
if (strcmp(argv[0],"exit") == 0) {
dbgprt("STOP/CLIENT\n");
break;
}
// send command and echo response to terminal
send_cmd(XMSG_CMD,cmdbuf,-1);
}
dbgprt("EXIT\n");
}
// server_cmd -- process command on server
void
server_cmd(int new_socket)
{
xmsg_t xmsg;
char cmdbuf[MAXBUFF];
char *argv[MAXARG];
dbgprt("ENTER new_socket=%d\n",new_socket);
do {
// get command descriptor
xrecv(new_socket,&xmsg,sizeof(xmsg),0);
// get command text
xrecv(new_socket,cmdbuf,xmsg.xmsg_paylen,0);
cmdbuf[xmsg.xmsg_paylen] = 0;
dbgprt("COMMAND %s\n",dbgstr(cmdbuf,-1));
// tokenize the command
int argc = tokenize(argv,cmdbuf);
if (argc <= 0)
break;
// stop the server
if (strcmp(argv[0],"stop") == 0) {
dbgprt("KILL server_pid=%d\n",server_pid);
// FIXME -- we could send a "stopping server" message here
// send EOF to client
xmsg.xmsg_type = XMSG_EOF;
xmsg.xmsg_paylen = 0;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
// signal the server main process to stop (cleanly)
if (opt_s)
server_signo = STOP_SIGNO;
else
kill(server_pid,STOP_SIGNO);
break;
}
int pipefd[2];
int length;
if (pipe(pipefd))
perror("Failed to create pipe");
pid_t pid = fork();
dbgprt("FORK pid=%d\n",pid);
// invoke the target program (under a pipe)
if (pid == 0) {
tid = "exec";
dbgprt("DUP2\n");
fflush(stdout);
int err = dup2(pipefd[1],1); // duplicate pipefd[1] to stdout
if (err < 0)
perror("dup2");
CLOSEME(pipefd[0]); // close the readonly side of the pipe
CLOSEME(pipefd[1]); // close the write side of the pipe
dbgprt("EXECVP\n");
CLOSEME(dbgfd);
if (opt_h) {
int len = sprintf(cmdbuf,"HELO\n");
write(1,cmdbuf,len);
}
execvp(argv[0],argv); // finally execute the command
perror("execvp");
exit(1);
}
// fork error
if (pid < 0) {
perror("fork");
exit(1);
}
dbgprt("PARENT\n");
CLOSEME(pipefd[1]);
// grab all output from the target program and send in packets to
// client
while (1) {
dbgprt("READBEG\n");
length = read(pipefd[0],cmdbuf,sizeof(cmdbuf));
dbgprt("READEND length=%d\n",length);
if (length < 0) {
perror("readpipe");
break;
}
if (length == 0)
break;
dbgprt("READBUF %s\n",dbgstr(cmdbuf,length));
// send descriptor for this chunk
xmsg.xmsg_type = XMSG_DATA;
xmsg.xmsg_paylen = length;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
// send the payload
if (xsend(new_socket,cmdbuf,length,0) != length) {
perror("Failed");
}
}
CLOSEME(pipefd[0]);
// tell client we have no more data
xmsg.xmsg_paylen = 0;
xmsg.xmsg_type = XMSG_EOF;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
} while (0);
CLOSEME(new_socket);
dbgprt("EXIT\n");
}
void
sighdr(int signo)
{
server_signo = signo;
}
void
doserver(void)
{
int server_fd,new_socket;
struct sockaddr_in address;
pid_t pid;
tid = "serv";
dbgprt("ENTER\n");
server_pid = getpid();
#if 0
signal(STOP_SIGNO,(void *) sighdr);
#else
struct sigaction act;
sigaction(STOP_SIGNO,NULL,&act);
act.sa_sigaction = (void *) sighdr;
sigaction(STOP_SIGNO,&act,NULL);
sigset_t set;
sigemptyset(&set);
sigaddset(&set,STOP_SIGNO);
sigprocmask(SIG_UNBLOCK,&set,NULL);
#endif
#if 0
int addrlen = sizeof(address);
#else
socklen_t addrlen = sizeof(address);
#endif
dbgprt("SOCKET\n");
// Creating socket file descriptor
if ((server_fd = socket(AF_INET,SOCK_STREAM,0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(int)) < 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(portno);
dbgprt("BIND portno=%u\n",portno);
if (bind(server_fd,(struct sockaddr *) &address,sizeof(address)) < 0) {
perror("bind failed");
}
dbgprt("LISTEN\n");
if (listen(server_fd,MAXPENDING) < 0) {
perror("listen");
}
int pending = 0;
int status;
while (1) {
dbgprt("LOOP\n");
// reap all finished children
while (1) {
pid = waitpid(-1,&status,WNOHANG);
if (pid <= 0)
break;
dbgprt("REAP pid=%d pending=%d\n",pid,pending);
--pending;
}
// one of the children was given a stop command and it signaled us
if (server_signo) {
dbgprt("SIGNO server_signo=%d\n",server_signo);
break;
}
// wait for new connection from a client
// FIXME -- sending us a signal to stop cleanly is _broken_ because
// we do _not_ get an early return here (e.g. EINTR) -- we may need
// select with timeout
dbgprt("WAITACCEPT\n");
new_socket = accept(server_fd,(struct sockaddr *) &address,
(socklen_t *) &addrlen);
// stop cleanly
if (server_signo) {
dbgprt("SIGNO server_signo=%d\n",server_signo);
break;
}
if (new_socket < 0) {
if (errno == EINTR)
break;
perror("accept");
}
dbgprt("ACCEPTED\n");
// do command execution in main process (i.e. debug)
if (opt_n) {
server_cmd(new_socket);
continue;
}
pid = fork();
if (pid < 0) {
CLOSEME(new_socket);
continue;
}
// process the command in the child
if (pid == 0) {
server_cmd(new_socket);
exit(0);
}
++pending;
dbgprt("CHILD pid=%d\n",pid);
// server main doesn't need this after fork
#if 1
CLOSEME(new_socket);
#endif
}
// reap all children
while (pending > 0) {
pid = waitpid(-1,&status,0);
if (pid <= 0)
break;
dbgprt("REAP pid=%d pending=%d\n",pid,pending);
--pending;
}
dbgprt("EXIT\n");
}
int
main(int argc,char **argv)
{
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'h':
opt_h = ! opt_h;
break;
case 'n': // do _not_ fork server
opt_n = ! opt_n;
break;
case 'p':
portno = (*cp != 0) ? atoi(cp) : PORTNO;
break;
case 's': // invoke server
opt_s = ! opt_s;
break;
}
}
tsczero = tscgetf();
#if DEBUG
int flags = O_WRONLY | O_APPEND;
if (opt_s)
flags |= O_TRUNC | O_CREAT;
dbgfd = open("debug.txt",flags,0644);
if (dbgfd < 0) {
perror("debug.txt");
exit(1);
}
#endif
if (opt_s)
doserver();
else
doclient();
#if DEBUG
if (dbgfd >= 0)
close(dbgfd);
#endif
return 0;
}