14.1 Socket 套接字编程入门

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/156455.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于web的酒店客房管理系统

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 会员信息管理 客房信息管理 收藏客房管理 用户入住管理 客房清扫管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施…

Vue3 + Nodejs 实战 ,文件上传项目--实现图片上传

目录 技术栈 1. 项目搭建前期工作(不算太详细) 前端 后端 2.配置基本的路由和静态页面 3.完成图片上传的页面&#xff08;imageUp&#xff09; 静态页面搭建 上传图片的接口 js逻辑 4.编写上传图片的接口 5.测试效果 结语 博客主页&#xff1a;専心_前端,javascript,mys…

springboot json在线转换为实体类

json字符串映射到一个实体类。 这里有一个在线转换工具 http://www.bejson.com/json2javapojo/new/ 截图如下&#xff1a;

【总结】kubernates 插件工具总结

在此记录工作中用到的关于 kubernates 的插件小工具&#xff0c;以防以后忘记 1、能显示 kubernates 所处上下文的插件 kube-ps1 github 地址&#xff1a; https://github.com/jonmosco/kube-ps1 效果 2、能方便切换 kubernates 上下文的插件 kubecm github 地址&#xff1…

Excel 规范录入数据

文章目录 录入日期录入百分比 快捷键&#xff1a; tab&#xff1a;向右切换单元格 enter&#xff1a;向下切换行 shift tab&#xff1a;向左切换单元格 shiftenter&#xff1a;向上切换行 录入日期 输入今天的日期的快捷键&#xff1a;Ctrl ; 输入当时的时间的快捷键&a…

从零开始探索C语言(十一)----共用体和位域

文章目录 1. 共用体1.1 定义共用体1.2 访问共用体成员 2. 位域2.1 位域声明2.2 位域的定义和位域变量的说明2.3 位域的使用2.4 位域小结 1. 共用体 共用体是一种特殊的数据类型&#xff0c;允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体&#…

windows的最佳选项卡式窗口管理器软件TidyTabs

下载&#xff1a; https://jmj.cc/s/z1t3kt?pucodeS1wc https://download.csdn.net/download/mo3408/88420433 TidyTabs是一款Windows应用程序&#xff0c;它可以将多个打开的窗口整理成一个选项卡式的界面&#xff0c;使得用户可以更加方便地切换和管理不同的窗口。 Tidy…

实施运维02

一.网线制作 1.所需材料 网线&#xff0c;水晶头&#xff0c;网线钳&#xff0c;水晶头, 路由器或者网络测速仪 网线钳 网线制作标准 T568A标准&#xff08;交叉线&#xff09;&#xff1a;适用链接场合&#xff1a;电脑-电脑、交换机-交换机、集线器-集线器 接线顺序&…

gogs和drone如何配合使用

上篇介绍了drone和gogs安装方法&#xff0c;这次介绍这两个如何使用&#xff0c;此篇文章主要介绍在物理机上进行发布。 此处用到的java项目地址&#xff1a;https://gitee.com/huningfei/demo-test 一 配置gogs 1.1 在Gogs中配置指定仓库的”.drone.yml“文件 1.2 ssh-drone…

Linux-文件管理命令

绝对路径&#xff1a;从根目录开始描述的路径 pwd输入即为绝对路径&#xff0c; 开头一定是“/”&#xff0c;因为一定是从根目录开始走 相对路径&#xff1a;从当前路径开始描述的路径&#xff0c;开头不一定是“/”&#xff0c;因为不一定是从根目录开始走的 .:是当前目录 。…

【UnityUGUI】复合控件详解,你还记得多少

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;UI_…

【JVM】初步认识Java虚拟机

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 JVM 一、初识JVM1.1 什么是JVM1.2 JVM的功能…

ARM-day9作业

main.c: #include "uart.h"#include "key_it.h"int main(){char c;char *s;uart4_init(); //串口初始化//中断初始化key_it_config();key3_it_config();//完成GPIO相关初始化all_led_init();//风扇初始化fs_init();//蜂鸣器初始化fmq_init();while(1){…

SpringBoot-黑马程序员-学习笔记(四)

40.业务层Service的快速开发 1.写业务层接口并且继承IService类&#xff0c;泛型是对应的实体类 2.写实现类&#xff0c;除了和之前一样的实现Service类外&#xff0c;还要继承ServiceImpl类&#xff0c;泛型有2个&#xff0c;第一个是对应的Dao层&#xff0c;第2个是对应的实…

c++视觉---中值滤波处理

中值滤波&#xff08;Median Filter&#xff09;是一种常用的非线性平滑滤波方法&#xff0c;用于去除图像中的噪声。它不像线性滤波&#xff08;如均值滤波或高斯滤波&#xff09;那样使用权重来计算平均值或加权平均值&#xff0c;而是选择滤波窗口内的像素值中的中间值作为输…

LeetCode862 和至少为k的最短子数组

题目&#xff1a; 解析&#xff1a; 1、先构造前缀和数组 2、单调队列存放滑动窗口&#xff0c;目的求Sj-Si >k的情况下&#xff0c;窗口最小。 代码&#xff1a; class Solution {public int shortestSubarray(int[] nums, int k) {int n nums.length;long[] sums new …

读书笔记-《ON JAVA 中文版》-摘要26[第二十三章 注解]

文章目录 第二十三章 注解1. 基本语法1.1 基本语法1.2 定义注解1.3 元注解 2. 编写注解处理器2.1 编写注解处理器2.2 注解元素2.3 默认值限制 3. 使用javac处理注解4. 基于注解的单元测试5. 本章小结 第二十三章 注解 注解&#xff08;也被称为元数据&#xff09;为我们在代码…

Edge浏览器下载文件被保存为 .crdownload 文件的问题小记

问题 近期使用Edge浏览器下载文件时&#xff0c;文件都被保存为 .crdownload 格式的文件了&#xff0c;不确定从哪个版本开始的。除非下载未完成导致文件不完整&#xff0c;否则不会被保存为 .crdownload 格式的文件&#xff1b;实际上文件已完成了下载&#xff0c;且手工修改…

天锐绿盾加密软件——企业数据防泄密-CAD图纸、文档、源代码加密管理系统@德人合科技

天锐绿盾是一款专门为企业提供数据防泄密和文档加密管理的软件。该软件通过加密技术保护企业的核心数据&#xff0c;防止数据泄露和侵权行为&#xff0c;同时提供了全方位的文档加密管理系统&#xff0c;实现了对企业数据的安全保障和有效管理。 PC访问地址&#xff1a; isite…

基于保密信息学科平台系统

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 教师信息管理 学科动态管理 文献资源管理 征订目录管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步…