一、什么是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
send(socketclient, a, sizeof(a), 0)//int WSAAPI send( [in] SOCKET s, [in] const char *buf, [in] int len, [in] int flags );参数1.目标socket,服务端填客户端socket,客户端反之 参数2.发送的内容 参数3.发送内容的大小 参数4.数据的读取方式 一般填0就行 ,可以填MSG_OOB (带外数据:外带一个额外数据)也可以填MSG_DONTROUTE(Sends OOB data (stream-style socket such as SOCK_STREAM only.)

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;
servermsg.sin_family = AF_INET;
servermsg.sin_port = htons(12345);
servermsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
connect(socketserver, (const struct sockaddr *)&servermsg, sizeof(servermsg))//int WSAAPI connect( [in] SOCKET s, [in] const sockaddr *name, [in] int namelen ); 参数1.服务端的socket 参数2.端口号和ip 参数3.结构体缓冲区大小

3.c/s特性分析
        在服务端的accept函数,如果没有客户端连接,会阻塞,如果想多个客户端连接可以去设个数组来记录客户端,但是到accept函数处,如果连接的客户端没到数组的最大值仍会阻塞,而且recv函数和send函数会相互阻塞,所以最基本的c/s很难有程序用上。
 
自己写的服务端客户端