对于非网络应用,也就是本地进程,可以通过进程PID来唯一标识,但对网络中的网络进程来说是行不通的。TCP/IP协议族可以帮助我们解决网络进程的唯一标识问题,网络层的“IP地址”是唯一标识网络中的主机,而传输层的“协议 端口”是唯一标识主机中的网络应用(进程)。这样,利用三元组(IP地址、协议、端口)构成套接字(socket),就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其他进程进行交互。
套接字(Socket),是支持TCP/IP的 网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
Socket原意是“插座”。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
Socket可以看成在两个程序进行通讯连接中的一个端点,是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定与网络驱动建立关系。此后,应用程序送给Socket的数据,由Socket交给网络驱动程序向网络上发送出去。计算机从网络上收到与该Socket绑定IP地址和端口号相关的数据后,由网络驱动程序交给Socket,应用程序便可从该Socket中提取接收到的数据,网络应用程序就是这样通过Socket进行数据的发送与接收的。
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 sendBuf[100]; printf("please input data\n"); gets(sendBuf); //sprintf(sendBuf,"Welcome %s to here",inet_ntoa(addrClient.sin_addr)); send(sockConn,sendBuf,strlen(sendBuf) 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 sendBuf[100]; printf("please input data\n"); gets(sendBuf); char recvBuf[100]; recv(sockClient,recvBuf,100,0); //接收数据 printf("%s\n",recvBuf); send(sockClient,sendBuf,strlen(sendBuf) 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 sendBuf[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]) { sendto(sockSrv,"q",strlen("q") 1,0,(SOCKADDR*)&addrClent,len); printf("chat end!\n"); break; } sprintf(temp,"%s:%s",inet_ntoa(addrClent.sin_addr),recvBuf); printf("%s\n",temp); printf("please input data:\n"); gets(sendBuf); sendto(sockSrv,sendBuf,strlen(sendBuf) 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 sendBuf[100]; char recvBuf[100]; char temp[200]; int len=sizeof(SOCKADDR); while(1) { printf("please input data\n"); gets(sendBuf); sendto(sockClient,sendBuf,strlen(sendBuf) 1,0,(SOCKADDR*)&addrSrv, len); recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv, &len); if('q'==recvBuf[0]) { sendto(sockClient,"q",strlen("q") 1,0,(SOCKADDR*)&addrSrv,len); printf("chat end!\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 #endif //#include "mstcpip.h"↓ struct tcp_keepalive { u_long onoff; u_long keepalivetime; u_long keepaliveinterval; }; // New WSAIoctl Options #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1) #define SIO_RCVALL_MCAST _WSAIOW(IOC_VENDOR,2) #define SIO_RCVALL_IGMPMCAST _WSAIOW(IOC_VENDOR,3) #define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) #define SIO_ABSORB_RTRALERT _WSAIOW(IOC_VENDOR,5) #define SIO_UCAST_IF _WSAIOW(IOC_VENDOR,6) #define SIO_LIMIT_BROADCASTS _WSAIOW(IOC_VENDOR,7) #define SIO_INDEX_BIND _WSAIOW(IOC_VENDOR,8) #define SIO_INDEX_MCASTIF _WSAIOW(IOC_VENDOR,9) #define SIO_INDEX_ADD_MCAST _WSAIOW(IOC_VENDOR,10) #define SIO_INDEX_DEL_MCAST _WSAIOW(IOC_VENDOR,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 << endl; return 1; } //创建套接字 SnifferSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP); if (INVALID_SOCKET == SnifferSocket) { cout << "创建套接字失败:" << WSAGetLastError() << endl; WSACleanup(); return 1; } //获取本机名称 memset(HostName, 0, DEFAULT_NAMELEN); iResult = gethostname(HostName, sizeof(HostName)); if (SOCKET_ERROR == iResult) { cout << "获取本机名称失败:" << WSAGetLastError() << endl; WSACleanup(); return 1; } //获取本机IP local = gethostbyname(HostName); cout << "本机可用的IP地址有:" << endl; if (NULL == local) { cout << "获取IP失败:" << WSAGetLastError() << endl; 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) << endl; } 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() << endl; 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() << endl; closesocket(SnifferSocket); WSACleanup(); system("pause"); return 1; } //开始接受数据 cout << "开始接受数据" << endl; do { //接受数据 iResult = recvfrom(SnifferSocket, recvbuf, DEFAULT_BUFLEN, 0, (SOCKADDR*)&RemoteAddr, &addrlen); if (iResult > 0) { cout << "接受来自" << inet_ntoa(RemoteAddr.sin_addr) << "的数据包," << "长度为" << iResult << endl; } else cout << "接受失败:" << WSAGetLastError() << endl; } while (iResult > 0); { } return 0; } /* 本机可用的IP地址有: 1: 11.199.2.5 2: 192.168.2.129 请选择捕获数据包待使用的接口号:2 成功绑定套接字和2号接口地址开始接受数据 */
-End-
,