Linux Linux程序练习十八
题目:编写一个TCP服务器和客户端,基于TCP的并发远程shell 要求实现: 1)对于所有收到的客户端消息,作为命令行进行执行, 并且将命令行的输出结果返回给客户端 2)要求使用并发结构 3)实现关键代码 子进程执行命令 numbytes = read(connfd, buf, 128); buf[numbytes] = '\0'; sprintf(cmd, "%s > /tmp/cmd.txt", buf); system(cmd); fp = fopen("/tmp/cmd.txt", "r"); numbytes = fread(cmd, 1, 1024*2, fp); cmd[numbytes] = '\0'; fclose(fp);
核心代码展示
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/select.h> #include <fcntl.h> #include <termios.h> #include <signal.h> #include "commsock.h" #define MAXBUFSIZE 1020 //报文结构 typedef struct _packet { int len; char buf[MAXBUFSIZE]; } Packet; /** * readn - 读取固定大小的字节 * @fd:文件描述符 * @buf:接收缓冲区 * @count:指定读取字节数 * 成功返回count,失败返回-1,对等方连接关闭返回<count * */ int readn(int fd, void *buf, int count) { int nread = 0; int lread = count; char *pbuf = (char *) buf; while (lread > 0) { do { nread = read(fd, pbuf, lread); } while (nread == -1 && errno == EINTR); if (nread == -1) return -1; else if (nread == 0) return count - lread; lread -= nread; pbuf += nread; } return count; } /** * writen - 写固定大小字节数 * @fd:文件描述符 * @buf:写入缓冲区 * @count:指定写入字节数 * 成功返回count,失败返回-1 * */ int writen(int fd, void *buf, int count) { int lwrite = count; int nwrite = 0; char *pbuf = (char *) buf; while (lwrite > 0) { do { nwrite = write(fd, pbuf, lwrite); } while (nwrite == -1 && errno == EINTR); if (nwrite == -1) return -1; lwrite -= nwrite; pbuf += nwrite; } return count; } /** * read_timeout - 读超时检测函数,不含读操作 * @fd:文件描述符 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT * */ int read_timeout(int fd, unsigned int wait_seconds) { int ret = 0; if (wait_seconds > 0) { fd_set readfds; FD_ZERO(&readfds); FD_SET(fd, &readfds); struct timeval timeout; timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, &readfds, NULL, NULL, &timeout); } while (ret == -1 && errno == EINTR); //ret==-1 if (ret == 0) { errno = ETIMEDOUT; ret = -1; } else if (ret == 1) { ret = 0; } } return ret; } /** * write_timeout - 写超时检测函数,不含写操作 * @fd:文件描述符 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT * */ int write_timeout(int fd, unsigned int wait_seconds) { int ret = 0; if (wait_seconds > 0) { fd_set writefds; FD_ZERO(&writefds); FD_SET(fd, &writefds); struct timeval timeout; timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, NULL, &writefds, NULL, &timeout); } while (ret == -1 && errno == EINTR); //ret==-1 if (ret == 0) { errno = ETIMEDOUT; ret = -1; } else if (ret == 1) { ret = 0; } } return ret; } /** * activate_nonblock - 设置套接字非阻塞 * @fd:文件描述符 * 成功返回0,失败返回-1 * */ int activate_nonblock(int fd) { int ret = 0; int flags = fcntl(fd, F_GETFL); if (flags == -1) return -1; flags = flags | O_NONBLOCK; ret = fcntl(fd, F_SETFL, flags); //ret==-1 return ret; } /** * deactivate_nonblock - 设置套接字阻塞 * @fd:文件描述符 * 成功返回0,失败返回-1 * */ int deactivate_nonblock(int fd) { int ret = 0; int flags = fcntl(fd, F_GETFL); if (flags == -1) return -1; flags = flags & (~O_NONBLOCK); ret = fcntl(fd, F_SETFL, flags); return ret; } /** * connect_timeout - 带超时的connect(函数内已执行connect) * @fd:文件描述符 * @addr:服务器网络地址结构 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT * */ int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds) { int ret = 0; if (wait_seconds > 0) { if (activate_nonblock(fd) == -1) return -1; } ret = connect(fd, (struct sockaddr *) addr, sizeof(struct sockaddr_in)); if (ret == -1 && errno == EINPROGRESS) { fd_set writefds; FD_ZERO(&writefds); FD_SET(fd, &writefds); struct timeval timeout; timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; int nwrite = select(fd + 1, NULL, &writefds, NULL, &timeout); //nwrite==-1 此时ret==-1 if (nwrite == 0) errno = ETIMEDOUT; else if (nwrite == 1) { int err = 0; socklen_t len = sizeof(err); ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len); if (ret == 0) { if (err != 0) { errno = err; ret = -1; } } } } if (wait_seconds > 0) { if (deactivate_nonblock(fd) == -1) return -1; } return ret; } /** * sock_init - 初始化SOCKET环境 * @connid:连接套接字 * 成功返回0,失败返回错误码 * */ int sock_init(int *connid) { int ret = 0; if (connid == NULL) { ret = SckParamErr; printf("cltsock_init() params not correct !\n"); return ret; } //init ret = socket(AF_INET, SOCK_STREAM, 0); if (ret == -1) { ret = SckBaseErr; perror("socket() err"); return ret; } else { *connid = ret; ret = 0; } return ret; } /** * connect_server - 连接服务器 * @connid:连接套接字 * @port:端口号 * @ipaddr:IP地址 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回错误码 * */ int connect_server(int connid, int port, char *ipaddr, unsigned int wait_seconds) { int ret = 0; if (connid < 0 || port < 0 || port > 65535 || ipaddr == NULL || wait_seconds < 0) { ret = SckParamErr; printf("cltsock_init() params not correct !\n"); return ret; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8080); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); ret = connect_timeout(connid, &addr, wait_seconds); if (ret == -1) { if (errno == ETIMEDOUT) { ret = SckTimeOut; printf("connect_timeout() time out !\n"); return ret; } ret = SckBaseErr; perror("connect_timeout() err"); return ret; } return ret; } /** * send_packet - 发送数据包 * @fd:文件描述符 * @pack:数据包 * @buflen:数据包大小 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回错误码 * */ int send_packet(int fd, Packet *pack, int buflen, unsigned int wait_seconds) { int ret = 0; //可写检测 ret = write_timeout(fd, wait_seconds); if (ret == -1) { if (errno == ETIMEDOUT) { ret = SckTimeOut; printf("write_timeout() time out !\n"); return ret; } ret = SckBaseErr; perror("write_timeout() err"); return ret; } //发送数据 ret = writen(fd, pack, buflen); if (ret != buflen) { ret = SckBaseErr; perror("writen() err"); return ret; }else { ret=0; } return ret; } /** * send_packet - 接收数据包 * @fd:文件描述符 * @pack:数据包 * @buflen:数据包大小 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回错误码 * */ int recv_packet(int fd, Packet *pack, int *buflen, unsigned int wait_seconds) { int ret = 0; //读超时检测 ret = read_timeout(fd, wait_seconds); if (ret == -1) { if (errno == ETIMEDOUT) { ret = SckTimeOut; printf("read_timeout() time out !\n"); return ret; } ret = SckBaseErr; perror("read_timeout() err"); return ret; } //获取数据长度 int len = 0; ret = readn(fd, &pack->len, 4); if (ret == -1) { ret = SckBaseErr; perror("readn() err"); return ret; } else if (ret < 4) { ret = SckPeerClosed; printf("peer is closed !\n"); return ret; } //网络字节序转化成本地字节序 len = ntohl(pack->len); //获取包体 ret = readn(fd, pack->buf, len); if (ret == -1) { ret = SckBaseErr; perror("readn() err"); return ret; } else if (ret < len) { ret = SckPeerClosed; printf("peer is closed !\n"); return ret; } else if (ret == len) ret = 0; *buflen = len; return ret; } /** * product_ser - 处理服务器消息 * @fd:文件描述符 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回错误码 * */ int product_ser(int fd, unsigned int wait_seconds) { int ret = 0; Packet pack; int buflen = 0; while (1) { memset(&pack, 0, sizeof(pack)); ret = recv_packet(fd, &pack, &buflen, wait_seconds); if (ret != 0) { return ret; } //已经完全接收服务器所有数据 if (buflen == 3 && strncmp(pack.buf, "end", 3) == 0) { break; } //printf("数据包长度是%d;%s\n",buflen,pack.buf); fputs(pack.buf, stdout); } return ret; } /** * run_clt - 运行客户端 * @connid:连接套接字 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 失败返回错误码 * */ int run_clt(int connid, unsigned int wait_seconds) { int ret = 0; //安装信号 if (signal(SIGPIPE, handler) == SIG_ERR) { ret = SckBaseErr; printf("signal() failed !\n"); return ret; } Packet pack; memset(&pack, 0, sizeof(pack)); int buflen = 0; write(STDIN_FILENO,"请输入shell命令:",sizeof("请输入shell命令:")); while (fgets(pack.buf, MAXBUFSIZE, stdin) != NULL) { //去除\n buflen = strlen(pack.buf) - 1; pack.len = htonl(buflen); //发送数据 ret = send_packet(connid, &pack, buflen + 4, wait_seconds); if (ret != 0) { return ret; } memset(&pack, 0, sizeof(pack)); //接收服务器数据 ret = product_ser(connid, wait_seconds); if (ret != 0) return ret; write(STDIN_FILENO,"请输入shell命令:",sizeof("请输入shell命令:")); } return ret; } /** * close_socket - 关闭连接 * @fd:文件描述符 * 成功返回0 * */ int close_socket(int fd) { int ret = 0; close(fd); return ret; } /* * clear_back - 退格键不回显 * 成功返回0,失败返回错误码 * */ int clear_back() { int ret = 0; struct termios term; memset(&term, 0, sizeof(term)); //获取当前系统设置 if (tcgetattr(STDIN_FILENO, &term) == -1) { ret = SckBaseErr; perror("tcgetattr() err"); return ret; } //修改系统设置 term.c_cc[VERASE] = '\b'; //立即生效 if (tcsetattr(STDIN_FILENO, TCSANOW, &term) == -1) { ret = SckBaseErr; perror("tcsetattr() err"); return ret; } return ret; } /** * listen_socket - 创建服务器监听套接字 * @fd:套接字 * @port:端口号 * 成功返回0,失败返回错误码 * */ int listen_socket(int fd, int port) { int ret = 0; if (port < 0 || port < 0 || port > 65535) { ret = SckParamErr; printf("listen_socket() params not correct !\n"); return ret; } //reuse addr int optval = 1; ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); if (ret == -1) { ret = SckBaseErr; perror("setsockopt() err"); return ret; } //bind struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); ret = bind(fd, (struct sockaddr *) &addr, sizeof(addr)); if (ret == -1) { ret = SckBaseErr; perror("bind() err"); return ret; } //listen ret = listen(fd, SOMAXCONN); if (ret == -1) { ret = SckBaseErr; perror("listen() err"); return ret; } return ret; } /** * product_clt - 处理客户端信息 * @fd:文件描述符 * 成功返回0,失败返回错误码 * */ int product_clt(int fd) { int ret = 0; //接收客户端信息 Packet pack; memset(&pack, 0, sizeof(pack)); int buflen = 0; ret = recv_packet(fd, &pack, &buflen, 0); if (ret != 0) return ret; //重新拼接shell脚本 char *path = "/home/test/1/cmdres.txt"; char tempbuf[1024] = { 0 }; if (buflen > 1000) { ret = SckBaseErr; printf("用户输入数据过长,服务器无法接收!\n"); return ret; } sprintf(tempbuf, "%s > %s", pack.buf, path); //执行shell脚本 system(tempbuf); //打开文件 FILE *pfr = NULL; pfr = fopen(path, "r"); if (pfr == NULL) { ret = SckBaseErr; perror("fopen() err"); return ret; } memset(&pack, 0, sizeof(pack)); //读文件 while (fgets(pack.buf, MAXBUFSIZE, pfr) != NULL) { //每读取一次,发送一个数据包 buflen = strlen(pack.buf); pack.len = htonl(buflen); ret = send_packet(fd, &pack, buflen + 4, 100); if (ret != 0) { printf("发送数据失败!"); break; } memset(&pack, 0, sizeof(pack)); } //关闭文件流 if (pfr != NULL) { fclose(pfr); pfr = NULL; } //文件读取完毕之后,发送一个0数据包,告诉客户端数据已经发送完毕 memset(&pack, 0, sizeof(pack)); strcpy(pack.buf, "end"); buflen = strlen(pack.buf); pack.len = htonl(buflen); ret = send_packet(fd, &pack, buflen + 4, 100); if(ret!=0) { return ret; } //删除临时文件 system("rm ../1/*"); return ret; } /** * handler - 信号捕捉函数 * @sign:信号值 * */ void handler(int sign) { if (sign == SIGPIPE) { printf("accept SIGPIPE!\n"); } } /** * select_socket - select机制管理客户端连接 * @fd:文件描述符 * 失败返回错误码 * */ int select_socket(int fd) { int ret = 0; //安装信号 if (signal(SIGPIPE, handler) == SIG_ERR) { ret = SckBaseErr; printf("signal() failed !\n"); return ret; } //定义客户端套接字临时变量 int conn = 0; struct sockaddr_in peeraddr; socklen_t peerlen = 0; //已经处理的select事件 int nread = 0; //创建客户端连接池 int cltpool[FD_SETSIZE] = { 0 }; //初始化连接池 int i = 0; for (i = 0; i < FD_SETSIZE; i++) { cltpool[i] = -1; } //定义数组尾部元素下标 int maxindex = 0; //定义最大的套接字(初始值是监听套接字) int maxfd = fd; //定义最新的套接字集合 fd_set allsets; FD_ZERO(&allsets); //定义需要监听的套接字集合 fd_set readfds; FD_ZERO(&readfds); //将监听套接字加入最新的套接字集合 FD_SET(fd, &allsets); while (1) { //将最新的套接字集合赋值给需要监听的套接字集合 readfds = allsets; do { nread = select(maxfd + 1, &readfds, NULL, NULL, NULL); } while (nread == -1 && errno == EINTR); //屏蔽信号 if (nread == -1) { ret = SckBaseErr; perror("select() err"); return ret; } //1.服务器监听套接字处理 if (FD_ISSET(fd, &readfds)) { //接收到客户端的连接 memset(&peeraddr, 0, sizeof(peeraddr)); peerlen = sizeof(peeraddr); conn = accept(fd, (struct sockaddr *) &peeraddr, &peerlen); if (conn == -1) { ret = SckBaseErr; perror("accept() err"); return ret; } //将客户端连接添加到连接池 for (i = 0; i < FD_SETSIZE; i++) { if (cltpool[i] == -1) { if (i > maxindex) { maxindex = i; } cltpool[i] = conn; break; } } if (i == FD_SETSIZE) { ret = SckBaseErr; close(conn); printf("客户端连接池已满!\n"); return ret; } if (conn > maxfd) maxfd = conn; //将该客户端套接字加入到最新套接字集合 FD_SET(conn, &allsets); printf("server accept from :%s\n",inet_ntoa(peeraddr.sin_addr)); if (--nread <= 0) continue; } //处理客户端请求 if (nread <= 0) continue; for (i = 0; i <= maxindex; i++) { if (cltpool[i] == -1) continue; if (FD_ISSET(cltpool[i], &readfds)) { //处理客户端请求 ret = product_clt(cltpool[i]); if (ret != 0) { //从最新的套接字集合中删除 FD_CLR(cltpool[i], &allsets); //处理请求失败,关闭客户端连接 close(cltpool[i]); //从客户端连接池中清除 cltpool[i] = -1; break; } if (--nread <= 0) break; } } } return ret; }