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 套接字编程入门的更多相关文章

  1. socket套接字编程 HTTP协议

    socket套接字编程  套接字介绍  1. 套接字 : 实现网络编程进行数据传输的一种技术手段  2. Python实现套接字编程:import  socket  3. 套接字分类 >流式套接 ...

  2. socket套接字编程(1)——基本函数

    TCP交互流程: 服务器:1. 创建socket:2. 绑定socket和端口号:3. 监听端口号:4. 接收来自客户端的连接请求:5. 从socket中读取字符:6. 关闭socket. 客户端:1 ...

  3. linux网络环境下socket套接字编程(UDP文件传输)

    今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中, ...

  4. linux网络编程-(socket套接字编程UDP传输)

    今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中, ...

  5. socket 套接字编程

    今日内容 socket 套接字编程 简易服务端与客户端代码实现 通信循环 黏包现象(TCP协议) 报头制作.struct 模块.封装形式 内容详细 一.socket 套接字编程 实现一款能够进行数据交 ...

  6. linux 套接字编程入门--Hello World

    下述代码是linux套接字编程的入门代码.分为服务端和客户端源码. 服务端代码的主要流程是绑定ip地址和端口号建立套接字,等待客户端发起访问.接受客户端请求之后,向客户端发送字符串"hell ...

  7. Linux之socket套接字编程20160704

    介绍套接字之前,我们先看一下传输层的协议TCP与UDP: TCP协议与UDP协议的区别 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UD ...

  8. 基于TCP协议的socket套接字编程

    目录 一.什么是Scoket 二.套接字发展史及分类 2.1 基于文件类型的套接字家族 2.2 基于网络类型的套接字家族 三.套接字工作流程 3.1 服务端套接字函数 3.2 客户端套接字函数 3.3 ...

  9. 基于TCP连接的socket套接字编程

    基于TCP协议的套接字编程(简单) 服务端 import socket server = socket.socket() server.bind( ('127.0.0.1', 9999) ) serv ...

  10. day31 socket套接字编程

    为什么要有套接字编程? 在上节课的学习中,我们学习了OSI七层协议,但是如果每次进行编程时我们都需要一层一层的将各种协议使用在我们的程序中,这样编写程序实在是太麻烦了,所以为了让程序的编写更加的简单, ...

随机推荐

  1. 组合数学知识整理_USTC-IAT期末复习版(已完结)

    组合数学知识整理_USTC-IAT期末复习版(已完结) 第一章 排列与组合 第二章 递推关系与母函数 第三章 容斥原理与鸽巢原理 第四章 polya定理

  2. Matlab学习1

    Matlab 数据类型 数字 字符和字符串 矩阵 元胞数组 结构体 清空环境变量及命令 cls % 清除Command Windows中的所有命令 clear all % 清除Workspace*中的 ...

  3. JAVA 环境搭建(java 8为例)

    JAVA 环境搭建 下载JDK(java 8为例) JDK下载地址:直达 JDK镜像网站: 编程宝库 java jdk镜像 安装JDK(java 8为例) 双击启动下载的exe文件 单击下一步 可以选 ...

  4. python中的数据容器

    第六章:Python数据容器 数据容器入门 什么是数据容器 一种可以容纳多份数据的数据类型,容纳的每一份数据称之为1个元素,每一个元素,可以是任意类型的数据,如字符串.数字.布尔等. 根据特点的不同分 ...

  5. Stable Diffusion修复老照片-图生图

    修复老照片的意义就不多说了,相信大家都明白,这里直接开讲方法. 1.原理 这个方法需要一个真实模型,以便让修复的照片看起来比较真实,我这里选择:realisticVisionV20,大家有更好的给我推 ...

  6. linux中利用crontab设置定时任务

    linux中利用crontab设置定时任务: # 每隔1个小时运行一次命令,四个*分别代表分,时,日,月,周 # crontab -l查看:crontab -e编辑 */1 * * * * /root ...

  7. Java理论(一)

    什么是java Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因 此Java语言具有功能强大和简单易用两个特征.Java语言作为静态面向 ...

  8. The language server needs at least PHP 7.1 installed. Version found: 7.0.10

    解决方案: 打开VS Code 文件 -> 首选项 -> 设置 -> 在 settings.json 中编辑 在 settings.json 中,加入: "php.exec ...

  9. 解锁Spring组件扫描的新视角

    本文分享自华为云社区<Spring高手之路10--解锁Spring组件扫描的新视角>,作者: 砖业洋__. 首先,我们将探讨一些Spring框架中IOC(Inversion of Cont ...

  10. 微信的 h5 支付和 jsapi 支付

    目录 申请商户号 申请商户证书 设置APIv3密钥 下载 SDK 开发包 下载平台证书 关联 AppID 账号 开通 H5 支付 H5支付流程 开通 JSAPI 支付 JSAPI 支付流程 通用微信支 ...