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

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

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("服务器地址");//本机服务器地址

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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值