基础知识
TCP和IP协议
- TCP(传输控制协议):TCP是一种网络通信协议,它在互联网协议套件(TCP/IP)中负责确保数据的可靠传输。TCP提供了一种可靠的、面向连接的通信方式。这意味着在数据开始传输之前,两个网络设备(比如计算机)之间必须首先建立一个连接。TCP还负责确保数据的完整性和顺序正确。
- IP协议(互联网协议):IP协议是另一种关键的网络通信协议,负责在网络上路由和传输数据包。IP有两个主要版本:IPv4和IPv6。IPv4是目前最广泛使用的版本,但由于地址空间限制,IPv6正在逐渐被采用。
Socket
- Socket是什么:Socket是网络通信的端点。您可以将其想象为电话插座,它是网络上两个程序(运行在不同计算机上或同一计算机上的不同进程)进行数据交换的接口。
- Socket的用途:Socket使得程序可以读写网络上的数据。在创建Socket时,您需要指定使用的协议(如TCP)。一旦建立了Socket连接,数据就可以通过这个连接在网络上进行传输。
TCP连接和IP协议的关系
- 在进行TCP网络通信时,通常会使用IP协议(无论是IPv4还是IPv6)。这是因为TCP负责在两个端点之间建立一个稳定的连接,并确保数据可靠地传输,而IP协议负责将数据包路由到正确的目的地。
- 当您在编程中创建一个TCP Socket时,您需要指定使用的IP协议版本。例如,在Boost.Asio中,您可以创建一个使用IPv4的TCP Socket(
asio::ip::tcp::socket),这意味着它将使用IPv4协议来路由数据。
网络编程基本流程
服务端
1)socket——创建socket对象。
2)bind——绑定本机ip+port。
3)listen——监听来电,若在监听到来电,则建立起连接。
4)accept——再创建一个socket对象给其收发消息。原因是现实中服务端都是面对多个客户端,那么为了区分各个客户端,则每个客户端都需再分配一个socket对象进行收发消息。
5)read、write——就是收发消息了。
客户端
1)socket——创建socket对象。
2)connect——根据服务端ip+port,发起连接请求。
3)write、read——建立连接后,就可发收消息了。
终端节点的创建
客户端
客户端可以通过对端的ip和端口构造一个endpoint,用这个endpoint和服务端通信。
步骤如下:
- 设置服务器
IP地址(string)和 PORT(unsigned short)
- 定义
boost::system::error_code状态码,用于存储解析 IP时候可能发生的错误
- 解析IP地址,获取
asio::ip::address对象
- 如果解析失败,反悔错误码
- 解析成功,根据
ip::address和PORT建立TCP连接,获取asio::ip::tcp::endpoint对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include "endpoint.h" #include <iostream> #include <boost/asio.hpp> #include <boost/system/error_code.hpp>
namespace asio = boost::asio;
int client_end_point() { std::string raw_ip_address = "127.0.0.1"; unsigned short port_num = 3333;
boost::system::error_code ec;
asio::ip::address ip_address = asio::ip::address::from_string(raw_ip_address, ec);
if (ec.value() != 0) { std::cout << "Failed to parse the IP address. Error code = " << ec.value() << ". Message: " << ec.message(); return ec.value(); }
asio::ip::tcp::endpoint ep(ip_address, port_num);
std::cout << "Endpoint created: IP Address = " << ep.address() << ", Port = " << ep.port() << std::endl;
return 0; }
int main() { int result = client_end_point();
if (result != 0) { std::cerr << "Error occurred during endpoint creation. Error code: " << result << std::endl; return result; }
std::cout << "Endpoint creation successful." << std::endl;
return 0; }
|
服务端
步骤如下:
- 定义端口号(unsigned short)
- 根据本机IPv6
asio::ip::address_v6::any()协议创建asio::ip::address对象
- 根据
ip::address和Port创建asio::ip::tcp::endpoint对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include "endpoint.h" #include <iostream> #include <boost/asio.hpp> #include <boost/system/error_code.hpp>
namespace asio = boost::asio;
int server_end_point() { unsigned short port_num = 3333;
asio::ip::address ip_address = asio::ip::address_v6::any();
asio::ip::tcp::endpoint ep(ip_address, port_num);
std::cout << "Server endpoint created: IP Address = " << ep.address() << ", Port = " << ep.port() << std::endl;
return 0; }
int main() { int result = server_end_point();
if (result != 0) { std::cerr << "Error occurred during server endpoint creation. Error code: " << result << std::endl; return result; }
std::cout << "Server endpoint creation successful." << std::endl;
return 0; }
|
Socket
客户端
步骤:
- 创建
asio::io_context对象
- 创建一个代表TCP协议的
asio::ip::tcp对象protocol
- 创建TCP套接字对象
asio::ip::tcp::socket,将它与asio::io_context对象关联
- 定义一个
boost::system::error_code变量
- 打开套接字(使用TCP协议protocol,错误码error_code)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include <iostream> #include <boost/asio.hpp> #include <boost/system/error_code.hpp>
namespace asio = boost::asio;
int create_tcp_socket() { asio::io_context ios;
asio::ip::tcp protocol = asio::ip::tcp::v4();
asio::ip::tcp::socket sock(ios);
boost::system::error_code ec;
sock.open(protocol, ec);
if (ec.value() != 0) { std::cout << "Failed to open the socket! Error code = " << ec.value() << ". Message: " << ec.message(); return ec.value(); }
return 0; }
int main() { int result = create_tcp_socket();
if (result != 0) { std::cerr << "Error occurred during socket creation. Error code: " << result << std::endl; return result; }
std::cout << "Socket creation successful." << std::endl;
return 0; }
|
服务端
需要生成一个acceptor的socket,用来接收新的连接。
步骤:
- 创建
asio::io_context对象
- 创建一个代表TCP协议的
asio::ip::tcp对象protocol
- 实例化一个接收器(Acceptor) 套接字对象
asio::ip::tcp::acceptor,参数是ios
- 定义一个
boost::system::error_code变量
- 打开套接字(使用TCP协议protocol,错误码error_code)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include <iostream> #include <boost/asio.hpp> #include <boost/system/error_code.hpp>
namespace asio = boost::asio;
int create_acceptor_socket() { asio::io_context ios;
asio::ip::tcp protocol = asio::ip::tcp::v6();
asio::ip::tcp::acceptor acceptor(ios);
boost::system::error_code ec;
acceptor.open(protocol, ec);
if (ec.value() != 0) { std::cout << "Failed to open the acceptor socket! Error code = " << ec.value() << ". Message: " << ec.message(); return ec.value(); }
return 0; }
int main() { int result = create_acceptor_socket();
if (result != 0) { std::cerr << "Error occurred during acceptor socket creation. Error code: " << result << std::endl; return result; }
std::cout << "Acceptor socket creation successful." << std::endl;
return 0; }
|
绑定acceptor
acceptor类型的socket,服务器要将其绑定到指定的端点,所有连接这个端点的连接都可以被接收到
步骤:
- 设置端口号port
- 创建一个端点
asio::ip::tcp::endpoint,利用IP,prot
- 创建并打开一个接收器套接字
asio::ip::tcp::acceptor,根据ios和协议
- 定义一个error_code变量
- 将接收器套接字(accecptor)绑定到端点(endpoint),
acceptor.bind(ep, ec)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <iostream> #include <boost/asio.hpp> #include <boost/system/error_code.hpp>
namespace asio = boost::asio;
int bind_acceptor_socket() { unsigned short port_num = 3333;
asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(), port_num);
asio::io_context ios; asio::ip::tcp::acceptor acceptor(ios, ep.protocol());
boost::system::error_code ec;
acceptor.bind(ep, ec);
if (ec.value() != 0) { std::cout << "Failed to bind the acceptor socket. Error code = " << ec.value() << ". Message: " << ec.message(); return ec.value(); }
return 0; }
int main() { int result = bind_acceptor_socket();
if (result != 0) { std::cerr << "Error occurred during acceptor socket binding. Error code: " << result << std::endl; return result; }
std::cout << "Acceptor socket binding successful." << std::endl;
return 0; }
|
客户端连接到指定的端点
步骤:
- 获取目标服务器的IP地址和端口号
- 创建一个指向目标应用程序的端点endpoint,需要ip和端口号
- 创建并打开一个套接字socket,需要ios和IP协议
- socket连接endpoint
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <iostream> #include <boost/asio.hpp> #include <boost/system/error_code.hpp>
namespace asio = boost::asio;
int connect_to_end() { std::string raw_ip_address = "127.0.0.1"; unsigned short port_num = 3333;
try { asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num);
asio::io_context ios;
asio::ip::tcp::socket sock(ios, ep.protocol());
sock.connect(ep);
} catch (boost::system::system_error& e) { std::cout << "Error occurred! Error code = " << e.code() << ". Message: " << e.what(); return e.code().value(); } return 0; }
int main() { int result = connect_to_end();
if (result != 0) { std::cerr << "Error occurred during connection. Error code: " << result << std::endl; return result; }
std::cout << "Connection successful." << std::endl;
return 0; }
|
服务端接收连接
步骤:
- 设置端口号
- 创建端点endpoint,需要IP地址和Port
- 创建
接收器套接字,需要ios和IP协议
- 接收器套接字
accecptor绑定指定endpoint
- 开始监听传入的连接请求
acceptor.listen(BACKLOG_SIZE)
- 创建一个活动套接字sock,需要ios
- 接收连接请求,将活动套接字连接到客户端
acceptor.accept(sock)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include <iostream> #include <boost/asio.hpp> #include <boost/system/error_code.hpp>
namespace asio = boost::asio;
int accept_new_connection() { const int BACKLOG_SIZE = 30;
unsigned short port_num = 3333;
asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(), port_num);
asio::io_context ios;
try { asio::ip::tcp::acceptor acceptor(ios, ep.protocol());
acceptor.bind(ep);
acceptor.listen(BACKLOG_SIZE);
asio::ip::tcp::socket sock(ios);
acceptor.accept(sock);
} catch (boost::system::system_error& e) { std::cout << "Error occurred! Error code = " << e.code() << ". Message: " << e.what(); return e.code().value(); } return 0; }
int main() { int result = accept_new_connection();
if (result != 0) { std::cerr << "Error occurred during accepting connection. Error code: " << result << std::endl; return result; }
std::cout << "Accepting connection successful." << std::endl;
return 0; }
|
概念和疑问
概念
端点,套接字,接收器套接字对于客户端和服务端的作用
客户端端点创建 (client_end_point):
- 设置目标服务器的IP和端口,创建客户端端点以连接服务器。
服务器端点创建 (server_end_point):
- 设置端口号,创建服务器端点以便监听来自客户端的连接。
创建TCP套接字 (create_tcp_socket):
创建接收器套接字 (create_acceptor_socket):
- 创建接收器套接字,用于服务器端接受客户端的连接请求。
绑定接收器套接字 (bind_acceptor_socket):
- 将接收器套接字绑定到一个特定的端点,用于监听来自该端点的连接请求。
连接到端点 (connect_to_end):
接收新连接 (accept_new_connection):
- 服务端监听并接受来自客户端的新连接请求。创建一个新的套接字与客户端进行通信。(注意,创建新的套接字通信)
端点,套接字,接收器套接字代码
| 组件 |
功能 |
参数 |
示例代码 |
应用场景 |
| boost::system::error_code |
错误代码对象 |
- |
boost::system::error_code ec; |
用于处理网络操作中的错误,如套接字打开或绑定失败。 |
| asio::ip::address |
表示一个IP地址 |
1.IP地址字符串 2.错误代码对象 |
asio::ip::address ip_address = asio::ip::address::from_string(“127.0.0.1”, ec); |
定义网络通信的IP地址,用于创建端点。 |
| asio::ip::tcp::endpoint |
表示一个TCP网络端点 |
1.ip_address 2.port_num |
asio::ip::tcp::endpoint ep(ip_address, port_num); |
用于定义TCP连接的目标地址和端口,客户端和服务器都会使用。 |
| asio::io_context |
提供I/O功能 |
- |
asio::io_context ios; |
用于管理异步I/O操作,是Boost.Asio程序的核心。 |
| asio::ip::tcp::socket |
TCP套接字,用于网络通信 |
1.io_context对象 2.协议 |
asio::ip::tcp::socket sock(ios); |
用于客户端发起连接和服务器与客户端之间的数据交换。 |
| asio::ip::tcp::acceptor |
接收器套接字,用于接受连接 |
1.io_context对象 2.协议 |
asio::ip::tcp::acceptor acceptor(ios, protocol); |
服务器端使用,用于监听和接受来自客户端的连接。 |
疑问
1. 关于地址解析
问题:根据string类型的ip地址解析,创建address对象,如果这个ip不是本机ip,是别的服务器的ip,就有可能解析不成功。如果是本机ip,一定可以成功。
答案:当你使用一个字符串类型的IP地址来创建asio::ip::address对象时,如果IP地址格式正确,它应该能够成功解析,无论这个IP是否属于本机。解析失败通常是由于格式错误或不支持的地址类型。
2. 创建端点endpoint对象需要address对象和端口
问题:创建端点endpoint对象需要address对象和端口。
答案:正确,asio::ip::tcp::endpoint对象的创建需要一个IP地址(asio::ip::address对象)和一个端口号。
3. 关于创建和打开socket
问题:创建socket对象需要ios,打开socket需要protocol和error_code?为啥需要打开呢?打开的时候在什么情况下会出错呢?
答案:asio::ip::tcp::socket对象需要一个asio::io_context对象来处理I/O操作。打开socket指的是使其准备好进行网络通信。如果指定的协议不支持,或者系统资源不足(如端口号被占用),打开socket可能会出错。
4. 关于acceptor
问题:accecptor也是一个接收器socket,也需要打开,并且流程跟socket一样?
答案:asio::ip::tcp::acceptor类似于一个特殊的socket,专门用于监听和接受来自客户端的连接。它也需要被打开和绑定到一个端点上,以便知道在哪个端口上监听。
5. 绑定acceptor
问题:绑定accecptor,是先创建,然后在将他绑定到endpoint上么?然后listen,就可以接受ip:port的消息了?
答案:你需要先创建一个acceptor,然后将其绑定到一个endpoint上(一个IP地址和端口号的组合)。一旦绑定并调用了listen(),它就开始监听该端点上的入站连接。
6. 客户端访问服务端
问题:客户端访问服务端,需要socket绑定endpoint(服务器的ip:port)。
答案:客户端的socket需要连接到服务器的endpoint(服务器的IP地址和端口号)。
7. 关于asio::ip::address_v6::any()
问题:asio::ip::address_v6::any(),这种代码只会在服务器上创建endpoint上使用,而且基本不会出错?
答案:asio::ip::address_v6::any()在服务器上创建一个可以接受任何IPv6地址的端点,这意味着它可以从任何IPv6地址接收连接。这通常不会出错,除非有底层网络配置问题。
8. 关于接收器端点的访问
问题:接收器接受服务器的端点,此时是不是只用(端点的ip,和端口号)访问?比如开放了公网ip,那只可以用“公网ip:端口”访问,如果用“127.0.0.1:3333”就不可以(如果服务器在本地)。
答案:如果服务器监听公网IP和端口,那么客户端必须使用这个公网IP和端口来连接。如果服务器监听的是本地地址(如127.0.0.1),那么只有本地客户端(在同一台机器上)可以使用这个地址和端口连接。
9. 理解asio::io_context的重要性
问题:如果不理解asio::io_context,是不是可以先跳过?
答案:asio::io_context是Boost Asio库的核心,负责管理所有I/O服务。虽然一开始可能不容易理解,但它在Asio编程中非常重要,建议不要跳过。随着实践的增加,你会逐渐理解它的用途和重要性。