Linux Linux程序练习十八

时间:2022-03-03 21:10:40
题目:编写一个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;
}