一、实验目的
在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的文件传输系统,也同时了解了流式套接字、并行等多方面多角度的内容,将操作系统、计算机网络里面学习到的知识与实践相结合,最终完成该实验。
这篇文章真的超级良心了,希望对大家有用的话可以点个赞收个藏什么滴,这样我熬的几个大夜也有意义了呀。