1.同上两篇,学校的实验。
但是这第三个实验坑比较多,写了好长时间,百度了好久才写对。
我是先启动的服务器,在启动数据包捕获,最后在启动客户端,这样最初的通信也能捕获到。而且我的客户端与服务器时双向通信,也就是两者都会经过“127.0.0.1”这个地址,所以客户端的消息和服务器的消息都会捕获到。
捕获之后根据IP报文结构还有TCP报文结构来推算内容,开始位置在哪个地方,这个也与自己定义的头文件有关。
2. 数据包捕获
//.h文件
#pragma once
//IP报文格式
typedef struct IP {
//unsigned char version;//4位IP版本号
unsigned char headLen;//4位首部长度
unsigned char serviceType;//8位服务类型
unsigned short totalLen;//16位总长度
unsigned short identifier;//16位标识符
unsigned short flags;//3位标志位
//unsigned short fragOffset;//13位片偏移
unsigned char timeToLive;//8位生存时间
unsigned char protocal;//8位协议
unsigned short headCheckSum;//16位首部校验和
unsigned int sourceAddr;//32位源地址
unsigned int destinAddr;//32位目的地址
}IPHeader;
//TCP报文格式
typedef struct TCP {
unsigned short sourcePort;//16位源端口号
unsigned short destinPort;//16位目的端口号
unsigned int seqNum;//32位序列号
unsigned int ackNum;//32位确认号
unsigned char headLen;//4位首部长度
//unsigned char resv;//4位保留字
unsigned char flags;//8位标志位
unsigned short winSize;//16位窗口大小
unsigned short checkNum;//16位校验和
unsigned short urgPointer;//16位紧急指针
}TCPHeader;
//.cpp文件
#include <WinSock2.h>
#include <iostream>
#include "headers.h"
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)//将SIO_RCVALL定义为_WSAIOW(IOC_VENDOR,1)
int main() {
IP* ip;
TCP* tcp;
WSADATA wsd;
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
cout << "error:" << WSAGetLastError() << endl;
return -1;
}
SOCKET sock;
sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
if (sock == INVALID_SOCKET) {
cout << "error:" << WSAGetLastError() << endl;
closesocket(sock);
WSACleanup();
return -2;
}
BOOL flag = true;
if (setsockopt(sock, IPPROTO_IP, 2, (char*)&flag, sizeof(flag)) == SOCKET_ERROR) {
cout << "error:" << WSAGetLastError() << endl;
closesocket(sock);
WSACleanup();
return -3;
}
/*
创建了原始套接字后,就要设置套接字选项,这要通过setsocketopt函数来实现,setsocketopt函数的声明如下:
int setsocketopt (SOCKET s,int level,int optname,const char FAR *optval,int optlen );
参数s是标识套接口的描述字,要注意的是选项对这个套接字必须是有效的。
参数Level表明选项定义的层次,对TCP/IP协议族而言,支持SOL_SOCKET、IPPROTO_IP和IPPROTO_TCP层次。
参数Optname是需要设置的选项名,这些选项名是在Winsock头文件内定义的常数值。
参数optval是一个指针,它指向存放选项值的缓冲区。
参数optlen指示optval缓冲区的长度
*/
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(0);
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) == SOCKET_ERROR) {
cout << "error:" << WSAGetLastError() << endl;
closesocket(sock);
WSACleanup();
return -4;
}
DWORD dwBytesReturned;
DWORD dwBufferInLen = 1;
//将网卡设置为混听模式,就是接收所有数据
if (ioctlsocket(sock, SIO_RCVALL, &dwBufferInLen) == SOCKET_ERROR) {
cout << "error:" << WSAGetLastError() << endl;
closesocket(sock);
WSACleanup();
return -5;
}
/*
ioctsocket功能是控制套接口的模式。可用于任一状态的任一套接口。它用于获取与套接口相关的操作参数,而与具体协议或通讯子系统无关。
int ioctlsocket( int s, long cmd, u_long * argp);
s:一个标识套接口的描述字。
cmd:对套接口s的操作命令。
argp:指向cmd命令所带参数的指针。
*/
int bytesRecv;
char buffer[65535];//接收缓冲区的内容
//SOCKADDR_IN from;
struct sockaddr_in from;
int fromSize = sizeof(from);
//循环监听;
while (true) {
memset(buffer, 0, 65535);
bytesRecv = recvfrom(sock, buffer, 65535, 0, (struct sockaddr*) &from, &fromSize);
if (bytesRecv == SOCKET_ERROR) {
cout << "error:" << WSAGetLastError() << endl;
closesocket(sock);
WSACleanup();
return -6;
}
/*
与recv的功能差不多,都是接收数据,但是from可以适用于UDP,因为多了一个from,你懂的。
*/
ip = (struct IP*)buffer;
if (ip->protocal == 6) {//过滤其他协议,只留下TCP协议
tcp = (struct TCP*)(buffer + (4 * ip->headLen & 0xf0 >> 4));//得到TCP头
cout << "Network+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";//网络层数据
cout << "IP报文字节数:" << bytesRecv << "\n";
cout << "源IP:" << inet_ntoa(*(in_addr*)&ip->sourceAddr) << "\n";
cout << "目的IP:" << inet_ntoa(*(in_addr*)&ip->destinAddr) << "\n";
cout << "Transportation++++++++++++++++++++++++++++++++++++++++++++++++++++\n";//运输层数据
cout << "源端口:" << ntohs(tcp->sourcePort) << "\n";
cout << "目的端口:" << ntohs(tcp->destinPort) << "\n";
cout << "Applications++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";//应用层数据
char* start = buffer + 5 + 4 * ((tcp->headLen & 0xf0) >> 4 | 0);//计算数据头指针,从何处开始数据
int dataSize = bytesRecv - 5 - 4 * ((tcp->headLen & 0xf0) >> 4 | 0);//计算数据长度
cout << "数据内容:";
memcpy(buffer, start, dataSize);
for (int i = 0; i < dataSize; i++) {
if (buffer[i] >= 32 && buffer[i] < 255) {
printf("%c", (unsigned char)buffer[i]);
}
else {
printf(".");
}
}
cout << "\n";
}
}
}
3.服务器端
#include<WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
DWORD WINAPI clientChildThread(LPVOID ipParameter) {
SOCKET clientSocket = (SOCKET)ipParameter;
int const CLIENT_MSG_SIZE = 128;//接收缓冲区长度
char inMSG[CLIENT_MSG_SIZE];//接收信息的char数组
char outMSG[CLIENT_MSG_SIZE];//存储时间的char数组
char wx[] = "无效的命令";
int size;
while (true) {
memset(inMSG, 0, CLIENT_MSG_SIZE);//接收消息之前清空接收消息数组
size = recv(clientSocket, inMSG, CLIENT_MSG_SIZE, 0);//接收消息
if (size == SOCKET_ERROR) {//如果接收消息出错
cout << "对话中断,错误提示:" << WSAGetLastError() << endl;
closesocket(clientSocket);
break;
}
//否则,输出消息
cout << "客户端消息:" << inMSG << endl;
//如果客户端请求当前时间
if (strcmp(inMSG, "当前时间") == 0) {
SYSTEMTIME systime = { 0 };
GetLocalTime(&systime);//获取系统时间
sprintf(outMSG, "%d-%02d-%02d %02d:%02d:%02d",
systime.wYear, systime.wMonth, systime.wDay,
systime.wHour, systime.wMinute, systime.wSecond);
send(clientSocket, outMSG, CLIENT_MSG_SIZE, 0);
memset(outMSG, 0, CLIENT_MSG_SIZE);//每次回复之后,清空发送消息数组
}
//如果客户端要退出连接
else if (strcmp(inMSG, "退出连接") == 0) {
closesocket(clientSocket);
cout << "客户端退出连接成功" << endl;
break;
}
else {
send(clientSocket, wx, sizeof(wx), 0);
}
}
return 0;
}
int main() {
WSADATA wsd;
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
WSACleanup();
return -1;
}
SOCKET serverSocket;
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket == INVALID_SOCKET) {
cout << "error:" << WSAGetLastError() << endl;
WSACleanup();
return -2;
}
SOCKADDR_IN server;
server.sin_family = AF_INET;
server.sin_port = htons(2591);
server.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
if (bind(serverSocket, (struct sockaddr*) &server, sizeof(server)) == SOCKET_ERROR) {
cout << "error:" << WSAGetLastError() << endl;
closesocket(serverSocket);
WSACleanup();
return -3;
}
if (listen(serverSocket, 2) == SOCKET_ERROR) {
cout << "error:" << WSAGetLastError() << endl;
closesocket(serverSocket);
WSACleanup();
return -4;
}
cout << "服务器启动。。。监听中。。。" << endl;
SOCKET clientSocket;
SOCKADDR_IN client;
int addrsize = sizeof(SOCKADDR_IN);
HANDLE pThread;
while (true) {
clientSocket = accept(serverSocket, (struct sockaddr*) &client, &addrsize);
if (clientSocket == INVALID_SOCKET) {
cout << "客户端accept失败,错误提示:" << WSAGetLastError() << endl;
closesocket(serverSocket);
WSACleanup();
return -5;
}
else {
cout << "客户端\n"
<< inet_ntoa(client.sin_addr)//inet_ntoa将一个十进制网络字节序转换为点分十进制IP格式的字符串。
<< "\n通过端口:\n"
<< ntohs(client.sin_port)//ntohs将一个16位数由网络字节顺序转换为主机字节顺序
<< "\n连接成功" << endl;
pThread = CreateThread(NULL, 0, clientChildThread, (LPVOID)clientSocket, 0, NULL);
/*
lpsa:线程句柄的安全性,比如子进程是否可以继承这个线程句柄,一般设置为NULL
cbStack:线程栈大小,一般取0表示默认大小
lpStartAddr:线程入口函数
lpvThreadParam:线程入口函数的参数
fdwCreate:控制线程创建的标志,一般为0,表示线程立即启动。也可以选择可以挂起,使用CREATE_SUSPENDED,之后在代码中使用ResumeThread启动。
lpIDThread:线程的ID值,接收线程返回的ID
*/
if (pThread == NULL) {
cout << "创建子进程失败。" << endl;
break;
}
CloseHandle(pThread);
}
}
closesocket(serverSocket);
closesocket(clientSocket);
WSACleanup();
return 0;
}
4.客户端
#include<WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main() {
WSADATA wsd;//定义 WSADATA对象
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {//初始化WSA
WSACleanup();
return -1;
}
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET) {
cout << "error:" << WSAGetLastError() << endl;
WSACleanup();
return -2;
}
SOCKADDR_IN client;
client.sin_family = AF_INET;
client.sin_port = htons(2591);
client.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int const SERVER_MSG_SIZE = 128;
char inMSG[SERVER_MSG_SIZE] = { 0 };//用户输入的消息
char outMSG[SERVER_MSG_SIZE];//要发送给服务器的消息
//连接服务器失败
if (connect(clientSocket, (struct sockaddr*) &client, sizeof(client)) < 0) {
cout << "error:" << WSAGetLastError() << endl;
closesocket(clientSocket);
WSACleanup();
return -3;
}
//连接服务器成功
else {
cout << "连接服务器成功。。。。。。\n" << endl;
while (true) {
memset(outMSG, 0, SERVER_MSG_SIZE);
cout << "请输入请求。。。。。。:" << endl;
cin >> outMSG;
send(clientSocket, outMSG, SERVER_MSG_SIZE, 0);
if (strcmp(outMSG, "退出连接") == 0) {
break;
}
int size = recv(clientSocket, inMSG, SERVER_MSG_SIZE, 0);
cout << "服务器端回答:" << inMSG << endl;
memset(inMSG, 0, SERVER_MSG_SIZE);
}
}
closesocket(clientSocket);
WSACleanup();
system("pause");
return 0;
}