C-C++语言28|网络套接字编程

网络空间安全

  对于非网络应用,也就是本地进程,可以通过进程PID来唯一标识,但对网络中的网络进程来说是行不通的。TCP/IP协议族可以帮助我们解决网络进程的唯一标识问题,网络层的“IP地址”是唯一标识网络中的主机,而传输层的“协议+端口”是唯一标识主机中的网络应用(进程)。这样,利用三元组(IP地址、协议、端口)构成套接字(Socket),就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其他进程进行交互。

  套接字(Socket),是TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

  Socket原意是“插座”。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

  Socket可以看成在两个程序进行通讯连接中的一个端点,是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定与网络驱动建立关系。此后,应用程序送给Socket的数据,由Socket交给网络驱动程序向网络上发送出去。计算机从网络上收到与该Socket绑定IP地址和端口号相关的数据后,由网络驱动程序交给Socket,应用程序便可从该Socket中提取接收到的数据,网络应用程序就是这样通过Socket进行数据的发送与接收的。

  常用的TCP/IP协议的3种套接字有三种类型,并对应三种不同的网络应用服务的编程。

  (流式、数据报、原始套接字,前两者统称为标准套接字)

  1 流式套接字(SOCK_STREAM)及TCP服务端与客户端编程

  流式套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(TheTransmissionControlProtocol)协议。

  1.1 tcp server

  //tcp server#include <Winsock2.h>#include <stdio.h>void main(){WORD wVersionRequested;// 指定准备加载的Winsock库版本WSADATA wsaData;// Winsock库版本信息的结构体wVersionRequested = MAKEWORD( 1, 1);int err = WSAStartup( wVersionRequested, &wsaData );// 加载套接字库if ( err != 0 ) { return;}if ( LOBYTE( wsaData.wVersion ) != 1

   HIBYTE( wsaData.wVersion ) != 1 ) {WSACleanup( );// 释放为该应用程序分配的资源,终止对WinSock动态库的使用return; }SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);// 创建套接字AF_INET表示TCP/IP协议// SOCK_STREAM表示TCP连接,SOCK_DGRAM表示UDP连接// 第三个参数为零表示自动选择协议SOCKADDR_IN addrSrv;// 定义一个地址结构体的变量addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(6000);//htons把一个u_short类型从主机字节序转换为网络字节序bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); //将套接字绑定到本地的某个地址和端口上listen(sockSrv,5);//将指定的套接字设定为监听模式SOCKADDR_IN addrClient;int len=sizeof(SOCKADDR);SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len); //接受客户端发送的连接请求while(1) {char sBuf[100];printf("please input data\n");gets(sBuf);//sprintf(sBuf,"Welcome %s to here",inet_ntoa(addrClient.sin_addr));s(sockConn,sBuf,strlen(sBuf)+1,0);//通过一个已建立连接的套接字发送数据char recvBuf[100];recv(sockConn,recvBuf,100,0);//从一个已建立连接的套接字接收数据printf("%s\n",recvBuf);}closesocket(sockConn);}//添加ws2_32.lib:工程→设置→连接,添加该库(前面要有空格)1.2 Tcp client

  //Tcp client#include <Winsock2.h>#include <stdio.h>void main(){WORD wVersionRequested;// 指定准备加载的Winsock库版本WSADATA wsaData;// Winsock库版本信息的结构体wVersionRequested = MAKEWORD( 1, 1);int err = WSAStartup( wVersionRequested, &wsaData );// 加载套接字库if ( err != 0 ) { return;}if ( LOBYTE( wsaData.wVersion ) != 1

   HIBYTE( wsaData.wVersion ) != 1 ) {WSACleanup( );// 释放为该应用程序分配的资源,终止对WinSock动态库的使用return;}SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);// 创建套接字。AF_INET表示TCP/IP协议// SOCK_STREAM表示TCP连接,SOCK_DGRAM表示UDP连接//第三个参数为零表示自动选择协议SOCKADDR_IN addrSrv;//定义一个地址结构体的变量addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(6000);connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//向服务器发出连接请求while(1){char sBuf[100];printf("please input data\n");gets(sBuf);char recvBuf[100];recv(sockClient,recvBuf,100,0);//接收数据printf("%s\n",recvBuf);s(sockClient,sBuf,strlen(sBuf)+1,0);//发送数据}closesocket(sockClient);//关闭套接字WSACleanup();system("pause");}//添加ws2_32.lib:工程→设置→连接,添加该库(前面要有空格)

  2 数据报套接字(SOCK_DGRAM)及基于UDP的套接字编程

  数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(UserDatagramProtocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

  2.1 UDP server

  #include <Winsock2.h>#include <stdio.h>void main(){WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD( 1, 1);int err = WSAStartup( wVersionRequested, &wsaData );if ( err != 0 ){ return; }if ( LOBYTE( wsaData.wVersion ) != 1

  HIBYTE( wsaData.wVersion ) != 1 ) {WSACleanup( ); return; }SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(6000);bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));char sBuf[100],recvBuf[100],temp[200];SOCKADDR_IN addrClent;int len=sizeof(SOCKADDR);while(1) {recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClent,&len);if(q==recvBuf[0]) {sto(sockSrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClent,len);printf("chat !\n");break;}sprintf(temp,"%s:%s",inet_ntoa(addrClent.sin_addr),recvBuf);printf("%s\n",temp);printf("please input data:\n");gets(sBuf);sto(sockSrv,sBuf,strlen(sBuf)+1,0,(SOCKADDR*)&addrClent,len);}closesocket(sockSrv);WSACleanup();}2.2 UDP client

  #include <Winsock2.h>#include <stdio.h>void main(){WORD wVersionRequested;WSADATA wsaData;int err;wVersionRequested = MAKEWORD( 1, 1);err = WSAStartup( wVersionRequested, &wsaData );if ( err != 0 ){return;}if ( LOBYTE( wsaData.wVersion ) != 1

  HIBYTE( wsaData.wVersion ) != 1 ) {WSACleanup( );return;}SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(6000);char sBuf[100];char recvBuf[100];char temp[200];int len=sizeof(SOCKADDR);while(1){printf("please input data\n");gets(sBuf);sto(sockClient,sBuf,strlen(sBuf)+1,0,(SOCKADDR*)&addrSrv,len);recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv,&len);if(q==recvBuf[0]){sto(sockClient,"q",strlen("q")+1,0,(SOCKADDR*)&addrSrv,len);printf("chat !\n");break;}sprintf(temp,"%s:%s",inet_ntoa(addrSrv.sin_addr),recvBuf);printf("%s\n",temp);}closesocket(sockClient);WSACleanup();}

  3 原始套接字(SOCK_RAW)及编程

  流式套接字和数据报套接字统称为原始套接字与标准套接字。

  流式套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

  原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAWSOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAWSOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。

  使用SIO_RCVALL命令可以在原始套接字上设置网卡以混合模式工作,允许指定的套接字接受所有流经本机的IP数据包,如下面的实例:

  #include "winsock2.h"#include "iostream"using namespace std;#pragma comment(lib,"ws2_32.lib")#define DEFAULT_BUFLEN 65535#define DEFAULT_NAMELEN 512#if _MSC_VER > 1000#pragma once#if//#include "mstcpip.h"↓struct tcp_keepalive {u_long onoff;u_long keepalivetime;u_long keepaliveinterval;};// New WSAIoctl Options#define SIO_RCVALL _WSAIOW(IOC_VOR,1)#define SIO_RCVALL_MCAST _WSAIOW(IOC_VOR,2)#define SIO_RCVALL_IGMPMCAST _WSAIOW(IOC_VOR,3)#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VOR,4)#define SIO_ABSORB_RTRALERT _WSAIOW(IOC_VOR,5)#define SIO_UCAST_IF _WSAIOW(IOC_VOR,6)#define SIO_LIMIT_BROADCASTS _WSAIOW(IOC_VOR,7)#define SIO_INDEX_BIND _WSAIOW(IOC_VOR,8)#define SIO_INDEX_MCASTIF _WSAIOW(IOC_VOR,9)#define SIO_INDEX_ADD_MCAST _WSAIOW(IOC_VOR,10)#define SIO_INDEX_DEL_MCAST _WSAIOW(IOC_VOR,11)//#include "mstcpip.h"↑int main(){WSADATA wsaData;SOCKET SnifferSocket = INVALID_SOCKET;char recvbuf[DEFAULT_BUFLEN];int iResult;int recvbuflen = DEFAULT_BUFLEN;HOSTENT* local;char HostName[DEFAULT_NAMELEN];IN_ADDR addr;SOCKADDR_IN LocalAddr, RemoteAddr;int addrlen = sizeof(SOCKADDR_IN);int in = 0, i = 0;DWORD dwBufferLen[10];DWORD Optval = 1;DWORD dwBytesReturned = 0; //初始化套接字iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0){cout << "初始化失败:" << iResult << l;return 1;}//创建套接字SnifferSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);if (INVALID_SOCKET == SnifferSocket){cout << "创建套接字失败:" << WSAGetLastError() << l;WSACleanup();return 1;}//获取本机名称memset(HostName, 0, DEFAULT_NAMELEN);iResult = gethostname(HostName, sizeof(HostName));if (SOCKET_ERROR == iResult){cout << "获取本机名称失败:" << WSAGetLastError() << l;WSACleanup();return 1;}//获取本机IPlocal = gethostbyname(HostName);cout << "本机可用的IP地址有:" << l;if (NULL == local){cout << "获取IP失败:" << WSAGetLastError() << l;WSACleanup();return 1;}while (local->h_addr_list[i] != 0){addr.s_addr = *(u_long*)local->h_addr_list[i++];cout << "\t" << i <<":\t"<< inet_ntoa(addr) << l;}cout << "请选择捕获数据包待使用的接口号:";cin >> in;memset(&LocalAddr, 0, sizeof(LocalAddr));memcpy(&LocalAddr.sin_addr.S_un.S_addr, local->h_addr_list[in - 1], sizeof(LocalAddr.sin_addr.S_un.S_addr));LocalAddr.sin_family = AF_INET;LocalAddr.sin_port = 0;//绑定iResult = bind(SnifferSocket, (SOCKADDR*)&LocalAddr, sizeof(LocalAddr));if (SOCKET_ERROR == iResult){cout << "绑定失败:" << WSAGetLastError() << l;closesocket(SnifferSocket);WSACleanup();return 1;}cout << "成功绑定套接字和" << in << "号接口地址";//设置套接字接受命令iResult = WSAIoctl(SnifferSocket, SIO_RCVALL, &Optval, sizeof(Optval), &dwBufferLen, sizeof(dwBufferLen), &dwBytesReturned, NULL, NULL);if (SOCKET_ERROR == iResult){cout << "套接字设置失败:" << WSAGetLastError() << l;closesocket(SnifferSocket);WSACleanup();system("pause");return 1;}//开始接受数据cout << "开始接受数据" << l;do{//接受数据iResult = recvfrom(SnifferSocket, recvbuf, DEFAULT_BUFLEN, 0, (SOCKADDR*)&RemoteAddr, &addrlen);if (iResult > 0){cout << "接受来自" << inet_ntoa(RemoteAddr.sin_addr) << "的数据包," << "长度为" << iResult << l;}elsecout << "接受失败:" << WSAGetLastError() << l;} while (iResult > 0);{ } return 0;}/*本机可用的IP地址有: 1: 11.199.2.5 2: 192.168.2.129请选择捕获数据包待使用的接口号:2成功绑定套接字和2号接口地址开始接受数据*/-End-

标签: 网络空间安全