Unix网络编程学习日记(一):半双工非阻塞socket客户端的实现

本文介绍了Unix网络编程中不同I/O模型的特点,重点讲解了半双工非阻塞socket客户端的实现。文章包含一个简单的客户端程序示例,该程序具有交互界面,能实现消息和文件的收发,但在服务端发送消息后,客户端需手动进入接收模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

编写一个socket通信客户端,首先要做的是确定I/O模型。这决定了程序的性能和试用场景,也决定了程序的大致框架。
下图来自《Unix网络编程》,直观地比较了各种I/O模型间的特点。
这里写图片描述

  • 阻塞式I/O
    收发消息过程中,若没有完成消息接受/发送工作,则程序阻塞在recv()/send()函数处。
  • 非阻塞式I/O
    收发消息过程中,若没有完成消息接受/发送工作,则recv()/send()立即返回,通过全局变量errno返回此时连接状态。但这种方式耗费cpu资源较高,不推荐使用。另外,如果单纯地想检测连接状态,可以使用select()函数。
  • I/O复用
    主要用在高并发场合。
  • 信号驱动I/O
    由SIGIO信号实现异步操作。不过由于TCP协议产生信号频繁,捕获信号并调用相应函数的意义不大,通常用于UDP应用。
  • 异步I/O
    只有这种I/O模型是真正意义上的异步I/O,有待学习。

以下是一个简单的半双工非阻塞式的socket客户端程序。所谓半双工是指不能同时收/发,非阻塞则指收/发过程不会使请求进程停滞。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BUFSIZE 64 //缓冲区容量
#define CACHECNT 16 //暂存字符串数目
int main()
{
    char select,buf[BUFSIZE],cache[CACHECNT][BUFSIZE];
    int sockfd,cacheNum = -1;;
    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) <0)
    {
        perror("Create socket failed");
        exit(-1);
    }
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(struct sockaddr_in));
    char serverIP[15];
    int serverPort;
    printf("server ip:");
    scanf("%s",serverIP);
    printf("server port:");
    scanf("%d",&serverPort);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(serverPort);
    addr.sin_addr.s_addr = inet_addr(serverIP);
    if(connect(sockfd,(struct sockaddr *)(&addr),sizeof(struct sockaddr)) < 0)
    {
        perror("Connection error!");
        exit(-1);
    }
    else
        printf("Connected:%s:%d",serverIP,serverPort);
    fcntl(sockfd,F_SETFL,O_NONBLOCK);//设置socket非阻塞模式
    for(;;)
    {
        getchar();
        puts("\n1.Receive msg\n2.Save file\n3.Send msg\n4.Send file\n0.Exit\n");
        printf("[command]");
        scanf("%c",&select);
        switch(select)
        {
        case '0':exit(0);break;
        case '1':
        {
            memset(buf,0,sizeof(buf));
            if(recv(sockfd,buf,sizeof(buf),0) <=0)
            {
                perror("[error]Recv failed");
                break;
            }
            else
            {
                cacheNum ++;
                strcpy(cache[cacheNum],buf);
                printf("[recv][%d]%s\n",cacheNum+1,cache[cacheNum]);
                if(cacheNum == CACHECNT)
                {
                    puts("[error]The buffer is full,please save into file!");
                    break;
                }
            }
        }break;
        case '2':
        {
            if(cacheNum == -1)
            {
                puts("[error]The buffer is empty!");
                break;
            }
            char filePath[BUFSIZE];
            printf("[path]");
            scanf("%s",filePath);
            FILE *fp;
            fp = fopen(filePath,"at");
            if(fp == NULL)
            {
                perror("[error]Open file failed");
                break;
            }
            else
            {   
                int i;
                for(i = 0;i <= cacheNum;i++)
                {
                    if(fputs(cache[i],fp) == EOF)
                    {
                        perror("[error]Write file failed");
                        break;
                    }
                    else
                    {
                        fputs("\n",fp);
                        printf("[write]%s\n",cache[i]);
                        memset(cache[i],0,sizeof(cache[i]));
                    }
                }
                cacheNum = -1;//清空接收缓存
                fflush(fp);
                fclose(fp);
            }
        }break;
        case '3':
        {
            memset(buf,0,sizeof(buf));
            printf("[send]");
            gets(buf);
            if(send(sockfd,buf,BUFSIZE,0) == -1)
            {
                perror("send failed");
                break;
            }
            else
            {
                printf("-->OK\n");
            }
        }break;
        case '4':
        {
            char readBuf[BUFSIZE],filePath[BUFSIZE];
            printf("[path]");
            scanf("%s",filePath);
            FILE *fp = NULL;
            fp = fopen(filePath,"r");
            if(fp == NULL)
            {
                perror("read file failed");
                break;
            }
            else
            {
                /*按行读取文件,每行发送一次,发送格式为#BEGIN#-data-#EOF#*/
                send(sockfd,"#BEGIN#",8,0);
                while((fgets(readBuf,BUFSIZE,fp))!=NULL)
                {
                    send(sockfd,readBuf,BUFSIZE,0);
                    printf("[send]%s",readBuf);
                    usleep(200000);
                }
                memset(buf,0,sizeof(buf));
                send(sockfd,"#EOF#",6,0);
                fclose(fp);
            }
        }break;
        default:break;
        }
    }
    return 0;
}

这里写图片描述
程序运行截图如图所示,有简单的交互界面,实现了消息和文本文件的收发功能。
由于使用了非阻塞式通信方式,在服务端发送消息后,客户端必须手动进入选项1才能接受消息。如果这期间服务端发来多条消息,则都会进入缓冲区,并合并输出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值