网络协议与网络编程(单电脑信息传输)

2023-05-16

C/C++网络编程(单电脑信息传输)

Copyright: Jingmin Wei, Pattern Recognition and Intelligent System, School of Artificial and Intelligence, Huazhong University of Science and Technology

在这里插入图片描述


文章目录

      • C/C++网络编程(单电脑信息传输)
        • 说明(很重要!)
        • 1、套接字
          • Windows下的套接字
          • WSAStartup() 函数
        • 2、建立Socket
        • 3、bind() 函数
          • A.sockaddr_in 结构体
          • B.sockaddr
        • 4、connect() 函数
        • 5、linsten() 函数
        • 6、accept() 函数
        • 7、Windows 接受和发送数据
        • 8、示例与源代码
          • 简单的单电脑信息传输实践
            • C语言实现
            • C++实现
          • 实现基本的QQ聊天
            • C语言实现
            • C++实现
        • 9、深入理解Socket协议
          • 理解Socket
          • 理解TCP与UDP的区别以及三次握手和四次挥手
          • 两种协议区别
        • 10、TCP协议相关介绍
          • 1. Wireshark里与显示TCP数据包相关的过滤规则:
          • 2. TCP数据报中标记字段(flag)的含义
          • 3. TCP连接的建立过程
          • 4. TCP连接的释放过程
        • Reference


本文为华中科技大学 人工智能与自动化学院 自动化1801 魏靖旻编写整理,如有任何错误请及时指正。

说明(很重要!)

如果没有计算机网络基础,建议先看 9、深入理解socket协议10、TCP协议相关介绍

本文涉及到TCP和UDP协议,编程采用TCP协议,阅读完本文并实践后,可以基本掌握C/C++网络编程。

本文采用的编译器为dev-cpp集成编译环境。使用的函数为Windows下winsock2.h里的函数,Linux上的函数说明和使用方法请参考这篇文章。

在dev-cpp环境下,使用TCP/UDP协议编程应作如下修改(网上很多教程都没有说明如何允许网络协议,不改配置的话dev将会报错)

本文的C语言代码是参考多个CSDN上的代码然后优化而成,C++代码为个人编写而成。所有代码都经过本人测试并确认无误,实现了单电脑简单的QQ交互的过程。

在这里插入图片描述

1、套接字

socket,套接字,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。

常用的TCP/IP协议的3种套接字类型如下所示:

数据报套接字(SOCK_DGRAM):
数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

原始套接字(SOCK_RAW):
原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。

Windows下的套接字

WinSock(Windows Socket)编程依赖于系统提供的动态链接库(DLL),有两个版本:

较早的DLL是 wsock32.dll,大小为 28KB,对应的头文件为 winsock1.h;
最新的DLL是 ws2_32.dll,大小为 69KB,对应的头文件为 winsock2.h。

加载ws2_32.dll

#pragma comment (lib, "ws2_32.lib")
WSAStartup() 函数

使用DLL之前,还需要调用 WSAStartup() 函数进行初始化,以指明 WinSock 规范的版本

以下是结构指针*LPWSADATA类型定义:

typedef unsigned short WORD;//minwindef.h

typedef struct WSAData 
{
	WORD		wVersion;
	WORD		wHighVersion;
#ifdef _WIN64
	unsigned short	iMaxSockets;
	unsigned short	iMaxUdpDg;
	char		*lpVendorInfo;
	char		szDescription[WSADESCRIPTION_LEN+1];
	char		szSystemStatus[WSASYS_STATUS_LEN+1];
#else
	char		szDescription[WSADESCRIPTION_LEN+1];
	char		szSystemStatus[WSASYS_STATUS_LEN+1];
	unsigned short	iMaxSockets;
	unsigned short	iMaxUdpDg;
	char		*lpVendorInfo;
#endif
} WSADATA, *LPWSADATA;//_wsadata.h

WSAStartup() 函数原型:

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
  • wVersionRequested 为 WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号)
  • lpWSAData 为指向 WSAData 结构体的指针。

eg:

#define MAKEWORD(a,b) ((WORD) (((BYTE) (((DWORD_PTR) (a)) & 0xff)) | ((WORD) ((BYTE) (((DWORD_PTR) (b)) & 0xff))) << 8))//minwindef.h
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

2、建立Socket

socket() 函数原型:

WINSOCK_API_LINKAGE SOCKET WSAAPI socket(int af,int type,int protocol);
/*
af ,地址族(Address Family),常用AF_INET(IPv4) 和 AF_INET6(IPv6)。
type ,数据传输方式,常用的有 SOCK_STREAM(面向连接)和 SOCK_DGRAM(无连接)
protocol 表示传输协议,常用的有 IPPROTO_TCP(TCP协议) 和 IPPTOTO_UDP(UDP协议)
*/
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字

3、bind() 函数

将套接字与特定的IP地址和端口绑定起来

int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;//使用sockaddr_in

memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充

serv_addr.sin_family = AF_INET;  //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
serv_addr.sin_port = htons(1234);  //端口

//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
							//sockaddr_in强制转换为sockaddr

在最后一步中,我们通过bind函数将sockaddr_in强制转换为sockaddr,接下来我们对这两个结构体进行详细说明:

A.sockaddr_in 结构体
struct sockaddr_in
{
    sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
    uint16_t        sin_port;     //16位的端口号
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};//_ip_types.h
  • sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。
  • sin_prot 为端口号。需要用 htons() 函数转换
  • sin_addrstruct in_addr 结构体类型的变量

对其中in_addr 结构体,原型如下:

typedef struct in_addr 
{
    //n_addr_t  s_addr;  //32位的IP地址
  	union 
  	{
    	struct { u_char  s_b1, s_b2, s_b3, s_b4; } S_un_b;
    	struct { u_short s_w1, s_w2; } S_un_w;
    	u_long S_addr;//typedef unsigned long u_long;
  	} S_un;
} IN_ADDR, *PIN_ADDR, *LPIN_ADDR;//inaddr.h

需要inet_addr()转换

serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
B.sockaddr
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

bind() 第二个参数的类型为 sockaddr,而代码中却使用 sockaddr_in,然后再强制转换为 sockaddr,这是为什么呢?

sockaddr 结构体的定义如下:

struct sockaddr
{
    sa_family_t  sin_family;   //地址族(Address Family),也就是地址类型
    char         sa_data[14];  //IP地址和端口号
};

下图是 sockaddr 与 sockaddr_in 的对比(括号中的数字表示所占用的字节数):

在这里插入图片描述

sockaddr 和 sockaddr_in 的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。

4、connect() 函数

建立连接,与bind类似,函数原型如下:

int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);

5、linsten() 函数

套接字进入被动监听状态,其函数原型如下:

int listen(SOCKET sock, int backlog);  //Windows
  • sock 需要进入监听状态的套接字
  • backlog 请求队列的最大长度

6、accept() 函数

套接字处于监听状态时,接收客户端请求,返回新的套接字,其函数原型如下:

SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);
  • sock 为服务器端套接字
  • addr 为 sockaddr_in 结构体变量
  • addrlen 为参数 addr 的长度,可由 sizeof() 求得

7、Windows 接受和发送数据

发送数据,send函数:

int send(SOCKET sock, const char *buf, int len, int flags);	

接收数据,recv函数:

int recv(SOCKET sock, char *buf, int len, int flags);
  • sock 为要发送数据的套接字
  • buf 为要发送的数据的缓冲区地址
  • len 为要发送的数据的字节数
  • flags 为发送数据时的选项,一般设置为 0 或 NULL

8、示例与源代码

简单的单电脑信息传输实践

基本思路:先编译运行server产生中断(监听),后编译运行client。

即需要新建两个C源文件,server.c和client.c。先运行server.c产生监听,然后运行client.c。

在运行源代码前,请先修改如下措施:

工具->编译选项

在这里插入图片描述

C语言实现

服务器端 server端:

基本编程思路总结:

首先初始化DLL(WSAStartup),然后创建(socket)套接字(servSock),并将它与特定IP地址绑定(bind),以监听(listen)来自客户端的信息。当客户端发送请求时(connect),服务器端接收到请求(accept),并将老套接字(serSock)IP地址赋给新的套接字(clntSock),之后向客户端发送数据(send)。最后关闭(closesocket)套接字,终止DLL使用(WSACleanup)。

在整个程序运行过程中,涉及四次握手。握手定义参考第九部分的深入理解TCP/UDP协议

#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll

int main()
{
    //初始化 DLL
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    //创建套接字 PF_INET:IPv4
    SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    //将套接字与特定的IP地址和端口绑定起来
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
    
    sockAddr.sin_family = PF_INET;  //使用IPv4地址
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    sockAddr.sin_port = htons(1234);  //端口
  	bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));//searSock与设定的sockAddr绑定
    
    //进入监听状态
    listen(servSock, 20);
    	/*sock 需要进入监听状态的套接字
		backlog 请求队列的最大长度*/
    //printf("here");//用来调试
    //首先运行该程序,则程序运行到此处中断,等待来自客户端的请求
    
    //接收客户端请求
    SOCKADDR clntAddr;
    int nSize = sizeof(SOCKADDR);
    SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);//将server的servSock与客户端的sock相连接(二者地址配置相同),并赋给新的套接字clntSock
    	/*servsock 为服务器端套接字
		clntAddr 为 sockaddr_in 结构体变量
		nSize 为参数 addr 的长度,可由 sizeof() 求得*/
    //printf("here");//用来调试

    //向客户端发送数据
    char str[20] = "Hello World!";
    send(clntSock, str, strlen(str)+sizeof(char), NULL);//要发送的套接字,要发送的数据地址,要发送的字节数,发送选项
    
    //关闭套接字
    closesocket(clntSock);
    closesocket(servSock);
    
    WSACleanup();//终止 DLL 的使用
    return 0;
}

客户端 client端:

基本编程思路总结:

首先初始化DLL(WSAStartup),然后创建(socket)套接字(sock),配置相应的IP地址(sockAddr),并向服务器端发起请求(connect)。建立连接后,接收来自服务器端的数据(recv)并打印在屏幕上。最后关闭(closesocket)套接字,终止DLL使用(WSACleanup)。

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")  

//加载 ws2_32.dll
int main()
{
    //初始化DLL
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    //创建套接字
    SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    //向服务器发起请求
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
    sockAddr.sin_family = PF_INET;
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    sockAddr.sin_port = htons(1234);
    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));//套接字与地址建立连接,与服务器的accept相对应
    
    //接收服务器传回的数据
    char szBuffer[MAXBYTE] = {0};
    recv(sock, szBuffer, MAXBYTE, NULL);//#define MAXBYTE 0xff(winnt.h)
    
    //输出接收到的数据
    printf("Message form server: %s\n", szBuffer);
    //关闭套接字
    
    closesocket(sock);
    
    //终止使用 DLL
    WSACleanup();
    system("pause");
    return 0;
}
C++实现

采用c++实现的过程更加明了。

其中对象有重载,一个是初始的套接字servSock,另一个是将server的servSock与客户端的Sock相连接后(二者地址配置相同),建立的新的套接字clntSock。如对过程有疑问可以参考C语言的实现过程。

server端:

#include<iostream>
#include<string.h>
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")//加载 ws2_32.dll

using namespace std;

class Socket
{
	private:
		SOCKET sock;//套接字
		sockaddr_in sockAddr;//特定的IP地址 
	public:
		Socket();//构造servSock 
		Socket(int n,Socket servSock);//构造clntSock 
		void usebind2listen();
		void usesend(char *str);
		void close();
		//~Socket();
}; 


Socket::Socket()
{
	this->sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	memset(&(this->sockAddr),0,sizeof(this->sockAddr));
	this->sockAddr.sin_family = PF_INET;  //使用IPv4地址
    this->sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    this->sockAddr.sin_port = htons(1234);  //端口
}

Socket::Socket(int n,Socket servSock)
{
	this->sock=accept(servSock.sock,(SOCKADDR*)&(this->sockAddr),&n);
	//将server的servSock与客户端的sock相连接(二者地址配置相同),并赋给新的套接字clntSock 
}

void Socket::usebind2listen()
{
	bind(this->sock,(SOCKADDR*)&sockAddr,sizeof(SOCKADDR));//将servsock与特定地址绑定 
	listen(this->sock,20);//监听,主程序运行到此处中断 
}

void Socket::usesend(char *str)
{
	send(this->sock,str,strlen(str)+sizeof(char),0);
}

void Socket::close()
{
	closesocket(this->sock);
}

int main(int argc,char**argv)
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	
	Socket servSock;//构造套接字对象 PF_INET:IPv4
	servSock.usebind2listen();//servSock与设定的sockAddr绑定
	
	Socket clntSock(sizeof(sockaddr_in),servSock);//重载构造套接字对象,接收客户端请求 
	
	char str[20]="hello world";
	clntSock.usesend(str);
	
	servSock.close();
	clntSock.close();//关闭套接字
	
	WSACleanup();//终止 DLL 的使用
	
	return 0;
} 

client端:

#include<iostream>
#include<string.h>
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")//加载 ws2_32.dll

using namespace std;

class CSocket
{
	private:
		SOCKET sock;//套接字
		sockaddr_in sockAddr;//特定的IP地址
	public:
		CSocket();
		void useconnect();
		void userecieve(char *str);
		void close();
		//~CSocket(); 
};

CSocket::CSocket()
{
	this->sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	memset(&(this->sockAddr),0,sizeof(this->sockAddr));
	this->sockAddr.sin_family = PF_INET;  //使用IPv4地址
    this->sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    this->sockAddr.sin_port = htons(1234);  //端口
}

void CSocket::useconnect()
{
	connect(sock,(SOCKADDR*)&(this->sockAddr),sizeof(SOCKADDR));
	//套接字与地址建立连接,与服务器的accept相对应
}

void CSocket::userecieve(char* str)
{
	recv(this->sock,str,MAXBYTE,0); 
	cout<<"message from server:"<<str<<endl;
}

void CSocket::close()
{
	closesocket(this->sock);
}

int main(int argc,char**argv)
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	
	CSocket sock;
	sock.useconnect();
	
	char szBuffer[MAXBYTE]={0};
	sock.userecieve(szBuffer);
	sock.close();
	
	WSACleanup();
    system("pause");
    return 0;
}
实现基本的QQ聊天

在上面的单电脑信息传输中,适当位置加入循环条件,即可实现你来我往的QQ聊天

在这里插入图片描述

退出程序的方式:输入quit

在这里插入图片描述

C语言实现

服务器端 server端:

/*
 *  服务器端 Server.c
 *    
 */
#include <winsock2.h>
#include <stdio.h>
#include <string.h>
 
#define BUFFSIZE 1024
//#define SOCKET_ERROR	(-1) _socket_types.h
 
int main(int argc, char**argv)
{
    int             Ret;
    WSADATA         wsaData;
    SOCKET          ListeningSocket;
    SOCKET          NewConnection;
    SOCKADDR_IN     ServerAddr;
    SOCKADDR_IN     ClientAddr;
    int             ClientAddrLen = sizeof(ClientAddr);
    unsigned short  Port = 5150;
    char            sendData[BUFFSIZE];
    char            recvData[BUFFSIZE];
 
    if((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
    {
        printf("WSASTARTUP_ERROR: %d\n", Ret);
        return 0;
    }
 
    //创建一个套接字来监听客户机连接
    if((ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
    {
        printf("SOCKET_ERROR: %d\n", INVALID_SOCKET);
        return 0;
    }
 
    /*
     * 填充SOCKADDR_IN结构,这个结构将告知bind我们想要在5150端口监听所有接口上的连接
     */
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(Port); //将端口变量从主机字节顺序转换位网络字节顺序
    ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    //ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
 
 
    //使用bind将这个地址信息和套接字绑定起来
    if(bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
    {
        printf("BIND_ERROR: %d\n", SOCKET_ERROR);
        return 0;
    }
 
    //监听客户机连接。这里使用5个backlog
    if(listen(ListeningSocket, 5) == SOCKET_ERROR)
    {
        printf("LISTEN_ERROR: %d\n", SOCKET_ERROR);
        return 0;
    }
 
    //连接到达时,接受连接
    printf("正在接受连接...");
    if((NewConnection = accept(ListeningSocket, (SOCKADDR *)&ClientAddr, &ClientAddrLen)) == INVALID_SOCKET)
    {
        printf("ACCPET_ERROR: %d\n", INVALID_SOCKET);
        closesocket(ListeningSocket);
        return 0;
    }
    printf("检测到一个连接: %s 端口:%d\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
 
    //聊天
    while(true)
    {
        //接收数据
        Ret = recv(NewConnection, recvData, BUFFSIZE, 0);
        if(Ret > 0)
            printf("小民: %s\n", recvData);
        else if(Ret < 0)
            printf("RECV_ERROR: %d\n",  SOCKET_ERROR);
        else
        {
            printf("对方退出程序,聊天结束!");
            break;
        }
 
        //发送数据
        printf("\n小魏:");
        scanf("%s", sendData);
        if(strcmp(sendData, "quit") == 0)   //退出
            break;
        if(send(NewConnection, sendData, BUFFSIZE, 0) == SOCKET_ERROR)
        {
            printf("消息发送失败!\n");
            break;
        }
    }
    //从容关闭
    shutdown(NewConnection, SD_BOTH);
 
    //完成新接受的连接后,用closesocket API关闭这些套接字
    closesocket(NewConnection);
    closesocket(ListeningSocket);
 
    //应用程序完成对接的处理后,调用WSACleanup
    if(WSACleanup() == SOCKET_ERROR)
    {
        printf("WSACLEANUP_ERROR: %d\n", WSAGetLastError());
        return 0;
    }
 
    system("pause");
    return 0;
}

客户端 client端:

/*
 *  客户端 Client.c
 *
 */
#include <winsock2.h>
#include <stdio.h>
#include <string.h>
 
#define BUFFSIZE 1024
//#define SOCKET_ERROR	(-1) _socket_types.h
 
int main(int argc, char**argv)
{
    int             Ret;
    WSADATA         wsaData;
    SOCKET          s;
    SOCKADDR_IN     ServerAddr;
    unsigned short  Port = 5150;
    char            sendData[BUFFSIZE];
    char            recvData[BUFFSIZE];
 
    if((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
    {
        printf("WSASTARTUP_ERROR: %d\n", Ret);
        return 0;
    }
 
    if((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
    {
        printf("SOCKET_ERROR: %d\n", INVALID_SOCKET);
        return 0;
    }
 
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(Port);
    ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    //ServerAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.101");// 这里S_un.S_addr在不同的IDE中可能不一样,然后IPv4地址使用该程序所运行在的PC上的IPv4地址
 
    if((connect(s, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr))) == SOCKET_ERROR)
    {
        printf("CONNECT_ERROR: %d\n", SOCKET_ERROR);
        closesocket(s);
        return 0;
    }
 
    //Chat
    while(true)
    {
        printf("\n小民:");
        scanf("%s", sendData);
        if(strcmp(sendData, "quit") == 0)   //quit
            break;
        if(send(s, sendData, BUFFSIZE, 0) == SOCKET_ERROR)
        {
            printf("消息发送失败!\n");
            break;
        }
 
        Ret = recv(s, recvData, BUFFSIZE, 0);
        if(Ret > 0)
            printf("小魏: %s\n", recvData);
        else if(Ret < 0)
            printf("RECV_ERROR: %d\n", SOCKET_ERROR);
        else
        {
            printf("对方退出程序,聊天结束!");
            break;
        }
    }
    shutdown(s, SD_BOTH);
    closesocket(s);
 
    if(WSACleanup() == SOCKET_ERROR)
    {
        printf("WSACLEANUP_ERROR: %d\n", WSAGetLastError());
        return 0;
    }
 
    system("pause");
    return 0;
}
C++实现

server端:

#include<iostream>
#include<string.h>
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")//加载 ws2_32.dll

#define BUFFSIZE 1024

using namespace std;

class Socket
{
	private:
		SOCKET sock;//套接字
		sockaddr_in sockAddr;//特定的IP地址
		unsigned short  Port;//端口号 
	public:
		Socket();//构造servSock 
		Socket(int n,Socket servSock);//构造clntSock 
		void usebind2listen();
		void chat();
		void close();
		//~Socket();
}; 


Socket::Socket()
{
	this->Port=5150;
	this->sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	memset(&(this->sockAddr),0,sizeof(this->sockAddr));
	this->sockAddr.sin_family = PF_INET;  //使用IPv4地址
    this->sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    this->sockAddr.sin_port = htons(Port);  //端口
}

Socket::Socket(int n,Socket servSock)
{
	this->sock=accept(servSock.sock,(SOCKADDR*)&(this->sockAddr),&n);
	//将server的servSock与客户端的sock相连接(二者地址配置相同),并赋给新的套接字clntSock 
	cout<<"检测到一个连接:"<<inet_ntoa(this->sockAddr.sin_addr)<<"端口:"<<ntohs(this->sockAddr.sin_port)<<endl; 
}

void Socket::usebind2listen()
{
	bind(this->sock,(SOCKADDR*)&sockAddr,sizeof(SOCKADDR));//将servsock与特定地址绑定 
	listen(this->sock,5);//监听,主程序运行到此处中断 
	cout<<"正在接收连接..."; 
}

void Socket::chat()
{
	int ret;
	char sendData[BUFFSIZE];
    char recvData[BUFFSIZE];
	while(true)
	{
		//接收数据 
		ret=recv(this->sock,recvData,BUFFSIZE,0);
		if(ret>0)
			cout<<"小民:"<<recvData<<endl; 
		else if(ret<0)
			cout<<"recv_error"<<SOCKET_ERROR<<endl;
		else
		{
			cout<<"对方退出程序,聊天结束"<<endl;
			break;
		}
		
		//发送数据
		cout<<endl<<"小魏:";
		cin>>sendData;
		cin.get();//吃掉回车
		if(strcmp(sendData,"quit")==0)
		{
			break;
		} 
		if(send(this->sock,sendData,BUFFSIZE,0)==SOCKET_ERROR)
		{
			cout<<"发送失败";
			break;
		}
	}
	shutdown(this->sock, SD_BOTH);
}



void Socket::close()
{
	closesocket(this->sock);//关闭套接字 
}

int main(int argc,char**argv)
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	
	Socket servSock;//构造套接字对象 PF_INET:IPv4
	servSock.usebind2listen();//servSock与设定的sockAddr绑定
	
	Socket clntSock(sizeof(sockaddr_in),servSock);//重载构造套接字对象,接收客户端请求 
	
	clntSock.chat();//聊天 
	
	servSock.close();
	clntSock.close();
	
	WSACleanup();//终止 DLL 的使用
	
    system("pause");
	return 0;
} 

client端:

#include<iostream>
#include<string.h>
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")//加载 ws2_32.dll

#define BUFFSIZE 1024

using namespace std;

class CSocket
{
	private:
		SOCKET sock;//套接字
		sockaddr_in sockAddr;//特定的IP地址
		unsigned short  Port;//端口号 
	public:
		CSocket();
		void useconnect();
		void userecieve(char *str);
		void chat();
		void close();
		//~CSocket(); 
};

CSocket::CSocket()
{
	this->Port=5150;
	this->sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	memset(&(this->sockAddr),0,sizeof(this->sockAddr));
	this->sockAddr.sin_family = PF_INET;  //使用IPv4地址
    this->sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    this->sockAddr.sin_port = htons(Port);  //端口
}

void CSocket::useconnect()
{
	connect(sock,(SOCKADDR*)&(this->sockAddr),sizeof(SOCKADDR));
	//套接字与地址建立连接,与服务器的accept相对应
}

void CSocket::chat()
{
	int ret;
	char sendData[BUFFSIZE];
    char recvData[BUFFSIZE];
	while(true)
	{	
		//发送数据
		cout<<endl<<"小民:";
		cin>>sendData;
		cin.get();//吃掉回车
		if(strcmp(sendData,"quit")==0)
		{
			break;
		} 
		if(send(this->sock,sendData,BUFFSIZE,0)==SOCKET_ERROR)
		{
			cout<<"发送失败";
			break;
		}
		
		//接收数据 
		ret=recv(this->sock,recvData,BUFFSIZE,0);
		if(ret>0)
			cout<<"小魏:"<<recvData<<endl; 
		else if(ret<0)
			cout<<"recv_error"<<SOCKET_ERROR<<endl;
		else
		{
			cout<<"对方退出程序,聊天结束"<<endl;
			break;
		}
	}
	shutdown(this->sock, SD_BOTH);
}

void CSocket::close()
{
	closesocket(this->sock);
}

int main(int argc,char**argv)
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	
	CSocket sock;
	sock.useconnect();
	
	sock.chat();
	
	sock.close();
	
	WSACleanup();
    system("pause");
    return 0;
}

9、深入理解Socket协议

Socket是网络通讯经常采用的一种方式,它不是一个具体的物件也不是想http类的通讯协议。你可以把它看成是一组基于TCP和UDP通信协议的接口,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

在这里插入图片描述

理解Socket

好,了解了基本分层机构后,我们现在来形象的理解一下Socket到底是什么。我这里有一个很形象的实例可以帮助我们很好的理解Socket,这也是我当初在学习Socket编程时自己领悟出来的,那就是物流

我们要传送的数据就是物流中的货物,ip就是发货人和收货人的联系方式,那么Socket是什么?大家想想这送货这这一流程还缺少啥?对呀,物流公司啊,对吧!物流公司就是那个Socket,他负责帮你处理送货的琐碎事情,比如你的货物该怎么打包、该怎么防止易碎物品破碎、该用什么方式运输、等等,如果是境外货物还牵涉到报关等等一系列复杂琐碎的事情,这些物流公司(Socket)帮你处理好了。示意图如下:

在这里插入图片描述

理解TCP与UDP的区别以及三次握手和四次挥手

然后我们来说一下TCP和UDP的区别。前面讲到Socket就相当于是物流公司,那么TCP协议就相当于是:假设你是寄货方,你要寄给你朋友一份礼物,你首先得去物流公司去寄,这样你再物流公司就相当于建立了一个事物(Socket实例)。然后物流公司有两种服务方式让你选择(TCP和UDP)。

先说TCP,TCP这个协议它比较保守,物流公司说我必须确保你朋友会接收这份礼物,不然它不送。于是物流公司就回去联系你朋友。然后联系内容大致如下:

物流:你好,我这里有个你朋友寄的礼物要给你,你收吗?

你朋友:OK,收,什么时候发,多久到?

物流:现在就发,大概要12小时到。

然后物流才开始发货,并且会给你们一个物流单号,这个单号就是关于这份货物你和你朋友的发货收货凭证。这就是所谓的三次握手。示意图:

在这里插入图片描述

物流发送完后还会联系你朋友,大致对话内容:

物流:你好,货物已发送,请注意查收

你朋友:好的。

货物送到,你朋友:OK,货物收到,你们可以结案

物流:好的,我结案了。
这就是所谓的四次挥手(拜拜);

(注释:三次握手四次挥手这都是Socket在你建立的时候根据你选择的协议确定好的)

这样是不是很麻烦,估计也就顺丰有这服务了,其他大部分公司还是没有这么贴心的服务的,他们可能就是UDP协议方式了。

UDP比较简单,当你用物流(Socket)寄货物的时候物流公司并不会管你朋友是否能联系上,是否愿意接收,它只负责将你的货物发出去就完事。这中间是不需要通知你朋友的,你和你朋友也不需要有连接,这就是所谓的无连接。

两种协议区别

UDP 常用于一次性传输比较少量数据的网络应用中,UDP 也常用于多媒体应用(如 IP 电话、实时视频会议、流媒体等),显然,可靠数据传输对于这些应用来说并不是最重要的,但 TCP 的拥塞控制会导致数据出现较大的延迟,这是它们不能容忍的,我们最熟悉的视频通话大部分就是用的UDP协议。

TCP面向连接,即发送数据之前需要建立连接,并且TCP是拥有拥塞机制的,当前部分数据还没有被接收完,后部分的数据会被阻塞知道前部分数据传递OK后才会继续传递后半部分,所以通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;在数据完整性、安全性要求较高的场景需要使用TCP协议,比如你传送重要办公文件、图片等,这些东西丢包都是会影响数据品质的。

10、TCP协议相关介绍

1. Wireshark里与显示TCP数据包相关的过滤规则:

tcp.flags

tcp.flags.syn == 1

tcp.flags.syn == 1&& tcp.flags.ack == 0

tcp.flags.fin == 1&& tcp.flags.ack == 1

tcp.window_size == 0 && tcp.flags.reset== 1

ip.addr==?.?.?.?

2. TCP数据报中标记字段(flag)的含义

* F : FIN - 结束; 结束会话

* S : SYN - 同步; 表示开始会话请求

* R : RST - 复位;中断一个连接

* P : PUSH - 推送; 数据包立即发送

* A : ACK - 应答

* U : URG - 紧急

3. TCP连接的建立过程

一个虚拟连接的建立是通过三次握手来实现的。这三次握手过程如下:

  1. (B) -> [SYN] -> (A)

假设现在客户端B想与服务器A通信,首先 B 向A发一个SYN (Synchronize) 标记的包,告诉 A 现在要请求建立连接。

注意: 一个 SYN 包就是仅 SYN 标记设为 1 的 TCP 包(参见 TCP 包头Resources)。 认识到这点很重要,只有当 A 收到 B 发来的 SYN 包,才可建立连接,除此之外别无他法。因此,如果你的防火墙丢弃所有的发往外网接口的 SYN 包,那么你将不能与外部任何主机主动建立连接。

  1. (A) -> [SYN/ACK] -> (B)

接着,A 收到 B 发过来的 SYN 包后会发一个对 SYN 包的确认包(SYN/ACK)

回去,表示对第一个 SYN 包的确认,并继续握手操作。

注意: SYN/ACK 包是仅 SYN 和 ACK 标记为 1 的包。

  1. (B) -> [ACK] -> (A)

B 收到 SYN/ACK 包,B 发一个确认包(ACK),通知 A 连接已建立。至此,三次握手完成,一个 TCP 连接完成 。

注意: ACK 包就是仅 ACK 标记设为 1 的 TCP 包。 需要注意的是当三次握手完成、连接建立以后,TCP 连接的每个包都会设置 ACK 位 。

4. TCP连接的释放过程

关闭已建立的 TCP 连接是通过四次握手实现的,下面是 4 次握手的过程。

  1. (B) -> ACK/FIN -> (A)

  2. (A) -> ACK -> (B)

  3. (A) -> ACK/FIN -> (B)

  4. (B) -> ACK -> (A)

注意: 由于 TCP 连接是双向连接, 因此关闭连接需要在两个方向上做。 ACK/FIN 包(ACK 和 FIN 标记设为 1)通常被认为是 FIN(终结)包.然而, 由于连接还没有关闭, FIN 包总是打上 ACK 标记. 没有 ACK 标记而仅有 FIN标记的包不是合法的包,并且通常被认为是恶意的。

四次握手不是关闭 TCP 连接的唯一方法. 有时,如果主机需要尽快关闭连接(或连接超时,端口或主机不可达),RST (Reset)包将被发送。 注意在,由于 RST包不是 TCP 连接中的必须部分, 可以只发送 RST 包(即不带 ACK 标记). 但在 正常的 TCP 连接中 RST 包可以带 ACK 确认标记 。

Reference

1、https://blog.csdn.net/qq_41725312/article/details/90375742#Windows_271

2、https://mp.weixin.qq.com/s?__biz=MzI0ODU0NDI1Mg==&mid=2247492176&idx=1&sn=3be138f895ebaabe778f6a798c9a0bd7&chksm=e99d8cc3deea05d53036aa1651e29cb82f92fc2a6614d9d6c2f1d3b495dd6ca9cd8205243892&mpshare=1&scene=1&srcid=&sharer_sharetime=1574827986780&sharer_shareid=c322363ce8b98030db684603925979c8&key=51b99fa703fecc6bdcbec4658c05c6a71778ad95e9216e57ce0dacd0abc03ab0c1f8828082426dc50d896a10a396e7d2cf56c9d97c5bb831788431c578b4912863f6dce95644fb05a3df31cd7c1f3ef8&ascene=1&uin=MTk0MDc0NTUzMA%3D%3D&devicetype=Windows+10&version=62070158&lang=zh_CN&pass_ticket=Aqe5lFEauGsw14e6yvqQEb1wVB65V8IjWm3%2Fh3qMhXrIGr10eMZqXxUcZBjQzo9Q

3、武汉大学信息管理学院Z同学的TCP的Slide。

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

网络协议与网络编程(单电脑信息传输) 的相关文章

  • 8.岛问题

    岛问题 题目 一个矩阵中只有0和1两种值 xff0c 每个位置都可以和自己的上 下 左 右四个位置相连 xff0c 如果有一片1连在一起 xff0c 这个部分叫做一个岛 xff0c 求一个矩阵中有多少个岛 xff1f 例子 0 0 1 0
  • 9.KMP算法

    KMP算法 1 KMP算法解决的问题 字符串str1和str2 xff0c str1是否包含str2 xff0c 如果包含返回str2在str1中开始的位置 xff0c 如果不包含返回 1 如果做到时间复杂度O N 完成 xff1f 测试用
  • 10.Manacher算法(用于解决回文子串问题)

    Manacher算法 1 Manacher算法解决的问题 字符串str中 xff0c 最长回文子串的长度如何求解 xff1f 如何做到时间复杂度O N 完成 xff1f 回文序列是从左往右和从右往左看一样 xff0c 如abba xff0c
  • git push代码到远程仓库,报错解决:fatal: unable to access ‘https://github.com/.......‘: OpenSSL SSL_read: Connec

    报错如下 xff1a 产生原因 xff1a 一般是这是因为服务器的SSL证书没有经过第三方机构的签署 xff0c 所以才报错解除ssl验证后 xff0c 再次git即可 解决办法输入此条git命令 xff1a git config glob
  • 11.滑动窗口的最大值——重要结构双端队列

    滑动窗口最大 xff08 小 xff09 值 1 滑动窗口最大值结构 窗口概念 xff1a 一开始窗口左边界L 有边界R都停留在数组左侧 xff0c 窗口L和R都只能往数组右边移动 xff0c 并且左边界L永远不能超过有边界R 任何时刻都能
  • 12.单调栈——解决接雨水和柱状图中的最大矩形等问题

    单调栈 1 单调栈实现结构 单调栈解决的问题 xff1a 给你一个数组 想要用尽可能低的代价知道数组中每一个元素的左边元素比它大的或者右边元素比他大的信息是什么 如果用暴力方法 xff0c 左边遍历一次右边遍历一次 xff0c 时间复杂度为
  • 12.快速排序

    1荷兰国旗问题 问题1 xff1a 给定一个数组arr和一个数num xff0c 将小于等于num的数放在数组的左边大于num的数放在数组的右边 xff08 不要求有序 xff09 要求额外空间复杂度为O 1 时间复杂度为O N 遍历数组元
  • 死锁预防、死锁避免、死锁检测

    死锁 1 死锁的概念 1 1死锁的定义 多个进程并发执行 xff0c 由于竞争资源而造成的一种僵局 xff08 互相等待 xff09 xff0c 若无外力作用 xff0c 这些进程都将无法推进 xff0c 这就是死锁现象 例如 xff1a
  • 内存分配方式

    内存分配方式 1 基本概念 内存管理的基本概念 虽然计算机硬件发展 xff0c 内存容量在不断变大 xff0c 但是也不可能将所有用户进程和系统所需要的程序和数据放入内存中 xff0c 因此操作系统必须要对内存空间进行合理划分和有效动态分配
  • 虚拟内存和LRU页面置换算法

    虚拟内存 1 虚拟内存的基本概念 传统存储管理方式的特征 传统的内存管理策略都是为了同时将多个进程保存进内存中 xff0c 它们具有以下的共同特征 xff1a 一次性 作业必须一次性全部装入内存后 xff0c 才能开始运行 xff08 静态
  • 0.0C++和C的区别

    C 43 43 和C的区别 C 43 43 如今是一个同时支持面向过程 面向对象 函数形式 泛型形式 元编程形式的语言 我们该如何理解C 43 43 这门语言呢 xff1f Effective C 43 43 书中给出了一个简单的方法 xf
  • 15.9为什么要将成员变量设置为private

    为什么要将成员变量声明为private 为什么要将成员变量封装为private xff0c 主要有以下四个原因 xff1a 好处1 xff1a 如果成员变量不是public xff0c 那么客户唯一能访问成员变量的唯一方式就是通过成员函数
  • 2.7.C++中static关键字的5种基本用法

    static关键字 static关键字主要应用于以下几种情况 xff1a 情况1 xff1a static静态函数 定义静态函数 xff1a 在函数返回类型前加上static关键字 xff0c 函数即被定义为静态函数 静态函数只能在本源文件
  • 进程调度算法

    进程调度 在多道程序系统中 xff0c 进程数量往往多于处理机的个数 xff0c 因此进程竞争使用处理机的情况在所难免 处理机调度是对处理机进行分配 xff0c 即从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行 xff0c 以
  • git clone 出现fatal: unable to access ‘https://github 类错误解决方法

    git clone 遇到问题 xff1a fatal unable to access https github comxxxxxxxxxxx Failed to connect to xxxxxxxxxxxxx 问题 将命令行里的http
  • 进程通信的方式

    进程通信 1 进程通信的概念 进程是一个独立的资源分配单元 xff0c 不同进程 xff08 主要是指不同的用户进程 xff09 之间的资源是独立的 xff0c 没有关联的 xff0c 不能在一个进程中直接访问另一个进程的资源 但是 xff
  • 网络通信的过程

    网络通信的过程 封装 上层协议时如何使用下层协议提供的服务的呢 xff1f 其实这是通过封装实现的 应用程序是在发送到物理网络上之前 xff0c 将沿着协议栈从上往下依次传递 每层协议都将在上层数据的基础上加上自己的头部信息 xff08 有
  • TCP三次握手、四次挥手

    TCP通信流程 TCP和UDP TCP和UDP区别如下 xff1a UDP xff1a 用户数据报文协议 xff0c 面向无连接 xff0c 可以单播 xff0c 多播 xff0c 广播 xff0c 面向数据报 xff0c 不可靠 TCP
  • Qt的多线程编程

    Qt线程 基本概念 并发 当有多个线程在操作时 xff0c 如果系统只有一个CPU xff0c 则它根本不可能真正同时进行一个以上的线程 xff0c 它只能把CPU运行时间划分成若干个时间段 xff0c 再将时间段分配给各个线程执行 xff
  • CMake编译C++文件

    这篇文章介绍如何使用cmake工具编译一个最简单的helloworld cpp文件 首先创建一个空的文件夹 mkdir cmake test 在该文件夹下 xff0c 我们新建一个helloworld cpp文件 span class to

随机推荐

  • 智能小车建图导航-在rviz中导航(运行)

    笔记来源 xff1a 机器人开发与实践 xff08 古月 xff09 或者直接运行这个脚本文件 xff1a xff08 如果你没有在 bracsh文件中加入source xff0c 建议加入或者在脚本文件的上面中添加source xff0c
  • 004-S500无人机-相关的器件参数以及计算

    这篇博客主要是记录S500无人机的相关器件的参数 xff0c 参数的来源来源于holybro官网 xff1a https shop holybro com 我这里进行参数的归纳以及计算 一 电机 xff08 2216 880kv xff09
  • TX2 学习记录(开启板载/USB摄像头)

    刚拿到手一个TX2 xff0c 简单地学习一下这块板子 xff0c 因为是学长留下来的板子 xff0c 所以刷机的步骤我就省略了 xff0c 各位小伙伴可以参考其他大佬的博客进行刷机 xff0c 再来就记录一下一些操作指令吧 打开USB摄像
  • ubuntu16.04中进行ROS通信编程

    ROS通信学习 基础知识学习字段ROS通信小例子一 创建一个工作区二 创建一个ROS工程包三 创建通信的发 收节点四 测试程序的正确性 图像ROS通信小例子视频ROS通信小例子多机ROS通信 基础知识学习 x1f31f 话题与服务的区别 话
  • 2021电赛F题智能送药小车方案分析(openMV数字识别,红线循迹,STM32HAL库freeRTOS,串级PID快速学习,小车自动返回)

    2021全国大学生电子设计竞赛F题智能送药小车 前提 xff1a 本篇文章重在分享自己的心得与感悟 xff0c 我们把最重要的部分 xff0c 摄像头循迹 xff0c 摄像头数字识别问题都解决了 xff0c 有两种方案一种是openARTm
  • CARLA常见错误解决方案以及常见的问题解决方案

    记录Linux环境 Windows环境下常见的运行自动驾驶仿真器CARLA出现的错误 问题1 问题1比较基础 xff0c 创建虚拟环境以及删除虚拟环境 conda create span class token operator span
  • cmd找不到conda以及通过cmd启用Anaconda中的Python环境(base)

    问题 xff1a 在cmd中输入python无法进入或启用python ipython conda jupyter notebook 一 解决方法 xff1a 在系统环境中添加Anaconda路径 lt 1 gt 1 打开高级系统设置 xf
  • c语言实现strcat函数

    char strcat char strDestination const char strSource 一 函数介绍 作用 xff1a 连接字符串的函数 xff0c 函数返回指针 xff0c 两个参数都是指针 xff0c 第一个参数所指向
  • C/C++的static关键字作用(转载)

    一 限制符号的作用域只在本程序文件 若变量或函数 xff08 统称符号 xff09 使用static修饰 xff0c 则只能在本程序文件内使用 xff0c 其他程序文件不能调用 xff08 非static的可以通过extern 关键字声明该
  • crc校验

    参考链接 xff1a https www cnblogs com esestt archive 2007 08 09 848856 html 一 CRC校验原理 1 CRC校验全称为循环冗余校验 xff08 Cyclic Redundanc
  • ubuntu安装eclipse教程

    在安装eclipse之前 xff0c 要先安装JDK xff0c 一 安装JDK 1 从官网上下载JDK 链接 xff1a https www oracle com java technologies downloads 选择的jdk文件一
  • UDP通信入门篇

    UDP通信属于网络通信中的一种方式 xff0c 需要用套接字来进行通信 初接触UDP通信时 xff0c 不知道需要链接静态库 pragma comment lib ws2 32 lib xff0c 导致自己在前期浪费了很多时间去排查问题 除
  • window11配置深度学习环境

    Anaconda 43 PyCharm 43 CUDA 43 CUDNN 43 PyTorch 1 Anaconda安装 下载路径 xff1a https www anaconda com 安装方式 xff1a 以管理员身份安装 中间选项
  • python配置opencv环境后,读取图片,报错:can‘t open/read file: check file path/integrity

    运行出错代码 xff1a import cv2 import numpy as np image 61 cv2 imread 39 C Pictures 桌面背景图片切换 wallhaven 6oq1k7 jpg 39 cv2 IMREAD
  • 断言

    代码中放置一些假设 xff0c 通过判断假设是否为真 xff0c 进而判断程序是否正确 断言就是用来测试程序中的假设是否正确的 xff0c 若果假设被违反 xff0c 那么就中断程序的执行 断言assert是定义在assert h中的 宏
  • STM32输出SPWM波,HAL库,cubeMX配置,滤波后输出1KHz正弦波

    SPWM波 对于功率方向 输出SPWM波是必须要掌握的 工程 stm32生成spwm代码Keil工程链接资源 引用spwm波定义 PWM波形就是指占空比可变的波形 SPWM波形是指脉冲宽度按正弦规律变化且和正弦波等效的PWM波形 两者的区别
  • C语言链表写法,练习链表

    C语言链表写法 xff0c 练习链表 建立要一个文件 xff1a LinkList h 内容 xff1a span class token macro property span class token directive keyword
  • 树莓派摄像头 C++ OpenCV YoloV3 实现实时目标检测

    树莓派摄像头 C 43 43 OpenCV YoloV3 实现实时目标检测 本文将实现树莓派摄像头 C 43 43 OpenCV YoloV3 实现实时目标检测 xff0c 我们会先实现树莓派对视频文件的逐帧检测来验证算法流程 xff0c
  • RTK定位原理

    一 卫星测距原理说明 天上的卫星发送数据被便携式RTK终端接收到 xff0c 卫星和终端之间的距离D 61 C T C为光速 xff0c T为卫星发送的信号到达便携式RTK终端的时间 xff0c 通过时间乘以距离可以获得卫星和便携式终端的实
  • 网络协议与网络编程(单电脑信息传输)

    C C 43 43 网络编程 单电脑信息传输 Copyright Jingmin Wei Pattern Recognition and Intelligent System School of Artificial and Intelli