嵌入式学习DAY30 --- 文件的上传、Udp的服务器和客户端、IO模型

嵌入式入门学习笔记,遇到的问题以及心得体会!
DAY30


概述:

一、文件的上传
二、Udp的服务器和客户端
三、IO模型


一、文件的上传

1、本质:

客户端将本地的文件拷贝给服务器

2、客户端:

0、发送文件头(文件头 = 文件名+文件大小 fileHead = “1.txt#1199”)
1、以只读方式打开一个存在的文件(文件是你自己随机选择的)

While(1{
	(1)读文件
	(2)将读到的内容发送给服务器
      }

2、关闭文件

3、服务器:

0、接收文件头
1、以只写的方式创建并打开文件
While(1)
{
1、接收客户端发来的文件内容
2、将接收到的内容写入文件
}
3、关闭文件

4、send()

ssize_ t send(int socket, const void *buffer, size_ tlength, int flags);
返回值:
成功:实际发送的字节数
失败: -1,并设置errno
头文件:

 #include <sys/socket.h>

buffer:发送缓冲区首地址
length :发送的字节数
flags:发送方式(通常为0)

5、代码展示

1> tcpclient

1.>tcpclient-----------------------------------------------------------代码如下所示:

#include<stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

int main()
{
	//创建套接字
	int sockFd = socket(PF_INET, SOCK_STREAM, 0);
	if(sockFd < 0)
	{
		perror("socket error!");
		return -1;
	}
	printf("socket ok!\n");
	//请求连接
	struct sockaddr_in servAddr = {0};
	servAddr.sin_family = PF_INET;
	servAddr.sin_port = htons(8888);
	servAddr.sin_addr.s_addr = inet_addr("192.168.0.150");
	int ret = connect(sockFd, (struct sockaddr *)&servAddr, sizeof(servAddr));
    if(ret < 0)
	{
		perror("connect error!");
		close(sockFd);
		return -1;
	}
	printf("connect ok!\n");

	char fileName[20] = {0};
	int fileSize = 0;
	char fileHead[50] = {0};
	char buf[1024] = {0};
    //选择要上传的文件
	printf("请输入要上传的文件名;");
	gets(fileName);

	struct stat stBuf = {0};
	//获得文件头
    stat(fileName, &stBuf);
	fileSize = stBuf.st_size;

	sprintf(fileHead, "%s#%d", fileName, fileSize);

	//发送文件头
	ret = send(sockFd, fileHead, sizeof(fileHead), 0);
	if(ret < 0)
	{
		perror("send error!");
		close(sockFd);
		return -1;
	}

	//以只读方式打开文件
	int fd = open(fileName, O_RDONLY);
	if(fd < 0)
	{
		perror("open error!");
		close(sockFd);
		return -1;
	}
	//循环读文件,并将读到的内容发送给服务器
	while(1)
	{
		memset(buf, 0, sizeof(buf));
		ret = read(fd, buf, sizeof(buf));
		if(ret < 0)
		{
			perror("read error!");
			close(sockFd);
			close(fd);
			return -1;
		}
		else if(0 == ret)
		{
			break;
		}
		//读多少,发多少
		int retSend = send(sockFd, buf, ret, 0);
		if(retSend < 0)
		{
			perror("send error!");
			close(sockFd);
			close(fd);
			return -1;
		}
	}
	//关闭文件
	close(fd);
	//关闭套接字
	close(sockFd);
	return 0;
}
2> tcpserver

2.>tcpserver-----------------------------------------------------------代码如下所示:

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>

void getFileInfo(char *fileHead, char *fileName, int *pFileSize)
{
	int i = 0;
	while(fileHead[i] != '#')
	{
		fileName[i] = fileHead[i];
		i++;
	}

	fileName[i] = '\0';
	i++;
	char buf[20] = {0};
	strcpy(buf, &fileHead[i]);

	*pFileSize = atoi(buf);
}

int main()
{
	//创建套接字
	int sockFd = socket(PF_INET, SOCK_STREAM, 0);
	if(sockFd < 0)
	{
		perror("socket error!");
		return -1;
	}
	printf("socket ok!\n");
	//绑定地址信息(ip+port)
	struct sockaddr_in servAddr = {0};
	servAddr.sin_family = PF_INET;
	servAddr.sin_port = htons(8888);
	servAddr.sin_addr.s_addr = inet_addr("192.168.0.150");
	int ret = bind(sockFd, (struct sockaddr *)&servAddr, sizeof(servAddr));
	if(ret < 0)
	{
		perror("bind error!");
		close(sockFd);
		return -1;
	}
	printf("bind ok!\n");
	//监听
	//将主动的套接字变成被动等待连接的套接字
	ret = listen(sockFd, 1);
	if(ret < 0)
	{
		perror("listen error!");
		close(sockFd);
		return -1;
	}
	printf("listening\n");
	//建立连接
	int connFd = accept(sockFd, NULL, NULL);
	if(connFd < 0)
	{
		perror("accept error!");
		close(sockFd);
		return -1;
	}
	printf("accept ok!\n");

	char fileHead[50] = {0};
	char fileName[20] = {0};
	int fileSize = 0;
	char buf[1024] = {0};

	//接收文件头
	ret = recv(connFd, fileHead, sizeof(fileHead), 0);
	if(ret < 0)
	{
		perror("recv error!");
		close(sockFd);
		close(connFd);
		return -1;
	}

	//解析文件头
	getFileInfo(fileHead, fileName, &fileSize);

	printf("fileName = %s, fileSize = %d\n", fileName, fileSize);
	//以只写的方式创建并打开文件
	int fd = open(fileName, O_WRONLY | O_CREAT, 0666);
	if(fd < 0)
	{
		perror("open error!");
		close(sockFd);
		close(connFd);
		return -1;
	}
	//按照文件大小循环接收文件内容并将接收到的内容写入文件
	while(fileSize > 0)
	{
		memset(buf, 0, sizeof(buf));
		ret = recv(connFd, buf, sizeof(buf), 0);
		if(ret < 0)
		{
			perror("recv error!");
			close(sockFd);
			close(connFd);
			close(fd);
			return -1;
		}
		int wrRet = write(fd, buf, ret);
		if(wrRet < 0)
		{
			perror("write error!");
			close(sockFd);
			close(connFd);
			close(fd);
			return -1;
		}
		fileSize -= ret;
	}
	//关闭文件
	close(fd);
	//关闭套接字
	close(sockFd);
	close(connFd);
	return 0;
}

二、Udp的服务器和客户端

1、Udp的服务器:

1、创建套接字 socket
2、绑定IP地址和端口号 bind
3、接收 recvfrom
4、发送 sendto
5、关闭套接字 close

2、Udp客户端:

1、创建套接字 socket
2、发送 sendto
3、接收 recvfrom
关闭套接字 close

3、sendto(),recvfrom()

ssize_ t sendto(int socket, void *message, size_ t length, intflags, struct sockaddr *dest_ addr, socklen t dest len);

ssize_ t recvfrom(int socket, void *buffer, size_ t length, intflags, struct sockaddr *address, socklen t *address_ len);

这两个函数一般在使用UDP协议时使用

4、代码展示:

1> udpclient

1.>udpclient-----------------------------------------------------------代码如下所示:

#include<stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
	if(argc < 2)
	{
		printf("请输入ip\n");
		return -1;
	}
	//创建套接字
	int sockFd = socket(PF_INET, SOCK_DGRAM, 0);
	if(sockFd < 0)
	{
		perror("socket error!");
		return -1;
	}
	printf("socket ok!\n");
	//发送消息
	char buf[1024] = {0};

	struct sockaddr_in servAddr = {0};
	servAddr.sin_family = PF_INET;
	servAddr.sin_port = htons(9999);
	servAddr.sin_addr.s_addr = inet_addr(argv[1]);

	while(1)
	{
		memset(buf, 0, sizeof(buf));
		printf("请输入:");
		gets(buf);
		if(0 == strcmp(buf, "quit"))
		{
			break;
		}
		int ret = sendto(sockFd, buf, sizeof(buf), 0, (struct sockaddr *)&servAddr, sizeof(servAddr));
		if(ret < 0)
		{
			perror("sendto error!");
			close(sockFd);
			return -1;
		}

		memset(buf, 0, sizeof(buf));
		ret = recvfrom(sockFd, buf, sizeof(buf), 0, NULL, NULL);
		if(ret < 0)
		{
			perror("recvfrom error!");
			close(sockFd);
			return -1;
		}
		printf("recv:%s\n", buf);
	}
	//关闭套接字
	close(sockFd);
	return 0;
}
2> udpserver

2.>udpserver-----------------------------------------------------------代码如下所示:

#include<stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>


int main(int argc, char **argv)
{
	if(argc < 2)
	{
		printf("请输入ip地址!\n");
		return -1;
	}
    //创建套接字
	int sockFd = socket(PF_INET, SOCK_DGRAM, 0);
	if(sockFd < 0)
	{
		perror("socket error!");
		return -1;
	}
	printf("socket ok!\n");
	//绑定地址信息
	struct sockaddr_in servAddr = {0};
	servAddr.sin_family = PF_INET;
	servAddr.sin_port = htons(9999);
	servAddr.sin_addr.s_addr = inet_addr(argv[1]);

	int ret = bind(sockFd, (struct sockaddr *)&servAddr, sizeof(servAddr));
	if(ret < 0)
	{
		perror("bind error!");
		close(sockFd);
		return -1;
	}
	printf("bind ok!\n");
	//接收消息
	char buf[1024] = {0};
	struct sockaddr_in cliAddr = {0};
	socklen_t len = sizeof(cliAddr); //必须赋值
	while(1)
	{
		memset(buf, 0, sizeof(buf));
		ret = recvfrom(sockFd, buf, sizeof(buf), 0,(struct sockaddr *)&cliAddr, &len);
		if(ret < 0)
		{
			perror("recvfrom error!");
			close(sockFd);
			return -1;
		}
		printf("recv:%s\n", buf);

		memset(buf, 0, sizeof(buf));
		printf("input:");
		gets(buf);
		ret = sendto(sockFd, buf,sizeof(buf), 0, (struct sockaddr *)&cliAddr, len);
	    if(ret < 0)
		{
			perror("sendto error!");
			close(sockFd);
			return -1;
		}
	}
	//关闭套接字
	close(sockFd);
	return 0;
}

三、IO模型

1、在UNIX/Linux下主要有4种I/O模型:

阻塞I/O:
最常用、最简单、效率最低

非阻塞I/O:
可防止进程阻塞在I/O操作上,需要轮询

I/O多路复用:
允许同时对多个I/O进行控制

信号驱动I/O:
一种异步通信模型

目标:理解并掌握IO多路复用的原理(非常重要)
IO多路:同时有多个输入输出
复用:一个东西可以被用作多个功能,但是同一时刻只能用其中的一个功能

IO多路复用:可以处理多路IO,有IO操作需要,就操作哪路IO

Int main()
{
	Gets(buf);  //等待用户从终端输入
	Accept();  //等待客户端连接
}

上面的代码面临的问题是有客户端来连接,但是终端没有输入,所以就会一直阻塞在gets,导致不能和客户端连接成功,有问题

如何解决这个问题?

Int main()
{
    //一般select在阻塞,但是只要有客户端来连接或者终端有输入,select就会返回
	Select()
	If(终端输入)
	{
		Gets(buf);  //等待用户从终端输入
	}
	Else if(客户端来连接了)
	{
		Accept();  //等待客户端连接
	}
}

2、Select的工作原理:

1、创建一个文件描述符的集合,这个集合的大小是1024个位,128个字节

  Typedef Struct 
{
     Long long arr[16];
} fd_set;

1024个位:因为有1024个文件描述符,一个位对应一个文件描述符,刚好一一对应
也就是是第几位就是哪个文件描述符

2、将集合清空
1024个位全都是0 00000000000000000000000000000000000000

3、将要操作的文件描述符加入集合
将0和sockFd 3加入集合
100100000000000000000000000000000

4、将集合交给select
Select是一个系统调用函数,将这个从用户空间先拷贝到内核空间,内核会去对集合进行轮询(遍历的次数最大的文件描述符的值+1次),如果0和3都没有任何反应(没有终端输入也没有客户端来连接),那么集合还是保持不变,而且select阻塞
但是只要0和3其中有一个有反应了,比如终端有输入了,或者有客户端来连接了,那么内核会将其中没有反应的文件描述符的位清0,有反应的文件描述符保持不变,select返回,并且将修改后的集合从内核空间拷贝给用户空间
所以,如果是0准备好了,此时用户空间得到的集合就是:
100000000000000000000000000000000

5、遍历集合,看哪一位还是1((遍历的次数最大的文件描述符的值+1次))

6、看为1的这一位到底是谁,然后进行对应的IO操作

为了设置文件描述符我们要使用几个宏:

FD_ SET 将fd加入到fdset
FD_ CLR 将fd从fdset里面清除
FD_ ZERO从fdset中清除所有 的文件描述符
FD_ ISSET判断fd是否在fdset集合中

宏的形式:

void FD ZERO(fd_ set * fdset)
void FD_ SET(int fd,fd_ set *fdset)
voidFD_ CLR(int fd,fd_ set *fdset)
int FD ISSET(int fd,fd set *fdset)

int select(int n, fd_ set *read_ fds, fd_ set *write_ fds, fd_ _set*except_ fds, struct timeval *timeout);

参数:n 遍历的次数 最大的文件描述符+1
Read_fds:读集合
Write_fds:写集合
Except_fds:异常集合
返回值:失败 -1
成功:准备好的文件描述符的个数

3、代码展示:

1> tcpclient.c

1.>tcpclient.c-----------------------------------------------------------代码如下所示:

#include<stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>


int main()
{
	//创建套接字
	int sockFd = socket(PF_INET, SOCK_STREAM, 0);
	if(sockFd < 0)
	{
		perror("socket error!");
		return -1;
	}
	printf("socket ok!\n");
	//请求连接
	struct sockaddr_in servAddr = {0};
	servAddr.sin_family = PF_INET;
	servAddr.sin_port = htons(8888);
	servAddr.sin_addr.s_addr = inet_addr("192.168.0.150");
	int ret = connect(sockFd, (struct sockaddr *)&servAddr, sizeof(servAddr));
    if(ret < 0)
	{
		perror("connect error!");
		close(sockFd);
		return -1;
	}
	printf("connect ok!\n");
	//发送消息
	char buf[1024] = {0};
	gets(buf);
	ret = write(sockFd, buf, sizeof(buf));
	if(ret < 0)
	{
		perror("write error!");
		close(sockFd);
		return -1;
	}
	//关闭套接字
	close(sockFd);
	return 0;
}

2> tcpserver.c

2.>tcpserver.c-----------------------------------------------------------代码如下所示:

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/select.h>

/*
 * TCP服务器
 * 接收客户端发送的消息并打印
 */
int main()
{
	//创建套接字
	int sockFd = socket(PF_INET, SOCK_STREAM, 0);
	if(sockFd < 0)
	{
		perror("socket error!");
		return -1;
	}
	printf("socket ok!\n");
	//绑定地址信息(ip+port)
	struct sockaddr_in servAddr = {0};
	servAddr.sin_family = PF_INET;
	servAddr.sin_port = htons(8888);
	servAddr.sin_addr.s_addr = inet_addr("192.168.0.150");
	int ret = bind(sockFd, (struct sockaddr *)&servAddr, sizeof(servAddr));
	if(ret < 0)
	{
		perror("bind error!");
		close(sockFd);
		return -1;
	}
	printf("bind ok!\n");
	//监听
	//将主动的套接字变成被动等待连接的套接字
	ret = listen(sockFd, 1);
	if(ret < 0)
	{
		perror("listen error!");
		close(sockFd);
		return -1;
	}


	//创建文件描述符的集合
	fd_set rfds;
	//清空集合
	FD_ZERO(&rfds);
	//将要操作的文件描述符加入集合
	FD_SET(0, &rfds);
	FD_SET(sockFd, &rfds);

	int maxFd = sockFd;
	while(1)
	{
		fd_set tmp = rfds;
		//select
		printf("before select....\n");
		ret = select(maxFd+1, &tmp, NULL, NULL, NULL);
		if(ret < 0)
		{
			perror("select error!");
			continue;
		}
		int i;
		for(i = 0; i < maxFd+1; i++)
		{
			//遍历集合,看哪一位还是1
			if(FD_ISSET(i, &tmp))
			{
				//判断为1的是谁
				if(0 == i)
				{
					char buf[1024] = {0};
					//进行实际的IO操作
					gets(buf);
				}
				else if(i == sockFd)
				{
					//建立连接
					int connFd = accept(sockFd, NULL, NULL);
					if(connFd < 0)
					{
						perror("accept error!");
						close(sockFd);
						return -1;
					}
					printf("accept ok!\n");
					char buf[1024] = {0};
					//接收消息
					ret = read(connFd, buf, sizeof(buf));
					if(ret < 0)
					{
						perror("read error!");
						close(sockFd);
						close(connFd);
						return -1;
					}
					printf("recv:%s\n", buf);
					//关闭套接字
					close(connFd);

				}
			}
		}
	}
	close(sockFd);
	return 0;
}

如若有错误之处还望指出,共同学习!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值