Cpp-Socket网络编程(五)网络报文的数据格式定义及使用

1. 网络数据报文的格式定义:

报文由包头和包体组成,其中:

- 包头:描述本次消息包的大小,描述数据的作用

- 包体:存放数据


enum CMD {
    CMD_LOGIN,
    CMD_LOGINOUT
};

//包头
struct DataHeader {
    short dataLength; //数据长度
    short cmd; //命令
};

//包体 DataPackage
struct Login {
    char UserName[32];
    char PassWord[32];
};

struct LoginResult {
    int result;
};

struct LoginOut {
    char UserName[32];
    
};

struct LoginoutResult {
    int result;
};

2. 服务器端修改:

主要修改 6. 处理请求 和 7. 向客户端发送数据

增加上述几个结构体,服务器端将接收到的数据放在数据头header里,header中包含命令cmd和数据长度datalength,具体的处理方式根据cmd的不同用switch-case语句来组织。向客户端发送数据包括发送数据的数据头header和登陆/登出信息。

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

//三个命令:登入、登出和错误信息
enum CMD {
    CMD_LOGIN,
    CMD_LOGOUT,
    CMD_ERROR
};

//包头
struct DataHeader {
    short dataLength; //数据长度
    short cmd; //命令
};

//包体 DataPackage
struct Login {
    char UserName[32];
    char PassWord[32];
};

struct LoginResult {
    int result;
};

struct LogOut {
    char UserName[32];
    
};

struct LogoutResult {
    int result;
};

int main() {
    //1. 建立一个socket(传入socket族,socket类型, 协议类型)
    int _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (_sock == -1) {
        perror("socket error!");
        return 1;
    }
    
    //2. 绑定用于接受客户端连接的网络端口bind
    sockaddr_in _sin = {};
    _sin.sin_family = AF_INET;      //协议族IPV4
    _sin.sin_port = htons(4567);    //端口号 host_to_net
    //_sin.sin_addr.s_addr = inet_addr("127.0.0.1");    //服务器绑定的ip地址
    _sin.sin_addr.s_addr= INADDR_ANY; //不限定访问该服务器的ip
    if (bind(_sock, (sockaddr*)&_sin, sizeof(_sin)) == -1) {
        perror("bind error!");
        return 1;
    } else {
        printf("bind success!\n");
    }
    
    //3. 监听网络端口listen
    if (listen(_sock, 5) == -1) { //套接字,最大允许连接数量
        perror("listen error!");
    } else {
        printf("listen success!\n");
    }
    
    //4. 等待客户端连接accept
    sockaddr_in clientAddr = {};
    socklen_t nAddrLen = sizeof(sockaddr_in);
    int _cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen); //套接字,收到客户端socket地址,返回socket地址的大小
    if (_cSock == -1) {
        perror("client socket error!");
    } else {
        printf("client socket success! socket = %d, IP = %s \n", _cSock, inet_ntoa(clientAddr.sin_addr)); //打印客户端socket和IP地址
    }
    
    //循环接收客户端数据
    while (true) {
        DataHeader header = {}; //定义消息头结构体
        //5. 接收客户端的请求数据
        int nLen = recv(_cSock, (char*)&header, sizeof(header), 0);
        if (nLen <= 0) {
            printf("client has quit!\n");
            break;
        }
        printf("receive message:%d, data length:%d\n", header.cmd, header.dataLength); //提示收到命令
        //6. 处理请求
        switch (header.cmd) {
            case CMD_LOGIN:
            {
                Login login = {};
                recv(_cSock, (char*)&login, sizeof(Login), 0); //接收客户端的登陆信息(用户名+密码)
                //假设用户输入正确(这里忽略用户名和密码是否正确的验证过程)
                LoginResult inret = {0};
                //7. 向客户端发送数据send
                send(_cSock, (char*)&header, sizeof(DataHeader), 0); //向客户端发送消息头
                send(_cSock, (char*)&inret, sizeof(LoginResult), 0); //向客户端发送登陆结果
            }
            break;
            case CMD_LOGOUT:
            {
                LogOut logout = {};
                recv(_cSock, (char*)&logout, sizeof(LogOut), 0);
                LogoutResult outret = {1};
                //7. 向客户端发送数据send
                send(_cSock, (char*)&header, sizeof(DataHeader), 0);
                send(_cSock, (char*)&outret, sizeof(LogoutResult), 0);
            }
            break;
            default:
                header.cmd = CMD_ERROR;
                header.dataLength = 0;
                send(_cSock, (char*)&header, sizeof(DataHeader), 0);
            break;
        }
        
    }
    
    //8. 关闭套接字close socket
    close(_sock);
    
    printf("Server has quit!");
    getchar();
    return 0;
}

3. 客户端的修改:

增加了和服务器端相同的结构体,主要修改 4. 处理用户请求 5. 向服务器端发送请求 和 6. 接收服务器的返回数据。首先,根据用户输入的命令向服务器发送包头和包体,然后再接收服务器端的返回数据。

//
//  client.cpp
//  SocketStepByStep
//
//  Created by 刘君妍 on 2019/7/19.
//  Copyright © 2019 tower. All rights reserved.
//
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define SOCKET_ERROR -1

//三个命令:登入、登出和错误信息
enum CMD {
    CMD_LOGIN,
    CMD_LOGOUT,
    CMD_ERROR
};

//包头
struct DataHeader {
    short dataLength; //数据长度
    short cmd; //命令
};

//包体 DataPackage
struct Login {
    char UserName[32];
    char PassWord[32];
};

struct LoginResult {
    int result;
};

struct LogOut {
    char UserName[32];
    
};

struct LogoutResult {
    int result;
};

int main() {
    //1. 建立一个socket
    int _sock = socket(AF_INET, SOCK_STREAM, 0); //与服务器端不同,第三个参数无需声明使用TCP连接
    if (_sock == SOCKET_ERROR) {
        printf("socket build error!\n");
    } else {
        printf("socket build success!\n");
    }
    
    //2. 连接服务器
    sockaddr_in _sin = {};
    _sin.sin_family = AF_INET;
    _sin.sin_port = htons(4567);
    _sin.sin_addr.s_addr = inet_addr("127.0.0.1");
    int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
    if (ret == SOCKET_ERROR) {
        printf("connect error!\n");
    } else {
        printf("connect success!\n");
    }
    
    char cmdBuf[128] = {}; //大小与服务器端匹配,防止溢出
    while (true) {
        //3. 用户输入请求命令
        scanf("%s", cmdBuf);
        //4. 处理请求命令
        if (0 == strcmp(cmdBuf, "exit")) {
            printf("receive quit message!");
            break;
        } else if (0 == strcmp(cmdBuf, "login")){
            //5. 向服务器端发送请求
            Login login = {"tower", "1234"};
            DataHeader dh= {sizeof(login), CMD_LOGIN};
            send(_sock, (const char*)&dh, sizeof(DataHeader), 0);
            send(_sock, (const char*)&login, sizeof(Login), 0);
            //6. 接收服务器返回数据
            DataHeader retHeader = {};
            LoginResult loginRet = {};
            recv(_sock, (char*)&retHeader, sizeof(DataHeader), 0);
            recv(_sock, (char*)&loginRet, sizeof(LoginResult), 0);
            printf("LoginResult: %d\n", loginRet.result);
            
        } else if (0 == strcmp(cmdBuf, "logout")) {
            //5. 向服务器端发送请求
            LogOut logout = {"tower"};
            DataHeader dh = {sizeof(logout), CMD_LOGOUT};
            send(_sock, (const char*)&dh, sizeof(DataHeader), 0);
            send(_sock, (const char*)&logout, sizeof(LogOut), 0);
            //6. 接收服务器返回数据
            DataHeader retHeader = {};
            LogoutResult logoutRet = {};
            recv(_sock, (char*)&retHeader, sizeof(DataHeader), 0);
            recv(_sock, (char*)&logoutRet, sizeof(LogoutResult), 0);
            printf("LogoutResult: %d\n", logoutRet.result);
            
        } else {
            printf("unsupported command!");
        }
    }
    
    //7. 关闭套接字close socket
    close(_sock);
    
    printf("client has quit!");
    getchar();
    return 0;
}

4. 实验结果: 

connect4

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值