基于VS2015写一个socket通信的Client/Server小程序,验证TCP协议的三次握手与四次挥手。
Server:监听本机的8888端口。
Client:向本机地址(127.0.0.1:8888)发送信息。
Wireshark
Server代码:
#include <WinSock2.h> #include <iostream> using namespace std; #pragma comment(lib,"ws2_32.lib") const int port = 8888; int main() { //初始化WSA(windows下需要用WSAStarup) WORD sockVersion = MAKEWORD(2, 2); WSADATA wsaData; if (WSAStartup(sockVersion, &wsaData) != 0) { return 0; } // 创建套接字 SOCKET sock; sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { cout << "Socket error!" << endl; return -1; } // 将地址绑定到port 和 ip struct sockaddr_in name; name.sin_family = AF_INET; name.sin_port = htons(8888); name.sin_addr.S_un.S_addr = INADDR_ANY; // 通配地址 // 绑定套接字 if (bind(sock, (LPSOCKADDR)&name, sizeof(name)) == SOCKET_ERROR) { cout << "bind error!" << endl; return -1; } // 监听 if (listen(sock, 5) == SOCKET_ERROR) { cout << "listen error!" << endl; } // 等待连接 char data[102400] = { 0 }; SOCKET sClient; sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); // 要初始化,否则默认为0,accept()出错 while (1) { cout << "等待连接..." << endl; // 接受一个客户端的连接请求 sClient = accept(sock, (SOCKADDR*)&remoteAddr, &nAddrlen); if (sClient == INVALID_SOCKET) { cout << "accept error!" << endl; continue; } cout << "接受到一个连接: " << inet_ntoa(remoteAddr.sin_addr) << endl; // 接受数据 int ret = 0; int realSize = 0; int length = sizeof(data); while (1) { ret = recv(sClient, (char*)(data + ret), length, 0); if (ret <= 0) { break; } length -= ret; realSize += ret; } data[realSize - 1] = '\0'; cout << "接收:" << realSize << endl; closesocket(sClient); } return sock; }
Client代码:
#include <WinSock2.h> #include <iostream> using namespace std; #pragma comment(lib, "ws2_32.lib") #define SERVER_ADDRESS "127.0.0.1" //服务器端IP地址 #define PORT 8888 //服务器的端口号 const int MESSIGE_SIZE = 102400; void Send_data(int sockfd) { char* query; query = new char[MESSIGE_SIZE + 1]; for (int i = 0; i < MESSIGE_SIZE; i++) { query[i] = 'a'; } query[MESSIGE_SIZE] = '\0'; size_t remaining = strlen(query); while (remaining) { int n_written = send(sockfd, query, remaining, 0); cout << "send into buffer " << n_written << endl; if (n_written <= 0) { cout << "send failed" << endl; return; } remaining -= n_written; query += n_written; } } int main() { //初始化WSA(windows下需要用WSAStarup) WORD sockVersion = MAKEWORD(2, 2); WSADATA wsaData; if (WSAStartup(sockVersion, &wsaData) != 0) { return 0; } // 创建套接字和服务器地址 int sockfd; sockaddr_in server_addr; // 初始化套接字和服务器地址 sockfd = socket(PF_INET, SOCK_STREAM, 0); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8888); server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS); // 建立连接 if ( connect(sockfd, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR ) { cout << "连接失败" << endl; closesocket(sockfd); } // 发送数据 Send_data(sockfd); closesocket(sockfd); return 0; }工具使用方法:
接口:Adapter for loopback traffic capture(适于本机)过滤器:tcp.port == 8888 and (ip.src == 127.0.0.1 or ip.dst == 127.0.0.1)抓包结果:
三次握手(详细可参考:https://blog.csdn.net/qq_34386891/article/details/80515912)
三次握手 发送接收标志位同步信息补充ClientServerSYN=1seq=0, len=0发送自己的序号ServerClientACK=1, SYN=1ack=1, seq=0, len=0回复的ack=Client的seq + 1,并发送自己的序号ClientServerACK=1ack=1, seq=1, len=0发送自己的序号,自己的序号就是对面回复的ack发送数据
本例Client向Server发送120400个字节数据,client选的是阻塞式发送,向缓冲区发送102400字节,缓冲区装不下了会阻塞在send函数,Server读取了内容,缓冲区空出空间就可以继续向缓冲区写数据。本例的102400字节,向缓冲区写了2次才写完。
发送数据 发送接收标志位同步信息补充ClientServerACK=1ack=1, seq=1, len=65495回复对方的下一帧序号,发送自己的序号、数据长度ServerClientACK=1ack=65496, seq=1, len=0回复的ack=Client的seq + Client的len,并发送自己的序号、数据长度ClientServerPSH=1,ACK=1ack=1, seq=65496, len=36905PSH=1,提醒Server尽快取走报文;
回复的ack=Server的seq + Server的len,并发送自己的序号、数据长度
ServerClientACK=1ack=102401, seq=1, len=0回复的ack=Client的seq + Client的len,并发送自己的序号、数据长度
四次挥手
四次挥手 发送接收标志位同步信息 ClientServerFIN=1,ACK=1ack=1, seq=102401, len=0回复对方的下一帧序号,发送自己的序号ServerClientACK=1ack=102402, seq=1, len=0回复的ack=Client的seq + 1,并发送自己的序号ServerClientFIN=1,ACK=1ack=102402, seq=1, len=0回复对方下一帧的序号,发送自己的序号ClientServerACK=1ack=2, seq=102402, len=0回复的ack=Server的seq + 1,并发送自己的序号
