tcp

TCP(传输控制协议,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的通信协议。它是互联网协议套件(TCP/IP协议栈)的一部分,用于在网络上可靠地传输数据。

以下是 TCP 协议的主要特点和介绍:

  1. 可靠性: TCP 提供可靠的、面向连接的通信。通过使用确认机制和超时重传机制,确保数据的可靠性传输。如果数据包在传输过程中丢失或损坏,TCP 会负责重新发送丢失的数据。

  2. 面向连接: 在进行数据传输之前,TCP 首先在通信的两端建立连接。这个连接是全双工的,双方可以同时进行数据的收发。连接的建立、维护和释放是由 TCP 协议负责的。

  3. 流控制: TCP 使用流控制机制来防止发送方发送过多的数据导致接收方无法处理。通过窗口大小的动态调整,TCP 可以在发送和接收之间实现平衡。

  4. 拥塞控制: TCP 通过拥塞控制机制来适应网络的变化。它通过监测网络的拥塞状况来调整数据的传输速率,以避免过多的数据导致网络拥塞。

  5. 字节流传输: TCP 将数据看作是字节流,而不是分割成独立的消息。这意味着发送的数据可以按照任意大小的块进行划分,而接收方会将它们重新组装成完整的字节流。

  6. 面向字节流而非报文: TCP 将数据看作是一连串的字节而非报文。这使得它更加灵活,但也需要应用层协议来确定消息的边界。

    2024-01-04-113024.png

tcp服务端

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <iostream>
#include <thread>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <mutex>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>

std::mutex coutMutex;

std::string GetCurrentTimestamp()
{
auto now = std::chrono::system_clock::now();
auto time_point = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_point), "%Y-%m-%d %H:%M:%S");
return ss.str();
}

void HandleClient(int clientSocket)
{
char buffer[1024];
std::memset(buffer, 0, sizeof(buffer));
// 启动接收消息的线程
std::thread receiveThread([&]()
{
while(true)
{
// 读取客户端发送的数据
ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if(bytesRead <= 0)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error reading from client\n";
break;
}else{

std::lock_guard<std::mutex> lock(coutMutex);
std::cout << "[" << GetCurrentTimestamp() << "] Received from client: " << buffer << std::endl;

// 发送响应给客户端
const char* response = "Hello from server!";
ssize_t bytesSent = send(clientSocket, response, std::strlen(response), 0);
if(bytesSent <= 0)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error sending response to client\n";
break;
}
}
// 清空缓冲区
std::memset(buffer, 0, sizeof(buffer));
}
});

// 启动发送消息的线程
std::thread sendThread([&]()
{
while (true)
{
std::string message;
std::cout << "[" << GetCurrentTimestamp() << "] Enter message to send (or 'exit' to quit): ";
std::getline(std::cin, message);
// 发送消息到客户端
ssize_t bytesSent = send(clientSocket, message.c_str(), message.length(), 0);
if (bytesSent <= 0)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error sending message to client\n";
break;
}

if (message == "exit")
{
std::cout << "[" << GetCurrentTimestamp() << "] Exiting...\n";
break;
}
}
});
// 等待线程结束
receiveThread.join();
sendThread.join();
// 关闭客户端套接字
close(clientSocket);
}

int main() {
// 创建套接字
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error creating socket\n";
return -1;
}
int reuse = 1;
if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
std::cerr << "[" << GetCurrentTimestamp() << "] Error setting socket options\n";
close(serverSocket);
return -1;
}
// 设置服务器地址和端口
struct sockaddr_in serverAddress;
std::memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用的接口
serverAddress.sin_port = htons(8888);

// 绑定地址和端口
if(bind(serverSocket, reinterpret_cast<struct sockaddr*>(&serverAddress), sizeof(serverAddress)) == -1)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error binding address\n";
close(serverSocket);
return -1;
}
// 监听连接
if(listen(serverSocket, 5) == -1)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error listening for connections\n";
close(serverSocket);
return -1;
}

std::cout << "[" << GetCurrentTimestamp() << "] Server listening on port 8888\n";
while(true)
{
// 接受连接
struct sockaddr_in clientAddress;
socklen_t clientAddressLength = sizeof(clientAddress);
int clientSocket = accept(serverSocket, reinterpret_cast<struct sockaddr*>(&clientAddress), &clientAddressLength);
if(clientSocket == -1)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error accepting connection\n";
continue;
}
// 在新线程中处理客户端
std::thread(HandleClient, clientSocket).detach();
}
// 关闭服务器套接字
close(serverSocket);
return 0;
}

tcp客户端

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <iostream>
#include <thread>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <mutex>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>

std::mutex coutMutex;

std::string GetCurrentTimestamp()
{
auto now = std::chrono::system_clock::now();
auto time_point = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_point), "%Y-%m-%d %H:%M:%S");
return ss.str();
}

void ReceiveMessages(int clientSocket)
{
char buffer[1024];
std::memset(buffer, 0, sizeof(buffer));

while(true)
{
// 读取服务器发送的数据
ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if(bytesRead <= 0)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error reading from server\n";
break;
}else{
std::lock_guard<std::mutex> lock(coutMutex);
std::cout << "[" << GetCurrentTimestamp() << "] Received from server: " << buffer << std::endl;
}
// 清空缓冲区
std::memset(buffer, 0, sizeof(buffer));
}
}

void SendMessages(int clientSocket)
{
while(true){
std::string message;
std::cout << "[" << GetCurrentTimestamp() << "] Enter message to send (or 'exit' to quit): ";
std::getline(std::cin, message);

// 发送消息到服务器
ssize_t bytesSent = send(clientSocket, message.c_str(), message.length(), 0);
if(bytesSent <= 0)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error sending message to server\n";
break;
}

if(message == "exit")
{
std::cout << "[" << GetCurrentTimestamp() << "] Exiting...\n";
break;
}
}
}

int main()
{
// 创建套接字
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == -1)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error creating socket\n";
return -1;
}

// 设置服务器地址和端口
struct sockaddr_in serverAddress;
std::memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
serverAddress.sin_port = htons(8888);

// 连接到服务器
if (connect(clientSocket, reinterpret_cast<struct sockaddr*>(&serverAddress), sizeof(serverAddress)) == -1)
{
std::cerr << "[" << GetCurrentTimestamp() << "] Error connecting to server\n";
close(clientSocket);
return -1;
}

// 启动接收消息的线程
std::thread receiveThread(ReceiveMessages, clientSocket);

// 启动发送消息的线程
std::thread sendThread(SendMessages, clientSocket);

// 等待线程结束
receiveThread.join();
sendThread.join();

// 关闭套接字
close(clientSocket);

return 0;
}

socket函数

套接字初始化,根据需求设置套接字,

1
2
3
4
5
6
int socket(int __domain, int __type, int __[protocol])
/*__domain 协议族,如IPV4,IPV6
__type 套接字类型 SOCK_STREAM字节流套接字,SOCK_DGRAM数据报套接字,SOCK_RAW原始套接字
__protocol 协议编号 IPPROTO_TCPTCP传输协议,IPPROTO_UDPUDP传输协议
返回新套接字的文件描述符,或-1表示错误,错误可通过errno获得。
*/

bind函数

函数功能:套接字与端口绑定,即将套接字与地址结构进行绑定,绑定之后,在进行网络程序设计时,套接字所代表IP地址和端口地址及协议类型等参数按绑定值进行操作。
将套接字描述符(FD)与本机网络地址ADDR(长度为LEN字节)和端口号绑定在一起。

1
2
3
4
5
6
7
8
9
10
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len) __THROW; 
/*1.__fd:套接字描述符
socket()函数创建的文件描述符。
2.__addr:地址(指向特定协议的地址结构的指针):
包含地址相关信息:名称、端口、IP地址。
(note:需先将地址结构中的数值先设置后才可进行绑定)
3.__len:该地址结构的长度:
可设置为 sizeof(struct sockaddr)。
返回:0表示成功,-1表示错误,错误可通过errno获得。
*/

connect函数

函数功能:连接指定参数的服务器。客户端建立套接字之后,无需进行地址绑定,直接连接服务器。
在套接字FD上打开一个连接以对等地址ADDR(长度为LEN字节)。对于无连接套接字类型,只需设置发送到的默认地址和接收传输的唯一地址。

1
2
3
4
5
6
7
extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len); 
/*1.__fd:套接字描述符
socket()函数创建的文件描述符。
2.__addr:地址(指向特定协议的地址结构的指针):
存储远程计算机的IP地址和端口信息的结构。
3.__len:该地址结构的长度
成功返回0,错误返回-1,错误可通过errno获得。 */

listen服务器函数

函数功能:设置服务器侦听连接,初始化服务器可连接队列,由于服务器需满足多个客户端连接请求,而服务器在某时间仅能处理有限个数的客户端连接请求,故服务器需设置队列长度。

1
2
3
extern int listen (int __fd, int __n) __THROW;
/*1.int __fd:套接字描述符
2.__n:套接字排队的最大连接个数(建议5~10)*/

accept服务器函数

函数功能:接收客户端连接。当客户端的连接请求到达服务器主机侦听的端口时,此时客户端连接会在队列中等待,直到使用服务器处理接收请求。

1
2
3
4
5
6
extern int accept (int __fd, __SOCKADDR_ARG __addr,socklen_t *__restrict __addr_len);
/*1.int __fd:套接字描述符
2.__addr:地址(指向特定协议的地址结构的指针)
3.__addr_len:所指内容的长度
成功返回新套接口文件描述符表示客户端连接,客户端连接的信息可通过该描述符获得。
错误返回-1。*/

send()函数与recv()函数

1
2
3
4
5
6
extern ssize_t send (int __fd, const void *__buf, size_t __n, int __flags);
extern ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);
/*1.__fd:套接字描述符:
2.__buf:指向想 发送消息/接收消息 数据的缓冲区地址的指针
3. __n:想要 发送消息/接收消息 缓冲区的最大尺寸
4.__flags:发送/接收标记(一般设置0)*/