C/C++用socket实现简单的TCP文件传输

news2024/12/26 5:25:27

C/C++:用socket实现简单的TCP文件传输

  • 网络中进程之间如何进行通信
  • socket是什么
  • socket的基本操作
    • socket()函数
    • bind()函数
    • listen()、connect()函数
    • accept()函数
    • recv()/send()函数
    • close()函数
  • TCP的“三次握手”
      • “三次握手”的作用
  • TCP的“四次挥手”
      • 四次挥手的一些注意事项
  • 代码实现
    • 文件结构
    • 项目结构
      • socket连接建立
      • 文件传输
      • 服务器主程序
      • 客户端主程序
    • 一些注意事项
      • 防止重复包含和重复定义
      • 文件传输时超出缓冲区大小
  • 运行及结果
  • 结尾

网络中进程之间如何进行通信

进程通信的首要问题是如何唯一标识一个进程

  • 本地可以通过进程PID来唯一标识一个进程
  • 网络中ip可以唯一标识一台主机;“协议 + 端口”可以唯一标识主机中的进程

因此,通过三元组(ip地址,协议,端口)就可以唯一标识网络中的进程了

使用TCP/IP协议的应用程序通常采用应用程序接口UNIX BSD的套接字(socket)来实现网络进程之间的通信,本文中的程序便是采用socket来实现客户端与服务器之间的通信

socket是什么

socket指一种在两端(一般是服务器-客户端)之间建立连接、传输数据的一种方式或一种约定(接口)
socket起源于Unix,Unix/Linux的特点是把文件作为不分任何记录的字符流进行存取,文件、文件目录和设备具有相同的语法语义和相同的保护机制,即“一切皆文件”,因此可以把socket看成是一种特殊的文件,socket函数则是对这种文件进行的操作(读写I/O、开启、关闭)

socket的基本操作

socket()函数

用于创建一个唯一标识的socket描述字,后续的操作以其为参数,通过它来进行一些读写操作,对应文件的打开操作

#include<WinSock2.h>
int socket(int domain,int type,int protocol);
/*domain:协议族,决定了socket的地址类型
常用的协议族有AF_INET(IPV4)、AFINET6(IPV6)、AF_LOCAL(本地通信)等
*/
/*
type:指定socket类型
常用的socket类型有SOCK_STREAM(面向连接的套接字,基于TCP协议)、SOCK_DGRAM(无连接的套接字,基于UDP协议)
*/
/*
protocol:指定协议
常用的协议有IPPROTO_TCP、IPPROTO_UDP、IPPRPTO_SCTP、IPPRPTO_TIPC
protocol为0时会自动选择type对应的默认协议
*/

socket()创建的socket描述字存在于协议族空间中,但没有一个具体地址,需要使用bind()函数给它赋一个地址

bind()函数

服务器需要通过调用bind()函数来绑定一个地址(如ip地址+端口号)使其能够被客户端连接并向客户端提供服务
客户端不需要指定,系统自动分配一个端口号和自身的ip地址结合

#include<WinSock2.h>
int bind(SOCKET sockfd,const struct sockaddr *addr,socklen_t addrlen);
/*
sockfd:socket描述字
addr:指向要绑定给sockfd的协议地址
addrlen:对应地址的长度
*/

其中需要注意,bind()中的addr需要转换成网络字节序

/*   bind()之前对协议地址的设定   */
struct sockaddr_in addr:
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);// htons():把本地字节序改成网络字节序
	addr.sin_addr.S_un.S_addr = ADDR_ANY;

不同的CPU有不同的字节序类型,分为大端字节序和小端字节序,网络字节序则为大端字节序,如果字节序类型不对应而未进行转换则会出现不可预知的问题,因此为保险起见建议本地字节序通过htons()转换成网络字节序后再赋给socket

listen()、connect()函数

#include<WinSock2.h>
int listen(int sockfd,int backlog);
int connect(SOCKET sockfd,const struct sockaddr *addr,socklen_t addr);
/*
backlog:相应socket可以排队的最大连接个数
*/

socket()创建的socket默认为主动类型的,listen()将socket变为被动类型的,等待客户的连接请求
客户端通过调用connect()来建立与TCP服务器的连接

accept()函数

connect()之后客户端向TCP发送了一个连接请求,TCP服务器监听到该请求后调用accept()接收请求,连接建立完成,之后可以进行网络I/O操作,类似文件的读写I/O操作

#include<WinSock2.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t addrlen);

如果accept()成功,则返回值为一个由内核自动生成的一个全新的描述字,代表返回客户的TCP连接0

注意:accept()中的sockfd参数为服务器的socket描述字,是服务器调用socket()生成的,称为监听socket描述字,在服务器的生命周期中一直存在;accept()返回的socket描述字则是内核自动生成的已连接的描述字,仅仅在该客户端与服务器之间的连接过程中存在,当服务器完成了对该客户的服务之后就被关闭

recv()/send()函数

#include<WinSock2.h>
int recv(SOCKET sockfd,char *buf,int len,int flags);
//buf:指向用于接收传入数据的缓冲区的指针,一般设定大小为1500字节(网络协议规定传输的最大单元)
//len:buf参数指向的缓冲区的长度(以字节为单位)
//flags:一组影响此函数行为的标志,一般置为0

若未发生错误,则recv()函数返回收到的字节数,buf指向的缓冲区将包含接收到的数据,如果连接正常关闭,则返回值为0,否则返回SOCKET_ERROR值,可以通过调用WSAGetLastError来检索特定的错误代码

#include<WinSock2.h>
int send(SOCKET sockfd,char *buf,int len,int flags);
//buf:指向包含要传输数据的缓冲区的指针
//len:buf参数指向的缓冲区的长度(以字节为单位)

若未发生错误,则send()函数将返回发送的总字节数,该字节数可能小于在len参数中请求发送的数量,否则将返回SOCKET_ERROR值,同样可以通过调用WSAGetLastError检索指定的错误代码

close()函数

完成读写操作后,使用close()函数关闭socket描述字,对应文件的关闭操作

#include<unistd.h>
int close(int fd);

注意:close操作只是使相应socket描述字的引用次数减1,只有当引用计数器为0的时候,才会触发TCP客户端向服务器发送终止连接请求

TCP的“三次握手”

TCP连接中的“三次握手”:

  • 第一次握手:客户端向服务端发送SYN J,此时客户端处于SYN_Send状态
  • 第二次握手:服务器向客户端响应一个SYN K,并对J进行确认ACK J = J + 1,此时服务器处于SYN_Revd状态
  • 第三次握手:客户端向服务器发送确认ACK K = K + 1,此时客户端处于established状态

socket中“三次握手”发生的位置

  • 第一次握手:客户端调用connect(),触发连接请求,向服务器发送SYN J包,connect()进入阻塞状态
  • 第二次握手:服务器监听到连接请求,调用accept()接受请求并向客户端发送SYN K、ACK J,accept()进入阻塞状态
  • 第三次握手:客户端收到服务器发送的SYN K、ACK J后connect()返回(return);服务器收到ACK K后accept()返回

“三次握手”的作用

  • 确认双方的接收能力、发送能力是否正常
  • 指定自己的初始化序列号,为后面的可靠传送做准备
  • https协议中“三次握手”的过程中会进行数字证书的验证和加密秘钥的生成

注意:第三次握手,由于客户端已经处于established状态,因此可以携带数据,前两次握手是不能携带数据的

TCP的“四次挥手”

TCP连接中的“四次挥手”:

  • 第一次挥手:客户端发送FIN报文,包含序列号seq N = 初始值 + N(N:已传数据字节数),此时客户端处于FIN_WAIT1状态
  • 第二次挥手:服务端发送确认报文,包含ACK N = N + 1,seq M = 初始值 + M(M:已传数据字节数),此时服务端处于CLOSE_WAIT状态
  • 第三次挥手:服务器向客户端发送FIN报文,包含ACK N = N + 1,seq W = 初始值 + W(W:已传数据字节数),此时服务器处于LAST_ACK状态
  • 第四次挥手:客户端收到FIN报文后发送ACK报文作为应答,ACK W = W + 1,此时客户端处于TIME_WAIT状态,确保服务器收到确认报文后进入CLOSE状态;服务器收到ACK报文后则关闭,处于CLOSE状态
    注意:“四次挥手”过程中可能仍在传输数据,因此,N、M、W的值可能不同

socket中的“四次握手”发生的位置:

  • 第一次挥手:应用进程调用close()主动关闭连接,TCP发送FIN N
  • 第二次挥手:服务器接收到N后执行被动关闭,对N进行确认,该接收也作为文件结束符传递给服务器
  • 第三次挥手:执行被动关闭后,服务端调用close()关闭其socket,TCP向客户端发送FIN W
  • 第四次挥手:客户端接收到FIN报文后对其进行确认报文,确保服务器收到确认报文后进入CLOSE状态

四次挥手的一些注意事项

  • TCP双向通道相互独立,提供了连接的一方在结束它的发送后还能接收来自另一端的数据,因此服务端确认报文和FIN报文中seq的值可能会不同
  • 在socket网络编程中,执行close()会触发内核发送FIN报文,由用户态决定什么时候调用close(),如果服务器有大量数据需要处理,则服务器会等数据全部处理完后再调用close();ACK报文则是由系统内核来决定的,过程比较快,因此ACK报文和FIN报文不能合并发送
  • TIME_WAIT的等待时长是2MSL(Maximum Segment Life Time,报文最大生成时间),即数据报一来一回需要的最大时间,从客户端发送ACK报文开始计时。如果期间内服务器没有收到ACK报文则会重发FIN报文段,FIN报文段在TIME_WAIT期间被接收到,则客户端重置等待时间;若TIME_WAIT等待时间内未收到数据报,则客户端进入CLOSE状态

代码实现

本文中代码采用C++语法进行编写,但未采用面向对象的编程思想,因此代码结构仍然是封装成一个个功能函数,并未封装成对象,后续会自行改进,本文中就不贴出来了

文件结构

fileTransfer
fileTransfer/clien
fileTransfer/server
fileTransfer/tcpSocket

项目结构

在VS中创建解决方案,客户端和服务器为两个不同的项目用以分别运行进行测试
VS项目结构

socket连接建立

/*------------------------------tcpSocket.h------------------------------*/
#ifndef _TCPSOCKET_H_
#define _TCPSOCKET_H
#include<stdbool.h>
#include<iostream>
#include<WinSock2.h>	//头文件
#pragma comment(lib,"ws2_32.lib")	//库文件
#pragma warning(disable:4996)//兼容问题,用于让VS忽略安全检查

#define err(errMsg)	cout<<errMsg<<"failed,code "<<WSAGetLastError()<<" line:"<<__LINE__<<endl;
#define PORT 8401	//0-1024为系统保留


//初始化网络库
bool init_Socket();

//关闭网络库
bool close_Socket();

//服务器:创建服务器socket
SOCKET create_serverSocket();

//客户端:创建客户端socket
SOCKET create_clientSocket(const char *ip);
#endif
/*------------------------------tcpSocket.cpp------------------------------*/
#include<iostream>
#include"tcpSocket.h"
#include<WinSock2.h>

using namespace std;

bool init_Socket()
{
	//初始化代码
	WORD wVersion = MAKEWORD(2, 2);
	//MAKEWORD:将两个byte型合成一个word型,一个在高八位,一个在低八位
	//MAKEWORD(1,1)只能一次接收一次,不能马上发送,只支持TCP/IP协议,不支持异步
	//MAKEWORD(2,2)可以同时接收和发送,支持多协议,支持异步
	WSADATA wsadata;
	if (0 != WSAStartup(wVersion, &wsadata))	//WSA:widows socket ansyc	windows异步套接字
	{
		err("WSAStartup");
		return false;
	}
	//return true;
}

bool close_Socket()//反初始化操作
{
	if (0 != WSACleanup())
	{
		err("WSACleanup");
	}
	return true;
}

SOCKET create_serverSocket()
{
//1.创建一个空的socket
	//socket()无错误发生则返回引用新套接口的描述字,否则返回INVALID_SOCKET错误
	SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
	if (INVALID_SOCKET == fd)
	{
		err("socket");
		return INVALID_SOCKET;
	}
	//AF_INET:指定地址协议族,INET指IPV4
	//SOCK_STREAM:代表流式套接字
	//IPPROTO_TCP:指定使用TCP/IP中的协议,此处指定使用TCP协议

//2.给socket绑定本地ip地址和端口号
	struct sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_port = htons(PORT);	//htons():把本地字节序转成网络字节序
		addr.sin_addr.S_un.S_addr = ADDR_ANY;	//绑定本地任意ip
//3.bind绑定端口
		if (SOCKET_ERROR == bind(fd, (struct sockaddr*)&addr, sizeof(addr)))
		{
			err("bind");
			return INVALID_SOCKET;
		}

//4.开始监听
		listen(fd, 10);	//同时允许10个用户进行访问
	return fd;
}


SOCKET create_clientSocket(const char *ip)
{
	//1.创建一个空的socket
	SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == fd)
	{
		err("socket"); 
		return INVALID_SOCKET;
	}

//2.给socket绑定服务端ip地址和端口号
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);	//htons():把本地字节序转成网络字节序
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");	//绑定服务器ip,此处与本机另一进程通信,故ip为本机地址127.0.0.1
	if (INVALID_SOCKET == connect(fd, (struct sockaddr*)&addr, sizeof(addr)))
	{
		err("connect");
		return INVALID_SOCKET;
	}

	return fd;
}

文件传输

/*------------------------------fileOperation.h------------------------------*/
#ifndef __FILEOP_H_
#define __FILEOP_H_

#include"tcpSocket.h"
#define _CRT_SECURE_NO_WARNINGS//兼容问题可能会报错,使用该行让VS忽略安全检测

/***服务端***/
//发送文件
bool sendFile(SOCKET s, const char* fileName);

/***客户端***/
//接受文件
bool recvFile(SOCKET s, const char* fileName);

#endif
/*------------------------------fileOperation.cpp------------------------------*/
#include"fileOperation.h"
#include<iostream>


using namespace std;

long bufSize = 10*1024;	//缓冲区大小
char* buffer;	//缓冲区保存文件数据
//long recvSize = 10000000;
//char* recvBuf;

bool sendFile(SOCKET s, const char* fileName)
{
	FILE* read = fopen(fileName, "rb");
	if (!read)
	{
		perror("file open failed:\n");//输出描述性错误信息
		return false;
	}

//获取文件大小
	fseek(read, 0, SEEK_END);	//将文件位置指针移动到最后
	bufSize = ftell(read);	//ftell(FILE *stream):返回给定流stream的当前文件位置,获取当前位置相对文件首的位移,位移值等于文件所含字节数
	fseek(read, 0, SEEK_SET);	//将文件位置指针移动到开头
	cout<<"filesize:"<<bufSize<< endl;

//把文件读到内存中来
	buffer= new char[bufSize];
	cout << sizeof(buffer) << endl;
	if (!buffer)
	{
		return false;
	}

	int nCount;
	int ret = 0;
	while ((nCount = fread(buffer, 1, bufSize, read)) > 0)	//循环读取文件进行传送
	{
		ret += send(s, buffer, nCount, 0);
		if (ret == SOCKET_ERROR)
		{
			err("sendFile");
			return false;
		}
	}
	shutdown(s, SD_SEND);
	recv(s, buffer, bufSize, 0);
	fclose(read);

	cout << "send file success!"<<" Byte:"<<ret << endl;
	system("pause");
	return true;
}

bool recvFile(SOCKET s, const char* fileName)
{

	if (buffer == NULL)
	{
		buffer = new char[bufSize];
		if (!buffer)
			return false;
	}
//	创建空文件
	FILE* write = fopen(fileName, "wb");
	if (!write)
	{
		perror("file write failed:\n");
		return false;
	}

	int ret = 0;
	int nCount;
	while ((nCount = recv(s, buffer, bufSize, 0)) > 0)	//循环接收文件并保存
	{
		ret += fwrite(buffer,nCount, 1, write);
	}
	if (ret == 0)
	{
		cout << "server offline" << endl;
	}
	else if(ret < 0)
	{
		err("recv");
		return false;
	}
	cout << "receive file success!" << endl;

	fclose(write);
	cout << "save file success! Filename:"<<fileName << endl;
	system("pause");
	return true;
}

服务器主程序


#include"../tcpSocket/tcpSocket.h"
#include"../tcpSocket/fileOperation.h"
#include<iostream>

#include<string>

using namespace std;
char* sendBuf;

int main()
{
	init_Socket();

	SOCKET serfd = create_serverSocket();//创建服务器socket(该socket仅用于监听)
	cout << "server create success,wait client connect..." << endl;

	//等待客户端连接
	sockaddr_in caddr;
	caddr.sin_family = AF_INET;
	int caddrlen = sizeof(sockaddr_in);

	SOCKET clifd = accept(serfd, (sockaddr*)&caddr, &caddrlen);	//该socket用于与客户端进行连接
	if (clifd == INVALID_SOCKET)
	{
		err("accept");
	}
	cout << "connect success" << endl;
	//可以与客户端进行通信
	char fileName[100] = { 0 };
	cout << "please input the full path of the file: "<<endl;
	cin >> fileName;
		sendFile(clifd,fileName);


	closesocket(clifd);
	closesocket(serfd);
	close_Socket();
	
	return 0;
}

客户端主程序

#include"../tcpSocket/tcpSocket.h"
#include"../tcpSocket/fileOperation.h"
#include<iostream>


using namespace std;

int main()
{
	init_Socket();
	SOCKET fd = create_clientSocket("127.0.0.1");
	cout << "connect success!" << endl;

//接收服务器传输的数据
	char fileName[100] = { 0 };
	cout << "input filename to save:" << endl;
	cin>>fileName;
	recvFile(fd, fileName);

	closesocket(fd);
	close_Socket();
	
	return 0;
}

一些注意事项

防止重复包含和重复定义

  • 头文件被重复包含
    在较大型项目中,#include文件时一个头文件可能被多次包含,例如文件A中include了1.h,文件B中include了1.h和2.h,而文件C中include了文件A和文件B,则头文件1.h被重复包含
    解决方法:
  1. 使用 ifndef-define-endif
#ifndef xxx.h
#define xxx.h
...
#endif

  1. windows平台下可以使用如下代码:
#pragma once
  • 变量被重复定义
    工程中每个cpp文件都是独立解释的,每个cpp文件生成独立的标识符,在编译器链接时会将工程中所有的符号整合在一起,由于文件中有重名变量,会出现重复定义的错误
    解决方法:

在cpp文件中声明变量,然后建一个头文件在所有变量的声明前加上extern(此处不要对变量进行初始化),然后在其他需要使用全局变量的cpp文件中包含该头文件

文件传输时超出缓冲区大小

单次传输文件时如果只使用一次send/recv函数,一旦文件大小超出缓冲区大小,则传输的文件会不全出现错误
解决方法:

发送和接收文件的时候将TCP传输的字节流分成多个TCP报文传输

while ((nCount = fread(buffer, 1, bufSize, read)) > 0)	//循环读取文件进行传送
	{
		ret += send(s, buffer, nCount, 0);
		if (ret == SOCKET_ERROR)
		{
			err("sendFile");
			return false;
		}
	}
while ((nCount = recv(s, buffer, bufSize, 0)) > 0)	//循环接收文件并保存
	{
		ret += fwrite(buffer,nCount, 1, write);
	}

运行及结果

首先运行server.cpp,等待客户端程序连接
server.cpp_WaitForConnect
再运行客户端程序,服务器与客户端建立连接,此时在服务器进程选择server.cpp所处文件夹下的1.jpg作为发送文件
server/1.jpg
server.cpp_ChooseSendFile
客户端程序client.cpp运行并连接成功后询问接收到的文件命名成什么,此处我选择命名成2.jpg
client.cpp_ChooseFileName
命名完文件后回车运行,文件保存成功后返回文件名
在这里插入图片描述
查看client文件夹中确实存在2.jpg且完整接收保存
client/2.jpg
本文中的代码也保存到了github上,代码链接

结尾

我对于socket网络编程这一块还只能算稍有了解,在学习过程中借鉴了许多文章,许多问题也都是网上查询找到的解决方案,实际的相关原理可能只是一知半解,撰写本文也主要是为了对于网络编程这一块有更深的理解,对项目内容有更全面的认识,故有一些理解不清晰或者理解存在问题的还望各位读者大佬不吝赐教

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/745771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【附3.7安装包】python安装包下载及安装(超详细)

python3.7链接&#xff1a;https://pan.baidu.com/s/1Ett3XBMjWhkVOxkOU8NRqw?pwdqz3l 提取码&#xff1a;qz3l 今日资源&#xff1a;Python 适用系统&#xff1a;WINDOWS ​ Python 3.7.0 软件介绍&#xff1a; Python是一款通用型的计算机程序设计语言&#xff0c;Pytho…

【Leetcode】19. 删除链表的倒数第N个节点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 进阶&#xff1a;你能尝试使用一趟扫描实现吗&#xff1f; 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&…

有没有线上兼职的副业,在家可以做什么赚钱

线上兼职副业啊&#xff0c;确实是一个不错的副业选择&#xff01;不过&#xff0c;我得提醒你&#xff0c;如果想要那种点一下鼠标就会下金币的神仙项目&#xff0c;这样的期待的话&#xff0c;那我只能告诉你&#xff0c;你可能走错地方了&#xff0c;在这帮不了你。 在线上赚…

LeetCode[470]用Rand7()实现Rand10()

难度&#xff1a;Medium 题目&#xff1a; 给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数&#xff0c;试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数。 你只能调用 rand7() 且不能调用其他方法。请不要使用系统的 Math.random() 方法。 每个测试用例将有一个内部…

Python案例分析|基于模块的库存管理系统

本案例是通过一个多模块的库存管理系统案例&#xff0c;帮助大家深入了解基于模块的Python应用程序的开发流程。 01、库存管理系统API设计 本文实现一个简单的基于模块的库存管理系统。系统采用JSON文件来保存数据。产品信息设计为字典&#xff0c;键为sku_id&#xff08;产品…

软件工程——第13章软件项目管理知识点整理(完结)

本专栏是博主个人笔记&#xff0c;主要目的是利用碎片化的时间来记忆软工知识点&#xff0c;特此声明&#xff01; 文章目录 1.管理的定义&#xff1f; 2.软件项目管理地位&#xff1f;&#xff08;重要性&#xff09; 3.软件项目管理过程从一组项目计划活动开始&#xff0c…

怎么制作查询成绩的网页?这个不用代码的方法你用过没?

作为一名老师&#xff0c;与家长沟通交流是日常工作中重要的一部分。特别是每次考完试后&#xff0c;家长都急切地想了解孩子的成绩&#xff0c;以便能及时了解孩子的学习情况并给予适当的支持和指导。然而&#xff0c;为了保护学生的隐私&#xff0c;大部分学校不公开张榜学生…

字符设备驱动led灯实验

应用程序代码 #include<stdio.h> #include<string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h>int main (int argc, const char *argv[]) {char buf[128] &quo…

马上就要到2023年秋招的黄金时期了,计算机专业面试究竟需要注意些什么?

说说自己&#xff0c;不算很突出。本人 17 年就读于一所普通的本科学校&#xff0c;20 年 6 月在三年经验的时候顺利通过校招实习面试进入大厂&#xff0c;现就职于某大厂安全实验室。 所以我还是比较有话语权的吧。 话不多说直接进入主题。 1.理论知识准备&#xff1a;复习计算…

驱动 day9 作业

要求&#xff1a; 现象 按下key1 led1灯的状态取反&#xff0c;number的值取反&#xff0c;然后应用程序打印从内核读到的number的值 应用程序test.c #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #incl…

Bash 有效电话号码

193 有效电话号码 给定一个包含电话号码列表&#xff08;一行一个电话号码&#xff09;的文本文件 file.txt&#xff0c;写一个单行 bash 脚本输出所有有效的电话号码。 你可以假设一个有效的电话号码必须满足以下两种格式&#xff1a; (xxx) xxx-xxxx 或 xxx-xxx-xxxx。&…

三维重建以及神经渲染中的学习(三)

三维重建以及神经渲染中的学习 公众号AI知识物语 本文内容为参加过去一次暑期课程学习时的笔记&#xff0c;浅浅记录下。 三维图形可控生成&#xff1a; 1&#xff1a;学习一个图形生成模型 2&#xff1a;具有可控三维变量&#xff1a;1物体形状&#xff1b;2物体位置&…

ScannerException: while scanning for the next token found character ‘@‘ 问题解决

问题描述 后端项目启动的时候报ScannerException: while scanning for the next token found character ‘‘ 异常&#xff0c;自己有些疑问&#xff0c;项目前一会都还可以&#xff0c;到报错的过程中&#xff0c;项目都没有动过。 解决办法 重新刷新项目就解决了。

RxSwift 使用方式

背景 最近项目业务&#xff0c;所有模块已经支持Swift混编开发&#xff0c;正在逐步使用Swift 方式进行开发新业务&#xff0c;以及逐步替换老业务方式进行发展&#xff0c;所以使用一些较为成熟的Swift 的三方库&#xff0c;成为必要性&#xff0c;经过调研发现RxSwift 在使用…

HarmonyOS/OpenHarmony应用开发-程序包多HAP机制(上)

一、多HAP机制设计目标 方便开发者模块化的管理应用&#xff0c;好的应用一般都是模块化管理&#xff0c;模块之间属于松耦合关系。多HAP方便了开发者将业务划分成多个模块&#xff0c;每个模块放到独立的HAP中。例如支付类应用&#xff0c;有统一的主界面&#xff0c;主界面管…

命令注入(Command Injection)安全漏洞(SQL注入、LDAP注入、OS命令注入、XPath注入、JavaScript注入)

文章目录 命令注入&#xff08;Command Injection&#xff09;发生场景示例防范手段其他类型命令注入漏洞1. SQL注入&#xff08;SQL Injection&#xff09;2. LDAP注入&#xff08;LDAP Injection&#xff09;3. OS命令注入&#xff08;OS Command Injection&#xff09;4. XP…

VectorCAST对外部函数打桩和查看覆盖率

一、对外部函数打桩 在单元测试中&#xff0c;如果要调用到外部函数调用的时候&#xff0c;就要对外部函数进行打桩。 对外部函数进行打桩的目的&#xff0c;一方面是为了验证外部函数接口的正确性&#xff0c;另一方面是对外部函数打桩 之后就可以自定义外部函数返回值。 对…

Unity5.4.1打砖块游戏Breakout_Game_Starter_Kit

Unity5.4.1打砖块游戏Breakout_Game_Starter_Kit 童年的回忆 项目地址&#xff1a;https://download.csdn.net/download/Highning0007/88042779

vue 使用 npm run dev命令后 自动打开浏览器为谷歌

文章目录 需求分析 需求 vue 启动后&#xff0c;想要其自动打开指定浏览器&#xff08;谷歌&#xff09;并设置要打开的IP地址和端口号 分析 package.json 打开package.json文件加上 --open chrome index.js 打开index.js文件&#xff0c;将浏览器设置为自动打开

【力扣刷题 | 第十七天】

目录 前言&#xff1a; 55. 跳跃游戏 - 力扣&#xff08;LeetCode&#xff09; 45. 跳跃游戏 II - 力扣&#xff08;LeetCode&#xff09; 总结&#xff1a; 前言&#xff1a; 今天两道类型都是贪心算法&#xff0c;希望可以有所收获 55. 跳跃游戏 - 力扣&#xff08;LeetC…