这个程序使用的是多线程机制+缓冲机制。
服务端会创建一个子线程来对应一个客户端,这样就可以实现多个客户端同时在线,客户端会有一个队列,用于循环读取的文件的缓冲机制。
这个程序默认是把接受的文件放在当前目录下的。
服务端:
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<memory.h>
#include<time.h>
#include<string.h>
#include<sys/wait.h>
#include<signal.h>
#include<time.h>
#include<sys/time.h>
#include<pthread.h>
#include<semaphore.h>
#define SERV_PORT 8888
#define SOCK_COUNT 30
#define MAXSIZE 1000
#define BUF_COUNT 10
#define MSG_FILENAME 1
#define MSG_CONTINUE 2
#define MSG_ACK 3
#define MSG_DONE 4
#define MSG_EXCEPTION 5
//消息结构体
struct msg
{
int type;
int len;
char data[];
};
int main(int argc,char *argv[])
{
int sockfd,new_fd;
struct sockaddr_in servaddr,cliaddr;
int sin_size,numbytes;
pid_t pid;
struct timeval start;
struct timeval end;
char strptr[16];
//创建socket
if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket error");
exit(-1);
}
//初始化socket结构体
memset(&servaddr,0x00,sizeof(servaddr));
memset(&cliaddr,0x00,sizeof(cliaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//避免僵死进程
void sig_child(int signo)
{
pid_t pid;
int stat;
while((pid = waitpid(-1,&stat,WNOHANG))>0)
{
printf("chlid %d terminated\n",pid);
}
return;
}
//绑定套接口
if(bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) == -1)
{
perror("bind error");
exit(-1);
}
//创建监听套接口
if(listen(sockfd,SOCK_COUNT) == -1)
{
perror("listen error");
exit(-1);
}
//等待连接
//printf("0-1\n");
while(1)
{
sin_size = sizeof(struct sockaddr_in) ;
if((new_fd = accept(sockfd,(struct sockaddr *)&cliaddr,(socklen_t *)&sin_size)) == -1)
{
perror("accept error");
exit(-1);
}
//创建子进程
if((pid=fork()) == 0)
{
int head=0;//队头指针
int rear=0;//队尾指针
struct msg *buf_ptr[BUF_COUNT];//缓冲区队列
sem_t count;//已经使用的缓冲区
sem_t empty_count;//未使用的缓冲区
int i;
close(sockfd);
struct msg *sm,*rm;
pthread_t ptid1,ptid2;
rm=(struct msg *)malloc(MAXSIZE+sizeof(struct msg));
sm=(struct msg *)malloc(MAXSIZE+sizeof(struct msg));
//初始化确认信息
sm->type = MSG_ACK;
sm->len = 0;
gettimeofday(&start,NULL);
//将消息结构体,拷贝进缓冲区
void cpy_sm_to_buf(struct msg *sm)
{
sem_wait(&empty_count);
memset(buf_ptr[rear],0x00,MAXSIZE);
memcpy(buf_ptr[rear],sm,sizeof(struct msg)+sm->len);
//更新队列状态
rear = (rear+1)%BUF_COUNT;
//控制线程2退出
sem_post(&count);
}
//生产者,接收数据并放入缓存区
void* producer(void *arg)
{
int th_new_fd= *(int *)arg;
int producer_flag = MAXSIZE;
while( producer_flag != 0)
{
if((numbytes = recv(th_new_fd,(void *)rm,sizeof(struct msg),0)) == -1)
{
perror("recv error");
exit(-1);
}
if(numbytes == 0)//判断客户端是否断开
{
printf("客户端已断开!\n");
exit(-1);
}
producer_flag = rm->len;
if((rm->type == MSG_DONE)||(rm->type == MSG_EXCEPTION))
{
cpy_sm_to_buf(rm);
break;
}
memset(rm->data,0x00,MAXSIZE);
if((numbytes = recv(th_new_fd,rm->data,rm->len,0)) == -1)
{
perror("recv error");
exit(-1);
}
cpy_sm_to_buf(rm);
}
free(rm);
pthread_exit(NULL);
}
//消费者,将缓存区的内容写入文件
void* consumer()
{
FILE *fp;
int consume_flag = MAXSIZE;
while(consume_flag != 0)
{
sem_wait(&count);
consume_flag = buf_ptr[head]->len;
if(buf_ptr[head]->type == MSG_FILENAME)
{
if(buf_ptr[head]->len>256)
{
printf("文件名过长!\n");
exit(-1);
}
char file_name[256];
memset(file_name,0x00,256);
memcpy(file_name,buf_ptr[head]->data,buf_ptr[head]->len);
if((fp = fopen(file_name,"w+")) == NULL)
{
perror("fopen error");
exit(-1);
}
}
else if(buf_ptr[head]->type == MSG_CONTINUE)
{
if(fwrite(buf_ptr[head]->data,1,buf_ptr[head]->len,fp)<0)
{
printf("fwrite error\n");
exit(-1);
}
}
else if(buf_ptr[head]->type == MSG_DONE)
{
printf("send MSG_DONE!\n");
break;
}
else if(buf_ptr[head]->type == MSG_EXCEPTION)
{
printf("read the file failed!%d\n",buf_ptr[head]->type);
exit(-1);
}
else
{
printf("文件类型错误!rm->type=%d\n",buf_ptr[head]->type);
exit(-1);
}
//更新队列状态
head = (head+1)%BUF_COUNT;
sem_post(&empty_count);
}
fclose(fp);
pthread_exit(NULL);
}
//为存放数据的缓冲去分配内存
for(i=0; i<BUF_COUNT; i++)
{
buf_ptr[i]=(struct msg *)malloc(MAXSIZE+sizeof(struct msg));
}
if(sem_init(&count,0,0) == -1)
{
perror("sem_init error");
exit(-1);
}
if(sem_init(&empty_count,0,BUF_COUNT) == -1)
{
perror("sem_init error");
exit(-1);
}
gettimeofday(&start,NULL);
pthread_create(&ptid1,NULL,producer,&new_fd);
pthread_create(&ptid2,NULL,consumer,NULL);
pthread_join(ptid1,NULL);
pthread_join(ptid2,NULL);
gettimeofday(&end,NULL);
sem_destroy(&count);
sem_destroy(&empty_count);
printf("the time of receiving the file is %ld\n",(end.tv_sec - start.tv_sec)*1000000+(end.tv_usec - start.tv_usec));
close(new_fd);
free(sm);
//释放缓冲队列
for(i=0; i<BUF_COUNT; i++)
{
free(buf_ptr[i]);
}
exit(0);
}
//通过信号,防止僵尸进程
signal(SIGCHLD,sig_child);
//服务器端打印客户端的网址、端口号
if(inet_ntop(AF_INET,&cliaddr.sin_addr,strptr,(socklen_t)sizeof(strptr)) == NULL)
{
perror("convert error");
exit(-1);
}
printf("connect from %s,port is %d,pid is %d\n",strptr,ntohs(cliaddr.sin_port),pid);
close(new_fd);
}
close(sockfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include<string.h>
#include <unistd.h>
#include <errno.h>
#include<time.h>
#include<pthread.h>
#include<sys/time.h>
#include<semaphore.h>
#define SERV_PORT 8888//服务端口号
#define MAXSIZE 1000//缓存队列可以存储的最大数据
#define BUF_COUNT 2//缓存去的个数
#define MSG_FILENAME 1
#define MSG_CONTINUE 2
#define MSG_ACK 3
#define MSG_DONE 4
#define MSG_EXCEPTION 5
//消息结构体
struct msg
{
int type;
int len;
char data[];
};
sem_t count;//已经使用的缓冲区
sem_t empty_count;//未使用的缓冲区
int my_count;
int head;//队头指针
int rear;//队尾指针
struct msg *buf_ptr[BUF_COUNT];//缓冲区队列
//将消息结构体,拷贝进缓冲区
void cpy_sm_to_buf(struct msg *sm)
{
sem_wait(&empty_count);
memset(buf_ptr[rear],0x00,MAXSIZE);
memcpy(buf_ptr[rear],sm,sizeof(struct msg)+sm->len);
//更新队列状态
rear = (rear+1)%BUF_COUNT;
sem_post(&count);
}
//生产者,读取文件并放入缓存区
void* producer(void *arg)
{
char *filepath=(char *)arg;
FILE *fp;
int i,j;
int thread_exit_flag=0;
int datalen;
int produce_flag;
struct msg *sm;
sm = (struct msg*)malloc(MAXSIZE+sizeof(struct msg));
//由文件路径获取文件名
char file_name[256];
memset(file_name,0x00,256);
for(i=strlen(filepath)-1; i>=0; i--)
{
if(filepath[i] == '/')
{
break;
}
}
for(j=i+1; j<strlen(filepath); j++)
{
file_name[j-i-1]=filepath[j];
}
sm->type = MSG_FILENAME;
sm->len = strlen(file_name);
memset(sm->data,0x00,MAXSIZE);
memcpy(sm->data,file_name,sm->len);//将文件名拷贝到消息中
cpy_sm_to_buf(sm);
//打开文件
if((fp=fopen(filepath,"r")) == NULL)
{
perror("fopen error");
exit(-1);
}
//循环读取文件
produce_flag = MAXSIZE;
while(produce_flag == MAXSIZE)
{
sm->type = MSG_CONTINUE;
memset(sm->data,0x00,MAXSIZE);
if((datalen=fread(sm->data,1,MAXSIZE,fp)) == 0)
{
if(feof(fp))
{
printf("注意,文件为空!\n");
}
else
{
sm->type = MSG_EXCEPTION;
sm->len = 0;
memset(sm->data,0x00,MAXSIZE);
cpy_sm_to_buf(sm);
produce_flag = datalen;
printf("fread error\n");
exit(-1);
}
}
sm->len = datalen;
cpy_sm_to_buf(sm);
produce_flag = datalen;
}
sm->type = MSG_DONE;
sm->len = 0;
memset(sm->data,0x00,MAXSIZE);
cpy_sm_to_buf(sm);
fclose(fp);
free(sm);
pthread_exit(NULL);
}
//消费者,将缓存区的内容发送到服务端
void* consumer(void *arg)
{
int consufd = *(int *)arg;
int i;
int consume_flag = MAXSIZE;
while(consume_flag != 0)
{
sem_wait(&count);
//向服务端发送文件名消息
consume_flag = buf_ptr[head]->len;
if((send(consufd,buf_ptr[head],sizeof(struct msg)+buf_ptr[head]->len,0)) == -1)
{
perror("send error");
exit(-1);
}
//更新队列状态
head = (head+1)%BUF_COUNT;
sem_post(&empty_count);
}
pthread_exit(NULL);
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
printf("%s: input IP & filemane\n",argv[0]);
return 1;
}
int i;
head = 0;
rear = 0;
struct sockaddr_in their_addr;
pthread_t ptid1,ptid2;
int sockfd,numbytes;
struct timeval start;
struct timeval end;
struct hostent *he;
struct msg *rm;
rm = (struct msg*)malloc(sizeof(struct msg)+MAXSIZE);
//为存放数据的缓冲去分配内存
for(i=0; i<BUF_COUNT; i++)
{
buf_ptr[i]=(struct msg *)malloc(MAXSIZE+sizeof(struct msg));
}
//将基本名字和地址转换
he = gethostbyname(argv[1]);
//建立一个TCP套接口
if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(1);
}
//初始化结构体
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(SERV_PORT);
their_addr.sin_addr =*((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero),8);
//和服务端建立连接
if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))==-1)
{
perror("connect");
printf("%s\n",strerror(errno));
exit(1);
}
if(sem_init(&count,0,0) == -1)
{
perror("sem_init error");
exit(-1);
}
if(sem_init(&empty_count,0,BUF_COUNT) == -1)
{
perror("sem_init error");
exit(-1);
}
gettimeofday(&start,NULL);
pthread_create(&ptid1,NULL,producer,argv[2]);
pthread_create(&ptid2,NULL,consumer,&sockfd);
pthread_join(ptid1,NULL);
pthread_join(ptid2,NULL);
gettimeofday(&end,NULL);
sem_destroy(&count);
sem_destroy(&empty_count);
printf("the time of sending the file is %ld\n",(end.tv_sec - start.tv_sec)*1000000+(end.tv_usec - start.tv_usec));
//接受服务端发送的确认消息
if((numbytes = recv(sockfd,(void *)rm,sizeof(struct msg),0)) == -1)
{
perror("recv error");
exit(-1);
}
printf("send MSG_DONE!\n");
close(sockfd);
//释放缓冲队列
for(i=0; i<BUF_COUNT; i++)
{
free(buf_ptr[i]);
}
free(rm);
return 0;
}
由于使用到了多线程,编译的时候使用:gcc xxx.c -o xxx -lpthread
编译后使用:./server和./client 127.0.0.1文件路径即可运行