笔记一:建立基础TCP服务端/客户端 点我跳转
笔记二:网络数据报文的收发 点我跳转
笔记三:升级为select网络模型 点我跳转
笔记四:跨平台支持Windows、Linux系统 点我跳转
笔记五:源码的封装 点我跳转
笔记六:缓冲区溢出与粘包分包 点我跳转
笔记七:服务端多线程分离业务处理高负载 点我跳转
根据此数据结构,我们可以根据包头的内容,来灵活的对包体的数据进行处理。
通过上文对网络数据报文的定义,我们可以很轻易的想到:
按以上操作,即可实现网络数据报文的收发。
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")//链接此动态链接库 windows特有
using namespace std;
//枚举类型记录命令
enum cmd
{
CMD_LOGIN,//登录
CMD_LOGOUT,//登出
CMD_ERROR//错误
};
//定义数据包头
struct DateHeader
{
short cmd;//命令
short date_length;//数据的长短
};
//包体1 登录 传输账号与密码
struct Login
{
char UserName[32];//用户名
char PassWord[32];//密码
};
//包体2 登录结果 传输结果
struct LoginResult
{
int Result;
};
//包体3 登出 传输用户名
struct Logout
{
char UserName[32];//用户名
};
//包体4 登出结果 传输结果
struct LogoutResult
{
int Result;
};
int main()
{
//启动windows socket 2,x环境 windows特有
WORD ver = MAKEWORD(2,2);//WinSock库版本号
WSADATA dat;//网络结构体 储存WSAStartup函数调用后返回的Socket数据
if(0 != WSAStartup(ver,&dat))//正确初始化后返回0
{
return 0;
}
//建立一个socket
SOCKET _mysocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//IPV4 数据流类型 TCP类型
if(INVALID_SOCKET == _mysocket)//建立失败
{
return 0;
}
//绑定网络端口和IP地址
sockaddr_in _myaddr = {
};//建立sockaddr结构体 sockaddr_in结构体方便填写 但是下面要进行类型转换
_myaddr.sin_family = AF_INET;//IPV4
_myaddr.sin_port = htons(8888);//端口 host to net unsigned short
_myaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//网络地址 INADDR_ANY监听所有网卡的端口
if(SOCKET_ERROR == bind(_mysocket,(sockaddr*)&_myaddr,sizeof(sockaddr_in)))//socket (强制转换)sockaddr结构体 结构体大小
{
cout<<"绑定不成功"<<endl;
}
else
{
//cout<<"绑定成功"<
}
//监听网络端口
if(SOCKET_ERROR == listen(_mysocket,5))//套接字 最大多少人连接
{
cout<<"监听失败"<<endl;
}
else
{
//cout<<"监听成功"<
}
//等待接收客户端连接
sockaddr_in _clientAddr = {
};//新建sockadd结构体接收客户端数据
int _addr_len = sizeof(sockaddr_in);//获取sockadd结构体长度
SOCKET _temp_socket = INVALID_SOCKET;//声明客户端套接字
_temp_socket = accept(_mysocket,(sockaddr*)&_clientAddr,&_addr_len);//自身套接字 客户端结构体 结构体大小
if(INVALID_SOCKET == _temp_socket)//接收失败
{
cout<<"接收到无效客户端Socket"<<endl;
}
else
{
cout<<"新客户端加入"<<endl;
printf("IP地址为:%s \n", inet_ntoa(_clientAddr.sin_addr));
}
while(true)
{
//接收客户端发送的数据
DateHeader _head = {
};
int _buf_len = recv(_temp_socket,(char*)&_head,sizeof(DateHeader),0);
if(_buf_len<=0)
{
printf("客户端已退出\n");
break;
}
printf("接收到包头,命令:%d,数据长度:%d\n",_head.cmd,_head.date_length);
switch(_head.cmd)
{
case CMD_LOGIN://登录 接收登录包体
{
Login _login = {
};
recv(_temp_socket,(char*)&_login,sizeof(Login),0);
/*
进行判断操作
*/
printf("%s已登录\n",_login.UserName);
send(_temp_socket,(char*)&_head,sizeof(DateHeader),0);//发包头
LoginResult _result = {
1};
send(_temp_socket,(char*)&_result,sizeof(LoginResult),0);//发包体
}
break;
case CMD_LOGOUT://登出 接收登出包体
{
Logout _logout = {
};
recv(_temp_socket,(char*)&_logout,sizeof(Logout),0);
/*
进行判断操作
*/
printf("%s已登出\n",_logout.UserName);
send(_temp_socket,(char*)&_head,sizeof(DateHeader),0);//发包头
LogoutResult _result = {
1};
send(_temp_socket,(char*)&_result,sizeof(LogoutResult),0);//发包体
}
break;
default://错误
{
_head.cmd = CMD_ERROR;
_head.date_length = 0;
send(_temp_socket,(char*)&_head,sizeof(DateHeader),0);//发包头
}
break;
}
}
//关闭客户端socket
closesocket(_temp_socket);
//关闭socket
closesocket(_mysocket);
//清除windows socket 环境
WSACleanup();
printf("任务结束,程序已退出");
getchar();
return 0;
}
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")//链接此动态链接库 windows特有
using namespace std;
//枚举类型记录命令
enum cmd
{
CMD_LOGIN,//登录
CMD_LOGOUT,//登出
CMD_ERROR//错误
};
//定义数据包头
struct DateHeader
{
short cmd;//命令
short date_length;//数据的长短
};
//包体1 登录 传输账号与密码
struct Login
{
char UserName[32];//用户名
char PassWord[32];//密码
};
//包体2 登录结果 传输结果
struct LoginResult
{
int Result;
};
//包体3 登出 传输用户名
struct Logout
{
char UserName[32];//用户名
};
//包体4 登出结果 传输结果
struct LogoutResult
{
int Result;
};
int main()
{
//启动windows socket 2,x环境 windows特有
WORD ver = MAKEWORD(2,2);//WinSock库版本号
WSADATA dat;//网络结构体 储存WSAStartup函数调用后返回的Socket数据
if(0 != WSAStartup(ver,&dat))//正确初始化后返回0
{
return 0;
}
//建立一个socket
SOCKET _mysocket = socket(AF_INET,SOCK_STREAM,0);//IPV4 数据流类型 类型可以不用写
if(INVALID_SOCKET == _mysocket)//建立失败
{
return 0;
}
//连接服务器
sockaddr_in _sin = {
};//sockaddr结构体
_sin.sin_family = AF_INET;//IPV4
_sin.sin_port = htons(8888);//想要连接的端口号
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//想要连接的IP
if(SOCKET_ERROR == connect(_mysocket,(sockaddr*)&_sin,sizeof(sockaddr_in)))
{
cout<<"连接失败"<<endl;
closesocket(_mysocket);
}
else
{
cout<<"连接成功"<<endl;
}
while(true)
{
//输入请求
char _msg[256] = {
};
scanf("%s",_msg);
//处理请求
if(0 == strcmp(_msg,"exit"))
{
break;
}
else if(0 == strcmp(_msg,"login"))
{
//发送
Login _login = {
"河边小咸鱼","123456"};
DateHeader _head = {
CMD_LOGIN,sizeof(_login)};
send(_mysocket,(const char*)&_head,sizeof(_head),0);
send(_mysocket,(const char*)&_login,sizeof(_login),0);
//接收
DateHeader _head2 = {
};
LoginResult _result= {
};
recv(_mysocket,(char*)&_head2,sizeof(DateHeader),0);
recv(_mysocket,(char*)&_result,sizeof(LoginResult),0);
printf("result:%d\n",_result.Result);
}
else if(0 == strcmp(_msg,"logout"))
{
//发送
Logout _logout = {
"河边小咸鱼"};
DateHeader _head = {
CMD_LOGOUT,sizeof(_logout)};
send(_mysocket,(const char*)&_head,sizeof(_head),0);
send(_mysocket,(const char*)&_logout,sizeof(_logout),0);
//接收
DateHeader _head2 = {
};
LogoutResult _result= {
};
recv(_mysocket,(char*)&_head2,sizeof(DateHeader),0);
recv(_mysocket,(char*)&_result,sizeof(LogoutResult),0);
printf("result:%d\n",_result.Result);
}
else
{
printf("不存在的命令\n");
}
}
//关闭socket
closesocket(_mysocket);
//清除windows socket 环境
WSACleanup();
return 0;
}
由上文,我们可以知道,可以通过两次send和两次recv进行报文的收发,但是其中操作较为麻烦,需要多次声明DateHeader包头结构体,不仅消耗时间资源,也容易出错。
因此,我们可以尝试将分开收发改为一次收发。大致思路为完善报文的结构体,使包体继承包头结构体,或者使包体结构体中包含一个包头结构体。由此完善报文的结构体,只进行一次send操作,即可发送所有报文数据。
在进行数据接收时,我们先接收包头大小的数据,随后根据包头的内容,来确定接下来接收数据的大小,即接收 总数据大小 减去 包头数据大小 的数据。而在接下来使用recv接收剩下数据时,要使用指针偏移,跳过结构体包头的接收(因为接收过了),直接接收到包体数据位置上。
/*
因为包头已经接收过了 所以进行指针偏移:&_login+sizeof(DateHeader)
接收数据长短为:sizeof(Login)-sizeof(DateHeader) 减去包头的大小
*/
recv(_temp_socket,(char*)&_login+sizeof(DateHeader),sizeof(Login)-sizeof(DateHeader),0);
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")//链接此动态链接库 windows特有
using namespace std;
//枚举类型记录命令
enum cmd
{
CMD_LOGIN,//登录
CMD_LOGINRESULT,//登录结果
CMD_LOGOUT,//登出
CMD_LOGOUTRESULT,//登出结果
CMD_ERROR//错误
};
//定义数据包头
struct DateHeader
{
short cmd;//命令
short date_length;//数据的长短
};
//包1 登录 传输账号与密码
struct Login : public DateHeader
{
Login()//初始化包头
{
this->cmd = CMD_LOGIN;
this->date_length = sizeof(Login);
}
char UserName[32];//用户名
char PassWord[32];//密码
};
//包2 登录结果 传输结果
struct LoginResult : public DateHeader
{
LoginResult()//初始化包头
{
this->cmd = CMD_LOGINRESULT;
this->date_length = sizeof(LoginResult);
}
int Result;
};
//包3 登出 传输用户名
struct Logout : public DateHeader
{
Logout()//初始化包头
{
this->cmd = CMD_LOGOUT;
this->date_length = sizeof(Logout);
}
char UserName[32];//用户名
};
//包4 登出结果 传输结果
struct LogoutResult : public DateHeader
{
LogoutResult()//初始化包头
{
this->cmd = CMD_LOGOUTRESULT;
this->date_length = sizeof(LogoutResult);
}
int Result;
};
int main()
{
//启动windows socket 2,x环境 windows特有
WORD ver = MAKEWORD(2,2);//WinSock库版本号
WSADATA dat;//网络结构体 储存WSAStartup函数调用后返回的Socket数据
if(0 != WSAStartup(ver,&dat))//正确初始化后返回0
{
return 0;
}
//建立一个socket
SOCKET _mysocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//IPV4 数据流类型 TCP类型
if(INVALID_SOCKET == _mysocket)//建立失败
{
return 0;
}
//绑定网络端口和IP地址
sockaddr_in _myaddr = {
};//建立sockaddr结构体 sockaddr_in结构体方便填写 但是下面要进行类型转换
_myaddr.sin_family = AF_INET;//IPV4
_myaddr.sin_port = htons(8888);//端口 host to net unsigned short
_myaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//网络地址 INADDR_ANY监听所有网卡的端口
if(SOCKET_ERROR == bind(_mysocket,(sockaddr*)&_myaddr,sizeof(sockaddr_in)))//socket (强制转换)sockaddr结构体 结构体大小
{
cout<<"绑定不成功"<<endl;
}
else
{
//cout<<"绑定成功"<
}
//监听网络端口
if(SOCKET_ERROR == listen(_mysocket,5))//套接字 最大多少人连接
{
cout<<"监听失败"<<endl;
}
else
{
//cout<<"监听成功"<
}
//等待接收客户端连接
sockaddr_in _clientAddr = {
};//新建sockadd结构体接收客户端数据
int _addr_len = sizeof(sockaddr_in);//获取sockadd结构体长度
SOCKET _temp_socket = INVALID_SOCKET;//声明客户端套接字
_temp_socket = accept(_mysocket,(sockaddr*)&_clientAddr,&_addr_len);//自身套接字 客户端结构体 结构体大小
if(INVALID_SOCKET == _temp_socket)//接收失败
{
cout<<"接收到无效客户端Socket"<<endl;
}
else
{
cout<<"新客户端加入"<<endl;
printf("IP地址为:%s \n", inet_ntoa(_clientAddr.sin_addr));
}
while(true)
{
//接收客户端发送的数据
DateHeader _head = {
};
int _buf_len = recv(_temp_socket,(char*)&_head,sizeof(DateHeader),0);
if(_buf_len<=0)
{
printf("客户端已退出\n");
break;
}
printf("接收到包头,命令:%d,数据长度:%d\n",_head.cmd,_head.date_length);
switch(_head.cmd)
{
case CMD_LOGIN://登录 接收登录包体
{
Login _login;
recv(_temp_socket,(char*)&_login+sizeof(DateHeader),sizeof(Login)-sizeof(DateHeader),0);
/*
进行判断操作
*/
printf("%s已登录\n密码:%s\n",_login.UserName,_login.PassWord);
LoginResult _result;
_result.Result = 1;
send(_temp_socket,(char*)&_result,sizeof(LoginResult),0);//发包体
}
break;
case CMD_LOGOUT://登出 接收登出包体
{
Logout _logout;
recv(_temp_socket,(char*)&_logout+sizeof(DateHeader),sizeof(Logout)-sizeof(DateHeader),0);
/*
进行判断操作
*/
printf("%s已登出\n",_logout.UserName);
LogoutResult _result;
_result.Result = 1;
send(_temp_socket,(char*)&_result,sizeof(LogoutResult),0);//发包体
}
break;
default://错误
{
_head.cmd = CMD_ERROR;
_head.date_length = 0;
send(_temp_socket,(char*)&_head,sizeof(DateHeader),0);//发包头
}
break;
}
}
//关闭客户端socket
closesocket(_temp_socket);
//关闭socket
closesocket(_mysocket);
//清除windows socket 环境
WSACleanup();
printf("任务结束,程序已退出");
getchar();
return 0;
}
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")//链接此动态链接库 windows特有
using namespace std;
//枚举类型记录命令
enum cmd
{
CMD_LOGIN,//登录
CMD_LOGINRESULT,//登录结果
CMD_LOGOUT,//登出
CMD_LOGOUTRESULT,//登出结果
CMD_ERROR//错误
};
//定义数据包头
struct DateHeader
{
short cmd;//命令
short date_length;//数据的长短
};
//包1 登录 传输账号与密码
struct Login : public DateHeader
{
Login()//初始化包头
{
this->cmd = CMD_LOGIN;
this->date_length = sizeof(Login);
}
char UserName[32];//用户名
char PassWord[32];//密码
};
//包2 登录结果 传输结果
struct LoginResult : public DateHeader
{
LoginResult()//初始化包头
{
this->cmd = CMD_LOGINRESULT;
this->date_length = sizeof(LoginResult);
}
int Result;
};
//包3 登出 传输用户名
struct Logout : public DateHeader
{
Logout()//初始化包头
{
this->cmd = CMD_LOGOUT;
this->date_length = sizeof(Logout);
}
char UserName[32];//用户名
};
//包4 登出结果 传输结果
struct LogoutResult : public DateHeader
{
LogoutResult()//初始化包头
{
this->cmd = CMD_LOGOUTRESULT;
this->date_length = sizeof(LogoutResult);
}
int Result;
};
int main()
{
//启动windows socket 2,x环境 windows特有
WORD ver = MAKEWORD(2,2);//WinSock库版本号
WSADATA dat;//网络结构体 储存WSAStartup函数调用后返回的Socket数据
if(0 != WSAStartup(ver,&dat))//正确初始化后返回0
{
return 0;
}
//建立一个socket
SOCKET _mysocket = socket(AF_INET,SOCK_STREAM,0);//IPV4 数据流类型 类型可以不用写
if(INVALID_SOCKET == _mysocket)//建立失败
{
return 0;
}
//连接服务器
sockaddr_in _sin = {
};//sockaddr结构体
_sin.sin_family = AF_INET;//IPV4
_sin.sin_port = htons(8888);//想要连接的端口号
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//想要连接的IP
if(SOCKET_ERROR == connect(_mysocket,(sockaddr*)&_sin,sizeof(sockaddr_in)))
{
cout<<"连接失败"<<endl;
closesocket(_mysocket);
}
else
{
cout<<"连接成功"<<endl;
}
while(true)
{
//输入请求
char _msg[256] = {
};
scanf("%s",_msg);
//处理请求
if(0 == strcmp(_msg,"exit"))
{
break;
}
else if(0 == strcmp(_msg,"login"))
{
//发送
Login _login;
strcpy(_login.UserName,"河边小咸鱼");
strcpy(_login.PassWord,"123456");
send(_mysocket,(const char*)&_login,sizeof(_login),0);
//接收
LoginResult _result;
recv(_mysocket,(char*)&_result,sizeof(LoginResult),0);
printf("result:%d\n",_result.Result);
}
else if(0 == strcmp(_msg,"logout"))
{
//发送
Logout _logout;
strcpy(_logout.UserName,"河边小咸鱼");
send(_mysocket,(const char*)&_logout,sizeof(_logout),0);
//接收
LogoutResult _result;
recv(_mysocket,(char*)&_result,sizeof(LogoutResult),0);
printf("result:%d\n",_result.Result);
}
else
{
printf("不存在的命令\n");
}
}
//关闭socket
closesocket(_mysocket);
//清除windows socket 环境
WSACleanup();
return 0;
}