多线程并发远程控制服务器
实验目的
修改远程控制程序服务器模型程序,将其从循环模式或多进程模式修改为多线程模式
实验要求
-
服务器同时能够接收客户端多次连接,向多个客户端提供服务
-
客户输入命令,客户端将命令通过流套接字发送给客户端,服务器执行收到的命令,并将结果发送到客户端显示
-
客户端输入“quit”,客户端程序与服务器端程序打印退出信息,终止客户端程序的执行
服务器端程序设计
- 创建套接字:
socket()
- 绑定主机IP、端口号:将IP协议、地址、端口号封装成一个结构体,作为参数传递给
bind()
- 主循环侦听客户端连接要求
listen()
,并接受连接accept()
- 没有超出最大客户数(我设置的5),则使用pthread库来创建新的线程:
pthread_create()
输入参数是线程标识符、线程属性、线程函数和传给线程函数的参数,这里我传的是客户端套接字 - 新线程根据传递过来的参数进行不同的行为
threadFunc
,调用pthread_exit()
退出 - 线程执行命令时,调用自定义的函数
execute()
,其中再调用popen()
函数获取命令的输出,结果放入send_buf
- 主线程调用
pthread_join()
等待线程结束
实验代码
server.c
/*server*/
/*服务端实现接收执行远程命令并返回结果*/
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<string.h>
#include<netdb.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include <pthread.h>
/*定义端口和缓冲区大小*/
#define PORT 8888
#define MAXSIZE 4096
#define MAX_CLIENTS 10
/*定义执行客户端发来命令的函数*/
int execute(char* cmd,char* buf){//接收参数:命令、存放执行结果缓冲区
FILE *fp;//文件指针
int count;
count = 0;
/*建立管道*/
//popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c 来执行参数cmd的指令。"r"代表读取
if (NULL==(fp = popen(cmd,"r"))){
perror("create pipe error\n");
return -1;
}
/*统计命令长度*/
while(((buf[count] = fgetc(fp))!=EOF)&&count<4095)//EOF:enf of file
count++;
/*添加结束标志*/
buf[count]='\0';
pclose(fp);
return count;
}
/*定义新线程执行的函数*/
void *threadFunc(void* arg){
int recvnum;
char send_buf[MAXSIZE];
char recv_buf[MAXSIZE];
char cmd[MAXSIZE];
int fd=*((int *)arg);
while(1){
/*接收命令*/
if(-1==(recvnum = recv(fd,recv_buf,sizeof(recv_buf),0)))
{
perror("recv error\n");
break;
}
if (recvnum == 0)
{
close(fd);
break;
}
recv_buf[recvnum]='\0';//添加终止符
if (0==strcmp(recv_buf,"quit")){
perror("quit\n");
break;
}
/*服务端打印接收到的命令*/
printf("receive: %s\n",recv_buf);
// 指针指向缓冲区终止符
// 在/bin、/sbin、/usr/bin查找命令
strcpy(cmd,"/bin/");
strcat(cmd,recv_buf);
execute(cmd,send_buf);
if ('\0'==*send_buf)
{
memset(cmd,0,sizeof(cmd));
strcpy(cmd,"/sbin/");
strcat(cmd,recv_buf);
execute(cmd,send_buf);
if ('\0'==*send_buf){
memset(cmd,0,sizeof(cmd));
strcpy(cmd,"/usr/bin/");
strcat(cmd,recv_buf);
execute(cmd,send_buf);
}
if ('\0'==*send_buf){
memset(cmd,0,sizeof(cmd));
strcpy(cmd,"/usr/sbin/");
strcat(cmd,recv_buf);
execute(cmd,send_buf);
}
}
/*命令不存在*/
if ('\0'==*send_buf)
{
sprintf(send_buf,"cmd error\n");
break;
}
printf("reply:\n%s\n",send_buf);
if (-1==send(fd,send_buf,sizeof(send_buf),0))
{
perror("send error\n");
close(fd);
break;
}
}
close(fd);
pthread_exit(NULL);
}
int main(){
/*定义变量*/
int sockfd;
int fd;
struct sockaddr_in client;
struct sockaddr_in server;
char send_buf[MAXSIZE];
char recv_buf[MAXSIZE];
char cmd[MAXSIZE];
int sendnum;
int recvnum;
int len;
int i=0;
/*建立基于TCP的socket*/
if (-1==(sockfd=socket(AF_INET,SOCK_STREAM,0))){
perror("create socket error");
return -1;
}
/*网络字节序转换*/
memset(&server,0,sizeof(struct sockaddr_in));
memset(&client,0,sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);//本地IP
server.sin_port = htons(PORT);
/*绑定本地IP和端口号*/
if (-1==bind(sockfd,(struct sockaddr*)&server,sizeof(struct sockaddr))){
perror("bind error\n");
close(sockfd);
return -1;
}
pthread_t client_threads[MAX_CLIENTS];
int client_count = 0;
while(1)
{
/*调用listen侦听客户端的连接请求*/
if (-1==listen(sockfd,5)){
perror("listen error\n");
return -1;
}
memset(recv_buf,0,MAXSIZE);
memset(send_buf,0,MAXSIZE);
/*循环接受连接accept*/
// 保证client使用ctrl+c退出后server不挂,accept需要移入while
if(0>(fd=accept(sockfd,(struct sockaddr*)&client,&len))){
perror("create connect socket error\n");
continue;
}
if(client_count<MAX_CLIENTS){
//传递给新线程的参数是*fd
if (pthread_create(&client_threads[client_count], NULL, threadFunc, &fd) != 0)
{
perror("pthread_create error\n");
}
client_count++;
}else{
printf("Max clients reached. Connection rejected.\n");
close(fd);
}
}
close(sockfd);
for (i; i < client_count; i++)
{
pthread_join(client_threads[i], NULL);
}
return 0;
}
client.c
/*client*/
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<string.h>
#include<netdb.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#define PORT 8888
#define MAXSIZE 4096
/*输入提示*/
void print_usage(char* str){
fprintf(stderr," %s usage:\n",str);
fprintf(stderr,"%s IP_Addr",str);
}
int main(int argc, char** argv){
/*定义变量*/
int sockfd;
struct sockaddr_in client;
struct sockaddr_in server;
char send_buf[MAXSIZE];
char recv_buf[MAXSIZE];
int sendnum;
int recvnum;
int len;
int input_len;
/*参数检查*/
if(2!=argc){
print_usage(argv[0]);
return -1;
}
/*网络字节序转换*/
memset(&server,0,sizeof(struct sockaddr_in));
memset(&client,0,sizeof(struct sockaddr_in));
/*设置IP地址族结构体*/
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);//键入第二个参数为IP地址
server.sin_port = htons(PORT);//端口号
len = sizeof(struct sockaddr);
while(1){
memset(send_buf,0,MAXSIZE);
memset(recv_buf,0,MAXSIZE);
/*创建基于TCP通信的socket套接字,每次都新建socket,保证server不挂*/
if (-1==(sockfd=socket(AF_INET,SOCK_STREAM,0))){
perror("create socket error\n");
return -1;
}
/*建立连接*/
if (-1==(connect(sockfd,(struct sockaddr*)&server,sizeof(struct sockaddr)))){
perror("connect error");
close(sockfd);
break;
}
printf("TCP>");//打印远程连接符号
/*用户输入命令*/
fgets(send_buf,MAXSIZE,stdin);
input_len=strlen(send_buf);
send_buf[input_len-1]='\0';
/*发送命令至服务端,接收执行结果*/
if (-1==send(sockfd,send_buf,sizeof(send_buf),0)){
perror("send error!\n");
continue;
}
if (0==strcmp(send_buf,"quit")){
perror("quit\n");
break;
}
if (-1==recv(sockfd,recv_buf,sizeof(recv_buf),0)){
perror("recv error!");
continue;
}
/*打印接收到的服务端执行结果*/
fprintf(stdout,"%s\n",recv_buf);
close(sockfd);
}
close(sockfd);
return 0;
}
编译运行
-
环境:ubuntu20.04
-
服务端运行,连接一个客户端,客户端发送多次命令,服务端执行结果回显
-
连接上第二个客户端,第二个客户端发送命令,服务端执行结果回显
-
第一个客户端quit,第二个客户端发送命令,服务端继续执行且结果在第二个客户端回显