【QT学习】11.TCP协议

一。【window为例】TCP协议的解释(记忆方法)

物理结构(自己理解)

1.服务器端

服务器端:        

        首先使用套接字函数创建 套接字 (Socket),并使用 绑定 函数绑定到本地地址(bind)。 使用 listen 指定传入连接的积压工作(listen),然后使用 accept 函数接受连接(accept)。最后接收数据(recv)。

1.创建套接字socket().(确定协议)
int socket( int af, int type, int protocol);

        af:协议族。支持AF_INET格式(ipv4)和AF_INET6格式(ipv6)

        type:指定socket类型。如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)

        rotocol:就是指定协议,如调用者不想指定,可用0。常用的协议有,IPPROTO_TCP、IPPROTO_UDP。

        返回值:若无错误发生,socket()返回引用套接字的描述字。否则的话,返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()获取相应错误代码。

2. 绑定bind() :(确定具体ip与端口)
        服务器必须在一个已知的名称(ip地址、端口)监听,属于显性绑定。客户端不需要调用bind绑定端口,而是由内核自定选择临时端口,属于隐性绑定。

        int PASCAL FAR bind( SOCKET sockaddr, const struct sockaddr FAR* my_addr,int addrlen);

        sockaddr表示已经建立的socket编号(描述符);

        FAR是一个指向sockaddr结构体类型的指针;

        addrlen表示my_addr结构的长度,可以用sizeof操作符获得。

        ip地址使用的是网络字节顺序,所以需要调用htons、htonl 函数主机把ip地址和端口的主机字节序转换网络字节序。对服务器而言 对于IPv4来说,通配地址通常由INADDR_ANY来指定,其值一般为0。它告知内核去选择IP地址。

        如无错误发生,则bind()返回0。否则的话,将返回-1。

        如果tcp服务器没有把ip地址捆绑到套接字上,内核就把客户端发送的syn的目的ip地址作为服务器的源ip地址。服务器可以通过getsockname函数获取该地址。

        在bind函数调用的sockaddr 类型指针,可以直接用前面的sockaddr_in变量转换替换。

iPv4通配地址由INADDR_ANY指定,如果端口号为0表示由内核选择一个临时端口。

        套接字分为UDP套接字和TCP套接字,对于前者,其由(ip地址,端口号)来标识,后者由(源ip,源端口号,目的ip,目的端口号)标识。

        tcp:如多个不同的客户端连接服务端,会产生多个套接字,这些套接字实际上是共用了相同的服务端端口号,但源ip地址不一样。

        疑问:如果是多个套接字,或者是多个进程应用绑定在同一ip,端口上,会怎样?tcp、udp都可以吗?

        默认的情况下,如果一个套接字 绑定了一个端口,这时候,别的套接字就无法使用这个端口。但是端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。

3.监听 listen(激活套接字)
int listen( int sockfd, int backlog);

sockfd:用于标识一个已捆绑未连接套接口的描述字。

backlog:等待连接队列的最大长度。

如无错误发生,listen()返回0。否则的话,返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。

该函数应该在socket和bind函数之后,accept函数之前。套接字会从closed状态转换到listen状态。

4.接受连接 accept (三次握手成功)
SOCKET accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

默认是阻塞函数

sockfd:套接字描述符,该套接口在listen()后监听连接。

addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址(即客户端的地址)。Addr参数的实际格式由套接口创建时所产生的地址族确定。

addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。

返回值:如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码

参数的sockaddr 类型指针,可以直接用前面的sockaddr_in变量转换替换。该地址变量是输出参数,表示的是客户端的地址。

同时返回新的套接字,用于传输数据用。原来的套接字继续用来监听。这时候,注意3次握手的过程,还要考虑是否是阻塞,同步的状态。默认是阻塞。

accept()函数成功返回时,完成了关联的建立,即3次握手成功结束。

检测到达的连接请求:accept()函数调用成功,或者是select()函数指示监听socket上有可写数据。

5.接收数据 recv()函数
默认是阻塞函数

buf缓冲区的类型都是char   最后一个变量flag 一般是0 在tcp中,要注意缓冲区不够,导致要么重发要么重收。所有要判断返回值。

int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);

返回值:

若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

6.发送数据send() 函数
int send( SOCKET s, const char FAR *buf, int len, int flags );

若无错误发生,send()返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小,该值应该不可以为0)。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

7.shutdown 
只适合在tcp上,udp这函数没有意义。值关闭连接,不释放socket资源。

shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零

SHUT_RD:关闭连接的读端

SHUT_WR:关闭连接的写端

SHUT_RDWR:连接的读端和写端都关闭。 这与调用shutdown两次等效。第一次调用指定SHUT_RD,第二次调用指定SHUT_WR

8.关闭closesocket ()(四次挥手)
4次握手,该函数默认是非阻塞函数。只有对一个阻塞socket,并且调用setsockopt设置了非0的超时值来使能SO_DONTLINGER。它才是阻塞的。

int PASCAL FAR closesocket( SOCKET s);

如无错误发生,则closesocket()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

在linux中关闭套接字使用的函数是close().

close把描述符的引用计数减1,仅在该计数为0时才关闭套接字,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写。

close终止读写两个方向的数据传输。

可以通过setsockopt函数设置SO_DONTLINGER  、SO_LINGER选项。

二。客户端

1创建套接字socket()

2.连接connect()函数(地址簇name要连接套接字s<服务器套接字>)
int connect(SOCKET s, const struct sockaddr * name, int namelen);

s:标识一个未连接socket

name:指向要连接套接字的sockaddr 结构体的指针

namelen:sockaddr结构体的字节长度

返回值:成功则返回0, 失败返回-1.

成功后,套接字的状态从closed->syn_sent ->established.

注意阻塞函数,握手3步.connect函数由系统决定超时时间,一般是75s.

失败的多种原因:

        1.具体流程是:发送一个syn,若无响应的等待几秒再发送一个,连续好几次,等到75s仍不响应。

        在windows 则返回SOCKET_ERROR(也即-1).调用WSAGetLastError(),返回出错码是WSAETIMEDOUT。

        在linux 则返回-1.通过全局变量errno,返回出错码是ETIMEDOUT。

        2.若对客户端返回的是RST,表明在服务器主机指定的端口没有进程在等待与之连接。

出错代码是WSAECONNREFUSED(windows),ECONNREFUSED(linux)。

        3.中间某个路由引发icmp错误。按照1方式联系75s发送syn包,错误码是: WSAENETUNREACH

        所以需要把套接字改成非阻塞模式、并且使用select函数,自定义超时时间。可参考下面的做法。

        调用的sockaddr 类型指针,可以直接用前面的sockaddr_in变量转换替换。需要知道服务端的ip地址和端口。

        注意:客户端没必要调用bind进行绑定命名,主要原因是:

        如果没事前调用bind()函数,connect()函数会隐式对本地socket命名,并且是任取一个端口,避免端口冲突。

        同时,如果同时运行多个客户端,给套接字指定端口容易导致端口冲突。

        如果调用connect失败,推荐先调用closesocket()和socket()获取一下新的socket。

补充:

TCP协议中,接收方成功接收到数据后,会回复一个ACK数据包,表示已经确认接收到ACK确认号前面的所有数据。

发送方在一定时间内没有收到服务端的ACK确认包后,就会重新发送TCP数据包。

接收方在接收到数据后,不是立即会给发送方发送ACK的。这可能由以下原因导致:
1、收到数据包的序号前面还有需要接收的数据包。因为发送方发送数据时,并不是需要等上次发送数据被Ack就可以继续发送TCP包,而这些TCP数据包达到的顺序是不保证的,这样接收方可能先接收到后发送的TCP包(注意提交给应用层时是保证顺序的)。
2、为了降低网络流量,ACK有延迟确认机制。
3、ACK的值到达最大值后,又会从0开始。

接收方在收到数据后,并不会立即回复ACK,而是延迟一定时间。一般ACK延迟发送的时间为200ms,但这个200ms并非收到数据后需要延迟的时间。系统有一个固定的定时器每隔200ms会来检查是否需要发送ACK包。这样做有两个目的。
1、这样做的目的是ACK是可以合并的,也就是指如果连续收到两个TCP包,并不一定需要ACK两次,只要回复最终的ACK就可以了,可以降低网络流量。
2、如果接收方有数据要发送,那么就会在发送数据的TCP数据包里,带上ACK信息。这样做,可以避免大量的ACK以一个单独的TCP包发送,减少了网络流量。
 

MSS 是在建立连接时通过SYN数据包中的MSS选项里进行协商的(以太网的MTU能到1500,所以MSS可以为1460),如果没有协商,默认为536,MSS是数据净负荷,协议保证最小支持536(加上TCP和IP的头部后packet为576)

TCP中在发送的数据的ACK未回来前,能继续发送其他数据包吗

能不能发,取决于下面的条件是否满足:

1. 如果包长度达到MSS,则再根据CWND、AWND来做决定;
2. 如果该包含有FIN,则允许发送;
3. 如果没达到MSS且不包含FIN:
  
3.1. 设置了TCP_NODELAY选项,则允许发送;
3.2. 没设置TCP_NODELAY, 未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送(nagel算法起作用);设置了TCP_CORK选项时,需要包长度到MSS。

int on = 1;
setsockopt(fd,SOL_TCP,TCP_CORK,&on,sizeof(on));
4. 上述条件都未满足,但发生了超时(一般为200ms),则立即发送
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        
原文链接:https://blog.csdn.net/baidu_16370559/article/details/104646624

二。QT的TCP连接(基于事件(异常,中断)处理)

服务器

  1. 创建QTcpServer对象  (相当于windows的第一步)
  2. 监听   (相当于windows的二三四步)

QTcpServer类的listen函数

  1. 等待客户端连接
    1. QTcpServer类的newConnection信号
    2. QTcpServer类的nextPendingConnection函数返回客户端QTcpSocket

    通信

    1. 接受数据:   QTcpSocket   的 readyRead 信号

                                       QTcpSocket   的 read函数

    1. 发送数据:   QTcpSocket   的 write函数

客户端

  1. 创建QTcpSocket对象  (拿到服务器的ip地址和端口号,方式:自己写入)
  2. QTcpSocket类的connectToHost 函数连接服务器

通过QTcpSocket类的connected信号知道已经连接到服务器了

  1. 通信

接受数据:QTcpSocket   的 readyRead 信号

                  QTcpSocket   的 read函数

发送数据:QTcpSocket   的 write函数

2.使用QT写一个TCP客户端与TCP服务器端

1.TCP服务器端

页面制作:

1.服务器端类

private slots:void on_pushButtonSend_clicked();void on_pushButtonClose_clicked();private:Ui::Widget *ui;//服务器socket套接字QTcpServer* pTcpServer;//服务器这边代表 客户端的socket套接字QTcpSocket* pTcpSocket;
};

2.服务器实现

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);this->setWindowTitle("服务器端 端口9527");//创建QTcpServer对象pTcpServer=new QTcpServer(this);//指定父对象,自动回收pTcpSocket=NULL;//监听pTcpServer->listen(QHostAddress::Any,9527);//连接//客户端连接服务器--》触发(服务器端)NewConnection信号--》触发(客户端)connected信号connect(pTcpServer,&QTcpServer::newConnection,[=](){//获取客户端的socketpTcpSocket=pTcpServer->nextPendingConnection();//获取客户端端口与ipQString ipStr=pTcpSocket->peerAddress().toString();quint16 portUint=pTcpSocket->peerPort();//显示端口与ipQString buff=QString("客户端连接服务器成功:ip:%1,port:%2").arg(ipStr).arg(portUint);ui->textEditRecv->setText(buff);//只有客户端连接上服务器后,才会有此监听//触发服务器端接受数据connect(pTcpSocket,&QTcpSocket::readyRead,[=](){QByteArray data=pTcpSocket->readAll();ui->textEditRecv->append(data);});});
}

void Widget::on_pushButtonSend_clicked()
{if(pTcpSocket){QString str=ui->textEditSend->toPlainText();//向客户端的套接字写数据strpTcpSocket->write(str.toUtf8().data());}
}void Widget::on_pushButtonClose_clicked()
{if(pTcpSocket){pTcpSocket->disconnectFromHost();//断开网络连接pTcpSocket->close();}
}

2.TCP客户端

页面制作:

1.创建一个新的页面

2.页面为

3.main中添加页面,并展示

4.结果:

1.客户端类

2.客户端实现

Form::Form(QWidget *parent) :QWidget(parent),ui(new Ui::Form)
{ui->setupUi(this);this->setWindowTitle("客户端");//创建socketpTcpSocket = new QTcpSocket(this);//连接服务器connect(pTcpSocket,&QTcpSocket::connected,[=](){ui->textEditRecv->setText("连接服务器成功");});//放在连接服务器内外是不是都可以?connect(pTcpSocket,&QTcpSocket::readyRead,[=](){QByteArray data = pTcpSocket->readAll();ui->textEditRecv->append(data);});
}

void Form::on_pushButtonConnect_clicked()
{QString ipStr=ui->lineEditIp->text();quint16 portUint=ui->lineEditPort->text().toUInt();//客户端连接服务器--》触发(服务器端)NewConnection信号--》触发(客户端)connected信号pTcpSocket->connectToHost(ipStr,portUint);
}void Form::on_pushButtonSend_clicked()
{QString str=ui->textEditSend->toPlainText();pTcpSocket->write(str.toUtf8().data());
}void Form::on_pushButtonClose_clicked()
{pTcpSocket->disconnectFromHost();//断开网络连接pTcpSocket->close();
}

结果:相互发送数据正常

        端口是服务器设置的

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

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

相关文章

MobileNetV4 论文学习

论文地址&#xff1a;https://arxiv.org/abs/2404.10518 代码地址&#xff1a;https://github.com/tensorflow/models/blob/master/official/vision/modeling/backbones/mobilenet.py 解决了什么问题&#xff1f; 边端设备的高效神经网络不仅能带来实时交互的体验&#xff0c…

STL复习

vector STL详解及常见面试题_stl常见面试题-CSDN博客 C vector中resize()和reserve()区别_c vector resize和reserve区别-CSDN博客 释放vectro内存&#xff1a; map释放内存 deque&#xff1a; C STL deque 容器底层实现原理&#xff08;深度剖析&#xff09; - 知乎 (zhihu.…

python项目入门新手攻略

最近工作需要接手了代码量比较大的python开发的项目&#xff0c;平时写python不多&#xff0c;记录一下如何熟悉项目。 分析调用流程-pycallgraph 因为代码量比较大&#xff0c;所以希望通过工具生成代码调用流程&#xff0c;因此用到了pycallgraph。 pycallgraph&#xff0…

绿色低碳深入业务全生命周期 顺丰同城发布2023ESG报告

近年来&#xff0c;作为一种国际公认的可持续发展理念&#xff0c;强调生态环境保护、履行社会责任、提高治理水平的ESG成为衡量长期投资价值的重要维度之一。今年恰逢联合国契约组织提出ESG概念20周年&#xff0c;“ESG”从首次进入公众视野至今&#xff0c;现在已成为各国商业…

基于SpringBoot的“在线BLOG网”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“在线BLOG网”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 在线BLOG网结构功能图 管理员登录功能界面 用户信息…

SQL提升

1. SQL TOP 子句 TOP 子句用于规定要返回的记录的数目。 对于拥有数千条记录的大型表来说&#xff0c;TOP 子句是非常有用的。 **注释&#xff1a;**并非所有的数据库系统都支持 TOP 子句。 1.1 SQL TOP 语法 SQL Server 的语法&#xff1a; SELECT TOP number|percent c…

springboot 集成 flowable

随着企业对于业务流程管理需求的增加&#xff0c;流程引擎在企业信息化建设中的作用越来越重要。Flowable是一个开源的轻量级业务流程管理&#xff08;BPM&#xff09;和工作流引擎&#xff0c;它支持BPMN 2.0标准。 Flowable的一些特点&#xff1a; 安装集成&#xff1a;Flow…

OpenHarmony 实战开发——自测试执行框架

OpenHarmony为开发者提供了一套全面的开发自测试框架OHA-developer_test&#xff0c;开发者可根据测试需求开发相关测试用例&#xff0c;开发阶段提前发现缺陷&#xff0c;大幅提高代码质量。 本文从基础环境构建&#xff0c;用例开发&#xff0c;编译以及执行等方面介绍OpenH…

22 重构系统升级-实现不停服的数据迁移和用户切量

专栏的前 21 讲&#xff0c;从读、写以及扣减的角度介绍了三种特点各异的微服务的构建技巧&#xff0c;最后从微服务的共性问题出发&#xff0c;介绍了这些共性问题的应对技巧。 在实际工作中&#xff0c;你就可以参考本专栏介绍的技巧构建新的微服务&#xff0c;架构一个具备…

AI大模型日报#0430:疑似GPT4.5模型刷屏、上交实现「蛋白质功能定向进化」、微软紧急撤回WizardLM-2

导读&#xff1a; 欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”生成了今日要点以及每条资讯的摘要。 《AI大模型日报》今日要点&#xff1a; 在AI大模型领域&#xff0c;多项研究进展和行业应用动态引发关注。一夜之间&#x…

敏捷之Scrum开发

目录 一、什么是 Scrum 1.1 Scrum 的定义 二、Scrum 迭代开发过程 2.1 迭代开发过程说明 2.1.1 开发方法 2.1.1.1 增量模型 2.1.1.1.1 定义 2.1.1.1.2 模型方法说明 2.1.1.2 迭代模型 2.1.1.2.1 定义 2.1.1.2.2 模型方法说明 2.1.2 迭代过程 2.1.2.1 产品需求Produ…

牛客网刷题 | CC1 获取字符串长度

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 键盘输入一个字符串…