14.1 Socket 套接字编程入门

socket,接字,编程,入门 · 浏览次数 : 66

小编点评

**客户端通信流程** 1. 创建套接字 2. 连接到服务器 3. 建立连接 4. 发送数据 5. 关闭连接 **服务器通信流程** 1. 创建套接字 2. 填充通信结构体 3. 连接到客户端 4. 向客户端发送数据 5. 关闭连接 **初始化部分** * WSADATA WSAData:用于初始化 Winsock 二元数据结构体 * socket(): 创建套接字 * bind(): 指定本地地址和端口 * connect(): 连接到服务器 **发送数据** * send(): 发送数据 **关闭连接** *closesocket(): 关闭套接字

正文

Winsock是Windows操作系统上的套接字API,用于在网络上进行数据通信。套接字通信是一种允许应用程序在计算机网络上进行实时数据交换的技术。通过使用Windows提供的API,应用程序可以创建一个套接字来进行数据通信。这个套接字可以绑定到一个端口,以允许其他应用程序连接它。另外,Winsock可以使用TCP/IP、UDP等协议来完成不同类型的数据传输任务。在网络应用程序开发中,套接字通信可以帮助应用程序开发者实现客户端/服务端模型,并实现数据的可靠传输。

一般套接字通信需要经历,创建套接字(Socket),绑定(Bind),监听(Listen),接受(Accept),连接(Connect),发送数据(Send),接收数据(Receive),关闭(Close)等几个关键步骤,当读者需要使用网络通信时需引入winsock2.h头文件,并通过#pragma comment(lib,"ws2_32.lib")包含对应库,需要注意的是该头文件与windows.h头冲突,如果两者同时存在则会出现编译不通过的情况;

14.1.1 服务端通信

(1)WSAStartup(MAKEWORD(2, 0), &WSAData)

当读者需要使用套接字编程时,不论是服务端还是客户端都需要调用WSAStartup初始化套接字库,该函数接受两个参数传递,第一个参数一般默认会传递MAKEWORD(2, 0) 它是一个宏,用于将两个8位的字节合并成一个16位的字,在MAKEWORD(2, 0)中,括号内的数字分别代表高位字节(2)和低位字节(0),宏会将它们合并成一个16位的无符号short整型数据,即0000001000000000(二进制),表示Winsock的版本号为2.0。第二个参数WSADATA结构体,用于Winsock初始化时存储相关的信息,一般会在全局WSADATA WSAData;直接定义得到。

#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>

#pragma comment(lib,"ws2_32.lib")

// 定义结构体
WSADATA WSAData;

// 启动winsock中的WSAStartup()函数对Winsock DLL进行初始化
if (WSAStartup(MAKEWORD(2, 0), &WSAData) == SOCKET_ERROR)
{
    std::cout << "WSA动态库初始化失败" << std::endl;
    return 0;
}

(2)socket(AF_INET, SOCK_STREAM, 0)

通信的第二步则是调用Socket()函数,该函数是用于创建一个套接字的系统调用。在该函数中,给定三个参数,分别为地址族(Address Family)、套接字类型(Socket Type)和协议(Protocol),套接字在初始化并完成时会返回一个SOCKET类型的文件描述符句柄,此处我们将该句柄存储至server_socket变量内。AF_INET用于指定套接字地址族为IPv4类型,SOCK_STREAM则用于指定该套接字的类型为流式套接字,用于面向连接的可靠数据传输(TCP协议)。

// 服务进程创建套接字句柄(用于监听)
SOCKET server_socket;

// 调用socket()函数创建一个流套接字,参数(网络地址类型,套接字类型,网络协议)
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == ERROR)
{
  std::cout << "Socket 创建失败" << std::endl;
  WSACleanup();
  return 0;
}

(3)bind(server_socket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr))

套接字编程的第三步则是绑定,套接字的绑定需要调用bind()函数实现,该函数接受三个参数传递,第一个参数是socket()中创建的套接字文件描述符句柄,该参数用于指定针对哪一个套接字进行操作,第二个参数则是sockaddr_in类型的结构体,该结构体内用于指定需要绑定套接字的具体类型参数等信息,在如下代码中我们通过ServerAddr.sin_family = AF_INET;将套接字类型设置为了互联网域模式,通过ServerAddr.sin_port = htons(9999);指定了需要绑定的端口号,而ServerAddr.sin_addr.s_addr = inet_addr("0.0.0.0");则用于指定了要绑定本机的那个网口,一般而言如果读者需要在本机使用此处可填入127.0.0.1而如果侦听任意一个网口则可使用0.0.0.0,第三个参数则是传入结构体的长度,此处通过sizeof(ServerAddr)方法得到,最终将结构体ServerAddr直接填入绑定函数即可实现对网络套接字的绑定。

// 结构sockaddr_in用来标识TCP/IP协议下的地址,可强制转换为sockaddr结构
struct sockaddr_in ServerAddr;

// 字段sin_family必须设为AF_INET,表示该Socket处于Internet域
ServerAddr.sin_family = AF_INET;

// 字段sin_port用于指定服务端口,注意避免冲突
ServerAddr.sin_port = htons(9999);

// 字段sin_addr用于把一个IP地址保存为一个4字节,无符号长整型,根据不同用法还可表示本地或远程IP地址
// 该字段可以直接使用INADDR_ANY代表侦听所有地址,也可指定地址
ServerAddr.sin_addr.s_addr = inet_addr("0.0.0.0");

// 调用bind()函数将本地地址绑定到所创建的套接字上,以在网络上标识该套接字
if (bind(server_socket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
{
  std::cout << "绑定套接字失败" << std::endl;
  closesocket(server_socket);
  WSACleanup();
  return 0;
}

(4)listen(server_socket, 10)

当套接字被绑定后,接下来则是侦听套接字,通过调用listen()函数将套接字置入监听模式并准备接受连接请求,该函数需要传入两个参数,参数1为套接字套接字句柄,参数二为侦听套接字最大连接数,如果进入侦听状态则说明该套接字是等待连接状态,一旦服务器接受了连接,它可以使用返回的套接字对象与发起连接的客户端进行通信。

// 将 ServerAddr.sin_addr 网络字节序,转为本机侦听IP地址
char local_address[20];
inet_ntop(AF_INET, &ServerAddr.sin_addr, local_address, 16);
std::cout << "侦听本地地址: " << local_address << " 侦听本地端口: " << ntohs(ServerAddr.sin_port) << std::endl;

// 参数(已捆绑未连接的套接字描述字,正在等待连接的最大队列长度)
if (listen(server_socket, 10) == SOCKET_ERROR)
{
  std::cout << "侦听套接字失败" << std::endl;
  closesocket(server_socket);
  WSACleanup();
  return 0;
}

(5)accept(server_socket, (LPSOCKADDR)0, (int*)0)

当一个套接字进入侦听状态后则下一步是需要等待有客户端连接到本端,当服务器通过调用listen()函数开始监听连接请求时,客户端可以通过使用connect()函数尝试与服务器建立连接。一旦客户端发送连接请求,服务器将收到通知。然后服务器可以使用accept()函数接受连接请求并创建一个新的套接字对象,该对象可以用于与客户端进行通信。

accept() 函数通常在一个循环中使用,以便服务器可以在等待新连接时继续处理已连接的客户端。每次调用accept()函数时,如果有连接请求,则函数将阻塞直到一个连接请求被接受。一旦连接请求被接受,函数将返回一个新的套接字对象和客户端的地址信息。

在接受连接请求并创建新的套接字对象之后,服务器可以使用该对象与客户端进行通信。同时,服务器可以使用原始的server_socket套接字对象来等待更多的连接请求,以便能够接受更多的客户端连接。

如下的代码中当accept()接收到等待消息时,则会将该句柄保存至message_socket变量内,此时用户只需要向该指针中发送recv()或接收send()数据即可,此时套接字通信即可正式被建立起来。

// 数据接收缓冲区
SOCKET message_socket;
char buf[8192] = {0};
while (1)
{
  // 进入监听状态后,调用accept()函数接收客户端的连接请求,并把连接传给msgsock套接字
  // 原sock套接字继续监听其他客户机连接请求
  if ((message_socket = accept(server_socket, (LPSOCKADDR)0, (int*)0)) == INVALID_SOCKET)
  {
    continue;
  }

  // 初始化数据接收缓冲区
  memset(buf, 0, sizeof(buf));
   
  // 接收客户端发送过来的数据
  bool ref = recv(message_socket, buf, 8192, 0);
  if (ref != 0)
  {
    std::cout << "接收数据: " << buf << std::endl;
  }
  
  // 关闭子套接字
  closesocket(message_socket);
}

至此我们的服务端将被运行起来,需要注意的是服务端程序如果需要结束本次会话则需要手动调用closesocket(server_socket);关闭一个套接字句柄,当整个进程执行结束后读者还需要调用WSACleanup()终止对Winsock DLL的使用,并释放资源。

14.1.2 客户端通信

对于客户端通信而言其流程与服务端通信基本保持一致,该流程分别是,创建套接字,连接到服务器,建立连接,发送数据,关闭连接,对于初始化部分客户端通信与服务端没有任何区别,唯一的区别在于对于服务端而言一般是使用listen()函数侦听套接字,而对于客户端而言则是使用connect()函数连接到服务端,一旦连接建立成功,客户端可以通过向服务器发送数据来与服务器进行通信。

在调用connect(socket_addr)时,需要传递一个参数sockaddr。sockaddr 是一个结构体,包含了客户端与服务器的地址信息,包括其IP地址和端口号。在C/C++中,sockaddr 结构体通常被定义为sockaddr_in结构体,包含了IP地址和端口号等信息。如果连接建立成功,connect() 函数将返回 0。如果连接失败,则会返回一个错误代码,其中最常见的错误是连接超时或目标主机拒绝连接。

一旦连接建立成功,客户端可以使用新创建的套接字对象向服务器发送数据,并使用recv()函数从服务器接收数据。一般来说,在与服务器进行通信之前,客户端套接字需要使用bind()函数指定一个本地地址和端口,以确保数据可以正确地传输。

int main(int argc, char* argv[])
{
  char buf[8192] = { 0 };

  while (1)
  {
    std::cout << "发送数据: ";
    int inputLen = 0;
    memset(buf, 0, sizeof(buf));

    // 输入以回车键为结束标识
    while ((buf[inputLen++] = getchar()) != '\n'){ ; }

    // 初始化
    WSADATA WSAData;
    if (WSAStartup(MAKEWORD(2, 0), &WSAData) == SOCKET_ERROR)
    {
      continue;
    }

    // 创建套接字
    SOCKET client_socket;
    if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
    {
      WSACleanup();
      continue;
    }

    // 填充通信结构体
    struct sockaddr_in ClientAddr;
    ClientAddr.sin_family = AF_INET;
    ClientAddr.sin_port = htons(9999);
    ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 连接到服务端
    if (connect(client_socket, (LPSOCKADDR)&ClientAddr, sizeof(ClientAddr)) == SOCKET_ERROR)
    {
      closesocket(client_socket);
      WSACleanup();
      continue;
    }

    // 向服务端发送数据
    send(client_socket, buf, 8192, 0);

    // 关闭套接字
    closesocket(client_socket);
    WSACleanup();
  }

  return 0;
}

读者可自行运行上述程序,启动服务端与客户端,并发送测试数据观察变化,当发送数据后读者应该能看到如下图所示的提示信息;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/83cf7258.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

与14.1 Socket 套接字编程入门相似的内容:

14.1 Socket 套接字编程入门

Winsock是Windows操作系统上的套接字API,用于在网络上进行数据通信。套接字通信是一种允许应用程序在计算机网络上进行实时数据交换的技术。通过使用Windows提供的API,应用程序可以创建一个套接字来进行数据通信。这个套接字可以绑定到一个端口,以允许其他应用程序连接它。另外,Winsoc...

14.3 Socket 字符串分块传输

首先为什么要实行分块传输字符串,一般而言`Socket`套接字最长发送的字节数为`8192`字节,如果发送的字节超出了此范围则后续部分会被自动截断,此时将字符串进行分块传输将显得格外重要,分块传输的关键在于封装实现一个字符串切割函数,将特定缓冲区内的字串动态切割成一个个小的子块,当切割结束后会得到该数据块的个数,此时通过套接字将个数发送至服务端此时服务端在依次循环接收数据包直到接收完所有数据包之后

14.2 Socket 反向远程命令行

在本节,我们将继续深入探讨套接字通信技术,并介绍一种常见的用法,实现反向远程命令执行功能。对于安全从业者而言,经常需要在远程主机上执行命令并获取执行结果。本节将介绍如何利用 `_popen()` 函数来启动命令行进程,并将输出通过套接字发送回服务端,从而实现远程命令执行的功能。在实现反向远程命令执行时,我们可以使用 `_popen(buf, "r")` 函数来执行特定的命令,并将其输出重定向到一个

14.4 Socket 双向数据通信

所谓双向数据传输指的是客户端与服务端之间可以无差异的实现数据交互,此类功能实现的核心原理是通过创建`CreateThread()`函数多线程分别接收和发送数据包,这样一旦套接字被建立则两者都可以异步发送消息,本章将实现简单的双向交互功能。首先我们需要封装两个函数,这里`RecvFunction`函数用于接收数据,`SendFunction`函数则用于发送数据,这两段代码在服务端与客户端之间是一致的

14.5 Socket 应用组播通信

组播通信是一种基于UDP协议的网络通信方式,它允许发送方将消息同时传递给多个接收方。在组播通信中,发送方和接收方都会加入一个共同的组播组,这个组播组对应一个特定的IP地址,所有加入该组播组的主机都能够接收到发送方发送的消息。组播通信可以有效地减少网络流量和网络负载,因为在传统的点对点通信方式下,每个消息都需要单独传输到每个接收方,而在组播通信中,每个消息只需要传输一次,就可以同时传递给多个接收方。

[转帖]Linux内核 TCP/IP、Socket参数调优

文章系转载,便于整理和分类,原文地址:http://www.360doc.com/content/14/0606/16/3300331_384326124.shtml Doc1: /proc/sys/net目录 所有的TCP/IP参数都位于/proc/sys/net目录下(请注意,对/proc/sy

踩坑记录:windows11下使用 VS2022 和 PCL1.14.1 配置点云开发环境

闲话不多说,具体在windows下下载PCL与解压pcl可以看https://www.yuque.com/huangzhongqing/pcl/这位大佬的文章,那我就具体说一下踩过点坑: 踩坑点1: 按照大佬的文章的步骤进行解压与下载,我的PCL环境下在了K盘中,但是最后不知怎么的我的openni2

[转帖]Sql Server中通过sql命令获取cpu占用及产生锁的sql

https://www.jb51.net/article/266255.htm 这篇文章主要介绍了Sql Server中通过sql命令获取cpu占用及产生锁的sql,需要的朋友可以参考下 获取SQLSERVER中产生锁的SQL语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1

简单算法题-完美数

对于一个 正整数,如果它和除了它自身以外的所有 正因子 之和相等,我们称它为 「完美数」。 输入:num = 28 输出:true 解释:28 = 1 + 2 + 4 + 7 + 14 1, 2, 4, 7, 和 14 是 28 的所有正因子。 /** * @param {number} num *

1.14 手工插入ShellCode反弹

PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等,本次的目标是手工修改或增加节区,并给特定可执行程序插入一段`ShellCode`代码,实现程序运行自动反弹一个Shell会话。