Linux下基于TCP的简易文件传输(socket编程)

OSI和TCP/IP:

OSI 模型本身不是网络体系结构的全部内容,它并未确切地描述用于各层的协议和服务,仅提出每一层应该做什么。不过OSI 已经为各层制定了标准,但并不是参考模型的一部分,而作为单独的国际标准公布的。
TCP/IP 是一组用于实现网络互连的通信协议。Internet 网络体系结构以TCP/IP 为核心。基于TCP/IP 的参考模型将协议分成四个层次,它们分别是:网络访问层、网际互联层、传输层(主机到主机)、和应用层。
OSI模型和TCP/IP模型的对应关系

关于TCP/IP协议

TCP/IP是一个网络通信模型,以及一整个网络传输协议家族,是网际网络的基础通信架构。因为该协议家族的两个核心协议:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准。故此它常被通称为TCP/IP协议族(TCP/IP Protocol Suite),简称TCP/IP。

. TCP/IP模型 :TCP/IP 是一组用于实现网络互连的通信协议。Internet 网络体系结构以 TCP/IP 为核心。基于TCP/IP 的参考模型将协议分成四个层次,它们分别是:网络访问层、网际互联层、传输层(主机到主机)、和应用层。

  1. 应用层
    应用层对应于 OSI 参考模型的高层,为用户提供所需要的各种服务,例如:FTP、Telnet、DNS、SMTP 等。
  2. 传输层
    传输层对应于 OSI 参考模型的传输层,为应用层实体提供端到端的通信功能,保证了数据包的顺序传送及数据的完整性。该层定义了两个主要的协议:传输控制协议(TCP)和用户数据协议(UDP)。TCP 协议提供的是一种可靠的、通过“三次握手”来连接的数据传输服务;而 UDP 协议提供的则是不保证可靠的(并不是不可靠)、无连接的数据传输服务.
  3. 网际互联层网际互联层对应于 OSI 参考模型的网络层,主要解决主机到主机的通信问题。它所包含的协议设计数据包在整个网络上的逻辑传输。注重重新赋予主机一个 IP 地址来完成对主机的寻址,它还负责数据包在多种网络中的路由。该层有三个主要协议:网际协议(IP)、互联网组管理协议(IGMP)和互联网控制报文协议(ICMP)。IP 协议是网际互联层最重要的协议,它提供的是一个可靠、无连接的数据报传递服务。
  4. 网络接口层(即主机-网络层)
    网络接入层与 OSI 参考模型中的物理层和数据链路层相对应。它负责监视数据在主机和网络之间的交换。事实上,TCP/IP 本身并未定义该层的协议,而由参与互连的各网络使用自己的物理层和数据链路层协议,然后与 TCP/IP 的网络接入层进行连接。地址解析协议(ARP)工作在此层,即 OSI 参考模型的数据链路层;

. IP协议 :IP(Internet Protocol)协议是 TCP/IP 的核心协议。IP 协议(Internet Protocol)又称互联网协议,是支持网间互连的数据报协议。它提供网间连接的完善功能, 包括 IP 数据报规定互连网络范围内的 IP 地址格式。
目前的 IP 地址(IPv4:IP 第 4 版本)由 32 个二进制位表示,每 8 位二进制数为一个整数,中间由小数点间隔,如 159.226.41.98,整个 IP 地址空间有 4 组 8 位二进制数,由表示主机所在的网络的地址以及主机在该网络中的标识共同组成,它通常被分为A,B,C,D,E五类,其中商业应用只用到A,B,C三类1
在这里插入图片描述

关于TCP协议

(1)TCP(传输控制协议)
TCP 是一种可靠的面向连接的传送服务。主机交换数据必须首先建立连接,传输完毕后断开连
接。它用位流通信,即数据被作为无结构的字节流。它提供反馈重发机制,从而保证数据的可靠传
输。
(2)三次握手
TCP 连接建立是一个三次握手过程,三次握手的目的是使数据的发送和接收同步。TCP 连接过程
如下图所示:
TCP 连接过程如下:

  1. 服务器必须准备好接受外来的连接。通过调用 socket, bind, listen 函数完成。称为被动打开。
  2. 客户通过调用 connect 进行主动打开。这引起客户 TCP 发送一个 SYN 分节,告诉服务器客户将在
    连接中发送的数据的初始序列号。
  3. 服务器必须确认客户的 SYN,同时自己也得发送一个 SYN 分节。服务器以单个分节向客户发送
    SYN 和对客户的 SYN 的 ACK。
  4. 客户必须确认服务器的 SYN。

在这里插入图片描述

TCP编程的一般步骤2

TCP编程的服务器端一般步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt(); * 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();
  4、开启监听,用函数listen();
  5、接收客户端上来的连接,用函数accept();
  6、收发数据,用函数send()和recv(),或者read()和write();
  7、关闭网络连接;
  8、关闭监听;

TCP编程的客户端一般步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt();* 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
  4、设置要连接的对方的IP地址和端口等属性;
  5、连接服务器,用函数connect();
  6、收发数据,用函数send()和recv(),或者read()和write();
  7、关闭网络连接;

TCP文件传输实现

功能概述

利用socket编程基础实现一个基础的文件传输程序,可以在不同设备之间传输文件,视频,图片,音乐等内容。

服务器编程


#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>


#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
#define SERVER_PORT 8000 //监听本机8000端口
#define MAX 4096
#define BUF_SIZE 10240

void empty_stdin() {
    int c;
    do {
        c = getchar();
    } while (c != '\n' && c != EOF);
}



int main(void) 
{
	struct sockaddr_in serveraddr,clientaddr;
	int sockfd,addrlen,confd,len;
	char ipstr[128];
	char buf[16];
	pid_t pid;
	//1.socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	//2.bind
	bzero(&serveraddr,sizeof(serveraddr));
	//地址族协议ipv4
	serveraddr.sin_family = AF_INET;
	//ip地址
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(SERVER_PORT);
	bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	//3.listen
	listen(sockfd,20);//128作为可同时链接的数量上线
	//4. accept阻塞监听客户端的链接请求
	addrlen = sizeof(clientaddr);
	confd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen);
	//如果有客户端连接上服务器,就输出客户端的ip地址和端口号
	printf("client ip %s\tport %d\n",
	inet_ntop(AF_INET,(struct sockaddr *)&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)),ntohs(clientaddr.sin_port));
		
    
	int flag=1;
	while(flag){
		 char fdownload[100] = {0};
        recv(confd,fdownload,100,0);//接收客户端的下载请求获得文件名
        
		if(!strcmp(fdownload,"quit")) break;//客户端输入quit退出程序  
		else{
			FILE *fp = fopen(fdownload, "rb"); //以二进制方式打开文件
        if(fp == NULL){
            printf("Cannot open file, press any key to exit!\n");
			break;
            }
		struct stat statbuf; //这三行代码获得文件的大小,得到文件字节数
        stat(fdownload,&statbuf);
        int size=statbuf.st_size;
		char t[20];
		printf("%d \n",size);//文件大小
		sprintf(t,"%d",size);//转换整数到字符型数据
		//printf("start transfer\n");
		send(confd,t,20,0);//把文件大小发给客户端
	    char buffer[BUF_SIZE] = {0}; //缓冲区
        long nCount,mc=0;
		long si,sj,m,c;
        recv(confd,t,4,0);//获得开始传输指令
		if(t[0]=='o'){
		printf("start transfer\n");
/*一下内容开始发送文件内容 ,因为文件大小会超出发送缓冲区大小,因此在这里循环调用send()函数进行发送,每一次发送nCount个字节,传输缓冲区这里设置为10240(并不合理,但不想花脑子想这个问题)*/
        while( (nCount = fread(buffer, 1, BUF_SIZE, fp)) > 0 ){
			mc=mc+nCount;
			si=mc*30/size;//这里开始计算传输比例,式字计算顺序不能更改,否则出现数据溢出,而产生错误。
			m=si-sj;
				 
		    printf("%*s|%d%%",30-si,"",(mc*100/size));//在固定位置打印百分数
			printf("\r\033["); //退格
            for(int t=0;t<si+1;t++)
				 {
					  printf(">");
					  setbuf(stdout, NULL);
				 }

            send(confd, buffer, nCount, 0);
				 
			sj=si;
            }
			printf("transfer success!\n");
			fclose(fp);
		}
		}
	}
	close(confd);
	//close(sockfd);
		

	return 0;
}

客户端编程

#include <netinet/in.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdlib.h>
 
#define HELLO_WORLD_SERVER_PORT       6666  
#define BUFFER_SIZE                   1024  
#define FILE_NAME_MAX_SIZE            512  
#define BUF_SIZE 10240  
 
#define SERVER_PORT 8000
#define MAXLINE 4096
 
int main(void)
{
	struct sockaddr_in serveraddr;
	int confd,len;
	char ipstr[] = "192.168.1.100";//这是服务器的地址,使用ifconfig来查看
	char buf[MAXLINE];
	//1.创建一个socket
	confd = socket(AF_INET,SOCK_STREAM,0);
	//2.初始化服务器地址,指明我要连接哪个服务器
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	inet_pton(AF_INET,ipstr,&serveraddr.sin_addr.s_addr);
	serveraddr.sin_port = htons(SERVER_PORT);
	//3.链接服务器
	connect(confd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	

    int flag=1,size;
    char t[20];
    memset(&t,0,sizeof(t));
    while(flag){
         char fdownload[100] = {0};
         printf("Input filename to download: ");
         gets(fdownload);
         if(!strcmp(fdownload,"quit")) break;
         send(confd,fdownload,100,0);
         char filename[100] = {0}; //文件名
         len=recv(confd,t,20,0);
         size=atoi(t);
         double sizel;
         sizel=size/1048576.0;
         printf("filesize; %.2f MB\n ",sizel);
         printf("Input filename to save: ");
         gets(filename);
         if(!strcmp(filename,"quit")) break;
         else{
                FILE *fp = fopen(filename, "wb"); //以二进制方式打开(创建)文件
                if(fp == NULL){
                    printf("Cannot open file, press any key to exit!\n");
                    break;
                }
                send(confd,"o",4,0);
                char buffer[BUF_SIZE] = {0}; //文件缓冲区
                long nCount,mc=0;
                long si,sj,m,c;
                printf("Start receive!\n");
            
                while( (nCount = recv(confd, buffer, BUF_SIZE, 0)) > 0 ){
	                    mc=mc+nCount;
			            si=mc*30/size;
			            m=si-sj;
				 
		                printf("%*s|%d%%",30-si,"",(mc*100/size));
			              printf("\r\033["); //退格
                        for(int t=0;t<si+1;t++)
				                {
					                 printf(">");
					                 setbuf(stdout, NULL);
				                }
                        fwrite(buffer, nCount, 1, fp);
                        if(mc==size) break;
                }
            printf("Transfer success!\n");
            fclose(fp);
        }
    }
close(confd);
return 0;
}

运行结果

在这里插入图片描述
手机客户端传输截图,手机端安装"手机CAPP"app即可运行客户端程序:
在这里插入图片描述

总结

遇到的问题

服务器编程

  1. 进度条不在传输过程中打印:在客户端发送数据时进度条不打印,只在传输结束以后才打印一串进度条符号
    原因:C语言的标准输入输出函数都具有一个输入输出缓冲区,一般来说,只有在遇到“\n”或者缓冲区满的条件下才输出缓冲区的数据,在打印进度条的时候没有进行回车,所以再这一过程printf是不会输出数据的,只有在结束以后遇到回车才输出
    解决:把输入缓冲区大小设置为零,这么一来可以加快打印效率。如果在windows下面可以使用fflush来清空缓冲区使得数据及时输出。

  1. 关于IP地址的更多信息 ↩︎

  2. 这部分来自这位博主的博客 ↩︎

  • 11
    点赞
  • 111
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 基于TCP和UDP的socket编程网络编程中常用的两种方式。TCP是面向连接的可靠传输协议,UDP是无连接的不可靠传输协议。 使用TCP协议进行socket编程时,需要先建立连接,然后再进行数据传输。建立连接时,需要使用服务器端的IP地址和端口号来连接服务器。连接建立后,数据传输可以通过套接字进行。TCP协议保证了数据传输的可靠性,但是会影响传输速度。 使用UDP协议进行socket编程时,不需要建立连接,可以直接进行数据传输。数据传输时,需要指定目标地址和端口号。UDP协议没有连接的开销,可以提高传输速度,但是传输过程中可能会发生数据包丢失或乱序。 无论是基于TCP还是UDP的socket编程,都需要使用socket函数来创建套接字,然后通过bind函数绑定IP地址和端口号,最后通过send和recv函数进行数据传输。 ### 回答2: Socket(套接字)编程是指利用套接字来实现网络通信的编程技术。套接字是一种通信机制,应用程序通过套接字向网络发出请求,然后接收网络的响应。常用的套接字类型有基于TCP(传输控制协议)和UDP(用户数据报协议)两种,下面将分别介绍基于TCP和UDP的Socket编程。 1. 基于TCPSocket编程 TCP是一种面向连接的协议,在进行数据传输之前必须先建立连接,然后才能进行数据传输。TCP协议通过三次握手建立连接,保证数据可靠传输。基于TCPSocket编程主要流程如下: (1)创建Socket套接字。 (2)绑定Socket到本地地址和端口。 (3)监听连接请求,等待客户端连接。 (4)接受客户端连接请求,建立TCP连接。 (5)发送和接收数据。 (6)关闭Socket,释放资源。 在TCP协议中,客户端和服务端之间都需要建立连接,因此在建立连接之前需要先创建Socket套接字,指定本地地址和端口。然后服务端通过listen函数监听连接请求,等待客户端连接。客户端连接成功后,服务端accept函数接收连接请求,建立TCP连接。连接建立后,客户端和服务端可以通过send和recv函数进行数据传输。传输结束后,需要通过close函数关闭连接,释放资源。 TCP协议主要特点是安全可靠,传输速度较慢。因此TCP协议适用于数据传输要求高可靠性的场景,如文件传输、电子邮件等。 2. 基于UDP的Socket编程 UDP是一种无连接的协议,数据传输时不需要建立连接,发送端发送数据后,接收端直接收到数据。虽然UDP传输速度快,但数据传输并不可靠,可能丢失数据或出现乱序。基于UDP的Socket编程主要流程如下: (1)创建Socket套接字。 (2)绑定Socket到本地地址和端口。 (3)发送数据。 (4)接收数据。 (5)关闭Socket,释放资源。 UDP协议主要特点是速度快、传输不可靠。因此UDP适用于实时性要求高、数据可靠性要求不高的场景,如网络游戏、实时视频等。 总之,基于TCP和UDP的Socket编程网络编程中常用的技术,应用广泛。在实际开发中,要根据实际需求选择合适的协议,保证数据传输的可靠性和效率。 ### 回答3: Socket编程是一项网络编程技术,它允许程序员使用TCP/IP协议来进行通信。Socket通常与网络编程、Web开发相关。计算机的应用程序间通过Socket传输数据,Socket是应用程序和传输层协议之间的接口,通常使用TCP/IP协议进行网络通信。 TCP(传输控制协议)提供了端到端的可靠数据传输服务,它基于连接进行通信,确保数据不会丢失或损坏,并且按照发送顺序到达接收方。TCP通过三次握手建立连接,然后进行数据传输,最后断开连接。在Socket编程中,如果要使用TCP协议,则必须创建一个TCP套接字。TCP套接字可以用于客户端或服务器端,服务器端必须先创建套接字并等待客户端的连接请求,客户端则通过TCP连接到服务器。在连接成功后,双方可以交换数据,结束时断开连接。 UDP(用户数据报协议)是无连接的,不可靠的数据传输服务,在数据传输过程中可能会丢失一定量的数据,但它具有高效性、实时性和简单性。UDP套接字不需要建立连接,直接发送数据报到目的地。在Socket编程中,如果要使用UDP协议,则必须创建一个UDP套接字,可以用于客户端或服务器端。UDP允许发送数据报到一个多播地址,这意味着可以一次向多个客户端发送数据。 在Socket编程中,使用TCP和UDP协议需要注意以下几点: 1. 建立TCP连接时要进行三次握手,结束时要进行四次挥手,这会增加一些延迟和开销;而建立UDP连接不需要握手和挥手,速度更快。 2. TCP保证数据不会丢失或损坏,而UDP可能会丢失一些数据,因此需要在应用程序中进行数据包的重新传输和处理。 3. 在Socket编程中,应尽可能使用可重用的套接字,以免出现套接字资源短缺的问题。 4. 应尽可能使用异步I/O编程,以避免阻塞线程。在异步I/O模式中,应用程序可以发出I/O请求并继续执行其他操作,当操作完成时,操作系统会通知应用程序进行下一步处理。这种方式比同步I/O编程更高效,并且可以实现更高的并发性。 总之,TCP和UDP是Socket编程中最常用的协议,它们各自具有优缺点,应根据应用程序的需求选择合适的协议。在使用Socket编程时,应尽可能考虑性能、可靠性和安全性等问题,以保证应用程序的稳定和可靠。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值