网络基础-实现在Windows系统下的socket环境地址通信

news2024/12/23 23:33:59

实现客户端和服务端的数据交互

1.写所要实现功能的声明(封装在tcpsocket.h文件)

#ifndef TCPSOCKET_H
#define TCPSOCKET_H

//在Windows下进行网络编程,需要引入Windows的socket库
#include <winsock2.h>
//做一些预编译工作,将一些静态库进行初始化,这样才能使用一些函数接口
#pragma comment(lib,"ws2_32.lib")

//开始准备用到的函数,在头文件做声明
//初始化socket环境
bool init_socket();
//使用完后,要关闭网络库链接资源
bool close_socket();
//创建服务端的socket对象,来实现具体的API
SOCKET serverSocket();
//创建客户端的socket对象,来实现具体的API
SOCKET clientSocket(const char* ip);//给客户端增加一个参数,代表它要连接的服务端的ip地址

#endif

2.写功能的具体实现(tcpsocket.c文件)

#include "tcpsocket.h"
#include <iostream>
using namespace std;
#pragma warning(disable:4996)

//接下来实现头文件中的函数
//初始化socket环境
bool init_socket()
{
	//先准备一个结构体WSADATA,它位于头文件中的<winsock2.h>,用于存放socket的一些初始化信息。包括了版本号,最大并发数等内容,直接使用默认值即可
	WSADATA wasdata;
	//启动socket环境,直接使用WSAStartup函数即可,它有几个参数:
	//1、协议版本,需要使用MAKEWORD函数来传参
	//2、socket初始化信息存放的位置,也就是刚刚定义的wasdata的地址
	//返回值:成功会返回0,失败我们打印相应的错误码
	if (WSAStartup(MAKEWORD(2, 2), &wasdata) != 0)
	{
		cout << "socket初始化失败,错误码:" << WSAGetLastError() << endl;//WSAGetLastError()可以输出当前的错误码
		return false;
	}
	else
	{
		return true;
	}
}

//使用完后,要关闭网络库链接资源
bool close_socket()
{
	//调用WSACleapup()即可完成网络链接资源的释放
	//返回值:成功会返回0,否则我们打印错误码
	if (WSACleanup()!=0)
	{
		cout << "socket链接关闭失败,错误码:" << WSAGetLastError() << endl;//WSAGetLastError()可以输出当前的错误码
		return false;
	}
	else
	{
		return true;
	}
}

//创建服务端的socket对象,来实现具体的API
SOCKET serverSocket()
{
	//1.创建socket对象
	//调用类库提供的socket方法,来创建对象,有三个参数:
	//第一参数,指定地址家族,对于我们常用的ipv4来说,是通过ip:port的方式来解析的,这个地址家族叫做AF_INET
	//第二个参数,指定socket的类型:我们使用的是流式套接字SOCK_STREAM(用于tcp通信),另外一种叫做数据报套接字SOCK_DGRAM(用于udp通信)
	//第三个参数,直接指定成0,让它自动选择跟地址家族匹配的相关协议。
	//返回值:成功会返回一个socket对象,失败会返回一个常量:INVALID_SOCKET
	SOCKET soc = socket(AF_INET, SOCK_STREAM, 0);
	if (soc== INVALID_SOCKET)
	{
		cout << "服务端socket创建失败,错误码:" << WSAGetLastError() << endl;//WSAGetLastError()可以输出当前的错误码
		return soc;
	}

	//2.给socket对象绑定相关信息用于通信,包括地址族、IP地址、端口号
	//直接使用绑定函数bind(),这个函数有三个参数,其中一个参数是个结构体类型,需要先定义出来,这个结构体类型是sockaddr
	//sockadd是通过另外一个结构体sockaddr_in转换而来的,所以我们要先通过sockaddr_in来绑定相关的信息:地址族、IP地址、端口号
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;//绑定地址家族,要跟上面socket函数的地址族保持一致
	addr.sin_port = htons(8888);//绑定端口号,但是端口号需要通过htons函数完成本地到网络数据顺序的转换
	//网络传输和本地存储的字节顺序刚好是相反,网络传输的顺序叫做大端字节序,本地存储的字节顺序叫做小端字节序。
	//例如:存储的数据是0x12345678,共4个字节,如果是大端字节序将0x78存入最高位,将0x12存入最高。而小端字节序就正常了。
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//绑定本地也就是自己的IP地址,也需要通过inet_addr完成大小端字节序的转换
	//inet_addr函数可能会碰到一个警告信息,建议我们使用新函数,但是为了兼容性,还是坚持使用它。警告可以通过加一个行预处理命令屏蔽掉:#pragma warning(disable:4996)

	//接下来使用bind函数完成绑定,有三个参数:
	//第一个参数,我们要绑定的socket对象,也就是上面创建的soc
	//第二个参数,是要绑定的结构体,是sockaddr*类型,需要将sockaddr_in类型强转
	//第三个参数,代表addr结构体的大小,字节为单位
	//返回值:失败会常量SOCKET_ERROR,打印错误吗
	if (bind(soc,(sockaddr*) & addr,sizeof(addr)) == SOCKET_ERROR)
	{
		cout << "服务端socket绑定失败,错误码:" << WSAGetLastError() << endl;
	}

	//3.服务器选择一个端口,启动对这个端口的监听,等待客户端的连接
	//使用listen函数即可,有两个参数:
	//第一个参数,是socket对象soc
	//第二个参数,监听队列的最大长度,一般取10个就够了
	//返回值:失败会返回跟bind函数一样的返回值:SOCKET_ERROR,我们打印错误码
	if (listen(soc,10)== SOCKET_ERROR)
	{
		cout << "服务端监听失败,错误码:" << WSAGetLastError() << endl;
	}

	return soc;
}

//创建客户端的socket对象,来实现具体的API
SOCKET clientSocket(const char* ip)
{
	//1.创建socket对象
	SOCKET soc = socket(AF_INET, SOCK_STREAM, 0);
	if (soc == INVALID_SOCKET)
	{
		cout << "客户端socket创建失败,错误码:" << WSAGetLastError() << endl;//WSAGetLastError()可以输出当前的错误码
		return soc;
	}

	//2.给socket对象绑定通信需要的信息,跟上面类似
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;//绑定地址家族,要跟上面socket函数的地址族保持一致
	addr.sin_port = htons(8888);//绑定服务器的端口号
	addr.sin_addr.S_un.S_addr = inet_addr(ip);//参数ip告诉客户端去连那个服务器

	//3.主动连接服务器
	//直接使用connect函数即可,这个函数的用法跟服务端的bind是一样
	if (connect(soc, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
	{
		cout << "客户端连接失败,错误码:" << WSAGetLastError() << endl;
	}
	return soc;
}

3.实现服务端的功能(myserver.c文件)

#include "../SocketDemo/tcpsocket.h" //这里需要引用正确的路径位置
#include <iostream>
using namespace std;

//接下来将业务代码写在main中
int main()
{
    //1.初始化socket
    init_socket();

    //2.创建socket对象
    SOCKET serversocket = serverSocket();
    cout << "服务端已就位,等待连接..." << endl;

    //3.如果收到了客户端的连接请求,需要通过accept函数来接收这个请求,它有三个参数:
    //第一个,已经处于监听状态的socket,也就是上面的serversocket
    //第二个和第三个,都填NULL,代表两个地址,让它自动指定
    //返回值,成功会返回一个已经建立了连接的socket对象,失败会返回INVALID_SOCKET
    SOCKET accept_soc = accept(serversocket, NULL, NULL);
    if (accept_soc == INVALID_SOCKET)
    {
        cout << "服务端accept失败,错误码:" << WSAGetLastError() << endl;//WSAGetLastError()可以输出当前的错误码
    }

    //4.这里通信已经建立好了,可以进行数据传输了
    //数据传输主要通过两个函数来完成:一个是发送send(),一个是接收recv()
    //收发之前,需要准备好消息存放的地方,我们使用字符数组来存储
    char buf[512] = { '\0' };

    //接下来就是写收发的逻辑,作为服务端来说,先收消息,再回复消息
    //为了能一直聊天,需要一个死循环
    while (true)
    {
        //还需要考虑一个问题,每次聊天的内容都放到buf中,所以每次新聊天之前,需要清空之前的内容,不然的话,新旧内容会混到一起
        for (int i = 0; i < strlen(buf); i++)
        {
            buf[i] = '\0';
        }
        //使用recv()收消息
        //第一个参数,建立连接的socket对象,第二个是消息存放的地址,第三个是最大接收数,第四个是个标志位,填0即可
        if (recv(accept_soc,buf,511,0)>0)//recv的返回值如果大于零,证明收到了消息,此时消息就存入buf中了
        {
            cout << "客户端说:" << buf << endl;
        }
        else
        {
            cout << "接收失败,没收到任何消息,错误码:" << WSAGetLastError() << endl;
            break;
        }

        //回消息给客户端
        //使用send()函数,它的用法跟recv()是一样的
        cout << "服务端给客户端回消息:" << endl;
        //回消息之前也要清空buf
        for (int i = 0; i < strlen(buf); i++)
        {
            buf[i] = '\0';
        }
        //接下来键盘输入消息,保存到buf中
        cin.getline(buf, 511);
        if (send(accept_soc, buf, 511, 0) == SOCKET_ERROR)//失败打印错误吗
        {
            cout << "服务端发送失败,错误码:" << WSAGetLastError() << endl;
            break;
        }
    }

    //清理工作,先清理socket对象,再断开系统资源连接
    closesocket(serversocket);
    closesocket(accept_soc);
    close_socket();

    return 0;
}

4.实现客户端的功能(myclient.c文件)

#include "tcpsocket.h"
#include <iostream>
using namespace std;

int main()
{
    //1.初始化socket
    init_socket();

    //2.创建socket对象
    SOCKET clientsocket = clientSocket("127.0.0.1");//根据项目配置来决定ip,目前客户端和服务端都是本地

    //3.发消息给服务端,客户端是先发再收
    //收发之前,需要准备好消息存放的地方,我们使用字符数组来存储
    char buf[512] = { '\0' };
    //为了能一直聊天,需要一个死循环
    while (true)
    {
        //还需要考虑一个问题,每次聊天的内容都放到buf中,所以每次新聊天之前,需要清空之前的内容,不然的话,新旧内容会混到一起
        for (int i = 0; i < strlen(buf); i++)
        {
            buf[i] = '\0';
        }

        //使用send()函数
        cout << "客户端给服务端发消息:" << endl;
        //接下来键盘输入消息,保存到buf中
        cin.getline(buf, 511);
        if (send(clientsocket, buf, 511, 0) == SOCKET_ERROR)//失败打印错误吗
        {
            cout << "客户端端发送失败,错误码:" << WSAGetLastError() << endl;
            break;
        }

        //使用recv()收消息
        for (int i = 0; i < strlen(buf); i++)
        {
            buf[i] = '\0';
        }
        //第一个参数,建立连接的socket对象,第二个是消息存放的地址,第三个是最大接收数,第四个是个标志位,填0即可
        if (recv(clientsocket, buf, 511, 0) > 0)//recv的返回值如果大于零,证明收到了消息,此时消息就存入buf中了
        {
            cout << "服务端说:" << buf << endl;
        }
        else
        {
            cout << "接收失败,没收到任何消息,错误码:" << WSAGetLastError() << endl;
            break;
        }

        
    }

    //清理工作,先清理socket对象,再断开系统资源连接
    closesocket(clientsocket);
    close_socket();
    return 0;
}

5.编译环境

在vs中设置 解决方案 的属性为多个项目启动:

设置好之后开始调式运行项目:

6.实现本地电脑和其他电脑通信(无线局域网适配器 WLAN:ipv4地址)

客户端访问服务器

修改内容:将客户端的访问地址修改为服务器的地址。(在myclient.c里面修改)

    //1. 初始化
	init_socket();

	//创建socket对象
	SOCKET clientsocket = clientSocket("服务器的地址");//写入服务器的IP

服务器将地址改为自己的本机地址(在tcpsocket.c里面修改)

addr.sin_addr.S_un.S_addr = inet_addr("服务器地址");//本机服务器地址

修改完成之后,两台电脑单独运行自己所要执行的服务,服务器端要优先运行于客户端。(注意:服务器端要关闭防火墙)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatis结果集复杂映射超详细版(一对多关系映射)

目录 1.一对多关系映射 1.1创建两个表&#xff1a;goods表与goods_class表 1.2xml文件中两部分&#xff1a;与(存放SQL语句)1.3数据库中&#xff1a;测试SQL语句&#xff0c;涉及到的知识点&#xff1a;左连接 1.一对多关系映射 1.1创建两个表&#xff1a;goods表与goods_c…

C++对C的扩充(8.28)

1.使用C手动封装一个顺序表&#xff0c;包括成员数组1个&#xff0c;成员变量n个 代码&#xff1a; #include <iostream>using namespace std;//类型重命名 using datatype int; #define MAX 30struct seqList { private: //私有权限datatype *data; //相当于 …

【项目源码】终于有人将打字游戏和编程英语结合起来啦!编程初学者的福音

Hello&#xff01;各位彦祖&#xff0c;亦菲们&#xff01;又是美好的一天&#xff01;今天给大家分享一个Java项目源码&#xff1a;Java打字游戏项目源码&#xff01; 看到这里&#xff0c;你可能会说&#xff01; 一个破打字游戏有什么可神气的&#xff01;&#xff01;&…

【自由能系列(中级)】状态与动作的协同机制解析 ——从马尔可夫毯到大脑功能的全方位剖析

状态与动作的协同机制解析 ——从马尔可夫毯到大脑功能的全方位剖析 Synergistic Mechanism of States and Actions —— A Comprehensive Analysis from Markov Blanket to Brain Function 核心结论&#xff1a; 中文总结&#xff1a; 系统将状态划分为内部状态和隐藏或外…

Flutter中的Key

在Flutter 中&#xff0c;Key 是 几乎所有 widget 都具有的属性。为什么 widget 具有 Key 呢&#xff1f;Key的作用是什么&#xff1f; 什么是 Key Key是Widget、Element 和 SemanticNodes 的标识符。 Key 是Widget、Element 和 SemanticNodes的唯一标识。例如对于 Widget 在 …

MyBatis的学习————下篇

目录 一、动态SQL 简介 1、if标签 2、where标签 3、trim标签 4、choose、when、otherwise 5、foreach 5.1、批量删除 5.2、批量添加 6、sql标签 二、MyBatis的缓存 1、一级缓存 2、二级缓存 3、二级缓存的相关配置 4、MyBatis缓存查询的顺序 5、 第三方缓存EHCac…

如何在Windows 11上关闭无响应的应用程序?这里有详细步骤

序言 无响应的应用程序令人沮丧,但更糟糕的是这些应用程序拒绝关闭。如果你发现自己处于这种情况,我们有几种方法可以帮助你强制关闭Windows 11 PC上的这些应用程序。让我们找出可用的解决方案。 使用键盘快捷键结束程序 关闭无响应应用程序的最简单方法是使用Windows键盘…

DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛-task2

DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛 YOLO(You Only Look Once)上分心得分享 YOLO(You Only Look Once) YOLO算的上是近几年最火的目标检测模型了&#xff0c;被广泛的应用在工业、学术等领域。 YOLOv1&#xff08;You Only Look Once 第一版&#xff09;于 2…

基于麒麟信安操作系统的光伏发电功率预测系统完成大规模部署建设

麒麟信安操作系统&#xff0c;作为行业数智化建设的安全根基&#xff0c;为电力业务系统提供了稳定可靠的底层平台&#xff0c;在全球能源结构转型大潮中扮演着至关重要的角色。某光伏电站项目中&#xff0c;基于麒麟信安操作系统的光伏发电功率预测系统完成大规模部署建设&…

c#如何加密exe程序防止反编译附软件

1. 先说软件&#xff0c;使用的软件是Dotfuscator&#xff0c;下载地址如下&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/6f2e785c003f2. 软件使用方法&#xff0c;打开软件&#xff0c;选择Create New Project 3. 找到input&#xff0c;把你需要加密的文件导入 4.…

k8s项目的发布

目录 三种发布方式 1.蓝绿发布 2.金丝雀发布&#xff08;灰度发布&#xff09; 实验&#xff1a;k8s实现金丝雀发布 3.滚动发布&#xff08;默认形式&#xff09; 因为应用升级以及新旧业务切换&#xff0c;所以在这个过程当中如何保证对外的服务正常是一个非常重要的问题…

手把手教你如何使用Python连接MySQL数据

数据库编程是在应用程序中与数据库交互和管理数据的关键部分。MySQL是一种流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;在Python中进行MySQL数据库编程相对容易。 本文介绍如何使用Python进行MySQL数据库编程&#xff0c;包括连接数据库、执行SQL查询…

高频面试题:SpringMVC的执行流程

SpringMVC一直以来都是面试中的重点&#xff0c;尽管随着近年来springboot和微服务的广泛流行&#xff0c;关于对springMVC的考察比重略有下降&#xff0c;但依然是面试中的重点&#xff0c;也需要我们对其有一个比较清楚和全面的认识。 如果将java的发展史中重要的组件进行排…

备忘录模式 详解

备忘录模式 简介: 保存一个对象的某个状态&#xff0c;以便在适当的时候恢复对象, 允许在不破坏封装性的前提下&#xff0c;捕获和恢复对象的内部状态。 场景: 很多地方都用到了备忘录模式, 比如网络消息的序列化和反序列化, 数据的本地保存与加载等, 最简单的json的dump和loa…

全能与专精:探索AI模型的未来之路

AI模型&#xff1a;追求全能还是专精&#xff1f; 近日&#xff0c;OpenAI预计在秋季推出代号为“草莓”的新AI。从专注于数学问题到处理主观营销策略&#xff0c;"草莓"模型展现出惊人的多样性。而这种全能型 AI 是否代表了未来趋势&#xff1f;相比专攻于某一领域…

OpenAI 将于今年秋天推出新的先进“Strawberry草莓”生成式人工智能产品

今年秋季&#xff0c;OpenAI将推出一款备受瞩目的新型生成式人工智能产品——“草莓”。据悉&#xff0c;这款名为“草莓”的AI程序将带来一系列创新突破&#xff0c;它将大幅提升人工智能处理复杂数学题、执行战略任务以及深入探索各种主题的能力&#xff0c;而这一切无需依赖…

前景堪忧?SaaS巨头Salesforce,25年辉煌后能否继续领跑市场?

最近&#xff0c;时常听到有人说Salesforce失去了活力&#xff0c;这或许是对整个生态系统的普遍感受。多年来&#xff0c;Salesforce一直保持着巨大的发展势头&#xff0c;通过收购、创新和建立良好的合作伙伴关系已发展成为云计算行业巨头。在经历了近25年创纪录的增长和创新…

记Spring HTTP Invoker远程调用的使用(二)基于Servlet方式,配置servlet映射url-pattern实现

目录 前言 一、概念 二、代码实现 1. 服务端实现 2. 客户端实现 前言 本篇接上一篇记Spring HTTP Invoker远程调用的使用&#xff08;一&#xff09;基于Url映射方式&#xff0c;DispatcherServlet统一处理实现-CSDN博客https://blog.csdn.net/u011529483/article/details/141…

论文解读:Prompt-aligned Gradient for Prompt Tuning

摘要 得益于CLIP等大型预训练的视觉语言模型VLM&#xff0c;我们可以通过离散的提示设计构建Zero-shot分类器&#xff0c;例如&#xff0c;利用图像与提示语句" a photo of a [ CLASS ] "之间的相似度&#xff0c;可以获得图像属于某个类别的置信度分数。此外&#…

【Material UI】Select组件的Customization详解

文章目录 一、Customization的基本概念1. 什么是Customization&#xff1f;2. 为什么要定制化Select组件&#xff1f; 二、定制化InputBase组件1. 定制化InputBase的步骤2. 定制化Select组件3. NativeSelect的定制化 三、Customization的最佳实践1. 使用标准变体&#xff08;St…