欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

实现网络版加法计算器

程序员文章站 2022-07-14 13:40:20
...

再谈“协议”

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)运行结果:

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

实现网络版加法计算器