![]() |
---|
一、什么是tcp/ip |
tcp/ip(Transmission ControlProtocal/Internet Protocal)即传输控制协议/网际协议,是由罗伯特·卡恩和温顿·瑟夫在1974年12月正式发表了TCP/IP协议并对其进行了详细的说明,TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。 |
二、什么是tcp |
tcp(Transmission ControlProtocal)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC793定义,tcp建立连接方式是三次握手,传输方式是传输确认,关闭连接是四次挥手。 |
三、什么是三次握手 |
![]() |
在TCP协议中,通信双方将通过三次TCP报文实现对以上信息的了解,并在此基础上建立一个TCP连接,而通信双方的三次TCP报文段的交换过程,也就是通常所说的TCP连接建立实现的三次握手(Three-Way Handshake)过程 |
第一次握手:建立连接时,客户端发送syn包到服务器,并进入SYN_SENT状态,等待服务器确认;(SYN:同步序列编号) |
第二次握手:服务器收到syn包,必须确认客户端的SYN,同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态。 |
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。 |
个人理解来说就是客户端向服务器发送连接请求,服务器收到后向客户端发送同意连接请求,最后客户端收到同意连接请求再向服务器发送确认收到请求,完成三次握手。 |
三次握手可以避免客户端发送syn包到服务器因为网络原因导致传输失败,而又恢复传输导致状态不一。 |
四、什么是传输确认 |
为了避免丢包和乱序问题,tcp会建立一个发送缓冲区(结构类似数组)用来存储发送端发送的内容,发送时会取缓冲区一部分内容,发送端发送的报文结构是:起始序列号、数据长度、数据内容,接受端收到报文后会发送确认报文ack(ack=序列号+长度)来确认下次的发送起始序列号。 |
五、什么是四次挥手 |
![]() |
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。 |
第一次挥手:TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。 |
第二次挥手: 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。 |
第三次挥手:服务器关闭客户端的连接,发送一个FIN给客户端。 |
第四次挥手:客户端发回ACK报文确认,并将确认序号设置为收到序号加1。 |
个人理解来说就是客户端向服务端发送断开请求,服务端收到后向客户端发送确认请求,这时候服务端可以把还未发的数据发完,接下来再发送确认请求,客户端收到服务端的确认请求后,再发送一个确认请求,完成四次挥手。 |
四次挥手可以确保客户端和服务端都能可靠的关闭连接。 |
六、c/s模型 |
注:一切已msdn(不是i tell you那个,而是microsoft docs)上的为准 |
1.服务端 |
(Windows平台)基本步骤(1).打开网络库(2).创建socket(3).bind绑定端口和地址(4).listen使套接字处于监听状态(5).accept创建客户端socket(6).recv接受||send发送 |
(1)打开网络库 |
#include<WinSock2.h>//windows提供的套接字函数的声明都封装在这里 #pragma commet(lib,"ws2_32.lib")// windows提供的套接字函数的实现都封装在这里,这个库文件一般电脑都有,如果没有这个库文件,网上也可以下载 |
WORD wdVersion =MAKEWORD(2,2) ;//WORD是u_short类型 ,wdVersion 记录着要打开的库版本,MAKEWORD里传主版本和副版本,传的主版本超过目前最大主版本会默认使用当前最大版本,传的副版本超过目前最大副版本使用当前主版本的最大副版本。 |
WSAData wdSockMsg;//是一个WSAData结构的指针,详细记录了Windows套接字的相关信息 |
WSAStartup(wdVersion,&wdSockMsg);// int WSAStartup (WORD wVersionRequired, [out] LPWSADATA lpWSAData );启动网络库,会返回一个int类型的值,用于检测错误,如果返回值是0则没错误 |
//检查错误 |
int res=WSAStartup(wdVersion,&wdSockMsg);//定义一个变量res来接收错误 if (res!=0) { switch (res)//以下错误摘自msdn { case WSASYSNOTREADY://错误:The underlying network subsystem is not ready for network communication. std::cout << "请检查网络库" << std::endl; break; case WSAVERNOTSUPPORTED://错误:The version of Windows Sockets support requested is not provided by this particular Windows Sockets implementation. std::cout << "请更新网络库" << std::endl; break; case WSAEINPROGRESS://错误:A blocking Windows Sockets 1.1 operation is in progress. std::cout << "请重新启动软件" << std::endl; break; case WSAEPROCLIM://错误:A limit on the number of tasks supported by the Windows Sockets implementation has been reached. std::cout << "请关闭其他程序,以提供本软件网络资源" << std::endl; break; default: break; } return 0; } |
(2).创建socket |
SOCKET socketserver = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//SOCKET WSAAPI socket( [in] int af, [in] int type, [in] int protocol ); 参数1.地址类型 参数2.套接字类型 3.协议类型 |
//返回值可以用INVALID_SOCKET检测是否出错 |
if (INVALID_SOCKET== socketserver) { int a = WSAGetLastError();//获得最近一次的错误码 std::cout<< a<<std::endl; WSACleanup();//记得清理网络库 return 0; } |
(3).bind绑定端口和地址 |
sockaddr_in si; si.sin_family = AF_INET;//地址类型 si.sin_port = htons(12345);//端口不要填已被占的 cmd命令netstat -anolfindstr"端口号"来检查是否被占用 si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//127.0.0.1是回环地址用于本地测试,可以填服务器地址 bind(socketserver, (const struct sockaddr *)&si, sizeof(si))//int bind( [in] SOCKET s, const sockaddr *addr, [in] int namelen ); 参数1.服务端的socket 参数2.端口号和ip 参数3.结构体缓冲区大小 |
(4).listen使套接字处于监听状态 |
listen(socketserver, SOMAXCONN)//int WSAAPI listen( [in] SOCKET s, [in] int backlog ); 参数1.服务端socket 参数2.队列(用来记录服务器来不及处理的client)最大长度 填SOMAXCONN让系统分配,也可以手动填参数,当运行完listen后,服务端就已经运行了 返回值可以用SOCKET_ERROR测试错误 |
(5).accept创建客户端socket |
sockaddr_in clientmsg; int len = sizeof(clientmsg); SOCKET socketclient = accept(socketserver,(struct sockaddr *)&clientmsg,&len);//SOCKET WSAAPI accept( [in] SOCKET s, [out] sockaddr *addr, [in, out] int *addrlen );参数1.服务端socket 参数2.这里是系统记录客户端的信息,也可以填null来不记录客户端的信息 参数3.同bind参数3 运行到这里会进入阻塞,直到有客户端连上才会继续 返回值可以用INVALID_SOCKET来测试错误 |
(6).recv接受||send发送 |
char a[1500] = { 0 };//网络最大传输单元是1500,是传输的最优值,建议填1500 |
char a[1500] = { 0 };//网络最大传输单元是1500,是传输的最优值,建议填1500 recv(socketclient, buf, 1499, 0);//int recv( [in] SOCKET s, [out] char *buf, [in] int len, [in] int flags ); 参数1.目标socket,服务端填客户端socket,客户端反之 参数2.发送的内容 参数3.发送内容的大小 参数4.数据的读取方式 一般填0就行 ,可以填MSG_PEEK(窥视输入的数据。数据被复制到缓冲区中,但不会从输入队列中删除。也就是说假如读取前2个数据,数据因为不会被删除,会反复读取这几个)可以填MSG_OOB (带外数据:外带一个额外数据)也可以填MSG_WAITALL(缓冲区里数据大小要到参数3一样才开始读取) |
记得在程序结尾关闭所有socket(关闭函数:closesocket(填要关闭的socket))和清理网络库(清理函数:WSACleanup()) |
2.客户端 |
(Windows平台)基本步骤(1).打开网络库(2).创建socket(4)connect连接服务端(5).recv接受||send发送 |
偷懒一下直接到(4) |
(4)connect连接服务端 |
struct sockaddr_in servermsg; |
3.c/s特性分析 |
在服务端的accept函数,如果没有客户端连接,会阻塞,如果想多个客户端连接可以去设个数组来记录客户端,但是到accept函数处,如果连接的客户端没到数组的最大值仍会阻塞,而且recv函数和send函数会相互阻塞,所以最基本的c/s很难有程序用上。 |
自己写的服务端和客户端 |