14.1 Socket 套接字编程入门

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

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 许可协议。转载请注明出处!

目录
相关文章
|
23天前
|
网络协议 Java
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
这篇文章全面讲解了基于Socket的TCP网络编程,包括Socket基本概念、TCP编程步骤、客户端和服务端的通信过程,并通过具体代码示例展示了客户端与服务端之间的数据通信。同时,还提供了多个案例分析,如客户端发送信息给服务端、客户端发送文件给服务端以及服务端保存文件并返回确认信息给客户端的场景。
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
|
2月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
27 4
|
2月前
|
网络协议 Python
网络世界的建筑师:Python Socket编程基础与进阶,构建你的网络帝国!
【7月更文挑战第26天】在网络的数字宇宙中,Python Socket编程是开启网络世界大门的钥匙。本指南将引领你从基础到实战,成为网络世界的建筑师。
49 2
|
2月前
|
网络协议 程序员 视频直播
|
2月前
|
开发者 Python
Python Socket编程:不只是基础,更有进阶秘籍,让你的网络应用飞起来!
【7月更文挑战第25天】在网络应用蓬勃发展的数字时代,Python凭借其简洁的语法和强大的库支持成为开发高效应用的首选。本文通过实时聊天室案例,介绍了Python Socket编程的基础与进阶技巧,包括服务器与客户端的建立、数据交换等基础篇内容,以及使用多线程和异步IO提升性能的进阶篇。基础示例展示了服务器端监听连接请求、接收转发消息,客户端连接服务器并收发消息的过程。进阶部分讨论了如何利用Python的`threading`模块和`asyncio`库来处理多客户端连接,提高应用的并发处理能力和响应速度。掌握这些技能,能使开发者在网络编程领域更加游刃有余,构建出高性能的应用程序。
22 3
|
2月前
|
消息中间件 网络协议 网络安全
Python Socket编程:打造你的专属网络通道,基础篇与进阶篇一网打尽!
【7月更文挑战第26天】在网络编程领域,Python以简洁语法和强大库支持成为构建应用的首选。Socket编程为核心,实现计算机间的数据交换。
50 1
|
2月前
|
安全 网络协议 网络安全
Python Socket编程大揭秘:从菜鸟到黑客的进阶之路,你准备好了吗?
【7月更文挑战第27天】Python Socket编程是网络开发的关键技能,它开启从简单数据传输到复杂应用的大门。Socket作为网络通信的基础,通过Python的`socket`模块可轻松实现跨网通信。
33 0
|
2月前
|
网络协议 Python
告别网络编程迷雾!Python Socket编程基础与实战,让你秒变网络达人!
【7月更文挑战第27天】在网络编程的广阔天地中,Socket编程常被视为一道难关。但用Python这把钥匙,我们可以轻松入门。Socket作为网络通信的基石,在Python中通过`socket`模块封装了底层细节,简化了开发过程。以下是一个基本的TCP服务器与客户端的示例,展示了如何建立连接、收发数据及关闭连接。为了应对实际场景中的并发需求,我们还可以借助多线程技术来提升服务器处理能力。掌握了这些基础知识后,你将逐步揭开网络编程的神秘面纱,踏上编程高手之路!
29 0
|
2月前
|
网络协议 Python
深度剖析Python Socket:从入门到精通,网络编程不再是难题!
【7月更文挑战第27天】在Python中,Socket编程是网络通信的核心。本文从Socket基础概念入手,介绍其作为网络通信端点的作用,并区分TCP(面向连接)与UDP(无连接)。通过示例代码展示如何创建TCP服务器及客户端:服务器监听12345端口,接收并回显客户端消息;客户端则连接服务器并发送消息,接收服务器回应。代码涵盖socket创建、连接管理及数据收发等关键步骤,并强调异常处理与数据编码的重要性。掌握这些基础知识,即可轻松开展网络编程项目。
56 0
|
2月前
|
网络协议 开发者 Python
颠覆传统!Python Socket编程新思维,基础与进阶并重,打造卓越网络能力!
【7月更文挑战第25天】在数字时代,网络通信至关重要,Python的Socket编程简化了这一复杂领域,使初学者也能轻松上手。通过Python的`socket`模块,我们能快速搭建服务器与客户端,实现数据交换。示例代码展示了如何创建、绑定及监听Socket,以及收发消息。掌握基础后,可利用asyncio库探索异步编程,提升通信效率,处理多连接。Python的Socket编程,结合传统与现代技术,助力开发者在网络通信领域取得非凡成就。
39 0