赞
踩
本文总结对TCP协议的学习以及对比c++与go语言在socket网络编程中的实现。具体而言从TCP以及socket编程原理出发。然后分别实现了基于c++以及go语言的网络通信并发服务器以及客户端。
序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
控制位:
ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。
IP 层是「不可靠」的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。
如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。
因为 TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。
简单来说就是,用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括 Socket、序列号和窗口大小称为连接。
所以我们可以知道,建立一个 TCP 连接是需要客户端与服务端达成上述三个信息的共识。
Socket:由 IP 地址和端口号组成
序列号:用来解决乱序问题等
窗口大小:用来做流量控制
socket是计算机网络中一种通信机制,它可以用于在不同的进程之间进行数据交换。在socket机制中,服务器和客户端之间建立连接,然后它们可以通过这个连接相互发送数据。
Socket的基本概念
IP地址:用于识别Internet上的主机,IPv4地址通常由4个8位数字组成。
端口号:用于标识一个进程,01023是系统保留端口,102465535为动态端口。
协议:TCP协议提供面向连接的服务,确保数据传输的可靠性;UDP协议则提供无连接服务,更适合实时应用。
Socket的类型
Socket有两种类型:
流式Socket(SOCK_STREAM):使用面向连接的TCP协议进行通信。
数据报式Socket(SOCK_DGRAM):使用无连接的UDP协议进行通信。
步骤:
服务端和客户端初始化 socket,得到文件描述符;
服务端调用 bind,将 socket 绑定在指定的 IP 地址和端口;
服务端调用 listen,进行监听;
服务端调用 accept,等待客户端连接;
客户端调用 connect,向服务端的地址和端口发起连接请求;
服务端 accept 返回用于传输的 socket 的文件描述符;
客户端调用 write 写入数据;服务端调用 read 读取数据;
客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。
这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。
成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。
对于一个网络通讯服务器,我们需要定义三个东西。
1、定义处理消息的回调函数。
2、定义用来并发执行的连接函数。
3、定义服务器启动函数,用来创建socket以及绑定端口号。
流程就是启动函数结构ip以及端口号还有执行消息处理的回调函数。
所以头文件如下:
typedef void* (*onMessageFunctionPtr)(void*);
class CPPServer {
private:
/* data */
int server_socket;
void ConnectMultiThread(onMessageFunctionPtr);
public:
void StartServer(string ip, int port, onMessageFunctionPtr);
};
1、网络编程中,需要使用套接字进行通信。在服务端,需要创建一个套接字对象。可以使用 socket() 函数来创建一个新的套接字。
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
// 创建失败的处理,可以进行记录或输出错误信息等操作。
LOG_ERR("server_socket create err!!");
}
2、绑定地址和端口,以及定义协议族
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr(ip.c_str());
server_address.sin_port = htons(port);
int result = bind(server_socket, (struct sockaddr*)&server_address,
sizeof(server_address));
if (result == -1) {
// 绑定失败的处理,可以进行记录或输出错误信息等操作。
LOG_ERR("bind server_socket err!!");
return;
}
3、一旦套接字绑定到了地址和端口上,就可以开始监听连接请求。可以使用 listen()函数来监听连接请求。并且转到连接处理函数。最后关闭套接字释放资源。
result = listen(server_socket, 5);
if (result == -1) {
// 监听失败的处理,可以进行记录或输出错误信息等操作。
LOG_ERR("listen err!!");
return;
}
LOG_INFO("start service at ip:%s port:%d", ip.c_str(), port);
ConnectMultiThread(onMessage);
close(server_socket);
一个死循环不断接受来自客户端的连接,每一次连接成功之后就会开启一个线程取处理客户端的信息。
while (1) {
struct sockaddr_in client_address;
int len = sizeof(client_address);
int newsockfd = accept(server_socket, (struct sockaddr*)&client_address,
(socklen_t*)&len
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。