之前实现的回声客户端按照先向服务器端发送数据,直到服务器端收到回声数据后,再传输下一批数据。很大程度会影响端与端之间的数据交换效率(存在一个等待周期)。那么有什么办法可以解决这个问题呢?
I/O分割
之前的回声客户端,采用的是单进程模式。我们不妨采用一种新的设计思路——用客户端的父进程接收数据,创建出子进程来发送数据,这样就可以使得同一客户端下不同进程分别去完成输入和输出操作。这样,无论服务器是否收到数据,都可以让客户端持续进行I/O操作。我们把这种处理方式称之为I/O分割。
通过I/O分割,可以一定程度上提高频繁交换数据的程序的性能。因为分割I/O之后,发送数据时无需再去考虑接收数据的情况,使得程序能够连续发送数据,进而提高数据的吞吐量。(在网速较慢的情况下,做了I/O分割的程序性能一般远高于未做分割的)
代码实现
知道了I/O分割的意义所在,那么接下来,让我们用I/O分割思想去实现对之前回声客户端的优化。
echo_mclient.cpp
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void Read_routine(int sock, char *buf);
void Write_routine(int sock, char *buf);
void Sender_message(char *message);
int main(int argc, char *argv[])
{
int sock;
int port;
pid_t pid;
std::string ipAddress;
char buf[BUF_SIZE];
struct sockaddr_in serv_adr;
std::cout << "Please input IP and port that you want to connect:" << std::endl;
std::cin >> ipAddress >> port;
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
Sender_message((char *)"Socket creation error");
}
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(ipAddress.c_str());
serv_adr.sin_port = htons(port);
if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
{
Sender_message((char *)"Connect error!");
}
// 创建子进程
pid = fork();
// 如果是子进程,那么进行写操作(发送数据);父进程则进行读操作(接收数据)
pid == 0 ? Write_routine(sock, buf) : Read_routine(sock, buf);
close(sock);
return 0;
}
void Read_routine(int sock, char *buf)
{
while (1)
{
int str_len = read(sock, buf, BUF_SIZE);
if (str_len == 0)
{
return;
}
buf[str_len] = 0;
printf("Message from server: %s", buf);
}
}
void Write_routine(int sock, char *buf)
{
while (1)
{
fgets(buf, BUF_SIZE, stdin);
// 如果输入为q或Q,则关闭套接字写权限
if (!strcmp(buf, "Q\n") || !strcmp(buf, "q\n"))
{
// 关闭套接字写权限
shutdown(sock, SHUT_WR);
return;
}
write(sock, buf, strlen(buf));
}
}
void Sender_message(char *message)
{
puts(message);
exit(1);
}