C++ socket编程基础二(三种Socket:TCP,UDP,原始Socket)

来源:互联网 发布:淘宝信誉查询客户达 编辑:程序博客网 时间:2024/06/09 10:09

一、基于TCP(面向连接)的的Socket

 

1、服务器端

 

创建套接字

SOCKET socket(  int af,  //参数af指定通信发生的区域:AF_UNIX、AF_INET、AF_NS等,而DOS、 WINDOWS中仅支持AF_INET,它是网际网区域。因此,地址族与协议族相同。int type,  //参数type 描述要建立的套接字的类型。这里分三种:TCP,UDP,RAWint protocol);//参数protocol说 明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。


绑定套接字到本地地址和端口上

int bind( SOCKET s, //参数s是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。const struct sockaddr* name, //参数name 是赋给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同。int namelen//namelen表明了name的长度.如果没有错误发生,bind()返回 0。否则返回SOCKET_ERROR。);


将套接字设为监听模式,准备接受请求

int listen( SOCKET s,  //参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。int backlog//backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。如果没有错误发生,listen()返回0。否则它返回SOCKET_ERROR。); 


等待客户端请求,当接受连接请求后,返回一个新的对应于此次连接的套接字SOCKET accept( SOCKET s, struct sockaddr* addr,  int* addrlen);

 

利用accept函数返回的套接字(里面有客户端的IP地址和端口号)进行通信-----发送信息

int send(  SOCKET s,//参数s为已连接的本地套接字描述符。  const char FAR* buf,//buf 指向存有发送数据的缓冲区的指针,其长度由len 指定。  int len,//  int flags//flags 指定传输控制方式,如是否发送带外数据等。);    

  

接受信息

int recv(  SOCKET s,  //参数s 为已连接的套接字描述符。char FAR* buf,  //buf指向接收输入数据缓冲区的指针,其长度由len 指定。int len,  int flags);//指定传输控制方式,如是否接收带外数据等。


等待另一客户端请求

关闭套接字


2、客户端

 

创建套接字

socket

向服务器发出连接请求

int connect(  SOCKET s,  const struct sockaddr FAR* name,  int namelen);

进行通信

send 和 recv

关闭套接字


二、基于UPD(面向无连接)的socket

 

1、服务器端

 

创建套接字-----socket

绑定地址和端口------bind

数据处理------接收数据  int recvfrom(

  __in          SOCKET s,

  __out         char* buf,

  __in          int len,

  __in          int flags,

  __out         struct sockaddr* from,

  __in_out      int* fromlen

);     

发送数据 int sendto(

  __in          SOCKET s,

  __in          const char* buf,

  __in          int len,

  __in          int flags,

  __in          const struct sockaddr* to,

  __in          int tolen

);

 

关闭套接字

 

2客户端

 

创建套接字-----socket

向服务器发送数据和接收数据 recvfrom/sendto

关闭套接字

不要忘记在开始的时候导入socket

int WSAStartup(

  __in          WORD wVersionRequested,

  __out         LPWSADATA lpWSAData

);

和引入头文件#include <WinSock2.h>

三、Raw Socket原始套接字

Windows2000TCP/IP协议组件上做了很多改进,功能也有增强。比如在协议栈上的调整,增大了默认窗口大小,以及高延迟链接新算法。同时在安全性上,可应用IPSec加强安全性,比NT下有不少的改进。 

 

  Microsoft TCP/IP 组件包含“核心协议”、“服务”及两者之间的“接口”。传输驱动程序接口 (TDI) 与网络设备接口规范 (NDIS) 是公用的。 此外,还有许多用户模型应用程序的更高级接口。最常用的接口是 Windows Sockets、远程过程调用 (RPC) 和 NetBIOS。 

 

  Windows Sockets 是一个编程接口,它是在加州大学伯克利分校开发的套接字接口的基础上定义的。它包括了一组扩展件,以充分利用 Microsoft Windows 消息驱动的特点。规范的 1.1 版是在 1993 年 月发行的,2.2.0 版在 1996 年 月发行。Windows 2000 支持 Winsock 2.2 版。在Winsock2中,支持多个传输协议的原始套接字,重叠I/O模型、服务质量控制等。 

 

  这里介绍Windows Sockets的一些关于原始套接字(Raw Socket)的编程。同Winsock1相比,最明显的就是支持了Raw Socket套接字类型,通过原始套接字,我们可以更加自如地控制Windows下的多种协议,而且能够对网络底层的传输机制进行控制。 

 

1、创建一个原始套接字,并设置IP头选项。 

 

SOCKET sock; 

sock = socket(AF_INET,SOCK_RAW,IPPROTO_IP); 

或者: 

s = WSASoccket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED); 

 

  这里,我们设置了SOCK_RAW标志,表示我们声明的是一个原始套接字类型。创建原始套接字后,IP头就会包含在接收的数据中,如果我们设定 IP_HDRINCL 选项,那么,就需要自己来构造IP头。注意,如果设置IP_HDRINCL 选项,那么必须具有 administrator权限,要不就必须修改注册表: 

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Afd\Parameter\ 

修改键:DisableRawSecurity(类型为DWORD),把值修改为 1。如果没有,就添加。 

 

BOOL blnFlag=TRUE; 

setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&blnFlag, sizeof(blnFlag); 

 

  对于原始套接字在接收数据报的时候,要注意这么几点: 

1、如果接收的数据报中协议类型和定义的原始套接字匹配,那么,接收的所有数据就拷贝到套接字中。 

2、如果绑定了本地地址,那么只有接收数据IP头中对应的远端地址匹配,接收的数据就拷贝到套接字中。 

3、如果定义的是外部地址,比如使用connect(),那么,只有接收数据IP头中对应的源地址匹配,接收的数据就拷贝到套接字中。 

 

 

2、构造IP头和TCP头 

 

这里,提供IP头和TCP头的结构: // Standard TCP flags #define URG 0x20 #define ACK 0x10 #define PSH 0x08 #define RST 0x04 #define SYN 0x02 #define FIN 0x01  typedef struct _iphdr //定义IP首部 { unsigned char h_lenver; //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地址 }IP_HEADER;  typedef struct psd_hdr //定义TCP伪首部 { unsigned long saddr; //源地址 unsigned long daddr; //目的地址 char mbz; char ptcl; //协议类型 unsigned short tcpl; //TCP长度 }PSD_HEADER;  typedef struct _tcphdr //定义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位紧急数据偏移量 }TCP_HEADER;  TCP伪首部并不是真正存在的,只是用于计算检验和。校验和函数:  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); }  


  当需要自己填充IP头部和TCP头部的时候,就同时需要自己计算他们的检验和。 

 

3、发送原始套接字数据报 

 

  填充这些头部稍微麻烦点,发送就相对简单多了。只需要使用sendto()OK。 

 

sendto(sock, (char*)&tcpHeader, sizeof(tcpHeader), 0, (sockaddr*)&addr_in,sizeof(addr_in)); 

 

下面是一个示例程序,可以作为SYN扫描的一部分。 

 

#include <stdio.h> #include <winsock2.h> #include <ws2tcpip.h>  #define SOURCE_PORT 7234 #define MAX_RECEIVEBYTE 255  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); }  void useage() { printf("******************************************\n"); printf("TCPPing\n"); printf("\t Written by Refdom\n"); printf("\t Email: refdom@263.net\n"); printf("Useage: TCPPing.exe Target_ip Target_port \n"); printf("*******************************************\n"); }  int main(int argc, char* argv[]) { WSADATA WSAData; SOCKET sock; SOCKADDR_IN addr_in; IPHEADER ipHeader; TCPHEADER tcpHeader; PSDHEADER psdHeader;  char szSendBuf[60]={0}; BOOL flag; int rect,nTimeOver;  useage();  if (argc!= 3) { return false; }  if (WSAStartup(MAKEWORD(2,2), &WSAData)!=0) { printf("WSAStartup Error!\n"); return false; }  if ((sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET) { printf("Socket Setup Error!\n"); return false; } flag=true; if (setsockopt(sock,IPPROTO_IP, IP_HDRINCL,(char *)&flag,sizeof(flag))==SOCKET_ERROR) { printf("setsockopt IP_HDRINCL error!\n"); return false; }  nTimeOver=1000; if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOver, sizeof(nTimeOver))==SOCKET_ERROR) { printf("setsockopt SO_SNDTIMEO error!\n"); return false; } addr_in.sin_family=AF_INET; addr_in.sin_port=htons(atoi(argv[2])); addr_in.sin_addr.S_un.S_addr=inet_addr(argv[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; ipHeader.proto=IPPROTO_TCP; ipHeader.checksum=0; ipHeader.sourceIP=inet_addr("本地地址"); ipHeader.destIP=inet_addr(argv[1]);  //填充TCP首部 tcpHeader.th_dport=htons(atoi(argv[2])); tcpHeader.th_sport=htons(SOURCE_PORT); //源端口号 tcpHeader.th_seq=htonl(0x12345678); tcpHeader.th_ack=0; tcpHeader.th_lenres=(sizeof(tcpHeader)/4<<4|0); tcpHeader.th_flag=2; //修改这里来实现不同的标志位探测,2是SYN,1是FIN,16是ACK探测 等等 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));  rect=sendto(sock, szSendBuf, sizeof(ipHeader)+sizeof(tcpHeader), 0, (struct sockaddr*)&addr_in, sizeof(addr_in)); if (rect==SOCKET_ERROR) { printf("send error!:%d\n",WSAGetLastError()); return false; } else printf("send ok!\n");  closesocket(sock); WSACleanup();  return 0; } 


 

4、接收数据 

  和发送原始套接字数据相比,接收就比较麻烦了。因为在WIN我们不能用recv()来接收raw socket上的数据,这是因为,所有的IP包都是先递交给系统核心,然后再传输到用户程序,当发送一个raws socket包的时候(比如syn),核心并不知道,也没有这个数据被发送或者连接建立的记录,因此,当远端主机回应的时候,系统核心就把这些包都全部丢掉,从而到不了应用程序上。所以,就不能简单地使用接收函数来接收这些数据报。 

 

  要达到接收数据的目的,就必须采用嗅探,接收所有通过的数据包,然后进行筛选,留下符合我们需要的。可以再定义一个原始套接字,用来完成接收数据的任务,需要设置SIO_RCVALL,表示接收所有的数据。 

 

SOCKET sniffersock; sniffsock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED);  DWORD lpvBuffer = 1; DWORD lpcbBytesReturned = 0 ; WSAIoctl(sniffersock, SIO_RCVALL, &lpvBuffer, sizeof(lpvBuffer), NULL, 0, & lpcbBytesReturned, NULL, NULL); 


 

  创建一个用于接收数据的原始套接字,我们可以用接收函数来接收数据包了。然后在使用一个过滤函数达到筛选的目的,接收我们需要的数据包。 

 

 

如果在XP以上的操作系统,微软封杀了Raw Soccket,只能用wincpap之类的开发包了。

0 0