原始套接字编程(C++)

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;

}

  • 9
    点赞
  • 83
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值