文章目录
- 前言
- 一、客户端设计
-
- 客户端设计部分代码详解
- 二、服务端设计
-
- 服务端设计部分代码详解
- 三、运行程序
- 总结
前言
今天是学校网络编程的第三天,前面我们学习了boost::asio同步读写的api函数,现在将前面的api串联起来,做一个能跑起来的客户端和服务器。但请注意!,之前讲过的api函数部分代码我下面将不会再此详细注解,如有不懂,请回顾之前知识点
客户端和服务器采用阻塞的同步读写方式完成通信
一、客户端设计
客户端设计基本思路是根据服务器对端的ip和端口创建一个endpoint,然后创建socket连接这个endpoint,之后就可以用同步读写的方式发送和接收数据了。
#include<boost/asio.hpp>
#include <iostream>
const int MAX_LENGTH = 1024;//表示发送和接收最大长度为1024字节
int main()
{
try
{
//创建上下文服务
boost::asio::io_context ios;
//创建endpoint
boost::asio::ip::tcp::endpoint
remote_ep(boost::asio::ip::make_address("127.0.0.1"), 10086);
//127.0.0.1是本机的回路地址 因为客户端和服务端在同一地址
//服务器就是本机
boost::asio::ip::tcp::socket sock(ios, remote_ep.protocol());
boost::system::error_code error = boost::asio::error::host_not_found;
sock.connect(remote_ep,error);
if (error)
{
std::cout << "connect failed,code is:" << error.value() <<
"error message is" << error.message() << std::endl;
return 0;
}
std::cout << "请输入需要发送的信息:";
char request[MAX_LENGTH];
std::cin.getline(request, MAX_LENGTH);
size_t request_length = strlen(request);//获取实际要发送的数据长度
//发送数据
boost::asio::write(sock, boost::asio::buffer(request, request_length));
char reply[MAX_LENGTH];//存储接收数据
size_t reply_length = boost::asio::read(sock, boost::asio::buffer(reply, reply_length));
std::cout << "回复:";
std::cout.write(reply, reply_length);
std::cout << "\\n";
}
catch (std::exception&e)
{
std::cerr << "Exception is: " << e.what() << std::endl;
}
}
客户端设计部分代码详解
在这里,我解释一下上述部分代码
(1)关于host_not_found函数 boost::asio::error::host_not_found 是 Boost.Asio 库中定义的一个错误码,用于指示在尝试解析主机名时发生错误。具体来说,当程序试图将一个主机名(如域名)转换为对应的 IP 地址时,如果该主机名不存在或无法解析,就会触发这个错误码。
(2)std::cin.getline(request, MAX_LENGTH);关于这里的getline函数 1.如果使用的是cin.getline那么()中的第一个参数为char数组,第二个参数为存储长度length,但实际存储长度为length-1,
2.如果使用的是getline(),那么中间参数第一个为cin表示输入,第二个为string字符串
(3).cout.write函数 这里直接将返回值输出到控制台std::cout.write(reply, reply_length) << std::endl;,第一个参数为char 数组,第二个参数为长度
二、服务端设计
session函数 创建session函数,该函数为服务器处理客户端请求,每当我们获取客户端连接后就调用该函数。在session函数里里进行echo方式的读写,所谓echo就是应答式的处理
server函数 server函数根据服务器ip和端口创建服务器acceptor用来接收数据,用socket接收新的连接,然后为这个socket创建session。
#include<boost/asio.hpp>
#include <iostream>
#include<set>
#include<memory>
const int max_length = 1034;
typedef std::shared_ptr<boost::asio::ip::tcp::socket> socket_ptr;
std::set<std::shared_ptr<std::thread>> thread_set;
//专门处理某一个客户端连接上来进行收发数据
void session(socket_ptr sock)//这个session是跑在线程中的
{
try
{
for (;;)//循环处理 客户端发给服务端 服务端再返回给客户端
{
char data[max_length];//用来缓存接收的数据
memset(data, '\\0', max_length);
boost::system::error_code error;
//这里我们用read
/*size_t length = boost::asio::read
(sock, boost::asio::buffer(data, max_length), error);*/
//上面这里这种写法其实不友好 这里需要读取1024个字节的数据
//才会返回,但如果客户端只发送了10个字节的数据呢?
//这就会一直等着不动
size_t length = sock->read_some(boost::asio::buffer
(data, max_length));
if (error == boost::asio::error::eof)//eof表示对端关闭
{
std::cout << "connection closed by peer" << std::endl;
break;
}
else if (error)//其他错误就比较严重了 我们要把它抛出去
{
throw boost::system::system_error(error);
}
std::cout << "receive from " << sock->
remote_endpoint().address().to_string() << std::endl;
std::cout << "receive message is " << data << std::endl;
//将搜的数据回传给对方
boost::asio::write(*sock, boost::asio::buffer(data, length));
std::cout << "Reply sent to client." << std::endl;
}
}
catch (std::exception& e)
{
std::cerr << "Exception in thread: " <<
e.what() << "\\n" << std::endl;
}
}
//接收客户端的连接
void server(boost::asio::io_context& io_context, unsigned short port)
{
//传入上下文 第二个参数是绑定本地的地址
boost::asio::ip::tcp::acceptor a
(io_context, boost::asio::ip::tcp::endpoint
(boost::asio::ip::tcp::v4(),port));
for (;;)
{
//此时来了一个客户端 我们专门生成一个socket来处理这里客户端的请求
socket_ptr socket(new boost::asio::ip::tcp::socket(io_context));
a.accept(*socket);
//单独创建一个session 让这个socket在这个session去处理这一个客户端的请求
//这个session是跑在独立的线程里面 这样不会影响其他客户端的连接和请求
auto t = std::make_shared<std::thread>(session, socket);
thread_set.insert(t);
}
}
int main()
{
try
{
boost::asio::io_context ioc;
server(ioc, 10086);
//如果主线程acceptor崩了 但子线程socket还在服务每一个客户端的接收
//子线程不能受影响 所有我们要等待子线程的完成才能关闭
for (auto& t : thread_set)
{
t->join();//join():
//阻塞当前线程,直到被调用的线程完成。
}
}
catch (std::exception& e)
{
std::cerr << "Exception1 " << e.what() << "\\n";
}
return 0;
}
服务端设计部分代码详解
(1).首先我们这里用了多线程的方式来写,在server中,当有一位客户端来了,我们就生成一个单独的socket去为他服务并且设计了一个线程的集合,然后将这个socket和这个客户端包装为一个线程session,这样不会导致如果我这个socket还没完,又来了一个客户端无法解决的情况
(2).代码中,socket_ptr是一个std::shared_ptr<boost::asio::ip::tcp::socket>。每个客户端连接都会创建一个新的套接字对象,并通过智能指针进行管理。这样可以确保套接字资源在不再需要时被正确释放。 在多线程环境中,手动管理动态内存可能会导致竞态条件或死锁等问题。智能指针通过原子操作管理引用计数,确保在多线程环境下也能安全地使用。
三、运行程序
总结
同步读写的优劣 1 同步读写的缺陷在于读写是阻塞的,如果客户端对端不发送数据服务器的read操作是阻塞的,这将导致服务器处于阻塞等待状态。 2 可以通过开辟新的线程为新生成的连接处理读写,但是一个进程开辟的线程是有限的,约为2048个线程,在Linux环境可以通过unlimit增加一个进程开辟的线程数,但是线程过多也会导致切换消耗的时间片较多。 3 该服务器和客户端为应答式,实际场景为全双工通信模式,发送和接收要独立分开。 4 该服务器和客户端未考虑粘包处理。 综上所述,是我们这个服务器和客户端存在的问题,为解决上述问题,我们在接下里的文章里做不断完善和改进,主要以异步读写改进上述方案。 当然同步读写的方式也有其优点,比如客户端连接数不多,而且服务器并发性不高的场景,可以使用同步读写的方式。使用同步读写能简化编码难度。
本文章主要代码注解我都写到代码旁的注解了,请大家多仔细看,有错误或者有不明白的地方请发表在评论区,一起讨论,此同步的方式写的大家了解即可,现在基本上不会用这种写法,接下来几天我们将学习异步
评论前必须登录!
注册