操作系统大作业——买卖商品的客户机-服务器多线程应用程序

  1. 请编写完成如下买卖商品的客户机-服务器多线程应用程序。服务器程序server启动后等待买家程序buyer连接。买家程序buyer启动后连接服务器程序server。连接成功后,服务器程序server创建一个专门服务于该买家程序buyer的卖家线程seller。卖家线程seller从服务器磁盘读取商品清单文件goods.txt并显示给卖家线程seller。每个商品项包括商品类、商品ID、商品名、商品单价、库存。买家程序buyer浏览商品清单内容后,选择若干个商品项加入购物车,提交购物车中的商品订单给服务器的卖家线程seller。卖家线程seller收到买家程序buyer提交的商品订单后,计算商品数量和总价,返回给买家线程buyer,减少商品清单文件goods.txt中被售出商品的库存量,将成交日期、成交顺序号、买家程序buyer进程号、商品清单、商品数量和总价记录到销售文件sell.txt中。然后买家程序buyer和该卖家线程seller终止。(50分)

①客户端client.c源代码:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
//商品最大数量
#define MAX_GOODS_ITEMS 100
//订单中的商品最大数量
#define MAX_ORDERFORM_ITEMS 10
//商品清单结构体
typedef struct goodsItems
{
    char type[20];
    char goodsID[12];
    char goodsname[10];
    float price;
    int repertory;
}goodsItems;
//商品清单
goodsItems goodslist[MAX_GOODS_ITEMS];
//订单结构体
typedef struct orderFormItem{
    char goodsID[12];
    int num;
}orderFormItem;
//订单
orderFormItem orderForm[MAX_ORDERFORM_ITEMS];
//商品总价(从服务器传来)
float totalPrice = 0;
//初始化订单信息
int initOrderInfo(){
    for(int i = 0; i < MAX_ORDERFORM_ITEMS;i++){
        memset(orderForm[i].goodsID,0,12);
        orderForm[i].num = 0;
    }
    totalPrice = 0;
    return 0;
}
//与服务器建立连接
int serverCon(int *sockfd,pid_t *cpid){
    int len = 0;
    struct sockaddr_in address;
    int result;
    *sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(*sockfd < 0){
        exit(-1);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(8081);
    if(inet_aton("127.0.0.1",&address.sin_addr) < 0){
        printf("inet_aton error.\n");
        exit(-1);
    }
     len = sizeof(address);
    *cpid = getpid();
    printf("客户机%d开始connect服务器……\n",*cpid);
    result = connect(*sockfd,(struct sockaddr *)&address,len);
    if(result == -1){
        perror("客户机connect服务器失败!\n");
        exit(-1);
    }
    else{
        printf("客户机连接服务器成功!\n");
        printf("将客户机进程号发给服务器……\n");
        write(*sockfd,cpid,sizeof(cpid));
    }
}
//接收服务器发来的商品信息
int getGoods(int sockfd){
    int goodsNum = 0;
    read(sockfd,goodslist,sizeof(goodslist));
    for(goodsNum = 0; goodsNum < MAX_GOODS_ITEMS;goodsNum++){
        if(goodslist[goodsNum].repertory == 0) break;
    }
    //检索从服务器接收过来的商品数量
    return goodsNum;
}
//打印商品信息和操作提示语
int printGoodsAndOpe(int goodsNum){
    printf("=========================================================\n");
    printf("=    商品类\t商品ID\t商品名称\t价格\t当前库存=\n");
    printf("=========================================================\n");
    for(int i = 0; i < goodsNum; i++){
            printf("=%10s\t%s\t%s\t\t%.2f\t%d\t=\n",
                  goodslist[i].type,goodslist[i].goodsID,goodslist[i].goodsname,goodslist[i].price,goodslist[i].repertory);
    }
    printf("=========================================================\n");
    printf("=  请输入操作符:    1.挑选商品      2.查看购物车       =\n");
    printf("=  请输入操作符:    3.提交订单      4.退出客户端       =\n");
    printf("=========================================================\n");
}
//根据商品ID查询商品名称和库存
int searchGoodsInfo(char *goodsID_tmp,int goodsNum){
    //找到商品ID对应的goodslist下标返回,找不到返回-1
    for(int i = 0;i < goodsNum;i++){
        if(strncmp(goodsID_tmp,goodslist[i].goodsID,6) == 0 && goodslist[i].repertory != 0)   return i;
    }
    return -1;
}
//根据商品ID查找对应的orderForm下标,找不到就返回-1
int searchOrderInfo(char *goodsID_tmp,int orderNum){
        for(int i = 0;i < orderNum;i++){
        if(strcmp(goodsID_tmp,goodslist[i].goodsID) == 0)   return i;
    }
    return -1;
}
//加入购物车
int addShoppingCart(int goodsNum,int orderNum){
     char goodsID_tmp[12]; char goodsname_tmp[10];int goodsNum_tmp;
    //goods_index指的是商品在goodslist的下标
    //order_index指的是商品在orderForm的下标
    int goods_index,order_index;
    printf("===添加商品至购物车===\n");
    while(1){
        memset(goodsID_tmp,0,12);goodsNum_tmp = 0;
        goods_index = 0;order_index = 0;
        printf("请输入商品ID:(0:quit)");
        scanf("%s",goodsID_tmp);
        if(strcmp(goodsID_tmp,"0") == 0)  {printf("退出挑选商品\n");return orderNum;}
        //查询商品ID是否存在
        goods_index = searchGoodsInfo(goodsID_tmp,goodsNum);
        if(goods_index == -1)    {printf("商品ID不存在,请重新输入!\n");continue;}
        //商品存在,就判断是否已在购物车中
        order_index = searchOrderInfo(goodsID_tmp,orderNum);
        //若不在购物车中,商品信息将记录在新的orderFormItem中
        if(order_index == -1) {
            order_index = orderNum; orderNum++;
            strcpy(orderForm[order_index].goodsID,goodsID_tmp);
            orderForm[order_index].num = 0;
            }
        printf("您选择的商品是:%s\n",goodslist[goods_index].goodsname);
        printf("请输入需要的数量:");
        scanf("%d",&goodsNum_tmp);
        printf("%d",goodsNum_tmp);
        printf("\n");
        //倘若未提交订单的商品数量大于库存,则报错
        if(goodsNum_tmp+orderForm[order_index].num > goodslist[goods_index].repertory){
            printf("订购数目大于库存,请重新输入\n");continue;
        }
        else
            orderForm[order_index].num += goodsNum_tmp;
       }
    return orderNum;
}
//查看购物车
void printShoppingCart(int orderNum){
    printf("当前购物车的内容为:\n");
    for(int i = 0;i < orderNum; i++){
        printf("%d   %s   %d\n",i,orderForm[i].goodsID,orderForm[i].num);
    }
}
//提交订单
void submitOrderForm(int sockfd,int orderNum){
    //发送缓冲区和接收缓冲区
    char send_buf[1024];
    char rcv_buf[1024];
    totalPrice = 0;
    if(orderNum){
        memset(send_buf,0,1024);
        totalPrice = 0;
        strcpy(send_buf,"submitForm");
        write(sockfd,send_buf,sizeof(send_buf));
        write(sockfd,orderForm,sizeof(orderForm));
        memset(rcv_buf,0,1024);
        read(sockfd,rcv_buf,sizeof(rcv_buf));
        printf("%s",rcv_buf);
        //接收价格
        read(sockfd,&totalPrice,sizeof(totalPrice));
        return;
    }
    else{
        printf("orderForm is null。\n");
    }
    return;
}
void quitClient(int sockfd,char *snd_buf){
    printf("客户端即将退出。\n");
    //清空发送缓冲区
    memset(snd_buf,0,1024);
    strcpy(snd_buf,"!q");
    write(sockfd,snd_buf,sizeof(snd_buf));
    close(sockfd);
    printf("客户端已退出。\n");
    exit(0);
}
int main(){
    int sockfd;
    //商品数目
    int goodsNum = 0;
    //订单数目
    int orderNum = 0;
    pid_t cpid;
    char snd_buf[1024];
    char rcv_buf[1024];
    //连接服务器
    serverCon(&sockfd,&cpid);
    printf("客户机%d,sockfd = %d 等待服务器发送商品信息……\n",cpid,sockfd);
    //连接成功后,获取商品信息
    goodsNum = getGoods(sockfd);
    printGoodsAndOpe(goodsNum);
    int oper = 0;
    while (1)
    {
        printf("操作符:");
        scanf("%d",&oper);
        switch (oper)
        {
        case 0:system("clear");printGoodsAndOpe(goodsNum);break;
        case 1:orderNum = addShoppingCart(goodsNum,orderNum);break;
        case 2:printShoppingCart(orderNum);break;
        case 3:
                submitOrderForm(sockfd,orderNum);
                if(!totalPrice) printf("订单提交失败,请稍后再试\n");
                else {
                    printf("订单提交成功!\n");
                    printf("来自服务器:您所选商品价格为:%.2f\n",totalPrice);initOrderInfo();orderNum = 0;
                    printf("系统即将刷新……\n");sleep(10);
                    system("clear");goodsNum = getGoods(sockfd);printGoodsAndOpe(goodsNum);
                    }
                break;
        case 4:quitClient(sockfd,snd_buf);break;
        default:printf("输入操作符号不存在,请重新输入!\n");break;
        }
    }
    printf("客户机%d与服务器sockfd = %d对话结束\n",cpid,sockfd);
    close(sockfd);
}

②服务器server.c源代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
//最大连接数
#define maxLinkNum 5
//货物清单中的最大数目
#define MAX_GOODS_ITEMS 100
//订单中的商品最大数量
#define MAX_ORDERFORM_ITEMS 10
//缓冲区大小
#define BUF_SIZE 200
//订单结构体
typedef struct orderFormItem{
    char goodsID[12];
    int num;
}orderFormItem;
//订单
orderFormItem orderForm[MAX_ORDERFORM_ITEMS];
//商品清单结构体
typedef struct goodsItems
{
    char type[20];
    char goodsID[12];
    char goodsname[10];
    float price;
    int repertory;
}goodsItems;
//时间戳结构体
typedef struct {
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
}Time_YMD_HMS;
//记录服务器端的socket和连接的多个客户端的socket
int client_sockfd[maxLinkNum];
//记录客户端的pid
pid_t client_pid[maxLinkNum];
//命名套接字
int server_sockfd = -1;
//商品信息
goodsItems goodslist[100];
//当前连接数
int curLink;
//是否允许写
int writePermission = 1;
//连接数资源信号量
sem_t mutex;
//写入文件信号量
sem_t writeNum;
//商品总数
int goodsNum = 0;
//订单总数
int orderNum = 0;
//销售号
int sellid = 0;
//按照yyyy-mm-dd格式获取当前日期
char* getNowTime()
{
    Time_YMD_HMS *curDate =(Time_YMD_HMS *)malloc(sizeof(Time_YMD_HMS));
    char *timeBuf =(char *)malloc(BUF_SIZE);
    bzero(timeBuf,BUF_SIZE);
    bzero(curDate,sizeof(Time_YMD_HMS));
    time_t now;
    struct tm *timeNow;
    time(&now);
    timeNow = localtime(&now);
    curDate->year=timeNow->tm_year+1900;
    curDate->month=timeNow->tm_mon+1;
    curDate->day=timeNow->tm_mday;
    curDate->hour=timeNow->tm_hour;
    curDate->minute=timeNow->tm_min;
    curDate->second=timeNow->tm_sec;
    // yyyy-MM-dd HH:mm:ss
    if(curDate->second < 10)
        sprintf(timeBuf, "%d-%d-%d %d:%d:0%d",curDate->year,curDate->month,curDate->day,
                                curDate->hour,curDate->minute,curDate->second);
    else
        sprintf(timeBuf, "%d-%d-%d %d:%d:%d",curDate->year,curDate->month,curDate->day,
                                curDate->hour,curDate->minute,curDate->second);
    free(curDate);
    return timeBuf;
}
//Linux stdlib.h头文件不包含itoa转换字符串函数,我们自己实现它
char *itoa(int value, char str[], int radix)
{
    char tmp_buff[33] = {0};
    char ch;
    int i = 0, j = 0;
    /* 超出转换进制范围,退出 */
    if ((radix < 2) || (radix > 36))
    {
        printf("radix err...\n");
        return NULL;
    }
    /* 不是10进制的负数,退出函数 */
    if ((value < 0) && (radix != 10))
    {
        printf("value err...\n");
        return NULL;
    }
    /* 10进制支持负数转换 */
    if ((value < 0) && (radix == 10))
    {
        value = -value;
        *str++ = '-';
    }
    /* 转换 */
    while (value)
    {
        ch = value % radix;
        value /= radix;
        
        if (ch < 10)
            tmp_buff[i++] = ch + '0';
        else 
            tmp_buff[i++] = ch + 'a' - 10;
    }    
    /* 逆序 */
    for (j=i-1; j>=0; j--)
    {
        *str++ = tmp_buff[j];
    }
    *str = '\0';        // 加上结束符
    return str;
}
//停止服务信息
char stopmsg[100];
void *serverQuit(void *arg){
//客户机与服务器断开连接,释放资源处理函数
    char *msg = "服务器即将关闭\n";
    while (1)
    {
        //服务器发送消息缓存区
        if(strcmp(stopmsg,"quit")==0){
            printf("%s",msg);
            for(int i = 0; i < maxLinkNum; i++){
                if(client_sockfd[i] != -1){
                    write(client_sockfd[i],msg,sizeof(msg));
                }
            }
            close(server_sockfd);
            sem_destroy(&mutex);
            exit(0);
        }
    } 
}
//从文件中读取商品信息
void initGoodsInfo(){
    goodsNum = 0;
    for(int i = 0; i < MAX_GOODS_ITEMS;i++){
        memset(goodslist[i].goodsID,0,12);
        memset(goodslist[i].type,0,20);
        memset(goodslist[i].goodsname,0,10);
        goodslist[i].price = 0;goodslist[i].repertory = 0;
    }
}
void getGoodsInfo(){
    FILE* fp;
    initGoodsInfo();
    if ((fp = fopen("/home/zzy/桌面/VSWorkSpace/goods.txt", "r")) == NULL)
    {
        printf("Can't open the goodsfile!\n"); exit(0);
    }
    else{
        while (!feof(fp))
        {
            //从goods.txt文件中读取数据保存到结构体数组中
            fscanf(fp,"%s",goodslist[goodsNum].type);
            fscanf(fp,"%s",goodslist[goodsNum].goodsID);
            fscanf(fp,"%s",goodslist[goodsNum].goodsname);
            fscanf(fp,"%f%d\n",&goodslist[goodsNum].price,&goodslist[goodsNum].repertory);
           // fscanf(fp,"%s%s%s%f%d\n",
           //     goodslist[goodsNum].type,goodslist[goodsNum].goodsID,goodslist[goodsNum].goodsname,&goodslist[goodsNum].price,&goodslist[goodsNum].repertory);
            goodsNum++;
        }
        fclose(fp);
        //打印商品信息
    //      for(int i = 0; i < goodsNum; i++ ){
    //         printf("%s %s %s %.2f  %d\n",
    //               goodslist[i].type,goodslist[i].goodsID,goodslist[i].goodsname,goodslist[i].price,goodslist[i].repertory);
    //   }
         return;
    }
}
//初始化订单信息
void initOrderInfo(){
    orderNum = 0;
    for(int i = 0; i < MAX_ORDERFORM_ITEMS;i++){
        memset(orderForm[i].goodsID,0,12);
        orderForm[i].num = 0;
    }
}
//获取订单信息
void getOrderInfo(){
    for(int i = 0; i < MAX_ORDERFORM_ITEMS;i++){
        if(orderForm[i].num == 0)   continue;
        else orderNum++;
    }
}
//根据商品ID查goodslist下标
int searchGoodsInfo(char *goodsID_tmp){
    //找到商品ID对应的goodslist下标返回,找不到返回-1
    for(int i = 0;i < MAX_GOODS_ITEMS;i++){
        if(goodslist[i].price == 0) return -1;
        if(strncmp(goodsID_tmp,goodslist[i].goodsID,6) == 0)   return i;
    }
    return -1;
}
//将销售信息写入库存中
void writeList(long *write_flag){
    printf("正在将订单信息写入内存……\n");
    int goods_index = searchGoodsInfo(orderForm[*write_flag].goodsID);
    if(goodslist[goods_index].repertory >= orderForm[*write_flag].num){
        goodslist[goods_index].repertory = goodslist[goods_index].repertory-orderForm[*write_flag].num;
    }
    else *write_flag = -1;
    writePermission++;
   sem_post(&writeNum);
}
//将销售信息写入文件中
void writeOrder(long *n){
    FILE *fp1,*fp2;
    printf("正在将文件写入数据库中……\n");
    //写入goods.txt
    fp1 = fopen("/home/zzy/桌面/VSWorkSpace/goods.txt", "w");
    fp2 = fopen("/home/zzy/桌面/VSWorkSpace/sell.txt","a");
    if(fp1== NULL || fp2 == NULL){
        printf("Can't open the file!\n");*n = -1;return;
    }
    else{
        for(int i = 0;i < goodsNum;i++){
                    fprintf(fp1,"%s %s %s %.2f %d\n",
                    goodslist[i].type,goodslist[i].goodsID,goodslist[i].goodsname,goodslist[i].price,goodslist[i].repertory);
        }
        for(int i = 0;i < orderNum;i++){
                    printf("%d",orderNum);
                    int goods_index = 0;
                    sellid++;
                    goods_index = searchGoodsInfo(orderForm[i].goodsID);
                    fprintf(fp2,"%s    %d              %d        %s   %d   %.2f\n",
                        getNowTime(),sellid,client_pid[*n],goodslist[goods_index].goodsID,orderForm[i].num,(float)goodslist[goods_index].price*orderForm[i].num);
        }
    }
    fclose(fp1);fclose(fp2);
    writePermission++;
    sem_post(&writeNum);
}
//计算总价
float calcTotalPrice(long n){
    int oderFormNum = 0;
    int repertory_tmp = 0;
    int goods_index;
    //用于返回判断writeOrder线程是否正确修改文件
    //用于传递要修改的商品下标
    long write_flag = -1;
    float totalPrice = 0;
    for(int i = 0; i < MAX_ORDERFORM_ITEMS; i++){
        if(orderForm[i].num == 0)   continue;
        goods_index = 0;
        goods_index = searchGoodsInfo(orderForm[i].goodsID);
        write_flag = i;
        //repertory_tmp = goodslist[goods_index].repertory;
        //若有其他订单正在处理,writeOrder线程将会阻塞。
        if(!writePermission)    printf("当前有其他订单正在提交,请稍候……\n");
        writePermission--;
        sem_wait(&writeNum);
        writeList(&write_flag);
        if(write_flag == -1) {printf("库存修改失败!\n");return 0;}
        totalPrice += (float)goodslist[goods_index].price*orderForm[i].num;
    }
    //打印商品信息
    for(int i = 0; i < goodsNum; i++ ){
            printf("%s %s %s %.2f  %d\n",
                  goodslist[i].type,goodslist[i].goodsID,goodslist[i].goodsname,goodslist[i].price,goodslist[i].repertory);
      }
    if(!writePermission)    printf("当前有其他订单正在提交,请稍候……\n");
    writePermission--;
    sem_wait(&writeNum);
    writeOrder(&n);
    initOrderInfo();
    return  totalPrice;
}
//卖家线程
void seller(long n){
    int i = 0;
    int retval;
    //接收缓冲区
    char rcv_buf[1024];
    //发送缓冲区
    char send_buf[1024];
    float totalPrice = 0;
    int client_len = 0;
    int rcv_num;
    pthread_t tid;
    tid = pthread_self();
    printf("服务器seller线程id=%lu使用套接字%d,n=%ld与客户端进行对话开始……\n",tid,client_sockfd[n],n);
    printf("服务器seller线程向该客户端发送客户端商品信息\n");
    //把商品信息发送给客户端
    write(client_sockfd[n],goodslist,sizeof(goodslist));
    printf("服务器信息已发送\n");
    //监听客户端是否断开服务或发来订单
    char tidString[10];
    do{
            printf("等待客户端发来信号……\n");
            sleep(2);
            //将接收缓冲区重置
            memset(rcv_buf,0,1024);
            rcv_num = read(client_sockfd[n],rcv_buf,sizeof(rcv_buf));
            if(rcv_num == 0)    continue;
            else if(strcmp(rcv_buf,"submitForm")== 0){
                initOrderInfo();
                read(client_sockfd[n],orderForm,sizeof(orderForm));
                getOrderInfo();
                memset(send_buf,0,1024);
                memset(tidString,0,10);
                itoa(tid,tidString,10);
                sprintf(send_buf, "服务器seller线程id=%s已经收到订单,正在处理……\n",tidString); 
                write(client_sockfd[n],send_buf,sizeof(send_buf));
                totalPrice = calcTotalPrice(n);
               printf("总价为%.2f\n",totalPrice);
                write(client_sockfd[n],&totalPrice,sizeof(totalPrice));
                printf("截至订单号%d处理完成",sellid);
                if(totalPrice){
                getGoodsInfo();
                write(client_sockfd[n],goodslist,sizeof(goodslist));
                }
                }
            else if(strcmp(rcv_buf,"!q") == 0){
                printf("客户端发来退出请求。\n");break;         
            }
    }while (strncmp(rcv_buf,"!q",2) != 0);   
    printf("服务器线程 tid = %lu,套接字%d,n = %ld与客户机对话完毕\n",tid,client_sockfd[n],n);
    close(client_sockfd[n]);
    client_sockfd[n] = -1;
    curLink--;
    printf("线程tid=%lu即将销毁,当前server连接数为%d\n",tid,curLink);
    sem_post(&mutex);
    pthread_exit(&retval);
}
int main(){
    char recv_buf[1024];
    char send_buf[1024];
    int client_len = 0;
    pthread_t thread;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    server_sockfd = socket(AF_INET,SOCK_STREAM,0);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr= htonl(INADDR_ANY);
    server_addr.sin_port = htons(8081);
    long i = 0;
    int ret = bind(server_sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    printf("ret = %d\n",ret);
    printf("服务器开始监听……\n");
    listen(server_sockfd,maxLinkNum);
    signal(SIGCHLD,SIG_IGN);
    printf("输入!q,服务结束。\n");
    pthread_create(&thread,NULL,serverQuit,NULL);
    for (int i = 0; i < maxLinkNum; i++)
        client_sockfd[i] = -1;
     printf("当前连接数为0(<=%d)\n",maxLinkNum);
     //初始化信号量
    sem_init(&mutex,0,maxLinkNum);
    sem_init(&writeNum,0,writePermission);
    while (1)
    {
        for(i = 0; i < maxLinkNum;){
            if(client_sockfd[i] == -1 )    break;
            i++;
        }
        // for(int j = 0; j < maxLinkNum; j++)
        //     printf("client_sockfd[%d]=%d\n",j,client_sockfd[j]);
        if(i == maxLinkNum){
            printf("已到达最大连接数,请等待其他用户释放连接……\n");
            sem_wait(&mutex);
            continue;
        }
        client_len = sizeof(client_addr);
        printf("服务器开始accept……i = %ld\n",i);
        getGoodsInfo();
        client_sockfd[i] = accept(server_sockfd,(struct sockaddr *)&client_addr,&client_len);
        curLink++;
        printf("当前连接数为%d(<=%d)\n",curLink,maxLinkNum);
        read(client_sockfd[i],&client_pid[i],sizeof(client_pid[i]));
        printf("连接来自:连接套接字号=%d,IP Addr = %s,Port = %d\n",client_sockfd[i],inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
        pthread_create(malloc(sizeof(pthread_t)),NULL,(void *)(&seller),(void *)i);
        sem_wait(&mutex);
    }
}

③程序功能以及部分实现过程

程序主要分为两个部分,客户端client与服务器server,当client启动后会检测服务器server是否启动,若没有启动,则给出提示并停止运行。因此,我们需要先启动server。

server启动后会做以下事情:创建服务器套接字,监听主机上的8081端口,接收来自任何ip地址的请求,部分实现代码如下:

1.    struct sockaddr_in server_addr;  
2.    struct sockaddr_in client_addr;  
3.    server_sockfd = socket(AF_INET,SOCK_STREAM,0);  
4.    server_addr.sin_family = AF_INET;  
5.    server_addr.sin_addr.s_addr= htonl(INADDR_ANY);  
6.    server_addr.sin_port = htons(8081);   
7.    int ret = bind(server_sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));  
8.    printf("ret = %d\n",ret);  
9.    printf("服务器开始监听……\n");  
10.    listen(server_sockfd,maxLinkNum);  
11.    signal(SIGCHLD,SIG_IGN); 

若没有client提出连接请求,上述代码段的第10行就会阻塞,直到有客户端发来请求,当接收到客户端发来的请求时,server会根据当前的连接数决定是否立即相应请求,主要由以下两个变量控制。

1.    //最大连接数  
2.    #define maxLinkNum 5  
3.    //当前连接数  
4.    int curLink;  

若当前以达到最大连接数,则发生阻塞,大致实现过程如下:

1.    for(i = 0; i < maxLinkNum;){  
2.                if(client_sockfd[i] == -1 )    break;  
3.                i++;  
4.            }  
5.            // for(int j = 0; j < maxLinkNum; j++)  
6.            //     printf("client_sockfd[%d]=%d\n",j,client_sockfd[j]);  
7.            if(i == maxLinkNum){  
8.                printf("已到达最大连接数,请等待其他用户释放连接……\n");  
9.                sem_wait(&mutex);  
10.                continue;  
11.            }  
12.    ……………………

反之,若未达到最大连接数,服务器就会创建一个seller线程,为该client提供服务。

lient_len = sizeof(client_addr);  
2.            printf("服务器开始accept……i = %ld\n",i);  
3.            getGoodsInfo();  
4.            client_sockfd[i] = accept(server_sockfd,(struct sockaddr *)&client_addr,&client_len);  
5.            curLink++;  
6.            printf("当前连接数为%d(<=%d)\n",curLink,maxLinkNum);  
7.            read(client_sockfd[i],&client_pid[i],sizeof(client_pid[i]));  
8.            printf("连接来自:连接套接字号=%d,IP Addr = %s,Port = %d\n",client_sockfd[i],inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));  
9.            pthread_create(malloc(sizeof(pthread_t)),NULL,(void *)(&seller),(void *)i);  
10.            sem_wait(&mutex);  

连接成功后,服务器就会获取goods.txt文件中的商品信息,并发送出去。

1.    void getGoodsInfo(){  
2.        FILE* fp;  
3.        initGoodsInfo();  
4.        if ((fp = fopen("/home/zzy/桌面/VSWorkSpace/goods.txt", "r")) == NULL)  
5.        {  
6.            printf("Can't open the goodsfile!\n"); exit(0);  
7.        }  
8.        else{  
9.            while (!feof(fp))  
10.            {  
11.                //从goods.txt文件中读取数据保存到结构体数组中  
12.                fscanf(fp,"%s",goodslist[goodsNum].type);  
13.                fscanf(fp,"%s",goodslist[goodsNum].goodsID);  
14.                fscanf(fp,"%s",goodslist[goodsNum].goodsname);  
15.                fscanf(fp,"%f%d\n",&goodslist[goodsNum].price,&goodslist[goodsNum].repertory);  
16.               // fscanf(fp,"%s%s%s%f%d\n",  
17.               //     goodslist[goodsNum].type,goodslist[goodsNum].goodsID,goodslist[goodsNum].goodsname,&goodslist[goodsNum].price,&goodslist[goodsNum].repertory);  
18.                goodsNum++;  
19.            }  
20.            fclose(fp);  
21.            //打印商品信息  
22.        //      for(int i = 0; i < goodsNum; i++ ){  
23.        //         printf("%s %s %s %.2f  %d\n",  
24.        //               goodslist[i].type,goodslist[i].goodsID,goodslist[i].goodsname,goodslist[i].price,goodslist[i].repertory);  
25.        //   }  
26.             return;  
27.        }  
28.    }  

这里商品信息主要采用结构体数组的方式进行保存,goodsItems结构体的定义如下:

1.    //商品清单结构体  
2.    typedef struct goodsItems  
3.    {  
4.        char type[20];  
5.        char goodsID[12];  
6.        char goodsname[10];  
7.        float price;  
8.        int repertory;  
9.    }goodsItems; 

结构体主要包含的内容有:商品类型(type),商品ID号(goodsID)、商品名称(goodsname)、商品单价(price)、库存(repertory)。goods.txt文件的初始内容如下:

1.    食品类 100001 苹果 1.50 487  
2.    食品类 100002 橘子 0.50 1000  
3.    食品类 100003 香蕉 0.50 2000  
4.    食品类 110001 饼干 0.50 5000  
5.    食品类 110002 面包 1.00 2000  
6.    食品类 110003 糖果 0.10 9000  
7.    食品类 110004 薯片 5.00 1000  
8.    食品类 110005 巧克力 10.00 1000  
9.    食品类 110006 方便面 5.00 2000  
10.    生活用品类 200001 香皂 10.00 4000  
11.    生活用品类 200002 沐浴露 20.00 5000  
12.    生活用品类 200003 洗衣液 25.00 1000  
13.    生活用品类 200004 洗衣粉 20.00 2000  
14.    生活用品类 200004 洗发水 15.00 1000  

共有14条商品信息。

客户端client中可以选择添加商品至购物车,然后用户可以选择将购物车的内容作为订单提交给服务器,订单(OrderForm)也是一个结构体数组,定义如下:

1.    //订单结构体  
2.    typedef struct orderFormItem{  
3.        char goodsID[12];  
4.        int num;  
5.    }orderFormItem;  

客户端(client)在提交订单后,会等待服务器返回订单的总价,若返回的总价为0,说明订单没有创建成功,服务器当前处于seller线程处于阻塞状态或者是商品已被其他客户端购买。

1.    case 3:  
2.            submitOrderForm(sockfd,orderNum);  
3.            if(!totalPrice) printf("订单提交失败,请稍后再试\n");  
4.            else {  
5.                printf("订单提交成功!\n");  
6.                printf("来自服务器:您所选商品价格
7.                           为:%.2f\n",totalPrice);initOrderInfo();orderNum = 0;  
8.                printf("系统即将刷新……\n");sleep(10);  
9.                system("clear");goodsNum = getGoods(sockfd);printGoodsAndOpe(goodsNum);  
10.                }  
11.            break;  

服务器(server)则根据提交的订单调用calcTotalPrice函数计算订单价格,订单中的商品库存也会减少。在修改商品库存期间不能有其他线程也在修改库存或写入文件,否则该服务器线程会进入阻塞状态,等待先修改的线程结束后再进行修改。

1.    //计算总价  
2.    float calcTotalPrice(long n){  
3.        int oderFormNum = 0;  
4.        int repertory_tmp = 0;  
5.        int goods_index;  
6.        //用于返回判断writeOrder线程是否正确修改文件  
7.        //用于传递要修改的商品下标  
8.        long write_flag = -1;  
9.        float totalPrice = 0;  
10.        for(int i = 0; i < MAX_ORDERFORM_ITEMS; i++){  
11.            if(orderForm[i].num == 0)   continue;  
12.            goods_index = 0;  
13.            goods_index = searchGoodsInfo(orderForm[i].goodsID);  
14.            write_flag = i;  
15.            //repertory_tmp = goodslist[goods_index].repertory;  
16.            //若有其他订单正在处理,writeOrder线程将会阻塞。  
17.            //if(!writePermission)    printf("当前有其他订单正在提交,请稍候……\n");  
18.            //writePermission--;  
19.            //sem_wait(&writeNum);  
20.            writeList(&write_flag);  
21.            if(write_flag == -1) {printf("库存修改失败!\n");return 0;}  
22.            totalPrice += (float)goodslist[goods_index].price*orderForm[i].num;  
23.        }  
24.        //打印商品信息  
25.        for(int i = 0; i < goodsNum; i++ ){  
26.                printf("%s %s %s %.2f  %d\n",  
27.                      goodslist[i].type,goodslist[i].goodsID,goodslist[i].goodsname,goodslist[i].price,goodslist[i].repertory);  
28.          }  
29.        //if(!writePermission)    printf("当前有其他订单正在提交,请稍候……\n");  
30.       // writePermission--;  
31.      //  sem_wait(&writeNum);  
32.        writeOrder(&n);  
33.        initOrderInfo();  
34.        return  totalPrice;  
35.    }  

④程序运行结果

i.先启动客户端

可以看到,由于server没有启动,client启动后会停止,并给出提示。

(图1-1 先启动client)

ii.先启动服务器

服务器启动后,将监听client是否连接。

(图1-2 先启动server)

此时,我们再启动一个client,看服务器的提示信息。

(图1-3 启动一个client)

可以看到,当server与client建立连接后,server会打印出client的信息(IP地址,端口号,socket等),并将服务器从goods.txt获得的商品信息发送给client,这时client中显示的界面如下:

(图1-4 client界面)

iii.在client中挑选商品

我们可以在client的中挑选商品,挑选的商品会进入购物车中,不会被提交到server。

(图1-5 client挑选商品)

我们可以多次挑选相同的商品,client会将orderForm相同的部分其合并。

nt会将orderForm相同的部分其合并。

(图1-6 多次挑选相同的商品)

iv.查看购物车内容

选择若干商品加入购物车后,可以进行查看。

(图1-7 查看购物车的内容)

v.提交订单

选择提交订单后,可以将当前购物车里的商品提交给server,server会进行修改goods.txt的库存以及返回购物车商品的价格。

(图1-8 将订单提交至服务器)

等待系统刷新后,订单的库存量就会发生变化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值