Windows编程技术:Socket通信(上)

admin 2023年7月31日09:39:22评论10 views字数 5781阅读19分16秒阅读模式

数据传输是木马必备技术之一。按传输协议来分为:自定义协议、FTP、HTTP、HTTPS等。其中,自定义协议是通过Socket传输的报文格式。


    Socket翻译成中文就是“套接字”的意思,所谓的Socket编程就是指用计算机语言通过编程来实现计算机之间的通信问题。Socket通信技术即就是两台联网或者多台联网的计算机之间的数据交换技术,这就涉及着通信端的协议等等问题。


    socket的典型应用就是Web服务器和浏览器:浏览器获取用户输入的URL,向服务器发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。

Windows编程技术:Socket通信(上)

Windows编程技术:Socket通信(上)


    Socket类型在计算机的世界里,有着许许多多的套接字,主要阐述第三种最常用的socket—Internet Socket。根据数据传输方式,可以将Internet Socket分成几种,在使用socket()创建网络连接时,必须告诉它是哪一种数据传输方式,简单来说就是数据传输的控制协议。


    第一种,基于TCP协议的面向连接的套接字!计算机领域称之为:流格式套接字(Stream Sockets)SOCK_STREAM是一种可靠的、双向的通信数据流。由于是基于TCP协议的,故而数据在传输中是准确无误的,是自带重发机制的。SOCK_STREAM有以下特点:数据传输是按照顺序进行的;数据传输的可靠的,准确无误的;数据的发送和接收是不同步的,存在着缓冲区;流格式套接字的典型应用就是HTTP协议,因为浏览器在加载进行解析时必须要数据完整准确。

Windows编程技术:Socket通信(上)

    第二种,基于UDP协议的无连接的套接字!计算机领域称之为:数据报格式套接字(Datagram Sockets)SOCK_DGRAM是一种高效率、高速度的、不可靠的通信数据流。其是基于UDP协议的,不用进行数据校验,只进行数据传输。SOCK_DGRAM有以下特点:每次传输数据大小有限制;追求高效率,高速度;传输的数据可能丢失或者损坏(小概率事件);数据的发送和接收是同步的,也就是存在数据边界;数据报格式套接字的典型应用就是QQ视频和语音,因为这两者的需求是高效率和低延时,同时在小概率的丢失数据不会影响整个数据的传输质量。


    第三种,基于IP协议的原始套接字!原始套接字(Raw Sockets),允许直接发送和接收IP数据包,而不需要任何特定的传输层格式。


这里介绍Socket的TCP和UDP通信;


一、TCP通信

1、函数介绍

1.1、socket()函数

int socket(int domain, int type, int protocol);

socket函数的三个参数分别为: 

domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。 
type:指定socket类型。常用的socket类型有,SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等 
protocol:就是指定协议。常用的协议有,IPPROTO_TCP、PPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。


1.2、bind()函数

bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数的三个参数分别为: 
sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。 
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。


1.3 listen()、connect()函数

如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。


1.4 accept()函数

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为客户端协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。


1.5 recv()、send()等函数

至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组: 
•read()/write() 
•recv()/send() 
•readv()/writev() 
•recvmsg()/sendmsg() 
•recvfrom()/sendto()


2、实现原理

    服务器端初始化Winsock环境后,便调用Socket函数创建流式套接字;然后对sockaddr_in结构体进行设置,设置服务器绑定的IP地址和端口等信息并调用bind函数来绑定;绑定成功后,就可以调用listen函数设置连接数量,并进行监听。直到有来自客户端的连接请求,服务器便调用accept函数接受连接请求,建立连接。这时,可以使用recv函数和send函数与客户端进行数据收发。通信结束后,关闭套接字,释放资源。

Windows编程技术:Socket通信(上)


3、源码

3.1 服务端

对服务端来说,需要先绑定端口并监听来自客户端的连接。流程为:初始化Winsock库环境,创建流式套接字,绑定服务器IP地址和端口进行监听,创建多线程接收通信数据。

// 绑定端口并监听 BOOL SocketBindAndListen(char *lpszIp, int iPort) { // 初始化 Winsock 库 WSADATA wsaData = { 0 }; WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建流式套接字 g_ServerSocket = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == g_ServerSocket) { return FALSE; } // 设置服务端地址和端口信息 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(iPort);addr.sin_addr.S_un.S_addr = inet_addr(lpszIp); // 绑定IP和端口 if (0 != bind(g_ServerSocket, (sockaddr *)(&addr), sizeof(addr))) { return FALSE; } // 设置监听 if (0 != listen(g_ServerSocket, 1)) { return FALSE; } // 创建接收数据多线程 CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)AcceptRecvMsg, NULL, NULL, NULL); return TRUE; }


在服务器绑定端口进行监听的过程中,若服务器端来自客户端的连接请求,则会立即接受连接请求同时接收通信数据。

// 接受连接请求 并 接收数据 void AcceptRecvMsg() { sockaddr_in addr = { 0 }; // 注意:该变量既是输入也是输出 int iLen = sizeof(addr); // 接受来自客户端的连接请求 g_ClientSocket = accept(g_ServerSocket, (sockaddr *)(&addr), &iLen); printf("accept a connection from client!n"); char szBuf[MAX_PATH] = { 0 }; while (TRUE) { // 接收数据 int iRet = recv(g_ClientSocket, szBuf, MAX_PATH, 0); if (0 >= iRet) { continue; } printf("[recv]%sn", szBuf); } }


在服务端成功接受来自客户端的连接请求后,服务端即可与客户端进行数据通信。

// 发送数据 void SendMsg(char *pszSend) { // 发送数据 send(g_ClientSocket, pszSend, (1 + lstrlen(pszSend)), 0); printf("[send]%sn", pszSend); }


3.2 客户端

客户端根据服务器IP地址以及监听端口发送连接请求,建立数据通信连接。流程为:初始化Winsock库环境,创建流式套接字,设置服务器IP地址和监听端口信息并发送连接请求,创建多线程接收通信数据。

// 连接到服务器

BOOL Connection(char *lpszServerIp, int iServerPort) { // 初始化 Winsock 库 WSADATA wsaData = { 0 }; WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建流式套接字 g_ClientSocket = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == g_ClientSocket) { return FALSE; } // 设置服务端地址和端口信息 sockaddr_in addr = { 0 }; addr.sin_family = AF_INET; addr.sin_port = htons(iServerPort); addr.sin_addr.S_un.S_addr = inet_addr(lpszServerIp); // 连接到服务器 if (0 != connect(g_ClientSocket, (sockaddr *)(&addr), sizeof(addr))) { return FALSE; } // 创建接收数据多线程 CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvMsg, NULL, NULL, NULL); return TRUE; }


在服务器接受连接请求后,客户端成功地与服务端建立通信连接。这时可以向服务器发送数据,同时也可以接收来自服务器的数据。

// 发送数据 void SendMsg(char *pszSend) { // 发送数据 send(g_ClientSocket, pszSend, (1 + lstrlen(pszSend)), 0); printf("[send]%sn", pszSend); }


既然与服务器成功建立了连接,那么它便可以与服务器通信,向服务器发送数据。

// 接收数据 void RecvMsg() { char szBuf[MAX_PATH] = { 0 }; while (TRUE) { // 接收数据 int iRet = recv(g_ClientSocket, szBuf, MAX_PATH, 0); if (0 >= iRet) { continue; } printf("[recv]%sn", szBuf); } }

4、测试验证

运行服务端程序,监听13579端口,然后运行客户端程序,连接上服务端,

Windows编程技术:Socket通信(上)

看下网络连接图,

Windows编程技术:Socket通信(上)


抓包观察下,

Windows编程技术:Socket通信(上)

很明显的“三次握手”。


下载地址:

链接:https://pan.baidu.com/s/14K6Ucj-nGDI27ZhoF27U_g

提取码:d7vn


感谢无糖学院导师戴华老师分享。

欢迎关注公众号MicroPest

Windows编程技术:Socket通信(上)

原文始发于微信公众号(无糖反网络犯罪研究中心):Windows编程技术:Socket通信(上)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年7月31日09:39:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows编程技术:Socket通信(上)https://cn-sec.com/archives/1068350.html

发表评论

匿名网友 填写信息