1、将client.cpp和service.cpp放到linux上
出现问题,所以这里使用linux下的头文件
1、通过一个宏来判断是win还是linux
#ifdef _WIN32
#include<windows.h>
#include<WinSock2.h>
#else
#include<unistd.h> //unix std 前面表示unix操作系统,后面表示正常的std
#include<arpa/inet.h>
#endif
再次编译一下,发现socket没有定义
定义socket
Linux系统中,没有对SOCKET类型进行定义。但其实SOCKET类型就是简单的无符号整形变量。
#define SOCKET int;
同样的,包括INVALID_SOCKET、SOCKET_ERROR
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
上面这些在win上都是这样定义的
修改后为
此外,在linux看来,socket就是普通文件,所以直接通过close关掉
关掉Windows Socket启动和结束环境的代码
下面还有几个需要加上判定
修改sockaddr_in
其他问题
这里在linux中要添加string.h的头,但是下面还是报错,所以要么只使用一个头文件
要么使用两个头文件 <string.h>
对于这个弄了好久还是不行,那么只能返回到不安全的strcpy了。
修改对应的ip地址
修改后的代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
#include<WinSock2.h>
#else
#include<unistd.h> //unix std 这里使用unix的api
#include<arpa/inet.h> //对应上面的第二个 使用unix的socket的函数定义
#include<cstring>
#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif
#include<string>
#include<iostream>
#include <thread>
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGOUT_RESULT,
CMD_ERROR,
CMD_NEWUSERJOIN,
};
//消息头
struct DataHeader
{
short dataLength; //数据长度 32767字节
short cmd;
};
struct Login : public DataHeader
{
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char Password[32];
};
struct Logout :public DataHeader
{
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGINOUT;
}
char userName[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 NewUserJoin :public DataHeader
{
NewUserJoin()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_NEWUSERJOIN;
result = 0;
}
int sockId;
int result;
};
int processor(SOCKET _sock)
{
char* szRecv = new char[1024];
//5 首先接收数据包头
int nlen = recv(_sock, szRecv, sizeof(DataHeader), 0); //接受客户端的数据 第一个参数应该是客户端的socket对象
if (nlen <= 0)
{
//客户端退出
cout << "客户端:Socket = " << _sock << " 与服务器断开连接,任务结束" << endl;
return -1;
}
DataHeader* header = (DataHeader*)szRecv;
switch (header->cmd)
{
case CMD_NEWUSERJOIN:
{
NewUserJoin _userJoin;
recv(_sock, (char*)&_userJoin + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
cout << "收到服务器消息: CMD_NEWUSERJOIN:" << _userJoin.sockId << endl;
}break;
case CMD_LOGIN_RESULT:
{
LoginResult _lgRes;
recv(_sock, (char*)&_lgRes + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
cout << "收到服务器消息: CMD_LOGIN_RESULT:" << _lgRes.result << endl;
}break;
case CMD_LOGOUT_RESULT:
{
LogoutResult _lgRes;
recv(_sock, (char*)&_lgRes + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
cout << "收到服务器消息: CMD_LOGINOUT_RESULT:" << _lgRes.result << endl;
}break;
default:
{
header->cmd = CMD_ERROR;
header->dataLength = 0;
send(_sock, (char*)&header, sizeof(DataHeader), 0);
}
break;
}
return 0;
}
bool g_bRun = true;
void cmdThread(SOCKET _sock)
{
while (true)
{
// 3 输入请求命令
char cmdBuf[128] = {};
cout << "输入命令: ";
cin >> cmdBuf;
// 4 处理请求
if (strcmp(cmdBuf, "exit") == 0)
{
cout << "退出cmdThread线程" << endl;
g_bRun = false;
return;
}
else if (0 == strcmp(cmdBuf, "login"))
{
Login _login;
#ifdef _WIN32
strcpy_s(_login.userName, "Evila");
strcpy_s(_login.Password, "Evila_Password");
#else
strcpy(_login.userName, "Evila");
strcpy(_login.Password, "Evila_Password");
#endif
// 5 向服务器发送请求命令
send(_sock, (const char*)&_login, _login.dataLength, 0);
}
else if (0 == strcmp(cmdBuf, "logout"))
{
Logout _logout;
#ifdef _WIN32
strcpy_s(_logout.userName, "Evila");
#else
strcpy(_logout.userName, "Evila");
#endif
//5 向服务器发送请求命令
send(_sock, (const char*)&_logout, _logout.dataLength, 0);
}
else
{
cout << "不受支持的命令" << endl;
}
}
return;
}
int main()
{
#ifdef _WIN32
//启动 windows socket 2.x 环境
WORD versionCode = MAKEWORD(2, 2); //创建一个版本号
WSADATA data;
WSAStartup(versionCode, &data); //启动Socket网络API的函数
#endif ///
//(1) 用Socket API建立简易的TCP客户端
// 1. 建立一个Socket 下面第三个参数不需要指定
SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0); // ipv4 面向字节流的 tcp协议
if (INVALID_SOCKET == _sock)
{
cout << "错误,建立socket失败" << endl;
}
else
{
cout << "成功建立客户端socket" << endl;
}
// 2. 连接服务器 connect
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567); //端口号 host to net sockaddr_in中port是USHORT类型
//网络中port是 unsigend short类型 因此需要Htons进行转换
//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //服务器绑定的IP地址 127.0.0.1是本地地址
#ifdef _WIN32
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
#else
_sin.sin_addr.s_addr = inet_addr("127.0.0.1");
#endif
int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
if (SOCKET_ERROR == ret)
{
cout << "错误,connect失败" << endl;
}
else
{
cout << "成功,connect 成功" << endl;
}
//3 接收服务器数据 resv
char recvBuf[256] = {};
//启动线程
std::thread t1(cmdThread, _sock);
t1.detach();
while (g_bRun)
{
fd_set fdReads;
FD_ZERO(&fdReads);
FD_SET(_sock, &fdReads);
timeval t = { 1,0 };
int ret = select(_sock + 1, &fdReads, NULL, NULL, &t);
if (ret < 0)
{
cout << "select任务结束" << endl;
break;
}
if (FD_ISSET(_sock, &fdReads)) //如果_sock在fdRead里面,表明有需求等待处理
{
FD_CLR(_sock, &fdReads);
if (processor(_sock) == -1)
{
cout << "Select任务已结束2" << endl;
break;
}
}
}
#ifdef _WIN32
// 6. 关闭socket
closesocket(_sock);
// 清除Windows socket环境
WSACleanup();
#else
close(_sock);
#endif
getchar();//防止一闪而过
return 0;
}
服务端的移植
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#ifdef _WIN32
#include<Windows.h>
#include<WinSock2.h>
#else
#include<unistd.h> //unix std 这里使用unix的api
#include<arpa/inet.h> //对应上面的第二个 使用unix的socket的函数定义
#include<cstring>
#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif
#include<iostream>
#include<string>
#include<vector>
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库
enum CMD //消息枚举
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGOUT_RESULT,
CMD_ERROR,
CMD_NEWUSERJOIN,
};
//消息头
struct DataHeader
{
short dataLength; //数据长度 32767字节
short cmd;
};
struct Login : public DataHeader
{
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char Password[32];
};
struct Logout :public DataHeader
{
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGINOUT;
}
char userName[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 NewUserJoin :public DataHeader
{
NewUserJoin()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_NEWUSERJOIN;
result = 0;
}
int sockId;
int result;
};
int processor(SOCKET _cSocket)
{
//使用一个缓冲区接收数据 暂定最大收发1024个字节 后续会改进大文件的传输
char* szRecv = new char[1024];
//5 首先接收数据包头
int nlen = (int)recv(_cSocket, szRecv, sizeof(DataHeader), 0); //接受客户端的数据 第一个参数应该是客户端的socket对象
if (nlen <= 0)
{
//客户端退出
cout << "客户端已退出,任务结束" << endl;
return -1;
}
//用一个指针指向这个头部
DataHeader* header = (DataHeader*)szRecv;
switch (header->cmd)
{
case CMD_LOGIN:
{
Login* _login;
//读取Header->dataLength的数据长度 将数据继续读入saRecv这个块里面
recv(_cSocket, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
_login = (Login*)szRecv;
cout << "收到命令:CMD_LOGIN" << " 数据长度 = " << header->dataLength << " UserName = " << _login->userName << " Password = " << _login->Password << endl;
//忽略了判断用户名密码是否正确的过程
LoginResult _loginres;
_loginres.result = 200;
send(_cSocket, (char*)&_loginres, sizeof(LoginResult), 0);
}break;
case CMD_LOGINOUT:
{
Logout* _logout;
recv(_cSocket, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
_logout = (Logout*)szRecv;
cout << "收到命令:CMD_LOGOUT" << " 数据长度 = " << header->dataLength << " UserName = " << _logout->userName << endl;
LogoutResult _logoutres;
_logoutres.result = 200;
send(_cSocket, (char*)&_logoutres, sizeof(LogoutResult), 0);
}break;
default:
{
header->cmd = CMD_ERROR;
header->dataLength = 0;
send(_cSocket, (char*)&header, sizeof(DataHeader), 0);
}
break;
}
}
std::vector<SOCKET>g_clinets;
int main()
{
#ifdef _WIN32
//启动 windows socket 2.x 环境
WORD versionCode = MAKEWORD(2, 2); //创建一个版本号
WSADATA data;
WSAStartup(versionCode, &data); //启动Socket网络API的函数
#endif ///
//(1) 用Socket API建立简易的TCP服务端
// 1. 建立一个Socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // ipv4 面向字节流的 tcp协议
// 2. 绑定接受客户端连接的端口 bind
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567); //端口号 host to net sockaddr_in中port是USHORT类型
//网络中port是 unsigend short类型 因此需要Htons进行转换
//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //服务器绑定的IP地址 127.0.0.1是本地地址
#ifdef _WIN32
_sin.sin_addr.S_un.S_addr = INADDR_ANY; //不限定访问该服务端的IP
#else
_sin.sin_addr.s_addr = INADDR_ANY;
#endif
if (bind(_sock, (sockaddr*)&_sin, sizeof(_sin)) == SOCKET_ERROR) //sockaddr 不利于编码
{
cout << "ERROR: 绑定用于接受客户端连接的网络端口失败..." << endl;
}
else
{
cout << "SUCCESS: 绑定端口成功..." << endl;
}
// 3. 监听网络端口 listen
if (listen(_sock, 5) == SOCKET_ERROR)//第二个参数 backbag 最大允许连接数量
{
cout << "ERROR: 监听用于接受客户端连接的网络端口失败..." << endl;
}
else
{
cout << "SUCCESS: 监听端口成功..." << endl;
}
fd_set fdRead;
fd_set fdWrite;
fd_set fdExpect;
while (true)
{
FD_ZERO(&fdRead); //清空fd集合的数据
FD_ZERO(&fdWrite);
FD_ZERO(&fdExpect);
//这个宏的功能是 将服务端的_sock 放到fdRead这个集合中
//当socket在listen状态,如果已经接收一个连接请求,这个socket会被标记为可读,例如一个accept会确保不会阻塞的完成
//对于其他的socket,可读性意味着队列中的数据适合读,当调用recv后不会阻塞。
FD_SET(_sock, &fdRead); //将服务端的socket放入可读列表,确保accept不阻塞
FD_SET(_sock, &fdWrite);
FD_SET(_sock, &fdExpect);
SOCKET maxSock = _sock;
for (size_t n = 0; n < g_clinets.size(); n++)
{
FD_SET(g_clinets[n], &fdRead); //所有连入的客户端放入可读列表 保证recv不阻塞
if (maxSock < g_clinets[n])
{
maxSock = g_clinets[n];
}
}
//nfds第一个参数 是一个整数值 是指fd_set集合中所有socket值的范围 不是数量 。。
int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExpect, NULL);
if (ret < 0)
{
cout << "select任务结束" << endl;
break;
}
if (FD_ISSET(_sock, &fdRead)) //判断_sock是否在fdRead中
{
FD_CLR(_sock, &fdRead);
// 4. 等待接受客户端连接 accept
sockaddr_in _clientAddr = {};
int cliendAddrLen = sizeof(_clientAddr);
SOCKET _clientSock = INVALID_SOCKET; // 初始化无效的socket 用来存储接入的客户端
#ifdef _WIN32
_clientSock = accept(_sock, (sockaddr*)&_clientAddr, &cliendAddrLen);//当客户端接入时 会得到连入客户端的socket地址和长度
#else
_clientSock = accept(_sock, (sockaddr*)&_clientAddr, (socklen_t*)&cliendAddrLen);//当客户端接入时 会得到连入客户端的socket地址和长度
#endif
if (INVALID_SOCKET == _clientSock) //接受到无效接入
{
cout << "ERROR: 接受到无效客户端SOCKET..." << endl;
}
else
{
cout << "新Client加入:" << "socket = " << _clientSock << " IP = " << inet_ntoa(_clientAddr.sin_addr) << endl; //inet_ntoa 将ip地址转换成可读的字符串
}
for (int n = g_clinets.size() - 1; n >= 0; n--)
{
NewUserJoin userJoin;
userJoin.cmd = CMD_NEWUSERJOIN;
userJoin.sockId = _clientSock;
send(g_clinets[n], (const char*)&userJoin, userJoin.dataLength, 0);
}
g_clinets.push_back(_clientSock);
}
#ifdef _WIN32
for (size_t n = 0; n < fdRead.fd_count; n++)
{
if (processor(fdRead.fd_array[n]) == -1)//processor函数是处理命令的逻辑 recv接到的数据并做出相应的判断和输出日志
{
auto it = find(g_clinets.begin(), g_clinets.end(), fdRead.fd_array[n]);
if (it != g_clinets.end())
g_clinets.erase(it);
}
}
#else
for (int n = (int)g_clinets.size() - 1; n >= 0; n--)
{
if (FD_ISSET(g_clinets[n], &fdRead))
{
if (-1 == processor(g_clinets[n]))
{
auto iter = g_clinets.begin(); //std::vector<SOCKET>::iterator
if (iter != g_clinets.end())
{
g_clinets.erase(iter);
}
}
}
}
#endif
}
#ifdef _WIN32
// 6. 关闭socket
for (int n = (int)g_clinets.size() - 1; n >= 0; n--)
{
closesocket(g_clinets[n]);
}
closesocket(_sock);
// 清除Windows socket环境
WSACleanup();
#else
for (int n = (int)g_clinets.size() - 1; n >= 0; n--)
{
close(g_clinets[n]);
}
close(_sock);
#endif
return 0;
}