TCP socket多客户端文件传输
来源:互联网 发布:电视盒子推荐 知乎 编辑:程序博客网 时间:2024/05/19 02:17
这一节讲一个tcp socket编程中常见的多客户端向服务器传输数据的例子。
程序流程如下:
- 服务器端:socket建立套接字,bind绑定地址,listen开始监听。
- 客户端:socket建立套接字,connect连接服务器,用户输入想要传输的文件名,用send函数把文件发送出去。
- 服务器端:每次监听到客户端的连接,服务器端就调用accept函数,返回新的套接字描述符client_fd,然后创建一个新的线程用recv来读取客户端发送的数据,不断循环这个过程。
这里需要重点理解的函数是accept,函数原型如下:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
- sockfd:套接字描述符,该套接口在listen()后监听连接。
- addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
- addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。
函数accept所返回的文件描述符是套接字描述符,该描述符连接到调用connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字没有关联到这个连接,而是继续保持可用状态并接收其他连接请求。如果没有连接请求在等待,accept会阻塞直到一个请求到来。如果sockfd处于非阻塞模式,accept会返回-1,并将errno设置为EAGAIN或EWOULDBLOCk。
接下来看看多客户端向服务器传输程序源码。
C语言版代码如下:
/*fileserver.c*//* 所有的文件都已经以二进制到形式访问,所以打开读写文件都以二进制到形式进行 */#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/ioctl.h>#include <unistd.h>#include <netinet/in.h>#define BUFFER_SIZE 4096#define MAX_QUE_CONN_NM 5#define PORT 6000#define MAXSOCKFD 10#define FILE_NAME_MAX 512void recv_mul_file(int sockfd);void* pthread_func(void * arg);int main(int argc,char* argv[]){ int sockfd; int sin_size = sizeof(struct sockaddr); struct sockaddr_in server_sockaddr, client_sockaddr; int i = 1;/*使得重复使用本地地址与套接字进行绑定 */ /*建立socket连接*/ if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1) { perror("socket"); exit(1); } printf("Socket id = %d\n",sockfd); /*设置sockaddr_in 结构体中相关参数*/ server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); server_sockaddr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_sockaddr.sin_zero), 8); setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); /*绑定函数bind*/ if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1) { perror("bind"); exit(1); } printf("Bind success!\n"); /*调用listen函数*/ if (listen(sockfd, MAX_QUE_CONN_NM) == -1) { perror("listen"); exit(1); } printf("Listening....\n"); recv_mul_file(sockfd); close(sockfd); return 0;}void recv_mul_file(int sockfd){ pthread_t tid; struct sockaddr_in client_sockaddr; int client_fd, sin_size = sizeof(struct sockaddr); while(1) { if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, (socklen_t *)&sin_size)) == -1) { perror("accept"); exit(1); } pthread_create(&tid, NULL, pthread_func, &client_fd); }}void* pthread_func(void * arg){ //recv file imformation int client_fd; char buff[BUFFER_SIZE]; char filename[FILE_NAME_MAX]; int count; bzero(buff,BUFFER_SIZE); client_fd = *(int *)arg; printf("recv from client,client_fd = %d\n",client_fd); //把接受到到字符放在长度为BUFFER_SIZE的buff地址上,接收成功返回接收到到字节数目 count=recv(client_fd,buff,BUFFER_SIZE,0); if(count<0) { perror("recv"); exit(1); } //把filename地址上的内容复制到地址buff上,第三个参数表明复制多少个字节 strncpy(filename,buff,strlen(buff)>FILE_NAME_MAX?FILE_NAME_MAX:strlen(buff)); printf("Preparing recv file : %s\n",filename ); //recv file //告诉函数库,打开的是一个二进制到可写文件,地址在指针filename FILE *fd=fopen(filename,"wb+"); if(NULL==fd) { perror("open"); exit(1); } bzero(buff,BUFFER_SIZE); //缓冲区清0 int length=0; while(length=recv(client_fd,buff,BUFFER_SIZE,0)) //这里是分包接收,每次接收4096个字节 { if(length<0) { perror("recv"); exit(1); } //把从buff接收到的字符写入(二进制)文件中 int writelen=fwrite(buff,sizeof(char),length,fd); if(writelen<length) { perror("write"); exit(1); } bzero(buff,BUFFER_SIZE); //每次写完缓冲清0,准备下一次的数据的接收 } printf("Receieved file:%s finished!\n",filename ); fclose(fd); close(client_fd); return 0;}
从上面代码看出,在recv_mul_file(sockfd)函数中会不断循环调用accept等待客户端请求的到来。当有客户端连接时,会返回一个新的套接字描述符,然后pthread_create创建一个新的线程pthread_func,用于接收来自客户端发送的数据。
/*fileclient.c*/#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/ioctl.h>#include <unistd.h>#include <netdb.h>#include <netinet/in.h>#include <pthread.h>#define PORT 6000#define BUFFER_SIZE 4096#define FILE_NAME_MAX 512int main(int argc,char* argv[]){ int sockfd; struct hostent *host; struct sockaddr_in serv_addr; if(argc != 2) { fprintf(stderr,"Usage: ./fileclient Hostname(or ip address) \ne.g. ./fileclient 127.0.0.1 \n"); exit(1); } //地址解析函数 if ((host = gethostbyname(argv[1])) == NULL) { perror("gethostbyname"); exit(1); } /*创建socket*/ if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { perror("socket"); exit(1); } /*设置sockaddr_in 结构体中相关参数*/ serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); //将16位的主机字符顺序转换成网络字符顺序 serv_addr.sin_addr = *((struct in_addr *)host->h_addr); //获取IP地址 bzero(&(serv_addr.sin_zero), 8); //填充0以保持struct sockaddr同样大小 if(connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(struct sockaddr))== -1) { perror("connect"); exit(1); } //send file name information char filename[FILE_NAME_MAX]; bzero(filename,FILE_NAME_MAX);//字符串数组清零 char buff[BUFFER_SIZE]; //定义读写缓冲区 int count; bzero(buff,BUFFER_SIZE); //把缓冲区buff上的4096个字节清0 printf("Please input the file name you wanna to send:"); scanf("%s",filename);//在终端输入文件名 //把filename地址上的内容复制到地址buff上,第三个参数表明复制多少个字节 strncpy(buff,filename,strlen(filename)>FILE_NAME_MAX?FILE_NAME_MAX:strlen(filename)); count=send(sockfd,buff,BUFFER_SIZE,0); if(count<0) { perror("Send file name"); exit(1); } //read file //告诉函数库,打开的是一个二进制到可读文件,地址在指针filename FILE *fd=fopen(filename,"rb"); if(fd==NULL) { printf("File:%s not found in current path\n",filename); } else { bzero(buff,BUFFER_SIZE); //把缓冲区清0 int file_block_length=0; //每次读BUFFER_SIZE个字节,下一次读取时内部指针自动偏移到上一次读取到位置 while((file_block_length=fread(buff,sizeof(char),BUFFER_SIZE,fd))>0) { printf("file_block_length:%d\n",file_block_length); //把每次从文件中读出来到4096字节到数据发出去(最后一次就没有4096字节) if(send(sockfd,buff,file_block_length,0)<0) { perror("Send"); exit(1); } bzero(buff,BUFFER_SIZE);//发送一次数据之后把缓冲区清零 } fclose(fd); printf("Transfer file finished !\n"); } close(sockfd);}
从上面程序可以看出,客户端以二进制可读的形式打开一个文件,然后循环读取文件内容到缓冲区,每次读取4096字节的数据send发出去,直到文件读取完毕。
打开终端测试一下:
用如下命令编译两个源文件
gcc xxx.c -o xxx -lpthread
在终端A执行./fileserver
终端A:
ubuntu:~/test/filetransfer/test-20170526/server$ ./fileserverSocket id = 3Bind success!Listening....
在终端B执行./fileclient 127.0.0.1,然后输入想要传输的文件4.tar
同时在终端C执行./fileclient 127.0.0.1,然后输入想要传输的文件5.tar
会发现终端B和终端C同时在向服务器传输文件,一次发送的数据是4096字节
终端B:
ubuntu:~/test/filetransfer/test-20170526/client$ ./fileclient 127.0.0.1 Please input the file name you wanna to send:4.tarfile_block_length:4096file_block_length:4096file_block_length:4096file_block_length:4096......file_block_length:2581Transfer file finished !
终端C:
ubuntu:~/test/filetransfer/test-20170526/client$ ./fileclient 127.0.0.1Please input the file name you wanna to send:5.tarfile_block_length:4096file_block_length:4096file_block_length:4096.....file_block_length:1664Transfer file finished !
再观察终端A,多了如下打印,表示接收到了来自终端B和终端C的文件
recv from client,client_fd = 4recv from client,client_fd = 5Preparing recv file : 4.tarPreparing recv file : 5.tarReceieved file:4.tar finished!Receieved file:5.tar finished!
- TCP socket多客户端文件传输
- win32 tcp文件传输客户端
- 循序渐进Socket网络编程(多客户端、信息共享、文件传输)
- 循序渐进Socket网络编程(多客户端、信息共享、文件传输)
- 循序渐进Socket网络编程(多客户端、信息共享、文件传输)
- 循序渐进Socket网络编程(多客户端、信息共享、文件传输)
- Socket网络编程(多客户端、信息共享、文件传输)
- java Socket--tcp 一个服务器多客户端
- socket编程—TCP/IP 多客户端
- linux下的TCP/IP socket 文件传输
- linux下的TCP/IP socket 文件传输
- Linux下的TCP/IP socket 文件传输
- Linux下的TCP/IP socket 文件传输
- Socket TCP通信简单实现与文件传输
- Socket TCP 客户端
- 启动TCP Socket客户端
- socket tcp客户端
- Socket多文件传输
- PartitionStateMachine分析
- Coursera吴恩达机器学习课程 总结笔记及作业代码——第7周支持向量机
- android activity之间传递bean类型数据
- iOS- NSThread/NSOperation/GCD 三种多线程技术的对比及实现
- vue.js做简易留言板
- TCP socket多客户端文件传输
- Java 实例
- leetcode 117. Populating Next Right Pointers in Each Node II
- js获得时间参数
- 投资风口再起:医疗机器人成为热点
- StringUtils.isEmpty和StringUtils.isBlank用法和区别
- ios CoreData和FMDB最新使用问题总结
- js控制textarea禁止输入表情
- Angular2-使用Angular CLI快速搭建工程(一)