Cortar
// 加这个宏定义也可以规避sock2和window.h的重编译问题
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
//winsock2一定要在windows前,否则会有宏定义重编译问题。sock2是新库,windows是老库。
#include<WinSock2.h>
#include<Windows.h>
#include<iostream>
//加入静态链接库,否则会出现问题:无法解析的外部符号 __imp__WSAStartup@8
//也可以在属性页、连接器、输入、附加依赖项中输入ws_32.lib
#pragma comment(lib,"ws2_32.lib")
//枚举定义命令,方便判断
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGOUT_RESULT,
CMD_LOGOUT,
CMD_ERROR
};
//消息结构体
struct DataHeader {
short dataLength;//数据长度
short cmd;//命令:接收的命令,服务器处理后反馈数据。
};
struct Login :public DataHeader {
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char PassWord[32];
};
struct LoginResult :public DataHeader {
LoginResult()
{
dataLength = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
result = 0;
}
int result;
};
struct LogoutResult :public DataHeader {
LogoutResult()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
result = 0;
}
int result;
};
struct Logout :public DataHeader {
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGOUT;
}
char userName[32];
};
int main()
{
//版本号
WORD ver = MAKEWORD(2, 2);
//数据指针
WSADATA dat;
//windows下socket的启动函数,启动socket环境
WSAStartup(ver, &dat);
//开始编写网络环境
//1.建立一个socket。套接字inet6为最新,数据类型:数据流,网络类型tcp,udp
//定义返回类型,用来储存socket数据(uint型)
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2 bind 绑定用于接受客户端连接的网络端口
//sock地址的结构体
sockaddr_in _sin = {
};
//网络类型
_sin.sin_family = AF_INET;
//端口号htons(host to net unsigned short)主机到网络的字节序的转换.用来寻找服务程序
_sin.sin_port = htons(4567);
//定义IP地址,主机不只一个IP地址。cmd中ipconfig可以查看。
//如果纯内网127就可以。还可以屏蔽外网的访问。
//S_un是一个联合体。
//INADDR_ANY不限定网路地址。
//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
_sin.sin_addr.S_un.S_addr = INADDR_ANY;
//sock名,地址,地址长度。强制转换地址类型sockaddr结构体里的数据类型不利于我们直接填写,
//addr_in的结构体与普通addr结构体相同,而且有常见的数据类型方便填写。
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))//判断是否绑定成功
{
//如果端口号被别的程序占用,就是绑定失败
printf("ERROR,绑定用于接受客户端连接的网络端口失败\n");
}
else
{
printf("绑定网络端口成功\n");
}
//3.listen监听网络端口
//最大等待5人来连接
if (SOCKET_ERROR == listen(_sock, 5))
{
printf("ERROR,监听失败\n");
}
else
{
printf("监听成功\n");
}
//4 accept 等待客户端连接
sockaddr_in clientAddr = {
};
//定义client返回的socket数据的长度
int nAddrlen = sizeof(sockaddr_in);
//定义一个无效的sock地址用来进行判断是否接收到客服端
SOCKET _clientSock = INVALID_ATOM;
//如果固定长读就在在循环外计算出发送数据的长度,节省计算时间
//int bufSize = sizeof(msgBuf);
//循环重复执行
//新用户加入
//返回一个客户端的socket网络地址
_clientSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrlen);
//判断
if (INVALID_SOCKET == _clientSock)
{
printf("错误,接收到无效客户端SOCKET\n");
}
else {
//inet_ntoa 把客户端转换成可读的ip地址
//inet_ntoa 过时了,需要定义一个宏define _WINSOCK_DEPRECATED_NO_WARNINGS
printf("新客户端加入:IP=%s \n", inet_ntoa(clientAddr.sin_addr));
}
//接收缓冲区
char _recvBuf[128] = {
};
while (true)
{
DataHeader header = {
};
//5.接收客户端数据
//第一次收了header的数据包,只剩下CMD的数据长度,指针移动到数据包包体位置
int nLen = recv(_clientSock, (char*)&header, sizeof(DataHeader), 0);
if (nLen <= 0)
{
printf("客户端已经退出,任务结束。");
break;
}
printf("收到命令:%d ,数据长度:%d\n", header.cmd, header.dataLength);
//6.处理请求
switch (header.cmd)
{
case CMD_LOGIN:
{
//结构体初始化
Login login = {
};
//因为数据包头以被接收,只剩下包体数据的长度,所以要做数据偏移,让数据指针对齐
//login+dataheader意思是从login数据包的头+dataheader长度的地方开始读取,读login完整结构体-包头的长度
recv(_clientSock, (char*)&login + sizeof(DataHeader), sizeof(Login) - sizeof(DataHeader), 0);
//判断用户名和密码是否正确
std::cout << login.userName << " " << login.PassWord << std::endl;
char name[32] = "李逵";
char pw[32] = "110";
if (0 == (strcmp(name, login.userName) | strcmp(pw, login.PassWord)))
{
//这个数据包的长度为32+32+2+2=68
printf("输入正确!,name is %s , password is %s ,数据长度:%d \n", login.userName, login.PassWord, login.dataLength);
//short 的长度为2
std::cout << "sizeof cmd " << sizeof(login.cmd) << " " << "sizeof datalength" << sizeof(login.dataLength) << std::endl;
}
else
printf("重新输入密码\n");
LoginResult result;
//7.发送请求
//一定要先发送消息头,然后在发送消息体,这样才是一个完整的报文
//传过来的header带有cmd,根据cmd选择参数命令。所以直接返回接收的header就行
send(_clientSock, (char*)&result, sizeof(LoginResult), 0);
}
break;
case CMD_LOGOUT:
{
Logout logout = {
};
recv(_clientSock, (char*)&logout + sizeof(DataHeader), sizeof(Logout) - sizeof(DataHeader), 0);
printf("登出!,name is %s ,数据长度:%d \n", logout.userName, logout.dataLength);
LogoutResult result;
send(_clientSock, (char*)&result, sizeof(LogoutResult), 0);
}
break;
//定义默认的错误信息
default:
header.cmd = CMD_ERROR;
header.dataLength = 0;
send(_clientSock, (char*)&header, sizeof(DataHeader), 0);
}
}
//8. 关闭自身的套节字
closesocket(_sock);
//结束编写网路环境
//结束socket
WSACleanup();
printf("任务结束。");
getchar();
return 0;
}
Cliente
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<WinSock2.h>
#include<Windows.h>
#include<iostream>
#pragma comment(lib,"ws2_32.lib")
//传输的数据结构,最简单的数据包
//每个函数的类型必须一样,而且在客户端和服务端传输和接收顺序一致,也就是内存对齐
//long类型的在64位编译器下就是64位,而32位编译器下就是32位,
//所以需要考虑平台和系统,关注是否内存对齐
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGOUT_RESULT,
CMD_LOGOUT,
CMD_ERROR
};
//消息结构体
struct DataHeader {
short dataLength;//数据长度
short cmd;//命令:接收的命令,服务器处理后反馈数据。
};
//定义登录的数据结构
//使用继承方式,是报文更完整,不容易出错,也不用每次单独定义dataheader
struct Login :public DataHeader {
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char PassWord[32];
};
struct LoginResult:public DataHeader {
LoginResult()
{
dataLength = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
int result;
};
struct LogoutResult:public DataHeader {
LogoutResult()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
}
int result;
};
struct Logout:public DataHeader {
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGOUT;
}
char userName[32];
};
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//1.建立一个socket
SOCKET _sock= socket(AF_INET,SOCK_STREAM,0);
//多做判断才能创建一个健壮的代码
if (INVALID_SOCKET == _sock)
{
printf("ERROR,建立socket失败\n");
}
else
{
printf("建立socket成功\n");
}
//2.链接服务器connet
//这样写快速初始化结构体,要不这样就是乱的
sockaddr_in _sin = {
};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
//error C4996: 'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//sizeof()传入类型更安全,不应该是变量,防止传入的是一个指针
int ret=connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
if (SOCKET_ERROR == ret)
{
printf("ERROR,链接服务器失败\n");
}
else
{
printf("连接服务器成功\n");
}
while (true)
{
//3.输入请求命令
//要跟服务器端字符串缓存匹配,否则容易溢出
char cmdBuf[128] = {
};
std::cin >> cmdBuf;
//4.处理请求
if (0 == strcmp(cmdBuf, "exit"))
{
printf("收到exit,任务结束.\n");
break;
}
else if (0 == strcmp(cmdBuf, "login"))
{
Login login ;
//这样赋值不行
/*login.userName = "李逵";
login.PassWord = "110";*/
strcpy_s(login.userName, "李逵");
strcpy_s(login.PassWord, "110");
//5.向服务器发送请求命令
send(_sock, (const char*)&login, sizeof(Login), 0);
LoginResult loginRet = {
};
recv(_sock,(char*)&loginRet, sizeof(LoginResult), 0);
printf("inResutlt is : %d.\n", loginRet.result);
}
else if (0 == strcmp(cmdBuf, "logout"))
{
Logout logout ;
strcpy_s(logout.userName,"李逵");
send(_sock, (const char*)&logout, sizeof(Logout), 0);
LoginResult logoutRet = {
};
recv(_sock, (char*)&logoutRet, sizeof(LoginResult), 0);
printf("outResult is %d. \n", logoutRet.result);
}
else
{
printf("The input information can't be accepted.\n");
}
}
//4.关闭套件字closesocket
closesocket(_sock);
//清除Windows socket环境
WSACleanup();
getchar();
return 0;
}