<网络> TCP:C/S模式设计 以及相关知识
一.服务器端Server
1.基本思路:
①加载库:WSAStartup();
②创建套接字:socket();
③选址绑定:bind():
④监听:listen();
⑤接受客户端连接:accept();
⑥收发数据:recv - send
⑦关闭套接字:closesocket();(两个)
⑧卸载库:WSACleanup();
2.代码实现:
1 #include<iostream> 2 #include<WinSock2.h> 3 using namespace std; 4 5 // Need to link with Ws2_32.lib 6 #pragma comment(lib, "ws2_32.lib") 7 8 int main() 9 { 10 //1.选择一个种类:加载库 11 WORD wVersionRequested; 12 WSADATA wsaData; 13 int err; 14 15 /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ 16 wVersionRequested = MAKEWORD(2, 2); 17 18 err = WSAStartup(wVersionRequested, &wsaData); 19 if (err != 0) { 20 /* Tell the user that we could not find a usable */ 21 /* Winsock DLL. */ 22 printf("WSAStartup failed with error: %d\n", err); 23 return 1; 24 } 25 26 /* Confirm that the WinSock DLL supports 2.2.*/ 27 /* Note that if the DLL supports versions greater */ 28 /* than 2.2 in addition to 2.2, it will still return */ 29 /* 2.2 in wVersion since that is the version we */ 30 /* requested. */ 31 32 if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { 33 /* Tell the user that we could not find a usable */ 34 /* WinSock DLL. */ 35 printf("Could not find a usable version of Winsock.dll\n"); 36 WSACleanup(); 37 return 1; 38 } 39 else 40 printf("The Winsock 2.2 dll was found okay\n"); 41 42 //2.雇佣店长:创建套接字 socket 43 SOCKET sockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 44 if(sockListen == INVALID_SOCKET) 45 { 46 WSACleanup(); 47 return 1; 48 } 49 50 //3.选址:绑定 bind 51 sockaddr_in addrServer; 52 addrServer.sin_family = AF_INET; 53 addrServer.sin_port = htons(1234); 54 addrServer.sin_addr.S_un.S_addr = INADDR_ANY; 55 56 if(bind(sockListen,(const sockaddr *)&addrServer,sizeof(addrServer)) == SOCKET_ERROR) 57 { 58 closesocket(sockListen); 59 WSACleanup(); 60 return 1; 61 } 62 63 //4.店长开始宣传:监听 listen 64 if(SOCKET_ERROR == listen(sockListen,3)) 65 { 66 closesocket(sockListen); 67 WSACleanup(); 68 return 1; 69 } 70 71 //5.把客人拉进店里 交给服务员:接受客户端连接 72 SOCKET sockWaiter = accept(sockListen,NULL,NULL); 73 74 //6.服务员 客人:recv - send 75 char szbuf[1024] = {0}; 76 while(1) 77 { 78 int nres = recv(sockWaiter,szbuf,sizeof(szbuf),0); 79 if(nres > 0) 80 { 81 cout << szbuf << endl; 82 cin >> szbuf; 83 send(sockWaiter,szbuf,sizeof(szbuf),0); 84 } 85 } 86 87 //7.客人离开 服务员下班 88 closesocket(sockWaiter); 89 90 //8.店长下班 91 closesocket(sockListen); 92 93 //9.挂门:卸载库 94 WSACleanup(); 95 96 system("pause"); 97 return 0; 98 }
二.客户端Client
1.基本思路:
①加载库:WSAStartup();
②创建套接字:socket();
③connect();
④收发数据:recv - send
⑤关闭套接字:closesocket();
⑥卸载库:WSACleanup();
2.代码实现:
1 #include<iostream> 2 #include<WinSock2.h> 3 using namespace std; 4 5 // Need to link with Ws2_32.lib 6 #pragma comment(lib, "ws2_32.lib") 7 8 int main() 9 { 10 //1.选择一个种类:加载库 11 WORD wVersionRequested; 12 WSADATA wsaData; 13 int err; 14 15 /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ 16 wVersionRequested = MAKEWORD(2, 2); 17 18 err = WSAStartup(wVersionRequested, &wsaData); 19 if (err != 0) { 20 /* Tell the user that we could not find a usable */ 21 /* Winsock DLL. */ 22 printf("WSAStartup failed with error: %d\n", err); 23 return 1; 24 } 25 26 /* Confirm that the WinSock DLL supports 2.2.*/ 27 /* Note that if the DLL supports versions greater */ 28 /* than 2.2 in addition to 2.2, it will still return */ 29 /* 2.2 in wVersion since that is the version we */ 30 /* requested. */ 31 32 if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { 33 /* Tell the user that we could not find a usable */ 34 /* WinSock DLL. */ 35 printf("Could not find a usable version of Winsock.dll\n"); 36 WSACleanup(); 37 return 1; 38 } 39 else 40 printf("The Winsock 2.2 dll was found okay\n"); 41 42 // 43 SOCKET sockClient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 44 if(sockClient == INVALID_SOCKET) 45 { 46 WSACleanup(); 47 return 1; 48 } 49 50 // 51 sockaddr_in addrClient; 52 addrClient.sin_family = AF_INET; 53 addrClient.sin_port = htons(1234); 54 addrClient.sin_addr.S_un.S_addr = inet_addr("192.168.2.167"); 55 56 if(connect(sockClient,(const sockaddr *)&addrClient,sizeof(addrClient)) == SOCKET_ERROR) 57 { 58 closesocket(sockClient); 59 WSACleanup(); 60 return 1; 61 } 62 63 // 64 char szbuf[1024] = {0}; 65 while(1) 66 { 67 cin >> szbuf; 68 send(sockClient,szbuf,sizeof(szbuf),0); 69 70 int nres = recv(sockClient,szbuf,sizeof(szbuf),0); 71 if(nres > 0) 72 { 73 cout << szbuf << endl; 74 } 75 } 76 77 // 78 closesocket(sockClient); 79 80 //卸载库 81 WSACleanup(); 82 83 system("pause"); 84 return 0; 85 }
三.TCP的相关知识:TCP 全双工通信
1.TCP的6个标志:发一次可以包含多个标志
①SYN:请求连接
②ACK:请求回复
③PSH:推送数据
④RST:重置连接
⑤FIN:结束连接
⑥URG:紧急指针
2.TCP建立连接的三次握手:
①序号和确认序号:校验包是否正确收到
②ESTablished:进入这个状态才可以进行通信
③TCP有重传和校验机制 不会丢包
④TCP接收时可能粘包:但不是所有粘包都需要处理 同种就不需要处理 只需处理不同种类的粘包(UDP就不会出现粘包的问题)
粘包的解决:
a.发包大小(用第一个字节) 即在包头加数据长度
b.socket短连接
3.TCP断开连接的四次挥手:
①MSL:数据包最长生命周期
在windows中为30s 在TCP文档中为2min
②在TIME_WAIT处要等2MSL(1min~·4min)的原因:
a.保证连接正常中止
b.允许老的重复分节在网络中消失
4.UDP和TCP的比较:
①UDP中:
当发送缓冲区<接受缓冲区:正常发送
当发送缓冲区>接受缓冲区:丢
TCP中:
当发送缓冲区<接受缓冲区:正常发送
当发送缓冲区>接受缓冲区:分片发送 不会产生丢数据的现象
②特点:
UDP:
a.面向无连接:广播 组播→1对多
b.数据报文
c.效率高
d.丢包→重传 乱序→序号标识
TCP:
a.面向连接:3握4挥→1对1
b.数据流:粘包 可拆
c.重传和校验:拥塞控制 流量控制→滑动窗口
5.慢开始与拥塞避免:
①发送方维持一个叫拥塞窗口cwnd的状态变量:每收到一个ACK就+1
②当cwnd>ssthresh的时候进入拥塞避免:每经过一个RTT往返时间cwnd就+1
③当发送方判断网络出现拥塞:把慢开始门限设置为出现拥塞时发送窗口大小的一半 然后把拥塞窗口设置为1 执行慢开始算法
6.快重传和快恢复:
①快重传:
当接收方在收到一个失序的报文段后就立即发出重复确认
当发送方收到连续三个重复确认ACK就立即传送对方尚未收到的报文段
②快恢复:
当发送方收到连续三个重复确认ACK把ssthresh为窗口cwnd的一半
cwnd设置为门限ssthresh大小(或门限ssthresh+3) 执行拥塞避免
注:如果有丢包 但是没有收到3个重复确认的ACK
比如说只收到两个 因为剩余只有两个包未发送 那么也认为发生了拥塞
将cwnd设为1开始慢启动 新门限设置为拥塞时门限的一半
这说明快重传和快恢复一定是在收到三个连续重复ACK的时候才执行的!