实现客户端和服务端的数据交互
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("服务器地址");//本机服务器地址
修改完成之后,两台电脑单独运行自己所要执行的服务,服务器端要优先运行于客户端。(注意:服务器端要关闭防火墙)