当前位置:   article > 正文

TCP/IP网络编程:实现客户-服务器之间的通信、文件上传与下载_编写c程序,使用网络套接字,实现1个客户端和服务器通信。使之能够实现2个客户端通

编写c程序,使用网络套接字,实现1个客户端和服务器通信。使之能够实现2个客户端通

TCP/IP通信:实现客户-服务器之间的通信、文件上传与下载

通信流程:

(一)搭建服务器(server)

在这里插入图片描述

1. socket 创建套接字

头文件:#include<sys/types.h>
       #include<sys/socket.h>
函数名:socket
函数功能:创建一个通信端口
参数1int domain :使用的协议族
参数2:type :套接字的类型
参数3:protocol :默认为0
返回值:int 成功-> 创建的新的套接字的文件描述符,失败->-1和错误代码

int  socket(int  domain  ,  int  type  ,  int  protocol);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

参数1:通信协议

是通信协议族,使用Lunix命令【man socket】可以查看他的手册:

为了实现稳定的TCP通信,传入AF_INET,标识使用IPv4网络协议。
我们使用的是IPV4通信方式,

参数2:数据传输方式/套接字类型

SOCK_STREAM:流式套接字,TCP使用
SOCK_DGRAM:数据报套接字,UDP使用
在这里插入图片描述

参数3:protocol:默认为0

2. bind 绑定

头文件:#include<sys/types.h>
       #include<sys/socket.h>
函数名:bind
函数功能:给socket绑定IP和端口(需要被找到的套接字才需要被绑定)
参数1int sockfd:创建出来的新的套接字的文件描述符
参数2const  struct  sockaddr  *addr:存储自己的IP地址、端口的结构的结构体首地址
参数3:socklen_t  addrlen :   addrlen结构体的大小
返回值:int 成功->返回0,失败->-1和错误代码

int bind(int  sockfd , const  struct  sockaddr  *addr  ,  socklen_t  addrlen);

struct sockaddr
{
         sa_family_t  sa_family;         //地址族
           char      sa_data[14];        //14字节,包含套接字中的目标地址和端口信息
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

需要注意的是:bind主要用于服务器端,客户端创建的套接字可以不绑定地址
TCP搭建服务器使用的地址结构并不是struct sockaddr , 而是struct sockaddr_in

struct  sockaddr_in
{
       short  int  sin_family;           //网络协议
       unsigned  short  int   sin_port;  //端口号
       struct  in_addr   sin_addr;       //32位IP地址  
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

sockaddr_in结构体内还包含一个结构体:struct in_addr

struct   in_addr
{
       unit32_t   s_addr;  //32位IP地址
};
  • 1
  • 2
  • 3
  • 4

3. listen 监听

listen本质上是一个监听队列,在accept接收客户端连接之前,对将要链接的套接字进行标记。如果监听队列已经满了,再有新的客户端发起连接请求时,则无法监听,此时客户端可能会收到连接被拒绝的错误。

服务器端成功建立套接字并与地址进行绑定后,调用listen函数,将套接字标记为被动(监听模式)。准备接收客户端的连接请求

头文件:#include<sys/types.h>
              #include<sys/socket.h>
函数名:listen
参数1int  sockfd:监听套接字文件描述符(即socket创建,被bind绑定的套接字)
参数2int backlog:监听队列的大小
返回值:int  成功返回0, 失败返回-1和错误代码

int  listen(int  sockfd  ,  int  backlog);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4. accept 连接

头文件:#include<sys/types.h>
       #include<sys/socket.h>
函数名:accpet
函数功能:接收一个套接字的连接请求
参数1int sockfd  :监听套接字的文件描述符(经过listen的sockfd)
参数2struct  sockaddr  *addr :用来存储对方(客户端)地址结构的内存首地址 
                                                  【对方的IP地址和端口号】
参数3:socklen_t  *addrlen:存储地址结构信息长度变量的地址
返回值:成功->通信套接字的文件描述符,失败返回-1和错误代码

 int  accept(int  sockfd  ,  struct  sockaddr  *addr  ,  socklen_t  *addrlen);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

accept接受连接的方式有两种:
1:不关心客户端的IP地址和端口号时,参数2和参数3设置为NULL
2:若要读取并保存客户端的IP地址和端口号,则需要新建一个socket_in结构体

做完前四步后就可以进行通信了。TCP/IP提供了一种通信方式:send函数和recv函数

5. send

头文件:#include<sys/types.h>
       #include<sys/socket.h>
函数名:send
函数功能:通过socket发送数据
参数1int  sockfd:通信套接字的文件描述符
参数2const  void  *buf :被发送的数据的首地址
参数3: sizeof_t  len:想要发送的字节数(数据长度)
参数4int  flags:默认为0
返回值:ssize_t 类型。成功->返回成功发送的字节数,失败->返回-和错误代码

ssize_t  send(int  sockfd  ,  const  void  *buf  , size_t  len  ,  int  flags);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

6. recv

头文件:#include<sys/types.h>
              #include<sys/socket.h>
函数名:recv
函数功能:通过socket接收数据
参数1int  sockfd :通信套接字的文件描述符
参数2const  void  *buf:用来存储接收的数据的内存首地址
参数3:size_t  len :想要接收的字节数(数据长度)
参数4int  flags:默认为0
返回值:ssize_t类型;成功->返回成功接收道德字节数,失败->返回-1 
              和错误码;0:表示对端执行了一个有序关闭。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

7. close

通信结束,关闭连接
如果malloc申请了空间,则需要free释放
如果打开了文件,则需要关闭fd
最后close(套接字)

以上时搭建TCP服务器的流程,一共分为7个步骤,其中前三个步骤可以打包为一个子函数。

(二)搭建客户端(client)

客户端的搭建较为简单,只需四个步骤:socket-connect-send/recv-close
在这里插入图片描述

1. socket ---->参考上面服务器介绍

2.connect

头文件:#include <sys/types.h>   
              #include <sys/socket.h>
函数功能:发起一个socket的连接请求
参数1int  sockfd:客户端socket函数创建的套接字的文件描述符
参数2const  struct  sockaddr  *addr: 存储服务器的IP地址和端口结构的内存首地址
参数3:socklen_t  addrlen:addr只想的内存空间大小
返回值:int类型:成功->返回0,失败->返回-1和错误码

int connect(int sockfd, const struct sockaddr *addr, socklen_t  addrlen);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.send /recv ---->参考上面服务器介绍

4.close

介绍完客户-服务器的搭建流程,开始写代码

代码实现

server【服务器端】

//服务器端模块化

//编写一子函数,实现监听客户端连接服务器
//参数1:IP地址 char * ip
//参数2:端口号 int port
//返回值:成功->返回监听的socket对象,失败返回-1
int tcp_server(char * ip , int port);
//函数实现
nt tcp_server(char * ip , int port)		//子函数实现
{
	//1.创建socket对象TCP
	//2.绑定自己的IP地址&端口号
	//3.监听是否有人链接
	
	//1.创建socket对象TCP
	int tcp_fd = -1;			//
	tcp_fd = socket(AF_INET , SOCK_STREAM , 0);
	//1.1判断是否绑定成功
	if(0 > tcp_fd)
	{
		perror("socket error!");
		return -1;
	}
	puts("socket success!");
	//2.绑定自己的IP地址&端口号
									//结构体见顶部注释
	struct sockaddr_in myaddr;		//定义一个结构体myaddr,数据类型为struct sockaddr_in
	myaddr.sin_family = AF_INET;			//协议
	myaddr.sin_port = htons(port);	//端口号,atoi()意思是将主机字节序转换为网络字节序
	myaddr.sin_addr.s_addr = inet_addr(ip);//IP地址
	//2.2bind
	if(bind(tcp_fd , (struct sockaddr *)&myaddr , sizeof(myaddr)) < 0)
	{
		perror("bind error!");	//打印错误原因
		close(tcp_fd);			//关闭
		return -1;
	}
	puts("bind success!");
	//3.监听是否有人链接
	if(0 > listen(tcp_fd , 5))				//参数2:监听队列的大小,一般设置为5
	{
		perror("listen error!");
		close(tcp_fd);
		return -1;
	}
	puts("listen success!");
	
	return tcp_fd;				//监听成功,返回socket对象tcp_fd	
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

接下来时accept和通信

//编写一子函数,和客户端进行通信
//参数:accept后新生成的socket对象
//返回值:void 
int tcp_server_communiction(int acp_fd);
//函数实现

int tcp_server_communiction(int acp_fd)
{
	//5.1:收
	//5.1.1:入参判断
	if(0 > acp_fd)
	{
		perror("This acp_fd is NULL!");
		return -1;
	}
	//5.1.2:	acp_fd的格式正确,首先接受client客户端发送的数据
	char buf[1024];							//定义一个数组,存储收发的文件
	memset(buf , 0 , sizeof(buf));			//清空数组
	//read(lst_fd , buf , sizeof(buf));		//将客户端发送的数据通过IO文件操作中的read方式读入到该数组中
	//换第二种文件操作方式:  send和recv,这是TCP/IP自带的一种文件收发方式
	int rec = -1;							//定义一个变量rec用于接收recv()函数的返回值
	rec = recv(acp_fd , buf , sizeof(buf) , 0);//执行接收函数
	//5.1.3:判断是否接收成功
	if(0 > rec)
	{
		perror("recv error!");
		return -1;
	}
	//接收正常,但还要判断是否为空数据
	if(0 < buf)
	{
		//证明数组中接收到了客户端发来的数据
		puts("buf");
		char ncmd[10] = {0};				//用来存放客户端的操作方式:是上传[up]还是下载[down]
		char filename[64] = {0};			//用于存放客户端要操作的对象名字

		//还需要定义一个子函数,以解析客户端发来的命令
		analysis_cmd(buf , ncmd , filename);	//调用命令解析函数,将客户端发送的命令和文件名解析出来,并且存储到前面定义的ncmd[]和filename[]数组中
		printf("%s\n" , ncmd);

		printf("%s\n" , filename);	
		//成功拿到命令和文件名

		//下载文件
		if(0 == strcmp(ncmd , "down"))			//调用字符串比较函数,两个字符串相同则返回0
		{
			//下载文件
			int fd = open(filename , O_RDONLY);//fn:文件名,O_RDONLY:以只读的方式打开文件
			//判断文件是否打开成功
			if(0 > fd)
			{
				//fd为open函数的返回值,小于0表明文件打开失败
				char msg[30] = "文件不存在";
				//调用send函数,将这个字符串发送给client客户端
				send(acp_fd , msg , sizeof(msg) , 0);
				return -1;
			}
			else
			{
				//文件打开成功
				int len = lseek(fd , 0 , SEEK_END);
				printf("要发送的文件长度为:%d\n" , len);
				char fl[30] = {0};
				sprintf(fl , "lend:%d" , len);//将计算到的文件长度输出重定向到fl[]数组中
				//将文件长度先返回给客户端,以便于客户端创建一个同等大小的数组用于接收文件
				send(acp_fd , fl , sizeof(fl) , 0);//发送
				
				//然后准备发送文件
				//由于lseek已经指向文件末尾了,所以需要让他回到文件开头
				lseek(fd , 0 , SEEK_SET);			//SEEK_SET:文件的起始位置
				
				/*大文件需要在栈区申请空间*/
				char *picture = NULL;				//创建一个图片指针
				picture = (char *)malloc(len);			//为这个指针开辟一片内存空间
				//判断是否开辟成功
				if(NULL == picture)
				{
					perror("malloc error!");
					return -1;
				}
				//开辟成功
				//清空
				memset(picture , 0 , sizeof(picture));
				int red = read(fd , picture , len);
				//判断是否读取文件成功 
				if(len == red)						//read()函数,读取文件成功时,会返回文件长度
				{
					//将读取到的文件数据发送给client客户端
					send(acp_fd , picture , sizeof(picture) , 0);
				}

				sleep(5);
				free(picture);
				close(fd);
			}

		}
		else if(0 == strcmp(ncmd , "up"))
		{
			//上传文件
			//首先创建一个数组用来接收客户端发来的文件大小信息
			char msg[30] = {0};
			int ret = -1;
			ret = recv(acp_fd , msg , sizeof(msg) , 0);		//接收客户端的文件信息
			//判断
			if(0 < ret)
			{
				puts(msg);			//客户发来的数组fl已经接收成功,打印出来
									//解析字符串中的数字
			}
			if(strstr(msg , "lenth"))	//通过字符串定位函数,定位到数字的位置
			{
				int len = atoi(msg + 6);//提取字符串中的数字
				//printf("文件大小为:%d\n" , len);
				int fd = -1;		//
				//调用open函数创建一个文件,大小为提取出的len
				fd = open(filename , O_WRONLY | O_CREAT , 0777);
				if(0 > fd)
				{
					puts("open | creat file error!");
					close(fd);
					return -1;
				}
				//接收文件,大文件需要存放在堆区,使用maloc申请,使用完需手动释放
				char *file = NULL;
				file = (char *)malloc(len);
				
				if(NULL == file)
				{
					puts("malloc error");
					close(fd);
					return -1;
				}
				 //大文件无法一次接收完,需要循环接受
				 int t = 0;
				 int rec = -1;
				 while(t < len)
				 {
						memset(file , 0 , len);
						rec = recv(acp_fd , file , len-t , 0);

						if(0 < rec)
						{
							write(fd , file , rec);
							t += rec;
						}
						sleep(1);
				 }
				puts("文件接收成功!");
				free(file);
				close(fd);
			}
		}

	}

	//5.2:发
	/*memset(buf , 0 , sizeof(buf));			//先清空存储数据的数组
	printf("write:");
	fgets(buf , sizeof(buf) , stdin);		//标准输入流:键盘获取数据到存储数据的数组中
	write(acp_fd , buf , strlen(buf));
	close(acp_fd);
	*/
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164

最后是字符串分析函数,通过此函数可以将客户端发来的【命令 文件】分离出来
臂如:down 1.jpg 说明客户端要从服务器下载一张名为1.jpg的图片

//定义一个子函数,解析从客户端接受到的命令
//从该命令(str字符串)中,解析出操作方式up 或 down
//                      解析出要操作的对象 filename
int analysis_cmd(char *str , char *cmd , char *filename);
//参数1:从客户端接收到的字符串
//参数2:用于存储解析出的命令
//参数3:用于存储解析出的文件名
//返回值:无

//函数实现
int analysis_cmd(char *str , char *cmd , char *filename)	//解析命令
{
	//1.入参判断
	if(NULL == str || NULL == cmd  || NULL == filename)
	{
		perror("参数不匹配,请重新输入!");
		return -1;
	}

	char *p = str;							//定义一个指针p指向接收的字符串str的首地址
	while(*p)
	{
		if(' ' == *p)						//命令与文件名之间有一个空格
		{
			break;							//跳出循环
		}
		else
		{
			*cmd = *p;						//从str的起始位置开始,将p指向的字符依次复制cmd
			cmd += 1;						//cmd是一个存储命令的数组,数组名就是数组的首地址,所以cmd+1,表示指针向后移动一个位置
		}
		//跳出while循环时表明已经将命令遍历出来了[解析出操作方式:up或down]
		p += 1;								//p指针向后移动								
	}
	strcpy(filename , p+1);					//这里的p还指在命令与文件名间的空格处,所以需要再向后移动一个位置才是文件名的首地址
											//使用strcpy函数将p指针之后的字符复制给存储filename的数组
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

最后是主函数,调用


int main(int argc ,  char *argv[])
{
	//0.判断main函数传参是否正确
	if(2 > argc)
	{
		perror("请输入正确的参数:IP地址 端口号");
		return -1;
	}
	
	//1.监听socket对象
	int tcp_fd = -1;
	tcp_fd = tcp_server(argv[1] , atoi(argv[2]));

while(1)			//accept,用于将客户端的链接请求与服务器建立TCP通信,并返回一个新的已经建立链接的accept套接字
	{
	//2.接受链接
					//若不关心客户端的IP地址和端口号,则将参数2,3设置为空
					//4.1:定义一个保存客户端IP地址和端口号的结构体并清空
	int lst_fd = -1;
	struct sockaddr_in client;				//创建一个结构体
	memset(&client , 0 , sizeof(client));
					//4.2:定义一个int型变量,保存结构体的大小
	int len = sizeof(client);
					//4.3:接受连接,并且保存客户端的信息
	lst_fd = accept(tcp_fd , (struct sockaddr *)&client , &len);
					//4.4:将网络字节序ip地址转换为主机字节序ip地址
	char * ip = inet_ntoa(client.sin_addr);
					//4.5:网络字节序port转换为主机字节序port
	unsigned short port = ntohs(client.sin_port);
					//4.6:打印出客户端的ip地址和端口号
	printf("client IP = %s , PORT = %d \n" , ip , port);

					//lst_fd = accept(tcp_fd , NULL , NULL);	//不需要获取客户端的ip地址和端口号
					//4.7判断是否链接成功
	if(0 > lst_fd)
	{
		perror("accept error!");
		close(tcp_fd);
		return -1;
	}
	puts("accept success!");

	//do_work()	
	//2.和客户端进行通信
	tcp_server_communiction(lst_fd);
	

	//6.关闭socket对象
	close(tcp_fd);

	return 0;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

client【客户端】

socket+connect,返回connect成功的套接字

//定义一个tcp连接的子函数
//参数1:ip地址  char *
//参数2:端口号  int port
//返回值:成功->返回链接好的socket对象,失败返回-1
int tcp_connect(char * ip , int port);

//函数实现
int tcp_connect(char * ip , int port)		//子函数实现
{
	//1.创建TCP socket
	//2.设置对方的IP和端口号
	//3.请求链接
	
	//1.创建TCP socket
	int tcp_fd = -1;		//初始化为-1
	tcp_fd = socket(AF_INET , SOCK_STREAM , 0);	//IPV4,流式套接字
	//1.1 判断是否创建成功
	if(0 > tcp_fd)
	{
		perror("socket error!");
		return -1;
	}
	puts("socket success!");
	//2.设置对方的IP和端口号[bind]
	//2.1 定义一个结构体变量
	struct sockaddr_in myser;			//类似于c++的实例化对象
										//
	myser.sin_family = AF_INET;			//协议
	myser.sin_addr.s_addr = inet_addr(ip);
	myser.sin_port = htons(port);		//主机字节序转网络字节序
	//3.请求链接
	int ret = -1;
	ret = connect(tcp_fd , (struct sockaddr *)&myser , sizeof(myser)); 	//connect连接
	if(0 != ret)
	{
		perror("connect error!");
		close(tcp_fd);
		return -1;
	}
	puts("connect success!");
	return tcp_fd;						//返回connect成功后新生成的套接字
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

和服务器进行通信

//定义一个子函数,和服务器端进行通信
//参数:创建好的socket对象
//返回值:void
int tcp_client_communiction(int tcp_fd);	//

//函数实现
/*do_work*/
int tcp_client_communiction(int tcp_fd)	//子函数:和服务器进行通信的实现
{
	//入参判断
	if(0 > tcp_fd)
	{
		printf("%s < 0 , connot to communictioning with server!\n" , tcp_fd);
		return -1;
	}
	//4.发送消息:给server发送下载或上传指令
	char buf[50];
	memset(buf , 0 , sizeof(buf));
	gets(buf);				//输入
	send(tcp_fd , buf , strlen(buf) , 0);

	char ncmd[10] = {0};
	char filename[64] = {0};
	analysis_cmd(buf , ncmd , filename);
	//puts(ncmd);
	//puts(filename);
	//sleep(2);

	if(0 == strcmp(ncmd , "down"))
	{

		//send后准备recv,接受服务器的文件
		//定义一个数组,用来接受server发过来的文件长度message
		char msg[30] = {0};
		int ret = -1;
		ret = recv(tcp_fd , msg , sizeof(msg) , 0);
		puts(msg);
		if(0 < ret)
		{
			//接收命令成功
			puts(buf);			//打印buf里接收到的文本字符串[待接收的文件大小]
		
			//判断是否是server发来的错误信息
			if(0 == strcmp(msg , "文件不存在"))
			{
				return 0;
			}
			else if(strstr(msg , "len"))
			{
				int len = atoi(msg + 5);	//提取字符串中的数字
				printf("文件长度为:%d\n" , len);
				//用open函数新建一个文件,大小为len
				int fd = -1;
				fd = open(filename , O_WRONLY | O_CREAT , 0777);
				//判断文件描述符是否创建成功
				if(0 > fd)
				{
					puts("open|creat file error!");
					close(fd);
					return -1;
				}			
				//接收文件
				char *file = NULL;
				file = (char *)malloc(len);
				//判断文件接收空间是否申请成功
				if(NULL == file)
				{
					puts("malloc error!");
					close(fd);
					return -1;
				}

				int rec = -1;
				//一次接收不完,需要循环接收
				int t = 0;
				while(t < len)
				{

					memset(file , 0 , len);
					rec = recv(tcp_fd , file , len-t , 0);
					//recv的成功的返回值是接受到文件的大小
					//判断是否接受成功
					//printf("%d\n" , rec);
					if(0 < rec)
					{
						//puts("进来了");
						write(fd , file , rec);
						t += rec;
					}
					//printf("%d\n",t);
					sleep(1);
				}
				puts("文件接收成功!");
				
				free(file);
				close(fd);
			}
		}
		else if(0 == ret)		//没有接受到任何字符
		{
			return 0;
		}
	}




	else if(0 == strcmp(ncmd , "up"))
	{
		//上传文件
		//文件操作,打开文件
		int fd = open(filename , O_RDONLY);	//以只读的方式打开文件
		//judge
		if(0 > fd)
		{
			//由于是客户向服务器上传文件,所以不必告诉服务器文件是否上传错误,只需告诉客户端就行
			puts("文件打开失败,请重新上传!");
			close(fd);
			return -1;
		}
		//打开成功,需要计算文件长度并发送给服务器
		int lenth = -1;
		lenth = lseek(fd , 0 , SEEK_END);		//调用lseek函数,从0位置跳到文件末尾,计算出文件长度
		printf("主人,您要上传的文件大小为:%d\n" , lenth);
		char fl[30] = {0};
		sprintf(fl , "lenth:%d" , lenth);		//将字符串输出重定向到数组中,发送该数组给服务器
		send(tcp_fd , fl , sizeof(fl) , 0);

		//准备发送文件
		lseek(fd , 0 , SEEK_SET);				//回到文件起始位置
		char *file = NULL;
		file = (char *)malloc(lenth);			//申请空间,将图片存放于此,等待发送
		if(NULL == file)
		{
			perror("malloc error!");
			return -1;
		}
		memset(file , 0 , lenth);
		int red = (fd , file , lenth);
		if(red == lenth)
		{
			//读取成功,准备向服务器发送
			send(tcp_fd , file , lenth , 0);
		}
		
		sleep(5);
		free(file);
		close(fd);
	}
	
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153

同样,客户端也需要一个字符串解析函数,所以直接拷贝服务器里面的函数


//字符串解析函数,解析出文件大小
int analysis_cmd(char *str , char *cmd , char *filename)	//解析命令
{
	//1.入参判断
	if(NULL == str || NULL == cmd  || NULL == filename)
	{
		perror("参数不匹配,请重新输入!");
		return -1;
	}

	char *p = str;							//定义一个指针p指向接收的字符串str的首地址
	while(*p)
	{
		if(' ' == *p)						//命令与文件名之间有一个空格
		{
			break;							//跳出循环
		}
		else
		{
			*cmd = *p;						//从str的起始位置开始,将p指向的字符依次复制cmd
			cmd += 1;						//cmd是一个存储命令的数组,数组名就是数组的首地址,所以cmd+1,表示指针向后移动一个位置
		}
		//跳出while循环时表明已经将命令遍历出来了[解析出操作方式:up或down]
		p += 1;								//p指针向后移动								
	}
	strcpy(filename , p+1);					//这里的p还指在命令与文件名间的空格处,所以需要再向后移动一个位置才是文件名的首地址
											//使用strcpy函数将p指针之后的字符复制给存储filename的数组
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

最后是main函数的调用:

int main(int argc , char *argv[])
{
	//0.首先判断传参是否格式正确
	if(2 > argc)
	{
		printf("传入参数不够,请重新传参!");
		return -1;
	}

	//1.连接服务器
	int tcp_fd = -1;
	tcp_fd = tcp_connect(argv[1] , atoi(argv[2]));	//atoi()将传入的端口号[例如:8888]转化为网络字节序
	

	//do_work
	//2.和服务器端进行通信
	tcp_client_communiction(tcp_fd);
	
	//3.关闭socket对象
	close(tcp_fd);
	
	return 0;	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

为了模块化编写程序,我们还需要把各种库函数封装成一个.h文件

【net.h】

#ifndef _NET_H
#define _NET_H

#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<fcntl.h>

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

代码中用到了一些字符串处理函数:例如strstr()、atoi()、
以及网络IP地址转换函数:htonl()或是htons()

char *strstr(const *str1 , const char *str2)
//从str1中寻找字符串str2第一次出现的位置

  • 1
  • 2
  • 3

结果就是:程序会从第一个xiao处打印后面的字符串在这里插入图片描述
在这里插入图片描述

//atoi()
#include<stdio.h>
#include<stdlib.h>

int main()
{
	char arr1[] = "-1234";
	char arr2[] = "len:569456";
	
	printf("%d\n" , atoi(arr1));
	printf("%d\n" , atoi(arr2+4));//提取字符串中的数字

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述

程序测试

在这里插入图片描述

在不同的文件夹中分别运行客户服务器端的程序,设定IP地址为本地回环地址:127.0.0.1
端口号:8888
分别在服务器目录下存放一张照片,在客户端目录下存放一个.txt文档,然后测试。
首先是客户端需要将服务器的图片下载至自己的目录下,然后再从自己目录上传一个文档至服务器。

连接成功

在这里插入图片描述

首先是图片下载成功

在这里插入图片描述

文件上传成功

在这里插入图片描述
结束!
一点点感悟,网编流程基本不变,服务器与客户端的搭建模式只要记清,剩下的就是文件操作了,只是需要考虑的一些细节很多,稍不注意可能会有Bug,没错,我的程序也有Bug,文件下载完成后客户端会进入最后的while死循环出不来,目前还没检查出来问题所在。TCP/IP这一块确实重要,需要认真学习。我自己也做了一点点笔记,如有需要程序源码和思维导图,请留言邮箱,我必回复。

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/144145
推荐阅读
相关标签
  

闽ICP备14008679号