TCP:特点
- 面向连接的(三次握手 四次挥手)
握手与挥手
这里在握手时 第二次握手即是请求包也是回复包
这里在三次握手的过程中TCP还进行了滑动窗口 确定了窗口尺寸
客户端先和服务器说明他的窗口尺寸和最大传输单元
然后服务器接收这些信息
服务器回复的时候也会将自己的窗口尺寸和最大传输单元传回给客户端
这里滑动窗口涉及到了几个协议:
- 1比特流协议 :一次只传1Bit
- 后退n协议 :当接收端从第n个包之后的包全没收到 会让发送端从第n个包开始往后 重传
- 选择重传协议:在接收端开辟了一个和发送端大小相同的一块缓冲区 接受完 查看缓冲区是否和发送端的数据一样 少哪个重传哪个
四次挥手:
每一次关闭一端的发送端或接收端
四次之后握手全部关闭
这里注意为什么c(客户端)会有TIMEWAIT 等待状态 而不是直接关闭
一般会等待
2MS
1MS=Maxinum Segment lifetime WINDOWS 2min RFC 30s
1.为了保证正常终止连接
c在最后关闭他的读的时候 并没有直接结束 而是等他的回复包真正的传给s(服务器)的时候在关闭 这是为了保证他的回复包传输的完整 进而真正的关闭s的写 如果c直接关闭了 一旦丢包 s会往回向c请求重传 但是c已经不在了 s就关闭不了了
2.为了保证老的重复分节在网络中消失 (保证垃圾数据在网络上消失)
- 安全可靠的(重传校验 滑动窗口 拥塞控制)
- 数据流的(可以任意拆分):
传输端发送缓冲区发m大小的数据
接收端就收缓冲区一次只能接受1个 他会分m次依次接受
确保发送的数据都会传到接受端
但是 数据流会出现粘包问题
[如:]我传了三个包 一个包大小100 接收到的可能是一个250大小的 一个50 大小的 包
如果我们穿的包不是一个类型的数据 接收到的会出现乱码
为了解决粘包问题 :
- 在包的头加长度
收到包长度判断包的大小
- 特殊字符结尾:
每个包的结尾都会有特殊字符 特殊字符不允许再包内出现 接受的时候一个字节一个字节的收 都到特殊字符 代表当前这个包结束了(限制这能一个字节一个字节收)
- 固定包的大小
- 短链接
每一个套接字在内核里都有自己的一个缓冲区 不会粘到一块
完整代码如下
服务器端
#include <QCoreApplication>
#include<winsock2.h>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
/*
*1.选择种类 --WSAStartup();
*2.雇店长 --创建套接字 socket();
*3.找地 --绑定 --bind();
*4.店长宣传 --监听 --listen();
*5.店长接受客人 分配给服务员 --接受链接--accept();
*6.客人与服务员 服务员等客人说话 --recv();
*7.回复 --send();
*8.下班--closesocket();
*9.关门--WASCleanup();
*
*/
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return 1;
}
else
printf("The Winsock 2.2 dll was found okay\n");
//创建套接字
SOCKET sock=socket(AF_INET,SOCK_STREAM,0);
if(sock==INVALID_SOCKET)
{
WSACleanup();
return 1;
}
//绑定
sockaddr_in addrserver;
//绑定IP
addrserver.sin_addr.S_un.S_addr=0;
addrserver.sin_port=htons(8899);
addrserver.sin_family=AF_INET;
if(SOCKET_ERROR==bind(sock,(sockaddr*)&addrserver,sizeof(addrserver)))
{
closesocket(sock);
WSACleanup();
return 1;
}
//监听
if(SOCKET_ERROR==listen(sock,10))
{
closesocket(sock);
WSACleanup();
return 1;
}
//店长接受客人 分配给服务员 --接受链接--accept();
sockaddr_in addrclient;
int nsize=sizeof(addrclient);
SOCKET sockWaiter=accept(sock,(sockaddr*)&addrclient,&nsize);
cout<<"客户端"<<inet_ntoa(addrclient.sin_addr)<<"链接了"<<endl;
//客人与服务员 服务员等客人说话 --recv();
char szbuf[1024];
int nRecvNum;
while(1)
{
nRecvNum=recv(sockWaiter,szbuf,sizeof(szbuf),0);
if(nRecvNum>0)
{
cout<<"client say:"<<szbuf<<endl;
cin>>szbuf;
send(sockWaiter,szbuf,sizeof(szbuf),0);
}
}
//回复 --send();
//下班--closesocket();
closesocket(sockWaiter);
closesocket(sock);
//关门--WSACleanup();
WSACleanup();
return a.exec();
}
客户端
#include <QCoreApplication>
#include<winsock2.h>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//加载库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return 1;
}
else
printf("The Winsock 2.2 dll was found okay\n");
//创建套接字
SOCKET sock=socket(AF_INET,SOCK_STREAM,0);
if(sock==INVALID_SOCKET)
{
WSACleanup();
return 1;
}
//connect()
//绑定IP
sockaddr_in addrserver;
addrserver.sin_addr.S_un.S_addr=0;
addrserver.sin_port=htons(8899);
addrserver.sin_family=AF_INET;
if(SOCKET_ERROR==bind(sock,(sockaddr*)&addrserver,sizeof(addrserver)))
{
closesocket(sock);
WSACleanup();
return 1;
}
sockaddr_in addrWaiter;
int nsize=sizeof(addrWaiter);
connect(sock,(sockaddr*)&addrWaiter,nsize);
//send()
int nRecvNum;
char szbuf[1024];
while(1)
{
cin>>szbuf;
send(sock,szbuf,sizeof(szbuf),0);
//recv()
nRecvNum= recv(sock,szbuf,sizeof(szbuf),0);
cout<<"fuwuqi say:"<<szbuf<<endl;
}
//closesocket()
closesocket(sock);
//WSACleanup()
WSACleanup();
return a.exec();
}