客户端程序需要输入服务器的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;
}