基于OpenCV C++的网络实时视频流传输——Windows下使用TCP/IP编程原理

1.TCP/IP编程

1.1 概念

IP 是英文 Internet Protocol (网络之间互连的协议)的缩写,也就是为计算机网络相互连接进行通信而设计的协议。任一系统,只要遵守 IP协议就可以与因特网互连互通。

所谓IP地址就是给每个遵循tcp/ip协议连接在Internet上的主机分配的一个32bit地址。按照TCP/IP协议规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号 “.” 分开不同的字节,如:192.168.1.1。在因特网中,主机的标识便是它ip地址。

常见的获取服务器ip的方法是使用系统自带的ping命令。例,打开cmd输入:ping http://www.xxxx.com

1.2 Socket

socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机制,取后一种意思。通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。Scoket程勋分为服务端与客户端,服务端程序监听端口,等待客户端程序的连接。客户端程序发起连接,等待服务端的相应。客户端想要连接上服务端的话,需要 知道服务端的ip地址。

例如,客户想要访问百度网站的首页,通过浏览器访问http://www.baidu.com。浏览器发出请求之后,先是DNS服务器将百度的域名解析成ip地址之后,访问到ip地址为115.239.210.27服务器的80端口(http协议默认在80端口),请求发送后,百度的服务器作为响应将页面的源代码发送至客户端(通过浏览器右键->源代码,或者ctrl+u到服务器返回的页面源代码),随后浏览器将源代码渲染成页面。这样用户就看到了百度网站的

1.3 服务端与客户端

服务端与客户端在计算机的世界里,凡是提供服务的一方我们称为服务端(Server),而接受服务的另一方我们称作客户端(Client)。网站提供网页数据的服务,使用者接受网站所提供的数据服务,所以使用者在这里就是客户端,响应使用者要求的网站即称为服务端。不过客户端及服务端的关系不见得一定建立在两台分开的机器上,同一台机器中也有这种主从关系的存在,提供服务的服务端及接受服务的客户端也有可能都在同一台机器上。例如我们在提供网页的服务器上执行浏览器浏览本机所提供的网页,这样在同一台机器上就同时扮演服务端及客户端。

实现流程:

服务器端创建套接字后连续调用bind、listen函数进入等待状态,客户端通过调用connect函数发起连接请求。需要注意的是,客户端只能等到服务器调用listen函数后才能调用connect()函数。同时要清楚,客户端调用connect()函数前,服务器端有可能率先调用accept()函数。当然,此时服务器在调用accept()函数是进入阻塞状态,直到客户端调用connect()函数为止。

2、Windows与Linux下TCP/IP编程

2.1 函数说明

Windows套接字大部分是参考BSD系列UNIX套接字设计的,所以很多地方都跟Linux套接字类似。大多数项目都在Linux系列的操作系统下开发服务器端,而多数客户端是在windows平台下开发的。

在windows下做网络编程,需要做如下准备:导入头文件winsock2.h、链接ws2_32.lib。

Windows 下的 socket 程序和 Linux 思路相同,但细节有所差别:

Windows 下的 socket 程序依赖Winsock.dll 或 ws2_32.dll,必须提前加载。Windows Socket编程依赖于系统提供的动态链接库(DLL),有两个版本:较早的DLL是 wsock32.dll,大小为 28KB,对应的头文件为 winsock1.h;最新的DLL是 ws2_32.dll,大小为 69KB,对应的头文件为 winsock2.h。几乎所有的 Windows 操作系统都已经支持 ws2_32.dll,包括个人操作系统 Windows 95 OSR2、Windows 98、Windows Me、Windows 2000、XP、Vista、Win7、Win8、Win10 以及服务器操作系统 Windows NT 4.0 SP4、Windows Server 2003、Windows Server 2008 等,所以你可以毫不犹豫地使用最新的 ws2_32.dll。

使用 DLL 之前必须把 DLL 加载到当前程序,你可以在编译时加载,也可以在程序运行时加载。这里使用#pragma命令,在编译时加载:#pragma comment (lib, “ws2_32.lib”)

使用 DLL 之前,还需要调用 WSAStartup() 函数进行初始化,以指明 WinSock 规范的版本,并初始化相应版本的库。它的原型为:int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);wVersionRequested:该参数为WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号);
lpWSAData:该参数为指向 WSAData 结构体的指针;WinSock 规范的最新版本号为 2.2,较早的有 2.12.01.11.0,ws2_32.dll 支持所有的规范,而 wsock32.dll 仅支持 1.01.1。若版本为1.2,则其中1是主版本号,2是副版本号,应传递0x0201。
wsock32.dll 已经能够很好的支持 TCP/IP 通信程序的开发,ws2_32.dll 主要增加了对其他协议的支持,不过建议使用最新的 2.2 版本。wVersionRequested 参数用来指明我们希望使用的版本号,它的类型为 WORD,等价于 unsigned short,是一个整数,所以需要用 MAKEWORD() 宏函数对版本号进行转换。例如:
MAKEWORD(1, 2); //主版本号为1,副版本号为2,返回 0x0201
MAKEWORD(2, 2); //主版本号为2,副版本号为2,返回 0x0202

Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;

Linux 中的一切都是文件,每个文件都有一个整数类型的文件描述符,socket 也是一个文件,也有文件描述符,所以Linux 不区分 socket 文件和普通文件,而 Windows 区分;

Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。

Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。

关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。

socket编程的基本函数有socket()、bind()、listen()、accept()、send()、sendto()、recv()以及recvfrom()等。

2.2 socket()

Linux 中的一切都是文件,每个文件都有一个整数类型的文件描述符;socket 也是一个文件,也有文件描述符。使用 socket() 函数创建套接字以后,返回值就是一个 int 类型的文件描述符。

Windows 会区分 socket 和普通文件,它把 socket 当做一个网络连接来对待,调用 socket() 以后,返回值是 SOCKET 类型,用来表示一个套接字。

不管是 Windows 还是 Linux,都使用 socket() 函数来创建套接字。socket() 在两个平台下的参数是相同的,不同的是返回值。

头文件

Linux

#include <sys/socket.h>

Windows

#include <winsock2.h>

函数原型

Linux

int socket(int family,int type,int protocol);

Windows

SOCKET socket(int  family, int type, int protocol);

作用

该函数用于创建一个套接字,同时指定协议和类型

参数

family:协议族(AF前缀和PF前缀是等价的)

AF_INET(PF_INET):IPv4协议族

AF_INET6(PF_INET6):IPv6协议族

AF_LOCAL:UNIX域协议族

AF_ROUTE:路由套接字协议族

AF_KEY:密钥套接字协议族

type:套接字类型,即套接字的数据传输方式(有的协议族中存在多种数据传输方式,所以协议族和数据传输方式分开)

SOCK_STREAM:流式套接字

SOCK_DGRAM:数据报套接字

SOCK_RAM:原始套接字

Protocol:传输协议(该参数一般置0,原始套接字除外),比如满足AF_INET和SOCK_STREAM这两个条件的只有IPPROTO_TCP,满足AF_INET和SOCK_DGRAM这两个条件的只有IPPTOTO_UDP

IPPROTO_TCP:TCP协议

IPPTOTO_UDP:UDP协议

返回值

成功

Linux

非负套接字文件描述符

Windows

套接字句柄

失败

Linux

-1

Windows

INVALID_SOCKET

注意:
protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。有了地址类型和数据传输方式,还不足以决定采用哪种协议吗?为什么还需要第三个参数呢?

正如大家所想,一般情况下有了 af 和 type 两个参数就可以创建套接字了,操作系统会自动推演出协议类型,除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。如果我们不指明使用哪种协议,操作系统是没办法自动推演的。

当参数 family 的值为 PF_INET时:

如果使用 SOCK_STREAM 传输数据,那么满足这两个条件的协议只有 TCP,因此可以这样来调用 socket() 函数:

int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //IPPROTO_TCP表示TCP协议 这种套接字称为 TCP 套接字

如果使用 SOCK_DGRAM 传输方式,那么满足这两个条件的协议只有 UDP,因此可以这样来调用 socket() 函数:

int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //IPPROTO_UDP表示UDP协议 这种套接字称为 UDP 套接字

上面两种情况都只有一种协议满足条件,可以将 protocol 的值设为 0,系统会自动推演出应该使用什么协议,如下所示:

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字

int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字

2.3 bind()

它主要用于服务器,客户端创建的套接字可以不绑定地址。绑定时一般要指定IP地址和端口号,否则内核会随意分配一个临时端口给该套接字,IP地址可以是本机的IP地址,或者使用宏INADDR_ANY,允许将套接字与服务器的任意网络接口(如eth0、eth0:1、eth1等)进行绑定。

头文件

Linux

#include <sys/socket.h>

Windows

#include <winsock2.h>

函数原型

Linux

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

Windows

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

作用

给服务器套接字分配IP地址和端口号

参数

sockfd

套接字描述符(服务器端socket函数的的返回值)

addr

绑定的地址(struct sockaddr_in强转)

addrlen

地址长度(sizeof(server_addr))

返回值

成功

Linux

0

Windows

0

失败

Linux

-1

Windows

SOCKET_ERROR

2.4 listen()

在服务器端程序成功建立套接字并与地址进行绑定之后,通过调用listen()函数将服务器套接字设置成被动监听模式(被动模式),并且设置客户端请求连接的队列长度。所谓被动监听,是指套接字一直处于“睡眠”中,直到客户端发起请求才会被“唤醒”。

头文件

Linux

#include <sys/socket.h>

Windows

#include <winsock2.h>

函数原型

Linux

int listen(int sockfd, int backlog);

Windows

int listen(SOCKET sockfd, int backlog);

作用

该函数将服务器套接字转为可接收连接状态

参数

sockfd

套接字描述符(服务器端的套接字)

backlog

请求队列中允许的最大请求数,大多数系统默认值为5,最大为1024

返回值

成功

Linux

0

Windows

0

失败

Linux

-1

Windows

SOCKET_ERROR

sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。

所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

当套接字正在处理客户端时,如果有新的客户端请求进来,套接字是没法处理的,只能把它放进缓冲区进行排队,待当前客户端处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。

缓冲区的长度(能存放多少个客户端请求)可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据你的需求来定,并发量小的话可以是10或者20。

如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。

当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误,对于 Windows,客户端会收到 WSAECONNREFUSED 错误。

注意:listen() 只是让套接字处于监听状态,将各个客户端的请求放入缓冲区进行排队,并没有处理客户端的请求。接受请求需要使用 accept() 函数。

2.5 accept()

服务器端通过调用accept()函数等待并处理客户端的连接请求,程序一旦执行到 accept() 就会被阻塞(暂停运行),直到客户端发起请求。建立好TCP连接后,该函数会返回一个新的已连接的套接字。

头文件

Linux

#include <sys/socket.h>

Windows

#include <winsock2.h>

函数原型

Linux

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

Windows

SOCKET accept(SOCKET sockfd, struct sockaddr *addr, int *addrlen);

作用

该函数用来处理连接请求,如果在没有连接请求,该函数不会返回。一直到有连接请求为止

参数

sockfd

套接字描述符(服务器的套接字)

addr

用于保存连接的客户端的地址

addrlen

地址长度

返回值

成功

Linux

建立好连接的套接字描述符

Windows

建立好连接的套接字句柄

失败

Linux

-1

Windows

INVALID_SOCKET

accept()函数受理连接请求等待队列呆处理的客户端连接请求。函数调用成功时,accept()函数内部将产生用于数据I/O的套接字,并返回其文件描述符。需要强调的是,套接字是自动创建的,并自动与发起连接请求的客户端建立连接

它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。

accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。

最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

2.6 connect()

客户端通过该函数向服务器端的监听套接字发送连接请求,直到服务器传回数据后,connect() 才运行结束。

客户端调用connect()函数后,发生以下情况之一才会返回:服务器端接受连接请求、发生断网等异常情况而中断连接请求。

需要注意,所谓的“接受连接请求”并不意味着服务器端调用accept()函数,其实是服务器端用listen()把客户端连接请求信息记录到等待队列。因此connect()函数返回后并不立即进行数据交换。

客户端在调用connect()函数时会自动给套接字分配IP地址和端口号,不需要调用bind()函数进行分配。

头文件

Linux

#include <sys/socket.h>

Windows

#include <winsock2.h>

Linux

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

Windows

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

作用

该函数用来向服务器发送连接请求

参数

sockfd

套接字描述符(客户端的套接字)

serv_addr

要连接的服务器端的IP地址信息

addrlen

地址长度

返回值

成功

Linux

0

Windows

0

失败

Linux

-1

Windows

SOCK_ERROR

2.7 send()和recv()

头文件

Linux

#include <sys/socket.h>

Windows

#include <winsock2.h>

函数原型

Linux

int send(int sockfd,const void *buf,int len,int flags);

Windows

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

作用

该函数用来发送数据

参数

sockfd

套接字描述符(客户端的套接字)

buf

发送缓冲区的地址

len

发送数据的长度

flags

一般为0

返回值

成功

Linux

实际发送的字节数

Windows

实际发送的字节数

失败

Linux

-1

Windows

SOCKET_ERROR

头文件

Linux

#include <sys/socket.h>

Windows

#include <winsock2.h>

函数原型

Linux

int recv(int sockfd,void *buf,int len,unsigned int flags);

Windows

int recv(SOCKET sockfd, char *buf, int len, int flags);

作用

该函数用来接收数据

参数

sockfd

套接字描述符(客户端的套接字描述符)

buf

存放接收数据的缓冲区地址

len

接收数据的长度

flags

一般为0

返回值

成功

Linux

实际接收到的字节数(收到EOF时为0)

Windows

实际接收到的字节数(收到EOF时为0)

失败

Linux

-1

Windows

SOCKET_ERROR

2.8 sendto()和recvfrom()

这两个函数一般在UDP通信过程中用于发送和接收数据。当用在TCP时,后面的几个与地址有关的参数不起作用,函数作用等同于send()和recv()**

头文件

#include <sys/socket.h>

函数原型

int sendto(int sockfd,const void *buf,int len, unsigned int flags,const struct sockaddr *to,int tolen);

作用

该函数用来发送数据

参数

sockfd

套接字描述符

buf

发送缓冲区的地址

len

发送数据的长度

flags

一般为0

to

接收方的套接字的IP地址和端口号

tolen

地址长度

返回值

成功

实际发送的字节数

失败

-1

头文件

#include <sys/socket.h>

函数原型

int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);

作用

该函数用来接收数据

参数

sockfd

套接字描述符

buf

存放接收数据的缓冲区地址

len

接收数据的长度

flags

一般为0

from

发送方的IP地址和端口号信息

Fromlen

地址长度

返回值

成功

实际接收到的字节数

失败

-1

2.9 close()

头文件

Linux

#include <sys/socket.h>

Windows

#include <winsock2.h>

函数原型

Linux

int close(int sockfd);

Windows

int closesocket(SOCKET sockfd);

作用

关闭套接字

参数

sockfd

套接字描述符

返回值

成功

Linux

0

Windows

0

失败

Linux

-1

Windows

SOCKET_ERROR

3.代码实现

3.1 server.cpp

#include<WINSOCK2.H>
#include<stdio.h>
#pragma comment(lib,"WS2_32.lib")
#define   BUFFER_SIZE 1024 //缓冲区大小
int main()
{WSADATA WSAData;SOCKET sClient;    //用于和客户端socket进行通信SOCKET sServer; //用于和本地地址绑定的socketint err_code; //存储返回的错误代码char buffer[BUFFER_SIZE]; //缓冲区if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0){printf("WSAStartup failed,错误代码:%d\n",WSAGetLastError());return -1;}      //初始化winsocksServer=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //创建监听的socketif(sServer==INVALID_SOCKET){printf("创建Socket失败,错误代码:%d\n",WSAGetLastError());WSACleanup();//清除初始化的Winsockreturn -1;}  SOCKADDR_IN addrServ;  //在监听的SOCKET绑定到本地之前,需要设置服务器Socket的地址addrServ.sin_family=AF_INET;addrServ.sin_port=htons(9990);//这里为什么不能用htonl()函数来转换呢,因为SOCKADDR_IN里面声明的端口号为u_shortk类型addrServ.sin_addr.S_un.S_addr=htons(INADDR_ANY);//服务器监听端口为INADDR_ANY,即在任意本地地址(0.0.0.0)上进行监听err_code=bind(sServer,(const sockaddr *)&addrServ,sizeof(SOCKADDR_IN));    //将用于绑定的sService绑定到本地地址if(err_code==SOCKET_ERROR){printf("bind failed,错误代码:%d\n",WSAGetLastError());closesocket(sServer);WSACleanup();return -1;}err_code=listen(sServer,1);  //监听客户端socket,第一个参数为与本地地址绑定的socket,第二个参数表示等待连接队列的最大长度if(err_code==SOCKET_ERROR){printf("listener failed,错误代码:%d\n",WSAGetLastError());closesocket(sServer);WSACleanup();return -1;}printf("TCP Server start...\n");sockaddr_in addrClient;int addrClientlen=sizeof(addrClient);sClient=accept(sServer,(sockaddr *)&addrClient,&addrClientlen);if(INVALID_SOCKET==sClient){printf("accept failed!,错误代码:%d\n",WSAGetLastError());closesocket(sServer);      WSACleanup();return -1;}//循环接收客户端的数据while(true){ZeroMemory(buffer,BUFFER_SIZE);//初始化缓冲区,把缓冲区置0err_code=recv(sClient,buffer,sizeof(buffer),0);if(err_code==SOCKET_ERROR){printf("Recv failed !,错误代码:%d\n",WSAGetLastError());closesocket(sClient);closesocket(sServer);WSACleanup();return -1;}if(strcmp(buffer,"quit")==0){ //当客户端发来quit时,服务器就关闭send(sClient,"quit",strlen("quit"),0);break;}else{char msg[BUFFER_SIZE]="我是服务器,我已收到:";printf("接收来自客户端[%s:%d]的消息 :%s\n",inet_ntoa(addrClient.sin_addr),addrClient.sin_port,buffer);strcat(msg,buffer);//拼接起来发送给主机,表示服务器已收到消息err_code=send(sClient,msg,strlen(msg),0);if(err_code==SOCKET_ERROR){printf("send failed !错误代码:%d\n",WSAGetLastError());closesocket(sServer);closesocket(sClient);WSACleanup();return -1;}}}closesocket(sClient); //关闭用于通信的socketclosesocket(sServer);//关闭与本地地址绑定的socketWSACleanup();//释放资源system("pause");return 0;
}

3.2 Client.cpp

#include<stdio.h>
#include<WINSOCK2.H>
#include<string.h>
#include<iphlpapi.h>
#pragma comment(lib,"WS2_32.lib")
#define BUFFER_SIZE 1024 //缓冲区大小
int main(){WSADATA WSAData;SOCKET s_Host;//本机socketsockaddr_in addrServer ;//服务器地址 char buf[BUFFER_SIZE]; //缓冲区字符数组int return_info;//存储返回的错误代码if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0){ //初始化Winsock2.2printf("无法完成初始化,错误代码:%d",WSAGetLastError());WSACleanup();return 0;}s_Host=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(s_Host==INVALID_SOCKET){printf("创建sockets失败,错误代码:%d\n",WSAGetLastError());WSACleanup();return -1;}addrServer.sin_family=AF_INET;addrServer.sin_port=htons(9990);addrServer.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");return_info=connect(s_Host,(sockaddr*)&addrServer,sizeof(addrServer));if(SOCKET_ERROR==return_info){printf("连接服务器失败,错误代码:%d",WSAGetLastError());closesocket(s_Host);WSACleanup();return -1;}//循环给服务器发送消息while(1){printf("请输入要发送的消息:");gets(buf);return_info=send(s_Host,buf,strlen(buf),0);//这里用strlen()哦,用sizeof()就吧缓冲区那么大的数据传过去了!!!if(return_info==SOCKET_ERROR){printf("发送消息失败,错误代码:%d",WSAGetLastError());closesocket(s_Host);WSACleanup();return -1;}return_info= recv(s_Host,buf,sizeof(buf),0);    //recv()函数返回接收到的字节数if(return_info==SOCKET_ERROR){printf("接受服务器消息失败, 错误代码:%d",WSAGetLastError());closesocket(s_Host);WSACleanup();return -1;}else{buf[return_info]='\0';}if(strcmp(buf,"quit")==0){//当收到"quit"退出printf("退出...\n");break;}else{printf("%s\n",buf);}}if(WSACleanup()==SOCKET_ERROR){printf("WSACleanUp出错!!!");}  //做一些清除工作system("pause");return 0;
}
/*乱码解决方案:
大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因 
char str[20]="0123456789";
int a=strlen(str); //a=10;
int b=sizeof(str); //而b=20;1.所以,在调用recv()函数时,用sizeof(),因为recv函数把接收缓冲中的数据copy到buf中
(注意协议接收到的数据可能大于buf的长度所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。
recv函数仅仅是copy数据,真正的接收数据是协议来完成的),
recv函数返回其实际copy的字节数。如果recv在copy时出错,
那么它返回SOCKET_ERROR;
如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
2.在调用send()函数时,用strlen(),因为这样就会将输入多少数据,转换为字节直接传过去。而接受就不一样了,接收的buf是缓冲区*/

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

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

相关文章

[QT开发_音乐播放器项目笔记01]

目录 一&#xff1a;常用类 26 QByteArray 是 Qt 框架中的一个类&#xff0c;用于处理字节数组。它提供了动态大小的字节数组&#xff0c;可以用于存储和操作二进制数据&#xff0c;比如文件内容、网络数据等。 QT项目记录&#xff1a; 一&#xff1a;常用类 26 QByteArray…

【JavaScript】详解JavaScript语法

文章目录 一、变量和数据类型二、运算符三、条件语句四、循环语句五、函数六、对象和数组七、ES6新特性八、实际应用案例 JavaScript是一门广泛应用于Web开发的编程语言。掌握JavaScript语法是成为前端开发者的第一步。本文将详细介绍JavaScript的基本语法&#xff0c;包括变量…

Python技能达到这个水平,高薪就业不是梦

一&#xff0c;高薪就业的必备基础 要达到高薪就业的水平&#xff0c;Python开发者通常需要具备以下几方面的技能和经验&#xff1a; 如需Python籽料直接戳&#xff1a; 2024年最新python教程全套&#xff0c;学完即可进大厂&#xff01;&#xff08;附全套视频 下载&#xf…

软件测试必备 - 14个接口与自动化测试练习网站

随着互联网和移动应用的快速发展,接口和自动化测试的重要性日益凸显。越来越多的企业开始重视API测试,因为它不仅能提升开发效率,还能确保系统的稳定性和安全性。这些练习网站为测试人员提供了宝贵的资源,帮助他们掌握必要的技能,应对日益复杂的测试需求。 在软件测试的世…

【Linux】TCP全解析:构建可靠的网络通信桥梁

文章目录 前言1. TCP 协议概述2. TCP报头结构3. 如何理解封装和解包呢&#xff1f;4. TCP的可靠性机制4.1 TCP的确认应答机制 4.2 超时重传机制5. TCP链接管理机制5.1 经典面试题&#xff1a;为什么建立连接是三次握手&#xff1f;5.2 经典面试题&#xff1a;为什么要进行四次挥…

Java每日一题 ~ 盛最多水的容器

. - 力扣&#xff08;LeetCode&#xff09; 1.题目解析 本题的要求就是&#xff1a;给定数组索引之间的差值为宽&#xff0c;元素值中小的为边长求面积。 2.算法分析 思路一&#xff1a;暴力枚举 暴力法的思路是对所有可能的容器组合进行穷举&#xff0c;计算它们能容纳的水…

[硬件]—电感传感器

电感传感器 1.概述 工作基础&#xff1a;电磁感应&#xff0c;即利用线圈自感或互感的改变来实现非电量测量。工作原理&#xff1a; 被测物理量&#xff08;非电量&#xff1a;位移、振动、流量&#xff09;&#xff1b;线圈自感系数L/互感系数M&#xff1b;电压或电流&#…

0729作业+梳理

一、作业 1.写一个日志文件&#xff0c;将程序启动后的每一秒时间写入到文件中 代码&#xff1a; #include<myhead.h> #include<time.h> //判断原本文件中的行数 int len_txt(FILE *fp) { char buf0; int count 1; while(1) { buffgetc…

计算是守恒与对称的,谋算(算计)是变通与破缺的

计算通常涉及到严格的数学或逻辑规则&#xff0c;这些规则保证了信息或量的守恒和对称性。例如&#xff0c;数学运算如加减乘除都遵循特定的规律&#xff0c;确保了结果的准确性和一致性。相比之下&#xff0c;谋算&#xff08;或称算计&#xff09;更多指策略性的考虑或具有权…

Redis 缓存中间件

目录 概念 安装redis redis基本命令 给redis添加密码 基础数据类型 string类型 list列表类型 set创建&#xff08;一个键对应一个值&#xff09; set 创建数据 get 获取数据 keys * 展示所有的键 exists 判断键值是否存在 type 查看数据的类型 del 删除键 rename…

学习008-02-04-03 Group List View Data(组列表查看数据)

Group List View Data&#xff08;组列表查看数据&#xff09; This lesson explains how to group the Employee List View data by department and position. 本课介绍如何按部门和职位对员工列表视图数据进行分组。 Note Before you proceed, take a moment to review the …

机械学习—零基础学习日志(高数15——函数极限性质)

零基础为了学人工智能&#xff0c;真的开始复习高数 这里我们将会学习函数极限的性质。 唯一性 来一个练习题&#xff1a; 再来一个练习&#xff1a; 这里我问了一下ChatGPT&#xff0c;如果一个值两侧分别趋近于正无穷&#xff0c;以及负无穷。理论上这个极限值应该说是不存…

2024下《系统架构设计师》案例简答题,刷这些就够了!

2024年软考下半年已经越来越近了&#xff0c;不知道今年备考架构的同学们准备得怎么样了呢&#xff1f; 简答题一直是架构拿分的重点区域&#xff0c;对于许多考生来说&#xff0c;也往往是最具挑战性的部分。今天我就把那些重要的案例简答题类型整理汇总给大家&#xff0c;希望…

02 Go语言操作MySQL基础教程_20240729 课程笔记

概述 如果您没有Golang的基础&#xff0c;应该学习如下前置课程。 Golang零基础入门Golang面向对象编程Go Web 基础Go语言开发REST API接口_20240728 基础不好的同学每节课的代码最好配合视频进行阅读和学习&#xff0c;如果基础比较扎实&#xff0c;则阅读本教程巩固一下相…

Qt Designer,仿作一个ui界面的练习(二):部件内容的填充

有了完成了布局的基本框架设计之后&#xff0c;对各个部件逐步完成内容的填充。 一、还是从顶边栏开始&#xff1a; 1、在顶边栏的topLogo里面拖入一个QLabel&#xff08;标签&#xff09;&#xff0c;命名为logoImage&#xff0c;删除标签的文字。 2、右键点击topLogo&#x…

计算机三级嵌入式(三)——嵌入式系统硬件组成

目录 考点1 嵌入式最小硬件系统 考点2 基于 ARM 内核的典型嵌入式应用系统硬件组成 考点3 ARM 的 AMBA 总线体系结构及标准 考点4 基于 ARM 内核的嵌入式芯片的硬件组成 考点5 存储器层次结构 考点6 存储器分类 考点7 存储器主要性能指标 考点8 片内存储器 考点9 外部…

IndexError: list index out of range

IndexError: list index out of range 目录 IndexError: list index out of range 【常见模块错误】 【解决方案】 原因分析 解决方法 示例代码 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身…

接口自动化中对于文件上传的处理方法

正常的接口自动化基本都是json的格式&#xff0c;对于文件上传是一种特殊的格式是表单格式针对这种表单格式在接口自动化中怎么处理&#xff0c;主要通过工作中使用的一个实际的例子进行分享 举例&#xff1a;web上需要导入一个文件实现相关的功能&#xff0c;主要通过两个接口…

C语言玩一下标准输出——颜色、闪烁、加粗、下划线属性

文章目录 C语言玩一下标准输出——颜色、闪烁、加粗、下划线属性转换Tip切换内容介绍显示方式字体色背景色 常用光标控制附示例和运行结果 C语言玩一下标准输出——颜色、闪烁、加粗、下划线属性 标准输出格式其属性可控制&#xff0c;控制由一系列的控制码指定。标准输出函数可…

这才是 PHP 高性能框架 Workerman 的立命之本

大家好&#xff0c;我是码农先森。 在这个大家都崇尚高性能的时代&#xff0c;程序员的谈笑间句句都离不开高性能&#xff0c;仿佛嘴角边不挂着「高性能」三个字都会显得自己很 Low&#xff0c;其中众所皆知的 Nginx 就是高性能的代表。有些朋友可能连什么是高性能都不一定理解…