Linux socket编程实例 多进程 文件传输 服务器 网络通信【C语言】


一、实验目的

Linux系统中,使用C语言编程设计一个基于TCP/IP文件传输系统,从而实现网络文件的收发。主要实现为一个面向连接的并发服务器,并由客户端程序请求文件。

二、实验内容

编写网络文件传输服务器和客户端程序,其中客户端能够将文件名称传输给服务器,服务器从客户端接收文件名,并用请求的文件内容应答客户端。同时要求一个服务器能够为多个客户并发提供服务。

  • 要求:
    ①用多进程来实现并发;
    ②为每个用户产生一个服务器进程;
    ③每个文件的传输要求再创建一个新的连接,负责文件内容传送,即每个用户除了一个传输文件名的命令连接,要为每个文件传输新创建一个连接;
    ④要求用信号处理僵尸子进程
    ⑤编写对应的客户端程序,客户端从终端输入名字,并显示回来的结果。

三、实验步骤

1.设计服务器和客户端的编程思路

  • 服务器 从终端读入可用端口号;然后创建套接字并等待连接;若监听到有客户端连接服务器,则创建子进程与客户端进行交互;若没有收到从服务器端发来的消息或者客户端关闭,则关闭子进程;若接收到消息,则创建新的流式套接字,并等待连接;当连接成功后,从系统中寻找文件;从第二个套接字将文件内容或未找到文件的信息传输给客户端
  • 客户端从终端接收IP地址和端口号;然后创建流式套接字;将套接字设置为IPv4、从终端读入的端口号以及IP地址;通过第一个套接字进行服务器连接;客户端若从终端接收到退出请求,则退出并关闭第一个套接字;客户端若每从终端接受到一个文件名,就通过第一个套接字发送一个消息,再依据上述步骤创建并连接新的套接字,并从第二个套接字接收返回的消息(可能是没有找到文件信息或是文件内容)。

2.设计程序测试步骤与预期结果

这一段内容于运行结果重复,在此省略。

四、程序以及运行结果

1.程序代码

  • 服务器
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
#include<signal.h>
#include<netdb.h>

#define PORT 9999

//与客户端进行交互 
void communication(int a){
	char recvMsg[512];
	
	while(1){//一个客户端与服务器多次交互
		memset(recvMsg,0,sizeof(recvMsg));

		printf("-------------------------------------------\n");
		fflush(stdout);
	    
		int n = read(a,recvMsg,512);
	    if(n<=0)
	    {
		    printf("~~~~~~~~~~~~~~~~~~~~One client over!~~~~~~~~~~~~~~~~~~~~\n");
			close(a);//关闭子进程的文件描述符
		    break;
	    }
	    printf("Receive from client:%s\n",recvMsg);
		fflush(stdout);

		int lfd2,cfd2;
		struct sockaddr_in serv_addr2,clin_addr2;
		socklen_t clin_len2;

		//second connect
		lfd2 = socket(AF_INET,SOCK_STREAM,0);
		if(lfd2 == -1){
		    perror("Create socket failed!\n");
		    exit(1);
	    }
	
		memset(&serv_addr2, 0, sizeof(serv_addr2));	//每个字节都用0填充
	    serv_addr2.sin_family = AF_INET;	//使用IPv4地址
	    serv_addr2.sin_addr.s_addr = inet_addr("127.0.0.1");	//具体的IP地址
	    serv_addr2.sin_port = htons(PORT);	//端口
	    
		int on=1;
		setsockopt(lfd2,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int));	//Address可以重复 
		if(bind(lfd2, (struct sockaddr *)&serv_addr2, sizeof(serv_addr2)) == -1){
			perror("Bind error!\n");
			exit(1);
		}
		if(listen(lfd2, 128) == -1){
			perror("Listen error!\n");
			exit(1);
		}
	    clin_len2 = sizeof(clin_addr2);

		cfd2 = accept(lfd2, (struct sockaddr *)&clin_addr2, &clin_len2);
		if(cfd2 == -1){
		    perror("Accept error!\n");
		    exit(1);
	    }
	    
	    FILE *fd = fopen(recvMsg,"r");
	    if(fd == NULL){	//如果没有找到文件 
			send(cfd2, "Can not read!", 13, 0);
			send(cfd2,"\n@\0",3,0);
	    	printf("Can not find file %s!\n",recvMsg);
			fflush(stdout);
		}
		else{	//找到文件,并传输回来 
			char buffer[1024];
			int file_block_length = 0;
			bzero(buffer,1024);
			while((file_block_length=fread(buffer,sizeof(char),1024,fd))>0){
				if(write(cfd2,buffer,file_block_length)<0){
					perror("Fail to send!\n");
					exit(1);
				}
				//printf("%s",buffer);
				bzero(buffer,1024);
			}
			fclose(fd);
			send(cfd2,"@\0",3,0);
			printf("Send successfully!\n");
			fflush(stdout);
		}
		close(lfd2);
		close(cfd2);
	}
}

//通过信号机制处理僵尸子进程 
void signal_wait(int isig)
{
	wait(NULL);
	//perror("child is cleaned\n");
}

int main(int argc,char *argv[])
{
	int lfd, cfd;
	struct sockaddr_in serv_addr,clin_addr;
	socklen_t clin_len;
	pid_t pid;
	
	if(argc != 2){	//如果没有写可以连接的端口号 
		perror("Please input the server port!\n");
		exit(1);
	}
	
	//first connect
	lfd = socket(AF_INET,SOCK_STREAM,0);
	if(lfd == -1){
        perror("Create socket failed!\n");
        exit(1);
    }
	
	memset(&serv_addr, 0, sizeof(serv_addr));	//每个字节都用0填充
    serv_addr.sin_family = AF_INET;	//使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");	//具体的IP地址
    serv_addr.sin_port = htons((unsigned short)atoi(argv[1]));	//端口

	printf("port:%s\n",argv[1]);
	printf("addr:127.0.0.1\n");

	int on=1;
	setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int));
	if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1){
        perror("Bind error!\n");
        exit(1);
    }
	if(listen(lfd, 128) == -1){
        perror("Listen error!\n");
        exit(1);
    }
    clin_len = sizeof(clin_addr);

	signal(SIGCHLD,signal_wait);
 
	while(1)//保证服务器可以与多个客户端交互
	{
		printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		
		cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len);
		if(cfd == -1){
        	perror("Accept error!\n");
            exit(1);
        }

 		pid = fork();	//fork创建一个与原来进程几乎完全相同的进程
		
		if(pid >0){	//父进程中关闭连接的套接字描述符,只是把cfd的引用数减少1,在子进程中还在使用cfd
			close(cfd);
			continue;
		}
		else if(pid == 0){	//子进程关闭lfd处理任务,使其回到TIME_WAIT状态值
            close(lfd);
            communication(cfd);//与客户端交互
			exit(0);
		}
		else{
			perror("Fork error!\n");
            exit(1);
		}
	}
    close(lfd);
    return 0;
}
  • 客户端
#include <stdio.h>
#include <string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netdb.h>

#define PORT 9999

int main(int argc,char *argv[])
{
	int sockfd,sockfile;
	int confd,confile;
	struct hostent *he;	//找到IP地址 
	struct sockaddr_in addr_ser;	//服务器的地址 
	struct sockaddr_in addr_file;	//服务器发来的文件地址 
	char sendMsg[512],recvMsg[1024];
	
	if(argc!=3){	//如果没有写服务器的连接 
		perror("Please input the server port!\n");
		exit(1);
	}
	he=gethostbyname((char *)argv[1]);	//通过域名获取IP地址 
	if(he==NULL){	//如果获取IP地址失败 
		perror("Cannot get host by name!\n");
		exit(1);
	} 
	
	sockfd=socket(AF_INET,SOCK_STREAM,0);	//创建socket套接字
	if(sockfd==-1){	//如果创建套接字失败 
		perror("Create socketfd failed!\n");
		exit(1);
	}
		
	memset(&addr_ser,0,sizeof(addr_ser));
	addr_ser.sin_family = AF_INET;
	addr_ser.sin_port = htons((unsigned short)atoi(argv[2]));
	addr_ser.sin_addr = *((struct in_addr *) he->h_addr);

	printf("port:%s\n",argv[2]);
	printf("addr:%s\n",argv[3]);
		
	confd = connect(sockfd, (struct sockaddr *)&addr_ser, sizeof(addr_ser));	//客户端连接服务器 
	if(confd == -1){	//如果连接失败 
		perror("Connectfd error!\n");
		exit(1);
	}
	
	while(1){	//循环使一个客户端不停的发送消息直到退出,同时建立新的连接 		
		printf("--------------------------------------------------\n");
		memset(sendMsg,0,sizeof(sendMsg));
		printf("Please input what you want to send:\0");
		scanf("%s",sendMsg);
		
		if(strncmp(sendMsg,"EXIT",4)==0){	//退出客户端 
			break;
		}
		
		int n;
		if(n=write(sockfd,sendMsg,strlen(sendMsg))==0){	//把文件消息发送给服务器 
			perror("wrong:");
			break;
		}

		sleep(1);
		
		sockfile=socket(AF_INET,SOCK_STREAM,0);	//创建socket套接字
		if(sockfile==-1){	//如果创建套接字失败 
			perror("Create socketfile failed!\n");
			break;
		}
			
		memset(&addr_file,0,sizeof(addr_file));	//每个字节都用0填充
		addr_file.sin_family = AF_INET;	//使用IPv4地址
		addr_file.sin_port = htons(PORT);	//端口
		addr_file.sin_addr = *((struct in_addr *) he->h_addr);	//具体的IP地址
			
		confile = connect(sockfile, (struct sockaddr *)&addr_file, sizeof(addr_file));	//客户端连接服务器 
		if(confile == -1){	//如果连接失败 
			perror("Connectfile error!\n");
			break;
		}
		
		printf("Response from server.\n");
		fflush(stdout);

		while(1){
			fflush(stdout);
			memset(recvMsg,0,sizeof(recvMsg));
			int bytenum;
			if((bytenum = recv(sockfile, recvMsg, 1024,0) == -1)){	//从服务器接收消息 
				perror("client: recv");
				break;
			}
			recvMsg[bytenum] ='\0';
			
			if(strstr(recvMsg,"@")!=NULL){	//依据标记删除后面的字符 
				char* a = strstr(recvMsg,"@");
				memset(a,0,sizeof(a));
				printf("%s",recvMsg);
				break;
			}
			printf("%s",recvMsg);
			fflush(stdout);
		}
		fflush(stdout);
		close(sockfile);
	}
	close(sockfd);
	return 0; 
}

2.运行结果截图

打开服务器,输入端口号——输出地址和端口号——(此处的端口号为网络序,为验证连接相同,原代码中已修改成主机序
请添加图片描述
打开一个客户端,输入要连接的IP地址和端口号——输出地址和端口号
请添加图片描述
客户端连接成功后发送一个有效的文件名,服务器将文件内容传输给客户端——服务器收到客户端消息,客户端收到服务器传输的文件
请添加图片描述
客户端向服务器发送一个无效的文件名——服务器显示未找到文件,并将此消息传递给客户端
请添加图片描述
再启动一个客户端,输入要连接的IP地址和端口号——输出地址和端口号
请添加图片描述
客户端连接成功后发送一个有效的文件名,服务器将文件内容传输给客户端——服务器收到客户端消息,客户端收到服务器传输的文件
请添加图片描述
通过pstree | grep server 检查进程数——进程数为2个
请添加图片描述
其中一个客户端发送退出请求——客户端退出,服务器显示一个客户端已退出 请添加图片描述
再次通过pstree | grep server 检查进程数——进程数为1个
请添加图片描述
另一个客户端发送退出请求——客户端退出,服务器显示一个客户端已退出
请添加图片描述
最后通过pstree | grep server 检查进程数——进程数为1个
请添加图片描述

五、流程图

服务器流程图:
其中包含两部分,父进程与子进程,还有子进程中与客户端进行交互的函数。

父进程与子进程:
在这里插入图片描述
Communication函数:
在这里插入图片描述
客户端流程图:
在这里插入图片描述

六、实验体会

通过这次实验,我充分理解了如何建立一个基于TCP/IP的文件传输系统,也同时了解了流式套接字、并行等多方面多角度的内容,将操作系统、计算机网络里面学习到的知识与实践相结合,最终完成该实验。

这篇文章真的超级良心了,希望对大家有用的话可以点个赞收个藏什么滴,这样我熬的几个大夜也有意义了呀。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋千的千秋

希望你喜欢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值