服务器开发之简单的TCP回射服务器(二):客户端程序

客户端程序需要输入服务器的IP地址,与服务器建立连接。代码如下:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <libgen.h>
#include <signal.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <iostream>

#include "sum.h"

#define SERV_PORT 9877
#define MAXLINE 4096

using namespace std;

// 输入一行文本发送给服务器,并接受服务器的回射
void str_cli(istream &is, int sockfd)
{
    ssize_t m, n; // m存储write的字节数 n为read的字节数
    char sendline[MAXLINE], recvline[MAXLINE];
    //while(is >> sendline)
    while((is.getline(sendline, 50)) != NULL)
    {
        m = send(sockfd, sendline, strlen(sendline), 0);
        if((n = recv(sockfd, recvline, MAXLINE, 0)) > 0)
        {
            cout << "send bytes: " << m << endl;
            cout << "recv bytes: " << n << endl;
            cout << "recv data: " << recvline << endl;
            memset(recvline, '\0', MAXLINE);
        }
    }
}

// 字符串接受处理函数
void str_cli_num(istream &is, int sockfd)
{
    ssize_t m, n; // m存储write的字节数 n为read的字节数
    char sendline[MAXLINE], recvline[MAXLINE];
    //while(is >> sendline)
    while((is.get(sendline, 50)) != NULL)
    {
        is.ignore();
         m = send(sockfd, sendline, strlen(sendline), 0);
        if((n = recv(sockfd, recvline, MAXLINE, 0)) > 0)
        {
            cout << "send bytes: " << m << endl;
            cout << "recv bytes: " << n << endl;
            cout << "sum is: " << recvline << endl;
        }
    }
}

// 二进制整数收发处理函数
void str_cli_byte(istream &is, int sockfd)
{
    char sendline[MAXLINE];
    ssize_t n;
    struct args args;
    struct result result;
    // cin 遇到space/enter/tab均停止(输入不能包含空格)
    // cin.get()可以读入空格
    while((is.get(sendline, 50)) != NULL)
    {
        is.ignore(); // 此处是为了去掉上次输入结束的enter键,不然第二个数变成了enter(的ASCII码)
        if(sscanf(sendline, "%ld%ld", &args.arg1, &args.arg2) != 2)
        {
            cout << "invalid input: " << sendline;
            continue;
        }
        send(sockfd, &args, sizeof(args), 0);
        if((n = recv(sockfd, &result, MAXLINE, 0)) > 0)
        {
            cout << "num1 is " << args.arg1 << endl;
            cout << "num2 is " << args.arg2 << endl;
            cout << "sum is: " << result.sum << endl;
        }
    }
}

// 测试dup函数
void str_dup(int fd)
{
    char recvdata[MAXLINE];
    int n;
    while((n = recv(fd, recvdata, MAXLINE, 0) > 0))
    {
        cout << recvdata << endl;
        memset(recvdata, '\0', strlen(recvdata));
    }
}

// 测试sendfile函数:零拷贝高效率传输文件
void str_cli_sendfile(int fd)
{
    char recvdata[MAXLINE];
    int n;
    while((n = recv(fd, recvdata, MAXLINE, 0) > 0))
    {
        cout << recvdata << endl;
        memset(recvdata, '\0', strlen(recvdata));
    }
}

// 测试select系统调用:同时监听标准输入和套接字,
// 标准输入可读则将读入的数据发送到服务器,套接字可读则从套接字读取数据显示到标准输出。
 void str_cli_select(FILE *fp, int fd)
 {
     int maxfdp1;  // 文件描述符的最大值
     fd_set rset;  //fd_set结构体
     char sendline[MAXLINE], recvline[MAXLINE];

     FD_ZERO(&rset);
     for( ; ; )
     {
         // 每次调用select前都要为fd_set重新设置文件描述符,因为时间发生后,文件描述符将被内核更改
         FD_SET(fileno(fp), &rset);
         FD_SET(fd, &rset);
         maxfdp1 = max(fileno(fp), fd) + 1;
         // select系统调用:在一段指定时间内,监听用户感兴趣的文件描述符上的可读/可写/异常等事件。
         // #include <sys/select.h>
         // int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
         // 参数:nfds指定被监听文件描述符的总数;readfds/writefds/exceptfds分别为可读/可写/异常等事件
         // 的文件描述符的集合;timeout参数设置超时时间。
         // 返回值:成功返回就绪(可读/可写/异常)文件描述符的总数,超时返回0,失败返回-1并设置errno
         int ret = select(maxfdp1, &rset, NULL, NULL, NULL);
         assert(ret != -1);
         if(FD_ISSET(fd, &rset)) // fd可读
         {
             if(recv(fd, recvline, MAXLINE, 0) == 0)
                 cout << "recv error" << endl;
             else
                cout << "recvline: " << recvline << endl;
         }
         if(FD_ISSET(fileno(fp), &rset)) // is可读
         {
             if((fgets(sendline, MAXLINE, fp) != NULL) && (send(fd, sendline, strlen(sendline), 0) > 0))
                cout << "send success!" << endl;
         }
     }
 }

// 改进版的select调用测试:使用了select和shutdown,只要服务器关闭就能接到通知,
// shutdown允许我们正确的处理批量输入,并且此版本输入改为了针对缓冲区操作。
void str_cli_select_modified(FILE *fp, int fd)
 {
     int maxfdp1, stdineof = 0;  // 文件描述符的最大值
     fd_set rset;  //fd_set结构体
     char buf[MAXLINE];
     int n;

     FD_ZERO(&rset);
     for( ; ; )
     {
         // 每次调用select前都要为fd_set重新设置文件描述符,因为时间发生后,文件描述符将被内核更改

         if(stdineof == 0)
            FD_SET(fileno(fp), &rset);
         FD_SET(fd, &rset);
         maxfdp1 = max(fileno(fp), fd) + 1;
         int ret = select(maxfdp1, &rset, NULL, NULL, NULL);
         assert(ret != -1);
         if(FD_ISSET(fd, &rset)) // 套接字fd可读
         {
             if(recv(fd, buf, MAXLINE, 0) == 0)
             {
                 if(stdineof == 1)
                    return ;
                 else
                     cout << "recv error" << endl;
             }
             else
             {
                 cout << "recvline: " << buf << endl;
                 memset(buf, '\0', MAXLINE);
             }

         }
         if(FD_ISSET(fileno(fp), &rset)) // 标准输入fp可读
         {
             if((n = recv(fileno(fp), buf, MAXLINE, 0)) == 0)
             {
                 stdineof = 1;
                 shutdown(fd, SHUT_WR); // 发送FIN
                 FD_CLR(fileno(fp), &rset);
                 continue;
             }
             if(send(fd, buf, strlen(buf), 0) == -1)
                cout << "send errno is: " << errno << endl;
            else
                 cout << "send success!" << endl;
         }
     }
 }

int main(int argc, char* argv[])
{
    if(argc < 2)
    {
        cout << "usage: " << basename(argv[0]) << " ip_address port_number" <<endl;
        return 1;
    }

    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    assert( sockfd >= 0);

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

    if( connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
        cout << "connection failed" << endl;
    else
    {
        // str_cli(cin, sockfd);       // 输入一行文本发送给服务器,并接受服务器的回射
        // str_cli_num(cin, sockfd);  // 字符串接受处理函数
        // str_cli_byte(cin, sockfd);   // 二进制整数收发处理函数
        // str_dup(sockfd);     // 测试dup函数
        // str_cli_sendfile(sockfd); // 测试sendfile函数
        str_cli_select(stdin, sockfd); // 测试select系统调用
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值