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

Linux Linux程序练习十八的更多相关文章

  1. 漫谈程序员(十八)windows中的命令subst

    漫谈程序员(十八)windows中的命令subst 用法格式 一.subst [盘符] [路径]  将指定的路径替代盘符,该路径将作为驱动器使用 二.subst /d 解除替代 三.不加任何参数键入  ...

  2. Linux系列教程(十八)——Linux文件系统管理之文件系统常用命令

    通过前面两篇博客,我们介绍了Linux系统的权限管理.Linux权限管理之ACL权限 介绍了通过设定 ACL 权限,我们为某个用户指定某个文件的特定权限,这在Linux只能对于一个文件只能有所有者权限 ...

  3. Linux学习之CentOS(十八)-----恢复Ext3下被删除的文件与 使用grep恢复被删文件内容(转)

    前言 下面是这个教程将教你如何在Ext3的文件系统中恢复被rm掉的文件. 删除文件 假设我们有一个文件名叫 'test.txt' $ls -il test.txt 15 -rw-rw-r– 2 roo ...

  4. Linux经常使用命令(十八) - find概述

    Linux下find命令在文件夹结构中搜索文件,并运行指定的操作.Linux下find命令提供了相当多的查找条件,功能非常强大.由于find具有强大的功能,所以它的选项也非常多.当中大部分选项都值得我 ...

  5. Linux 入门记录:十八、Linux 系统启动流程 + 单用户修改 root 密码 + GRUB 加密

    一.系统启动流程 一般来说,Linux 系统的启动流程是这样的: 1. 开机之后,位于计算机主板 ROM 芯片上的 BIOS 被最先读取,在进行硬件和内存的校验以及 CPU 的自检没有异常后, BIO ...

  6. Linux学习总结(十八)几个简单的文本处理工具cut sort tr split

    1 命令cut 用来截取某一个字段格式 : cut -d '分隔符' [-cf] n, n为数字,表示第几段-d:后面跟分隔符,分割符要加单引号-c:后面接的是第几个字符-f:后面接的是第几段那么意思 ...

  7. 学习笔记:CentOS7学习之十八:Linux系统启动原理及故障排除

    目录 学习笔记:CentOS7学习之十八:Linux系统启动原理及故障排除 18.1 centos6系统启动过程及相关配置文件 18.1.1 centos6系统启动过程 18.1.2 centos6启 ...

  8. 鸟哥的linux私房菜——第十六章学习(程序管理与 SELinux 初探)

    第十六章.程序管理与 SE Linux 初探 在 Linux 系统当中:"触发任何一个事件时,系统都会将他定义成为一个程序,并且给予这个程序一个 ID ,称为 PID,同时依据启发这个程序的 ...

  9. 最简单的方法是使用标准的 Linux GUI 程序之一: i-nex 收集硬件信息,并且类似于 Windows 下流行的 CPU-Z 的显示。 HardInfo 显示硬件具体信息,甚至包括一组八个的流行的性能基准程序,你可以用它们评估你的系统性能。 KInfoCenter 和 Lshw 也能够显示硬件的详细信息,并且可以从许多软件仓库中获取。

    最简单的方法是使用标准的 Linux GUI 程序之一: i-nex 收集硬件信息,并且类似于 Windows 下流行的 CPU-Z 的显示. HardInfo 显示硬件具体信息,甚至包括一组八个的流 ...

随机推荐

  1. 【工匠大道】Git的使用总结

    初衷是想将一些常用的代码整理在博客园上,但是考虑到博客园上的代码量多,需要折叠,折叠后就不能直接修改,于是想到了 大家都常用的 gitHub来进行代码的管理. 其实之前我是用过 Osa的git的,但是 ...

  2. jQuery获取Table-Input控件值封装

  3. js实现右下角可关闭最小化div

    本实例使用Javascript实现右下角可关闭最小化div,可以用于展示推荐内容,效果预览网址:http://keleyi.com/keleyi/phtml/xuanfudiv/3.htm效果图片: ...

  4. js 阻止事件冒泡和默认行为 preventDefault、stopPropagation、return false

    preventDefault: preventDefault它是事件对象(Event)的一个方法,作用是取消一个目标元素的默认行为.既然是说默认行为,当然是元素必须有默认行为才能被取消,如果元素本身就 ...

  5. Atitit。数据库 安全性 重要敏感数据加密存储解决方案

    Atitit.数据库 安全性 重要敏感数据加密存储解决方案 1.1. 加密存储的重要性1 1.2. 使用的加密算法aes1 1.3. 数据加密以后会有一些问题.1 1.3.1. 一个是统计,比如统计资 ...

  6. iOS之UI--富文本总结

    文章内容大纲 1.NSMutableAttributedString的基本使用 2.NSMutableAttributedString的简易封装 3.使用开源代码GOBMarkupPaser处理富文本 ...

  7. UIToolBar

    //UIToolBar 是导航控制器默认隐藏的工具条 //设置UIToolBar的隐藏状态 self.navigationController.toolbarHidden = NO; //如何找到UI ...

  8. 移动端App广告常见的10种形式

    什么是App广告?   App广告,或称In-App广告,是指智能手机和平板电脑这类移动设备中第三方应用程序内置广告,属于移动广告的子类别. App广告兴起得益于其载体—App的风行.平板电脑和大屏触 ...

  9. iOS 疑难杂症 — — 复制 Storyborad 莫名崩溃

    声明  欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯伯: http://over140.cnblogs.com 正文 复制无处不在,用 Story ...

  10. yii2分页的基本使用及其配置详解

    作者:白狼 出处:http://www.manks.top/yii2_linkpager_pagination.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...