ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

套接字编程

2022-02-05 20:30:00  阅读:160  来源: 互联网

标签:SOCKET int 编程 unsigned char 接字 struct


套接字编程

1.知识点

1.1套接字的分类

流套接字:TCP;数据报套接字:UDP;原始套接字:可以读写内核没有处理的IP数据包。

1.2 IP数据报格式

(1)4位版本,IPV4或者IPV6
 (2)4位首部长度,单位32字长,4字节。最大首部长度60字节。当IP首部长度不是4字节的整数倍,要填充,数据部分以4的整数倍开始。
 (3)16位总长度,65535字节
(4)16位标识,作用是将属于同一数据报的不同分片组装起来
(5)3位标志,只有两位有意义。最低位MF,MF=1表示后面还有分片。中间位DF,DF=0表示允许分片。
(6)13位片偏移,片偏移的作用是指出某片在原分组中的相对位置,片偏移以8个字节为偏移单位,每个分片的长度一定是8字节(64位)的整数倍。
(7)8位生存时间TTL,每经过一个路由器,TTL减去数据报在路由器中的消耗时间。当消耗时间小于1S,将TTL减1,TTL为0,丢弃数据报。
(8)8位协议,标志承载的是什么类型的报文。(TCP是6,UDP是17)
(9)16位首部检验和,一直变化(随着数据报的路由改变)。
(10)32位源,目的IP地址。
typedef struct IpHeader
{
	unsigned char Version_Hlen;
	unsigned char TOS;
	unsigned short Length; 
	unsigned short Ident;
	unsigned short Flags_Offset;
	unsigned char TTL;
	unsigned char Protocol;
	unsigned short Checksum;//short是两个字节16位
	unsigned int SourceAddr;
	unsigned int DestinationAddr;
}IpHeader;

在这里插入图片描述

1.3 TCP数据报格式

(1)源端口和目的端口,各占两个字节
(2)序号,4个字节,指的是本报文段所发送的数据的第一个字节的序号
(3)确认号,4个字节,确认号为N,表示前面的N-1都收到
(4)检验和,2字节,检验首部和数据,计算时应该加上12字节的伪首部
(5)紧急指针,2字节,仅在URG=1时有意义,指出本报文段中紧急数据的字节数,紧急数据的末尾在报文段的位置。
(6)选项,最长达到40字节,当没有选项,首部长度20字节。
#define URG 0x20
#define ACK 0x10
#define PSH 0x80
#define RST 0x40
#define SYN 0x02
#define FIN 0x01
typedef struct TcpHeader {
	USHORT SrcPort;
	USHORT DstPort;
	unsigned int SequenceNum;
	unsigned int Ackowledgement;
	unsigned char HdrLen;
	unsigned char Flags;
	USHORT AdvertisedWindow;
	USHORT Checksum;
	USHORT UrgPtr;

}TcpHeader;
typedef struct PsdTcpHeader {
	unsigned long SourceAddr;
	unsigned long DestinationAddr;
	char Zero;
	char Protcol;
	unsigned short TcpLen;
}; PsdTcpHeader//定义TCP伪首部

在这里插入图片描述

1.4校验和的计算

对需要检验的数据每16bit进行二进制求和,高16bit不为0时需要将高16bit和低16bit反复相加,从而获得一个16bit的值,将该16bit值取反
实例:简化成4bit
发送端:
数据:1000 0100 校验和0000
反码 0111 1011       1111
叠加0111+1011+1111=0010 0001
高于4bit叠加到低4位 0001+0010=0011得到校验和
接收端:
数据:1000 0100 校验和0011
反码 0111 1011       1100
叠加0111+1011+1100=0001 1110高低叠加1111 正确
USHORT checksum(USHORT *buffer, int size) {
	unsigned long cksum = 0;
	while (size > 1)
	{
		cksum += *buffer++;
		size -= sizeof(USHORT);
	}
	if (size)
	{
		cksum += *(UCHAR*)buffer;
	}
	cksum = (cksum >> 16) + (cksum & 0xffff);//高十六位和低十六位相加
	cksum += (cksum >> 16);//有溢出继续加
	return (USHORT)(~cksum);//按位取反

}

1.5 原始套接字编程

(1)Winsock头文件:Winsock2.h来使用Winsock的API,Ws2tcpip头文件包含了
在Windock2协议兼容文档中为TCP、IP用于检索IP地址的新函数和数据结构
(2)初始化Winsock环境
int WSAStartup(
WORD wVersionRequested,//wod双字节的指定版本号
LPWSADATA lpWSAData//指向WSADATA的指针
);
struct WSAData {
WORD wVersion;//Windows Sockets规范的版本,高位字节储存副版本号,低位字节存储主版本号
WORD wHighVersion;//DLL能够支持的Windows Sockets规范的最高版本
char szDescription[WSADESCRIPTION_LEN+1];//DLL对Windows Sockets实现的描述
char szSystemStatus[WSASYSSTATUS_LEN+1];//DLL把有关的状态或配置信息放在里面
unsigned short iMaxSockets;//最大的Sockets线程数量
unsigned short iMaxUdpDg;//能够接受或发送的最大数据报协议的字节数,不应该超过MTU
char *lpVendorInfo;
};
(3)Winsock库的释放,用WSACleanup()
if (WSACleanup() == SOCKET_ERROR)
 {
 printf("WSACleanup failed with error %d\n", WSAGetLastError());
 return 0;
 }
 return 1;

2.实例 (有些结构系统已经定义,为了便于理解都写上了)

(1)套接字的创建和关闭。使用套接字之前,必须使用Socket函数创建一个套接字对象,此函数调用成功将返回套接字句柄。注意用完之后调用closesocket将之关闭。
int closesocket(SOCKET s);//参数是要关闭的套接字的句柄,无错误返回0,否则返回SOCKET_ERROR,用WSAGetLastError()获取相应代码
	SOCKET socket(
		int af,//协议族,如果想建一个TCP或UDP,只能用AF_INET
		int type,//描述套接口的类型,af为AF_INET的时候只能为SOCK_STRAM/SOCK_DGRAM/SOCL_RAW
		int protocol//协议字段
	);

在这里插入图片描述

(2)创建接收套接字的socket
	SOCKET RecSocket;
	RecSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
(3)绑定套接字,使用Bind()函数
int bind(
Socket s,//标识未绑定的套接字
const struct sockaddr FAR*name,//与指定协议有关的地址结构指针,存储了套接口的地址信息
int namelen//表示地址参数的长度
);
Winsock中使用sockaddr_in结构指定IP地址和端口信息
struct sockaddr_in{
	short sin_family;//表示IP地址族,一般为AF_INET
	u_short sin_port;//16位端口号,若参数值为0,则系统自动指派一个1024~5000之间唯一的端口号
	struct in_addr sin_addr;//32位IP地址,若参数值为INADDR_ANY,则由系统内核指定
	char sin_sero[8];//用0填充
	}
绑定代码	
Result=bind(RecSocket,(PSOCKADDR)&sock,sizeof(sock));//由sock变量提供绑定的地址和端口号,类型为sockaddr_in
	if (Result2 == SOCKET_ERROR)
	{
		printf("bind failed with error%d\n", WSAGetLastError());
		closesocket(RecSocket);
		return 0;
	}
获取本机ip地址的程序
char Name[255];
	Result = gethostname(Name, 255);//返回本地主机的标准主机名
	if (Result == SOCKET_ERROR)
	{
		printf("gethostname failed with error%d\n", WSAGetLastError());
		return 0;
	}
	struct hostent* pHostent;//hostent记录该主机的信息
	pHostent = (struct hostent*)malloc(sizeof(struct hostent));
	pHostent = gethostbyname(Name);//返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针
	sock.sin_family = AF_INET;
	sock.sin_port = htons(5555);//htons的功能是将一个无符号短整型的主机值转换为网络字节序。整数在地址空间的存储方式为:高位字节存放在内存的低地址处。
	memcpy(&sock.sin_addr.S_un.S_addr, pHostent->h_addr_list[0], pHostent->h_length);//从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始地址中void *memcpy(void *dest,const void *src,size_t n);
hostent的定义
struct hostent{
	char *h_name;//地址的正式名称
	char **h_aliases;//地址的预备名称的指针
	int h_addrtype;//地址类型,通常是AF_INET
	int h_length;//地址的比特长度
	char **h_addr_list;//主机网络地址指针,网络字节序
	#define h_addr h_addr_list[0]//第一地址
(4)设置套接字利用函数setsockopt()实现
int setsockopt(SOCKET s,int level,int optname,const char FAR *optval,int optlen);
s://标识一个套接字的描述符
level://选项定义的层次,如SOL_SOCKET(套接字层)和IPPROTO_TCP层
optname://需设置的选项
optval://指针,指向存放选项值的缓冲区
optlen://optval缓冲区长度
设置手工填充ip数据包首部
	BOOL flag;
	flag = 1;
	int nTimeOver = 100;
	if (setsockopt(SendSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOver, sizeof(nTimeOver)) == SOCKET_ERROR)
	{
		printf("setsockopt faied with error%d\n\n", WSAGetLastError());
		return 0;
	}//SO_SNDTIMEO表示发送数据超时时间
设置SOCK_RAW为SIO_RCVALL,接收所有数据包
int WSAloctl(
	SOCKET s,//一个套接口的句柄
	DWORD dwloControlCode,//将进行的操作的控制代码
	LPVOID lpInBuffer,//输入缓冲区的地址
	DWORD cbInBuffer,//输入缓冲区的大小
	LPVOID IpvOutBuffer,//输出缓冲区的地址
	DWORD cbOutBuffer,//输出缓冲区的大小
	LPDWORD IpcbBytesReturned,//输出实际字节数的地址
	LPWSAOVERLAPPED IpOverlapped,//WSAOVERLAPPED结构的地址
	LPWSAOVERLAPPED_COMPLETION_ROUTINE IpCompletionRoutline//一个指向操作结束后调用的例程指针
Result =WSAIoctl(RecSocket, SIO_RCVALL, &dwBufferInLen, sizeof(dwBufferInLen), 
&dwBufferLen, sizeof(dwBufferLen), &dwBytesReturned, NULL, NULL);
 if (Result == SOCKET_ERROR)
 {
 printf("WSAIoctl failed with error %d\n", WSAGetLastError());
 closesocket(RecSocket);
 return 0;
 }//SIO_RCVALL 表示接收所有数据包
(5)用send()函数在已经建立的套接口上发送数据
int send(
SOCKET s, //标识已建立连接的套接字
33
const char FAR * buf, //一个字符缓冲区,内有将要发送的数据
int len, //即将发送的缓冲区中的字符数
int flags//用于控制数据传输方式,0 表示按正常方式发送数据;
//宏MSG_DONTROUTE 说明系统目标主机就在直接连接的本地网络中,无需路由选择;MSG_OOB 指出数据是按带外数据发送的
);
对于无连接的套接口使用sendto()函数
int sendto(
SOCKET s, //本机的套接字
const char FAR * buf, //待发送数据的缓冲区地址
int len, //指明 buf 缓冲区中要发送的数据长度
int flags, //调用方式标志位
const struct sockaddr * to, //可选指针,指向接收数据的目的套接口
地址
int tolento//所指的地址的长度
);
(6)套接字用recv()函数接收数据包	
int recv(
SOCKET s,//已建立连接的套接口
char FAR * buf, //用于接收数据的缓冲区
int len, //缓冲区的长度
int flags//指定调用的方式。0表示接收的是正常数据,无特殊行为。
//MSG_PEEK表示会使有用的数据复制到所提供的接收端缓冲区内,但是没有从系统缓冲区中将数据删除。MSG_OOB 表示处理带外数据。
);
对于无连接的套接字要用recvfrom()函数
int recvfrom(
SOCKET s,// 标识一个套接口的描述字
char FAR * buf,//接收数据的缓冲区
int len,//接收数据缓冲区的长度
int flags,//调用操作方式,同 recv()中的 flags
struct sockaddr FAR * from,//可选指针,指向装有源地址的缓冲区
int FAR * fromlen//可选指针,指向 from 缓冲区的长度值
);

3.练习

练习1

编写一个基本的原始套接字程序,屏幕输出wsaData中的各项参数。
主函数如下:
void main()
{
	int Result;
	struct WSAData wsaData;//引用WSAData变量
	Result = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (Result == SOCKET_ERROR)
	{
		printf("WSAStartup failed with error %d\n", Result);
	}//检验初始化
	else {
		printf("%x\n", wsaData.wVersion);
		printf("%x\n", wsaData.wHighVersion);
		printf("%s\n",wsaData.szDescription);
		printf("%s\n", wsaData.szSystemStatus);
		printf("%ld\n", wsaData.iMaxSockets);
		printf("%ld\n", wsaData.iMaxUdpDg);
	}
	WSACleanup();	
}

练习2

利用原始套接字构造并发送 TCP 包,输出显示所构造的 IP 头和 TCP 头字段内容。
#include "Winsock2.h"
#include <iostream>
#include <winsock.h>
#include <WS2TCPIP.h>
#pragma comment(lib,"Ws2_32.lib")

#define MAX 100
#define SOURCE_PORT 7234
#define MAX_RECEIVEBYTE 255 
using namespace std;
typedef struct ip_hdr //定义IP首部
{
	unsigned char h_verlen; //4位首部长度,4位IP版本号
	unsigned char tos; //8位服务类型TOS
	unsigned short total_len; //16位总长度(字节)
	unsigned short ident; //16位标识
	unsigned short frag_and_flags; //3位标志位
	unsigned char ttl; //8位生存时间 TTL
	unsigned char proto; //8位协议 (TCP, UDP 或其他)
	unsigned short checksum; //16位IP首部校验和
	unsigned int sourceIP; //32位源IP地址
	unsigned int destIP; //32位目的IP地址
}IPHEADER;
typedef struct tsd_hdr //定义TCP伪首部
{
	unsigned long saddr; //源地址
	unsigned long daddr; //目的地址
	char mbz;
	char ptcl; //协议类型
	unsigned short tcpl; //TCP长度
}PSDHEADER;

typedef struct tcp_hdr //定义TCP首部
{
	USHORT th_sport; //16位源端口
	USHORT th_dport; //16位目的端口
	unsigned int th_seq; //32位序列号
	unsigned int th_ack; //32位确认号
	unsigned char th_lenres; //4位首部长度/6位保留字
	unsigned char th_flag; //6位标志位
	USHORT th_win; //16位窗口大小
	USHORT th_sum; //16位校验和
	USHORT th_urp; //16位紧急数据偏移量
}TCPHEADER;

//CheckSum:计算校验和的子函数
USHORT checksum(USHORT* buffer, int size)
{
	unsigned long cksum = 0;
	while (size > 1)
	{
		cksum += *buffer++;
		size -= sizeof(USHORT);
	}
	if (size)
	{
		cksum += *(UCHAR*)buffer;
	}

	cksum = (cksum >> 16) + (cksum & 0xffff);
	cksum += (cksum >> 16);
	return (USHORT)(~cksum);
}

int main()
{
	int a;
	int Result;
	WSADATA wsaData;
	//初始化套接字
	Result = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (Result == SOCKET_ERROR)
	{
		printf("WSAStartup failed with error %d\n", Result);
		return 0;
	}
	//创建套接字
	SOCKET RecSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
	if (RecSocket == INVALID_SOCKET)
	{
		printf("socket failed with error %d\n", WSAGetLastError());
		closesocket(RecSocket);
		return 0;
	}
	//sock定义
	char Name[255];
	Result = gethostname(Name, 255);
	if (Result == SOCKET_ERROR)
	{
		printf("gethostname failed with error %d\n", WSAGetLastError());
		return 0;
	}
	struct hostent* pHostent;
	pHostent = (struct hostent*)malloc(sizeof(struct hostent));
	pHostent = gethostbyname(Name);
	SOCKADDR_IN sock;
	sock.sin_family = AF_INET;
	sock.sin_port = htons(5555);
	memcpy(&sock.sin_addr.S_un.S_addr, pHostent->h_addr_list[0], pHostent->h_length);

	//设置超时时间 
	int nTimeOver = 1000;
	if (setsockopt(RecSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOver, sizeof(nTimeOver)) == SOCKET_ERROR)
	{
		printf("setsockopt failed with error2 %d\n\n", WSAGetLastError());
		return false;
	}

	//sockaddr结构体定义
	struct sockaddr_in* Rsock;
	Rsock = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
	Rsock->sin_family = AF_INET;
	Rsock->sin_port = htons(8083);
	Rsock->sin_addr.s_addr = inet_addr("127.0.0.1");
	int lenth = 0;
	lenth = sizeof(Rsock->sin_port) + sizeof(Rsock->sin_addr);

	IPHEADER ipHeader;
	TCPHEADER tcpHeader;
	PSDHEADER psdHeader;
	char szSendBuf[60] = { 0 };
	int rect;

	SOCKADDR_IN addr_in;
	addr_in.sin_family = AF_INET;
	addr_in.sin_port = htons(8086);
	addr_in.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	//填充IP首部
	ipHeader.h_verlen = (4 << 4 | sizeof(ipHeader) / sizeof(unsigned long));
	// ipHeader.tos=0;
	ipHeader.total_len = htons(sizeof(ipHeader) + sizeof(tcpHeader));
	ipHeader.ident = 1;
	ipHeader.frag_and_flags = 0;
	ipHeader.ttl = 128;
	tcpHeader.th_flag = 2;
	ipHeader.proto = IPPROTO_TCP;
	ipHeader.checksum = 0;
	ipHeader.sourceIP = inet_addr("127.0.0.1");
	ipHeader.destIP = inet_addr("127.0.0.1");

	//填充TCP首部
	tcpHeader.th_dport = htons(8085);
	tcpHeader.th_sport = htons(8086); //源端口号
	tcpHeader.th_seq = htonl(0x12345678);
	tcpHeader.th_ack = 0;
	tcpHeader.th_lenres = (sizeof(tcpHeader) / 4 << 4 | 0);
	tcpHeader.th_win = htons(512);
	tcpHeader.th_urp = 0;
	tcpHeader.th_sum = 0;

	psdHeader.saddr = ipHeader.sourceIP;
	psdHeader.daddr = ipHeader.destIP;
	psdHeader.mbz = 0;
	psdHeader.ptcl = IPPROTO_TCP;
	psdHeader.tcpl = htons(sizeof(tcpHeader));
	//计算校验和
	memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));
	memcpy(szSendBuf + sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader));
	tcpHeader.th_sum = checksum((USHORT*)szSendBuf, sizeof(psdHeader) + sizeof(tcpHeader));

	memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
	memcpy(szSendBuf + sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader));
	memset(szSendBuf + sizeof(ipHeader) + sizeof(tcpHeader), 0, 4);
	ipHeader.checksum = checksum((USHORT*)szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader));

	memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));

	//输出TCP信息
	cout << endl << "TCP头部	" << endl;
	cout << "目标端口:" << tcpHeader.th_dport << endl;
	cout << "源端口:" << tcpHeader.th_sport << endl;
	cout << "序列号:" << tcpHeader.th_seq << endl;
	cout << "证书号:" << tcpHeader.th_ack << endl;
	cout << "标志位:" << tcpHeader.th_flag << endl;
	cout << "窗口尺寸:" << tcpHeader.th_win << endl;
	cout << "校验和:" << tcpHeader.th_sum << endl;

	//输出IP头部信息
	cout << endl << "IP头部" << endl;
	cout << "头长度:" << ipHeader.h_verlen << endl;
	cout << "总长度:" << ipHeader.total_len << endl;
	cout << "特征值:" << ipHeader.ident << endl;
	cout << "标志位:" << ipHeader.frag_and_flags << endl;
	cout << "存活时长:" << ipHeader.ttl << endl;
	cout << "源IP:" << ipHeader.sourceIP << endl;
	cout << "目标IP:" << ipHeader.destIP << endl;
	//发送数据包
	rect = sendto(RecSocket, szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader), 0, (struct sockaddr*)&addr_in, sizeof(addr_in));

	if (rect == SOCKET_ERROR)
	{
		printf("Falied!蠢驴");
	}
	if (WSACleanup() == SOCKET_ERROR)
	{
		printf("WSACleanup failed with error %d\n", WSAGetLastError());
		return 0;
	}
	cin >> a;
	return 0;
}

练习3

利用原始套接字接收网络接口数据包,统计接收的数据包的个数,并分析 IP 头字段,将分析结果输出。

标签:SOCKET,int,编程,unsigned,char,接字,struct
来源: https://blog.csdn.net/weixin_46206086/article/details/122316309

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有