实现网络版加法计算器

再谈“协议”

1. 基本概念

        我们知道,协议就是一种“约定”。套接字实现的网络通信的接口,在读写数据时,都是按照“字符串”的方式来发送和接收的,那如果我们要传输一些“结构化的数据”,该怎么办呢?

        这里我们就要提出两个概念:

(1)序列化:将对象的状态信息转化为可以存储或是可以传输的形式的过程,比如将结构体等类型转化为字符串类型;

(2)反序列化:将可以存储或是可以传输的形式转化为对象的状态信息的过程,比如将字符串类型转化为结构体等类型。

2. 具体实现

        比如,我们要实现一个网络版的加法计算器,有两种方法:

(1)客户端向服务器端发送一个形容“1+1”的字符串,该字符串以“+”为分隔,有两个操作数,且两个操作数都是整型,“+”即为运算符,且只能是“+”,同时数字与运算符间不能有空格。

(2)我们可以定义两个结构体来表示我们要交互的信息,一个用于传输要计算的数据,一个用于接收计算后的结果。发送数据时通过序列化将数据转换为一个字符串,接收时通过反序列化再将字符串转回结构体,具体实现代码如下:

1)要用到的头文件,以及结构体的定义:

#pragma once                                                                                                                                          

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

#define MAXSIZE 128

typedef struct
{//客服端发送的用于计算的两个数据
    int x;
    int y;
}request;

typedef struct
{//服务器端计算完返回给客户端的值
    int res;
}response;

2)服务器端——计算两个数的和

//实现网络版加法计算器,利用TCP多线程服务器计算客户端发过来数的和                                                                                     
#include "comm.h"

typedef struct
{
    int sock;
    char ip[24];
    int port;
}net_info_t;

int startup(int port, char* ip) 
{
    //1.创建套接字,这里是流式的套接字,因为TCP面向字节流
    int sock = socket(AF_INET, SOCK_STREAM, 0); 
    if(sock < 0)
    {   
        printf("socket error\n");
        exit(2);//套接字创建失败,直接终止进程,因为没有套接字网络通信根本无法实现,后续代码根本不用执行
    }   
    //2.绑定
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);//端口号
    local.sin_addr.s_addr = inet_addr(ip);//IP

    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {   
        printf("bind error\n");
        exit(3);
    }   

    //3.监听:一直等待客户来连接(因为TCP是面向连接的),提取新连接;
    if(listen(sock, 5) < 0)//最多容纳5个客户连接请求
    {   
        printf("listen error\n");
        exit(4);
    }
    return sock;//返回一个监听套接字
}

void service(int sock, char* ip, int port)
{
    while(1)
    {
        request r;
        //read函数可以读取任意类型的内容
        ssize_t s = read(sock, &r, sizeof(r));
        response rp;
        rp.res = r.x + r.y;
        write(sock, &rp, sizeof(rp));
    }
}

void* thread_service(void* arg)
{
    net_info_t *p = (net_info_t* )arg;
    service(p->sock, p->ip, p->port);

    close(p->sock);
    free(p);
}

//./tcp_server 127.0.0.1 8080                                                                                                                         
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        printf("Usage: %s [ip] [port]\n", argv[0]);
        exit(1);
    }
    
    //创建套接字
    int listen_sock = startup(atoi(argv[2]), argv[1]);
    
    struct sockaddr_in peer;
    char ipbuf[24];
    for( ; ; )
    {
        ipbuf[0] = 0;
        socklen_t len = sizeof(peer);
        //从监听套接字中拿连接
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
        if(new_sock < 0)//拿连接失败,不用管,因为还可以去拿其他连接
        {
            printf("accept error\n");
            continue;
        }
        //获得了一个新连接
        inet_ntop(AF_INET, (const void*)&peer.sin_addr, ipbuf, sizeof(ipbuf));//将四字节IP地址转换为点分十进制
        int p = ntohs(peer.sin_port);//端口号:网络序列转换为主机序列的端口号
        printf("get a new connect,[%s:%d]\n", ipbuf, p);//将新连接的IP和端口号打印

        //这里不用像多进程的版本关闭多余的文件描述符
        //因为线程共享地址空间,关掉一个文件,其他线程就看不到用不了了
        net_info_t* info = (net_info_t*)malloc(sizeof(net_info_t));
        if(info == NULL)
        {                                                                                                                                             
            perror("malloc");
            close(new_sock);
            continue;
        }
        info->sock = new_sock;
        strcpy(info->ip,ipbuf);
        info->port = p;

        pthread_t tid;
        pthread_create(&tid, NULL, thread_service, (void* )info);
        pthread_detach(tid);//线程分离后,该线程的资源会自动释放
    }
    return 0;
}           

3)客户端——发送要计算的数据,以及接收计算结果

//客户端,输入两个用于加法计算的数                                                                                                                     
#include "comm.h"

//./tcp_client 127.0.0.1 8080
int main(int argc, char* argv[])
{
    if(argc != 3)
    {   
        printf("Usage: %s [ip] [port]\n", argv[0]);
        return 1;
    }   
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0); 
    if(sock < 0)
    {   
        printf("socket error\n");
        return 2;
    }   
    //客户端不用绑定(bind),不用监听(listen),不用获取新连接(accept)
    
    //客户端有一个个性化操作connect,向服务器发起连接
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
    {   
        printf("connect error\n");
        return 3;
    }   
    //走到这,连接成功

    //发送数据,并收回结果
    char buf[MAXSIZE];
    while(1)
    {
        request r;
        printf("please enter# ");
        fflush(stdout);
        scanf("%d%d", &r.x, &r.y);

        write(sock, &r, sizeof(r));

        response rp;
        read(sock, &rp, sizeof(rp));

        printf("%d+%d = %d\n", r.x, r.y, rp.res);
        
    }
    close(sock);
    return 0;
}             

4)运行结果:

        因为上述代码实现的是多线程版本,所以可以接受多个连接请求,具体测试结果如下:


猜你喜欢

转载自blog.csdn.net/lycorisradiata__/article/details/80556519