网络 TCP协议(C++代码|通过tcp协议实现客户端与服务端之间的通信)

2023-05-16

目录

  • TCP通信编程
    • 各端的操作流程:
      • 服务端操作流程:
      • 客户端操作流程:

推荐先学习UDP协议在学习TCP协议
在UDP协议博客中讲解得更详细,看懂UDP协议就很容易理解TCP了↓↓↓
网络 UDP协议(C++|代码通过udp协议实现客户端与服务端之间的通信)

TCP通信编程

tcp是面向连接、可靠传输、面向字节流的传输层协议
面向连接:必须建立了连接且保证双方都具有数据收发的能力,才能开始通信。(udp是无连接的,只要知道对端地址就可以直接发送消息)
可靠传输:传送的数据,无差错、不丢失、不重复、并且按序到达
面向字节流

通信方面也是分为客户端和服务端
在这里插入图片描述

各端的操作流程:

服务端操作流程:

  1. 创建套接字端口:在内核中创建socket结构体,关联进程与网卡之间的联系
  2. 为套接字绑定地址信息:网络通信中的数据都必须带有源端IP、源端端口、对端IP、对端端口、协议,这5个信息称为五元组。在内核创建的socket结构体中描述IP地址端口以及协议,(必须主动绑定,告诉客户端自己的地址信息,如果不绑定客户端就不知道该发往哪个服务端了)为了告诉操作系统发往哪个IP地址,哪个端口的数据是交给我来处理的
  3. 开始监听:设置套接字的一个监听状态,只有处于监听状态的套接字才会接收客户端的连接请求。服务端会为每一个客户端的连接请求都创建一个新的socket结构体,通过这个新建的socket结构体与客户端进行通信
  4. 获取一个新建立连接的socket的描述符:获取到socket的操作句柄,通过这个指定的socket的操作句柄与指定的客户端进行通信
  5. 收发数据:每个新建的套接字包含了完整的五元组,知道自己与谁通信,因此收发数据的时候就不用设置地址信息了。
  6. 关闭套接字:释放资源
    在这里插入图片描述

客户端操作流程:

  1. 创建套接字:在内核中创建socket结构体,关联进程与网卡之间的联系
  2. 为套接字绑定地址信息:描述在内核中创建的socket结构体的源端地址信息;发送的数据中源端地址信息就是绑定的地址信息(不推荐主动绑定地址,降低端口冲突的概率,从而确保数据发送的安全性)
  3. 向服务端发起连接请求:当服务端处于监听状态时就可以进行连接;但是当服务端不处于监听状态,请求会丢失
  4. 收发数据:被服务端特定套接字服务
  5. 关闭套接字:释放资源

举一个足疗店的例子来帮助你理解服务端与客户端之间的通信
在这里插入图片描述
服务端接口信息
1、创建套接字int socket(int domain, int type, int protocol) 参数内容(domian:地址域(本地通信-AF_LOCAL、IPv4-AF_INET、IPv6-AF_INET6等)确定本次socket通信使用哪种协议版本的地址结构,不同的协议版本有不同的地址结构;type:套接字类型(流式套接字-SOCK_STREAM、数据报套接字-SOCK_DGRAM等);protocol:协议类型(TCP-IPPROTO_TCP、UDP-IPPROTO_UDP) ,默认为0-流式默认TCP,数据报默认UDP)
返回值:文件描述符-非负整数, 套接字所有其他接口的操作句柄,失败返回-1

2、为套接字绑定地址信息int bind(int sockfd, struct sockaddr *addr, socklen_t len)参数内容(sockfd:创建套接字返回的操作句柄;addr:要绑定的地址信息;len:要绑定的地址信息长度)

3、开始监听listen(int sockfd, int backlog) 参数内容(sockfd:将sockfd的套接字设置为监听状态,并且监听状态后可以开始接收客户端连接请求;backlog:同一时间的并发连接数,决定同一时间最多接收多少个客户端的连接请求<内核中可创建套接字数量是有限的,防止存在恶意请求导致资源耗尽>)

4、获取新建连接,从已完成连接队列中取出一个socket,并且返回这个socket的描述符操作句柄int accept(int sockfd, struct sockaddr* cli_addr, socklen_t *len)参数内容(sockfd:表示获取哪个tcp服务端套接字的新建连接;cli_addr:这个新建的套接字对应的客户端地址信息;len:地址信息长度) 返回值:新建的socket套接字的描述符,也就是外部进程中对该套接字的操作句柄

5、收发数据。在tcp套接字中已经标示了五元组,因此接收数据时不需要获取对方地址信息,发送数据时也不需要指定对方的地址信息。接收:ssize_t recv(int sockfd, char *buf, int len, int flag) 返回值:成功返回接收数据的长度,等于0表示断开连接,小于0表示出错。发送:ssize_t send(int sockfd, char *data, int len, int flag) :返回值:成功返回发送数据的长度,等于0表示断开连接,小于0表示出错。若断开连接触发异常信号SIGPIPE

6、关闭套接字int close(fd)

客户端接口信息:将服务端的2、3、4步去掉,合成一步
1、创建套接字int socket(int domain, int type, int protocol)
2、向服务端发起连接请求int connect(int sockfd, struct sockaddr *srv_addr, int len) 参数内容(sockfd:哪个服务端发送请求连接;src_addr:服务端地址信息,给该服务端发送请求)这个connect接口也会在sockfd客户端的套接字socket中描述对端的地址信息
3、收发数据。接收:ssize_t recv(int sockfd, char *buf, int len, int flag) 发送:ssize_t send(int sockfd, char *data, int len, int flag)
4、关闭套接字int close(fd)

代码实现
创建一个类用于封装各端的操作
tcpsocket.hpp

//tcpsocket.hpp
#include <cstdio>
#include <unistd.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;

//该值表示用一时间能够接收多少客户端连接
//并非指整个通信最多接收多少客户端连接
#define MAX_LISTEN 5
#define CHECK_RET(q) if((q) == false){return -1;}
class TcpSocket
{
	public:
		TcpSocket()
			:_sockfd(-1)
		{}
		bool Socket()
		{
			_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (_sockfd < 0)
			{
				perror("socket error");
				return false;
			}
			return true;
		}
		bool Bind(const string &ip, uint16_t port)
		{
			struct sockaddr_in addr;
			addr.sin_family = AF_INET;
			addr.sin_port = htons(port);
			addr.sin_addr.s_addr = inet_addr(ip.c_str());
			socklen_t len = sizeof(struct sockaddr_in);
			
			int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
			if (ret < 0)
			{
				perror("bind error");
				return false;
			}
			return true;
		}
		bool Listen(int backlog = MAX_LISTEN)
		{
			int ret = listen(_sockfd, backlog);
			if (ret < 0)
			{
				perror("listen error");
				return false;
			}
			return true;
		}
		bool Accept(TcpSocket *new_sock, string *ip = NULL, uint16_t *port = NULL)
		{
			struct sockaddr_in addr;
			socklen_t len = sizeof(struct sockaddr_in);
			int new_fd = accept(_sockfd, (struct sockaddr*)&addr, &len);
			cout << "dsdsdds";
			if (new_fd < 0)
			{
				perror("accept error");
				return false;
			}
			new_sock->_sockfd = new_fd;
			if (ip != NULL)
			{
				*ip = inet_ntoa(addr.sin_addr);
			}
			if (port != NULL)
			{
				*port = ntohs(addr.sin_port);
			}
			return true;
		}
		bool Recv(string *buf)
		{
			char tmp[4096] = {0};
			int ret = recv(_sockfd, tmp, 4096, 0);
			if (ret < 0)
			{
				perror("recv error");
				return false;
			}
			else if (ret == 0)//默认阻塞,没有数据就会等待,返回0表示连接断开
			{
				printf("connection broken\n");
				return false;
			}
			buf->assign(tmp, ret);
			return true;
		}
		bool Send(const string &data)
		{
			int ret = send(_sockfd, data.c_str(), data.size(), 0);
			if (ret < 0)
			{
				perror("send error");
				return false;
			}
			return true;
		}
		bool Close()
		{
			if (_sockfd > 0)
			{
				close(_sockfd);
				_sockfd = -1;
			}
			return true;
		}
		bool Connect(const string &ip, uint16_t port)
		{
			struct sockaddr_in addr;
			addr.sin_family = AF_INET;
			addr.sin_port = htons(port);
			addr.sin_addr.s_addr = inet_addr(ip.c_str());
			socklen_t len = sizeof(struct sockaddr_in);
			int ret = connect(_sockfd, (struct sockaddr*)&addr, len);
			if (ret < 0)
			{
				perror("connect error");
				return false;
			}
			return true;
		}
	private:
		int _sockfd;
};

tcp_srv.cpp

//tcp_srv.cpp
#include <cstdio>
#include <iostream>
#include "tcpsocket.hpp"

using namespace std;

int main(int argc, char *argv[])
{
	if (argc != 3)
	{
		cout << "Usage:./tcp_srv ip port" << endl;
		return -1;
	}
	string ip = argv[1];
	uint16_t port = stoi(argv[2]);

	TcpSocket lst_sock;
	//穿件套接字
	CHECK_RET(lst_sock.Socket());
	//为套接字绑定地址信息
	CHECK_RET(lst_sock.Bind(ip, port));
	//开始监听
	CHECK_RET(lst_sock.Listen());
	while (1)
	{
		TcpSocket new_sock;
		//获取连接
		bool ret = lst_sock.Accept(&new_sock);
		if (ret == false)
		{
			continue;//服务端不能因为获取一个新建套接字失败就退出
		}
		string buf;
		new_sock.Recv(&buf);
		cout << "client say: " << buf << endl;

		buf.clear();
		cout << "server say: ";
		cin >> buf;
		new_sock.Send(buf);
	}
	lst_sock.Close();
	return 0;
}

tcp_cli.cpp

//tcp_cli.cpp
#include <iostream>
#include <string>
#include "tcpsocket.hpp"
using namespace std;

int main(int argc, char *argv[])
{
	if (argc != 3)
	{
		cout << "Usage: ./tcp_cli ip port" << endl;
		return -1;
	}
	string srv_ip = argv[1];
	uint16_t srv_port = stoi(argv[2]);

	TcpSocket sock;
	CHECK_RET(sock.Socket());
	CHECK_RET(sock.Connect(srv_ip, srv_port));
	while (1)
	{
		string buf;
		cout << "client say: ";
		cin >> buf;
		sock.Send(buf);

		buf.clear();
		sock.Recv(&buf);
		cout << "server say: "<< buf << endl;
	}
	sock.Close();
	return 0;
}

makefile

all:tcp_srv tcp_cli
  tcp_srv:tcp_srv.cpp
      g++ -std=c++11 $^ -o $@
  tcp_cli:tcp_cli.cpp
      g++ -std=c++11 $^ -o $@

查看网卡信息
在这里插入图片描述
先运行服务端,等待新的连接。
在这里插入图片描述
运行客户端,并发送消息
在这里插入图片描述
服务端收到消息并回复
在这里插入图片描述
客户端收到消息并回复
在这里插入图片描述
但是此时客户端并没有接收到客户端发来的消息,一直停留在上一次发送消息完后的样子
在这里插入图片描述
简单分析服务端while循环,while循环中第一步获取连接时阻塞等待的,当一个新的客户端来是会为它创建一个新的套接字与它通信。客户端第一次发送消息,服务端走到第二步接收消息,再走到第三步发送消息。发送完消息第一次循环就结束了,又重新到了第一步等待获取连接。此时新创建的套接字丢失,所以此时客户端再给服务端发送消息,就无法接收。
在这里插入图片描述
这时候我们就得引入多线程或者多进程来完成这项任务。在获取到一个新的连接时,就启动一个新的执行流,让这个新的执行流去与该客户端进行通信。这样子做的好处是:没有了因为新连接到来的阻塞,就不会影响与客户端之间的通信;与客户端通信时的阻塞,并不会影响获取新的连接

多进程使用TCP实现通信
只要修改服务端
tcp_process.cpp

//tcp_process.cpp
#include <cstdio>
#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include "tcpsocket.hpp"

using namespace std;

void sigcb(int no)
{
	while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main(int argc, char *argv[])
{
	if (argc != 3)
	{
		cout << "Usage:./tcp_srv ip port" << endl;
		return -1;
	}
	//子进程退出处理方式
	signal(SIGCHLD, sigcb);
	string ip = argv[1];
	uint16_t port = stoi(argv[2]);

	TcpSocket lst_sock;
	//穿件套接字
	CHECK_RET(lst_sock.Socket());
	//为套接字绑定地址信息
	CHECK_RET(lst_sock.Bind(ip, port));
	//开始监听
	CHECK_RET(lst_sock.Listen());
	while (1)
	{
		TcpSocket new_sock;
		//获取连接
		bool ret = lst_sock.Accept(&new_sock);
		if (ret == false)
		{
			continue;//服务端不能因为获取一个新建套接字失败就退出
		}
		//创建子进程
		int pid = fork();
		if (pid == 0)
		{
			while (1)
			{
				string buf;
				new_sock.Recv(&buf);
				cout << "client say: " << buf << endl;

				buf.clear();
				cout << "server say: ";
				cin >> buf;
				new_sock.Send(buf);
			}
			new_sock.Close();
			exit(0);
		}
		new_sock.Close();//父进程关闭自己不使用的socket,不影响子进程
	}
	lst_sock.Close();
	return 0;
}

先执行服务端
在这里插入图片描述
再执行客户端并发送消息
在这里插入图片描述
服务端收到消息并回复
在这里插入图片描述
客户端收到并发送消息
在这里插入图片描述
服务端收到消息并回复
在这里插入图片描述
我们发现都已经正常完成tcp的通信了!
使用多进程实现注意事项:父子进程数据独有,父进程用不到新建套接字,要记得关闭;进程等待使用信号处理,不再阻塞父进程

多线程使用TCP实现通信
多线程也就是穿件一个新的线程,让该线程专门负责去与新建连接的客户去通信,原理和多进程差不多。但是存在一个问题,线程如何拿到新建的套接字对象呢?new_sock是一个局部对象,循环完毕的时候就会被释放,传地址就会造成错误。两种方案1、每次accept的时候new_sock在堆上申请一个新的,不会被自动释放,但是也要防止第二次获取的时候覆盖上次的值;2、或者直接传值–在函数栈中会新建空间存储值,仅限于数据占用空间比较小,防止栈溢出

我们接下来演示第二种方式将获取连接的套接字传到线程入口函数中,但是最后一个参数必须是void*类型的,我们并不能直接将对象当做值直接传过去。我们知道对象中只有一个属性,也就是套接字的操作句柄,我们只要将它传过去,就能利用该操作句柄创建一个一样的套接字。这样子就可以拿到获取连接时的套接字了。但是我们必须在封装套接字的类中额外提供两个函数,一个是获取操作句柄的函数GetFd和设置操作句柄的函数SetFd。SetFd函数时在线程穿建套接字的时候使用的。在线程中穿建一个新的套接字,将这个套接字的操作句柄修改为传过来的值。就能操作原先的套接字了
tcpsocket.hpp (只提供修改部分)

//tcpsocket.hpp
int GetFd()
{
	return _sockfd;
}
void SetFd(int fd)
{
	_sockfd = fd;
}

tcp_thread.cpp

#include <cstdio>
#include <iostream>
#include <pthread.h>
#include "tcpsocket.hpp"

using namespace std;

//线程入口函数,赋值收发数据通信
void* thr_start(void *arg)
{
	long fd = (long)arg;
	TcpSocket new_sock;
	new_sock.SetFd(fd);
	while (1)
	{
		string buf;
		new_sock.Recv(&buf);
		cout << "client say: " << buf << endl;
		buf.clear();
		cout << "server say: ";
		cin >> buf;
		new_sock.Send(buf);
	}
	new_sock.Close();//线程之间文件描述符表共享,这边关闭了描述符其他地方就用不了了
	return NULL;
}

int main(int argc, char *argv[])
{
	if (argc != 3)
	{
		cout << "Usage:./tcp_srv ip port" << endl;
		return -1;
	}
	string ip = argv[1];
	uint16_t port = stoi(argv[2]);

	TcpSocket lst_sock;
	//穿件套接字
	CHECK_RET(lst_sock.Socket());
	//为套接字绑定地址信息
	CHECK_RET(lst_sock.Bind(ip, port));
	//开始监听
	CHECK_RET(lst_sock.Listen());
	while (1)
	{
		TcpSocket new_sock;
		//获取连接
		bool ret = lst_sock.Accept(&new_sock);
		if (ret == false)
		{
			continue;//服务端不能因为获取一个新建套接字失败就退出
		}
		//线程
		pthread_t tid;
		int res = pthread_create(&tid, NULL, thr_start, (void*)new_sock.GetFd());
		if (res != 0)
		{
			cout << "pthread create error" << endl;
			continue;
		}
		pthread_detach(tid);//不关心线程返回值,也不想等待释放资源,因此将线程分离出去
	}
	lst_sock.Close();
	return 0;
}

makefile

all:tcp_thread tcp_cli
tcp_thread:tcp_thread.cpp
    g++ -std=c++11 $^ -o $@ -lpthread
udp_cli:udp_cli.cpp
    g++ -std=c++11 $^ -o $@

先运行服务端
在这里插入图片描述
再运行客户端并发送消息
在这里插入图片描述
服务端收到消息后回复
在这里插入图片描述
客户端收到并发送消息
在这里插入图片描述
服务端收到并回复
在这里插入图片描述
多线程实现注意事项:主线程创建线程之后,千万不能关闭新建套接字,因为线程之间共享文件描述符表。

服务端既可以通过多进程来实现,也可以通过多线程来实现,那哪种方式更合适呢?
不同场景应用不同方式解决:多线程灵活,资源占用少,通信方便,但是健壮性没多进程强;多进程资源占用多,但是安全,健壮性高。
若服务端针对客户端的业务比较简单,使用多线程,效率高且通信方便;若服务端针对客户端的业务比较复杂,使用多进程,安全性和健壮性高。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

网络 TCP协议(C++代码|通过tcp协议实现客户端与服务端之间的通信) 的相关文章

  • Jetson Nano的GPIO口学习

    1 配置GPIO库 https github com NVIDIA jetson gpio 1 安装pip工具 sudo apt get update sudo apt get install python3 pip sudo apt ge
  • 22.11.22 TCP与UDP 客户端与服务器 协议搭建

    ubuntu 64 ubuntu yuyu yu 11 cat Tcp Cli c 客户端 include lt stdio h gt include lt sys types h gt include lt sys socket h gt
  • cmake交叉编译配置

    cmake交叉编译配置 很多时候 xff0c 我们在开发的时候是面对嵌入式平台 xff0c 因此由于资源的限制需要用到相关的交叉编译 即在你host宿主机上要生成target目标机的程序 里面牵扯到相关头文件的切换和编译器的选择以及环境变量
  • OS——gcc、g++、gdb、vim、vs code的基本使用

    文章目录 g 43 43 的使用gdb的使用vim的使用vscode的使用vs code的安装vs code中C 43 43 的编译运行配置 如果想要学习如何在CentOS 7中安装配置gcc g 43 43 gdb zhs和oh my z
  • make和cmake

    编程人员已经使用CMake和Make很长一段时间了 当你加入一家大公司或者开始在一个具有大量代码的工程上开展工作时 xff0c 你需要注意所有的构建 你需要看到处跳转的 CMakeLists txt 文件 你应该会在终端使用 cmake 和
  • ubuntu自带python与anaconda python环境的切换

    ubuntu的python可分为三大类 xff1a 1 ubuntu自带的python环境 一般安装在 usr bin 中python2和python3可以共存 2 anaconda自带的base环境 3 在anaconda中创建的虚拟py
  • 详细介绍如何在ubuntu20.04中安装ROS系统,以及安装过程中出现的常见错误的解决方法,填坑!!!

    本篇文章写于2020 10 xff0c 经过很多小伙伴的验证 xff0c 文章所介绍的步骤是可以正常完成安装的 xff0c 现在是2021 10 xff0c 经过近期的探索 xff0c 我将安装步骤进行了进一步的优化 xff0c 使安装变得
  • VScode进行python开发出现 No module named “XXX“的解决方法

    VScode进行python开发出现 No module named 34 XXX 34 的解决方法 最近从pycharm转向vscode的时候 xff0c 遇到了如下问题 span class token keyword import s
  • CM3寄存器简介

    Cortex M3基础 寄存器组 通用目的寄存器组R0 R7 也被称为低组寄存器 xff0c 所有指令都能访问字长32位 通用目的寄存器组R8 R12 高组寄存器 32位寄存器 复位后的初始值不可预料 堆栈指针R13 CM3中共有两个堆栈指
  • 基于亚博K210开发板的学习之旅(一)

    本文参考亚博智能官方K210开源课程 五月份购买了亚博的K210开发板 xff0c 但由于课程压力就搁置了 xff0c 最近暑假得空又恰逢电赛清单里有这个 芯片 xff0c 就抽空学习一下 xff0c 特写下这些 xff0c 以作记录 按照
  • STM32标准库通用软件模拟IIC

    STM32标准库通用软件模拟IIC 继上次通用可移植的矩阵键盘之后 xff0c 便继续寻找着下一个能够拿来只需改改引脚就可以使用的通用方案 恰好最近在研究PCA9685 xff0c 这是一片能够产生最多十六路PWM信号的芯片 xff0c 通
  • STM32F103控制PCA9685产生16路PWM波控制SG90舵机

    STM32控制PCA9685产生16路PWM波控制SG90舵机 如果你能点开这篇文章 xff0c 说明你已经知道PCA9685是多么强大 xff0c NXP公司原本做这片芯片是为了提供给LED使用 xff0c 在其官方文档里也能看到所有PW
  • 从源代码来看HAL库函数(一) HAL基础函数

    从源代码来看HAL库函数 xff08 一 xff09 HAL基础函数 全局变量 IO uint32 t uwtick 这个变量充当了一个运行时长计数的作用 xff0c 每发生一次SysTick中断就会加一 xff0c 直至溢出 xff0c
  • 使用TCP+串口与板子进行网络通信

    最近做了一个嵌入式的project xff0c 大概要求就是做一个client端 xff0c 一个sensor端 xff0c 两者通过TCP UDP进行通信 xff0c 然后在client端输入不同的命令sensor需做出不同的处理 xff
  • 毕业论文格式(图片题注引用,表格,公式格式)

    本科毕业论文差不多写完了 xff0c 记录一下一些格式 xff0c 以后写作可能会用到 xff0c 就可以翻起来看看 首先 xff0c 如果可以找到一篇格式符合要求的word文档的话 xff0c 最简单的方法就是在这个文档的基础上进行内容的
  • 图像处理——相位恢复(GS,TIE,改进型角谱迭代法)(已更新代码)

    利用GS xff0c TIE xff0c 改进型角谱迭代算法进行相位恢复 角谱传播理论 角谱传播理论可以翻阅傅里叶光学的书 xff0c 就能找到定量分析的计算公式 xff0c 可以分析某个平面的角谱垂直传播到另外一个平面的角谱 xff0c
  • 串口应用:遵循uart协议,发送多个字节的数据(状态机)

    上一节中 xff0c 我们遵循uart协议 xff0c 它发送一次只能发送6 7 8位数据 xff0c 我们不能随意更改位数 xff08 虽然在代码上可行 xff09 xff0c 不然就不遵循uart协议了 xff0c 会造成接收端无法接收
  • 数码管动态显示Verilog实现(参考小梅哥教程)(视觉暂留)

    一个数码管有八个引脚 xff0c 控制八段二极管的亮灭 xff0c 用以显示需要的数字 当有N个数码管时 xff0c 一个一个控制的话需要N x 8 个引脚 xff0c 消耗资源较多 因此可以利用动态显示的方案通过人眼的视觉暂留特性达到静态
  • 彻底理解DDS(信号发生器)的fpga实现(verilog设计代码)

    DDS xff08 Direct Digital Synthesis xff09 是一种把一系列数字信号通过D A转换器转换成模拟信号的数字合成技术 它有查表法和计算法两种基本合成方法 在这里主要记录DDS查表法的fpga实现 查表法 xf
  • HDMI/DVI

    一 基础知识 1 历史 早期在FPGA芯片上实现HDMI控制显示是使用HDMI发送芯片 xff0c eg xff1a ADV7513 sil9022 xff0c CH7301等 用之前VGA控制中输出的RGB信号 行场同步信号和使能信号输入

随机推荐

  • HDMI/DVI____TMDS编码

    一 编码步骤 xff1a 基本方法 xff1a 取第一位数据为初值 xff0c 接下来输入的每一位与前一导出的位 xff08 根据判断条件 xff09 进行异或XOR或者同或XNOR xff08 最小化传输 xff0c 减少0 1翻转 xf
  • HDMI/DVI____串行发送器

    一 功能 xff1a 把10bit数据转化为串行数据在一个时钟周期全部输出 xff08 先输出高位 xff0c 再输出低位 xff09 二 框图 二 思路 对于TMDS编码器 xff0c 在每一个输入时钟周期 xff0c 输入一次数据到TM
  • keil添加新文件.c.h

    文章目录 添加文件到组中1 双击组名称2 点击快捷键 添加头文件路径 h1 点击魔术棒快捷键2 头文件加 添加文件到组中 1 双击组名称 双击组名称 xff0c 打开弹窗 xff0c 然后选择相应的组中的新文件 xff0c 在点击ADD 2
  • QT常用控件(二)——自定义控件封装

    引言 Qt已经提供了很多的基础控件供开发使用 xff0c 而Qt原生的控件有时候并不能满足我们的需求 xff0c 特别是在工业的运用上 xff0c 比如我们需要一个日期时间的选择器 xff0c Qt虽然已经提供了原生的QDateTime控件
  • STM32之串口通信USART模块学习(1)

    一 通信接口 通信的目的 xff1a 将一个设备的数据传送到另一个设备 xff0c 扩展硬件系统通信协议 xff1a 制定通信的规则 xff0c 通信双方按照协议规则进行数据收发 单端信号通信的双方必须要共地 xff0c 因为都是对GND的
  • 2019电赛总结(一)

    2019电赛总结 xff08 一 xff09 文章目录 2019电赛总结 xff08 一 xff09 4 那之前5 电赛初期6 电赛中期7 电赛强化练习8 电赛预热阶段8月初9 那以后 4 那之前 2019电赛总结 序 xff09 5 电赛
  • 统计从键盘输入的一行字符中小写字母,大写字母,数字字符和其它字符的个数。

    统计从键盘输入的一行字符中小写字母 xff0c 大写字母 xff0c 数字字符和其它字符的个数 C语言实现 vs 2019 span class token macro property span class token directive
  • c语言求1~10的阶乘和

    求1 43 2 43 3 43 43 10 的和 span class token macro property span class token directive keyword include span span class toke
  • C和Cpp区别

    1 输入 xff0c 输出不同 xff08 out xff0c put xff09 c语言 xff1a include lt stdio h gt scanf 34 d 34 amp a printf 34 a 61 d n 34 a cp
  • C++实现基于顺序搜索的动态分区分配算法

    目录 1 需求分析 2 代码实现 3 测试用例 4 总结与收获 1 需求分析 动态分区分配又称为可变分区分配 xff0c 他是根据进程的实际需要 xff0c 动态地为之分配内存空间 在实现动态分区分配时 xff0c 将涉及到分区分配中所有的
  • C语言实现TCP编程

    C语言实现TCP编程 1 主机字节序和网络字节序2 套接字的地址结构IP地址转化的方法 3 TCP的网络接口4 TCP服务器端的编程流程5 TCP客户端的编程流程6 运行结果 1 主机字节序和网络字节序 主机字节序 xff1a 不同的芯片
  • QT---用户登录注册案例实现

    用户登录 注册 span class token macro property span class token directive hash span span class token directive keyword include
  • C++中list详解

    list详解 list的介绍list函数说明成员类型构造函数元素访问迭代器容量修改器操作 vector和list区别总结vector和list的使用场景 仿写END xff01 96 在这里插入代码片 96 list的介绍 list是序列容
  • sip response 摘要认证

    详解摘要认证 1 什么是摘要认证 摘要认证与基础认证的工作原理很相似 xff0c 用户先发出一个没有认证证书的请求 xff0c Web服务器回复一个带有WWW Authenticate头的响应 xff0c 指明访问所请求的资源需要证书 但是
  • Prim算法实现最小生成树

    Prim算法实现最小生成树 1 最小生成树是什么2 最小生成树的用途3 Prim算法描述4 Prim算法演示最小生成树过程5 Prim算法实现END 1 最小生成树是什么 对连通图进行遍历 过程中所经过的边和顶点的组合可看做是一棵普通树 通
  • 哈夫曼树,哈夫曼编码及应用——(代码实现)

    哈夫曼树 xff0c 哈夫曼编码及应用 1 哈夫曼树1 1 什么是哈夫曼树 2 如何构造哈夫曼树 xff08 哈夫曼算法 xff09 2 1 举例实现哈夫曼树2 1 1手动实现具体步骤2 1 2代码实现具体步骤 3 哈夫曼编码3 1 什么是
  • 二叉排序树详解及实现

    二叉排序树详解及实现 1 什么是二叉排序树2 二叉排序树的数据结构2 1二叉排序树的节点类型2 2二叉排序树中插入某个元素2 3 二叉排序树中按值查找元素2 4 找排序二叉树中的最小值2 5返回排序二叉树中ptr中序遍历的后续节点2 6 寻
  • 平衡二叉树的一系列操作:删除、插入(在二叉排序树中插入新结点后,如何保持平衡)、调整平衡等等等

    平衡二叉树的插入 xff08 在二叉排序树中插入新结点后 xff0c 如何保持平衡 xff09 1 平衡二叉树的定义2 平衡二叉树的插入 xff08 调整最小不平衡子树A xff09 2 1LL xff08 在A的左孩子的左子树中插入导致不
  • 网络 UDP协议(C++|代码通过udp协议实现客户端与服务端之间的通信)

    这里写目录标题 udp通信编程各端的操作流程 xff1a 服务端操作流程 xff1a 客户端操作流程 xff1a 第2 3步与服务端不同 socket接口介绍udp客户服务端代码实现 推荐阅读 socket套接字编程就是在网络程序中编写代码
  • 网络 TCP协议(C++代码|通过tcp协议实现客户端与服务端之间的通信)

    目录 TCP通信编程各端的操作流程 xff1a 服务端操作流程 xff1a 客户端操作流程 xff1a 推荐先学习UDP协议在学习TCP协议 在UDP协议博客中讲解得更详细 xff0c 看懂UDP协议就很容易理解TCP了 网络 UDP协议