网络编程 - - TCP套接字通信及编程实现
概述
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的传输层协议。在网络编程中,TCP常用于实现客户端和服务器之间的可靠数据传输。本文将基于C语言实现TCP服务端和客户端建立通信的过程。
三次握手
在 TCP 连接建立之前,客户端和服务器之间需要进行三次握手来同步双方的序列号,并确认双方都准备好进行数据传输
- 第一次握手:客户端向服务器发送一个 SYN(同步序列编号)报文段,表示请求建立连接。客户端进入 SYN_SENT 状态
- 第二次握手:服务器收到 SYN 报文段后,回复一个 SYN-ACK(同步序列编号 + 确认)报文段,表示同意建立连接。服务器进入 SYN_RCVD 状态
- 第三次握手:客户端收到 SYN-ACK 报文段后,回复一个 ACK(确认)报文段,表示确认收到服务器的响应。客户端和服务器都进入 ESTABLISHED 状态,连接正式建立
图片来源:https://img-blog.csdnimg.cn/39bb4f4da21a4513b9506ecdf6a40cf3.png
四次挥手
通信结束时,客户端或服务器可以发起断开连接的请求。断开连接的过程称为四次挥手,以确保双方都能正确关闭连接并释放资源
- 第一次挥手:主动关闭方(通常是客户端)发送一个 FIN(终止)报文段,表示不再发送数据。主动关闭方进入 FIN_WAIT_1 状态
- 第二次挥手:被动关闭方(通常是服务器)收到 FIN 报文段后,回复一个 ACK 报文段,表示确认收到 FIN。被动关闭方进入 CLOSE_WAIT 状态,而主动关闭方进入 FIN_WAIT_2 状态
- 第三次挥手:被动关闭方在处理完所有未完成的数据后,发送一个 FIN 报文段,表示自己也不再发送数据。被动关闭方进入 LAST_ACK 状态
- 第四次挥手:主动关闭方收到 FIN 报文段后,回复一个 ACK 报文段,表示确认收到 FIN。主动关闭方进入 TIME_WAIT 状态,等待一段时间(通常为2倍的最大报文段生命周期,即2MSL),以确保被动关闭方收到了最后的 ACK。之后,主动关闭方进入 CLOSED 状态,连接完全关闭
2MSL:MSL 的默认值是 30 秒,基于经验选择的一个保守估计,用来确保大多数网络环境下的数据包都能被接收或者超时。
大部分操作系统都允许用户调整 MSL 的值,从而改变
TIME_WAIT
状态的持续时间
图片来源:https://i-blog.csdnimg.cn/blog_migrate/843f121dd50cd8458daf1fa834bc1f36.png
TCP保证可靠传输方式
- 序列号:每个TCP报文段都有一个序列号,表示该报文段中的第一个字节在整个数据流中的位置。接收方可以根据序列号重新排序接收到的报文段,确保数据按顺序传递
- 确认应答:接收方在收到报文段后,会发送一个确认应答,告诉发送方哪些数据已经成功接收。发送方根据确认应答判断是否需要重传丢失或损坏的报文段
- 超时重传:如果发送方在一定时间内没有收到确认应答,它会认为报文段可能丢失或延迟,并重新发送该报文段。TCP使用动态调整的超时机制来优化重传策略
- 流量控制:TCP使用滑动窗口机制来控制发送方的发送速率,确保接收方不会被过多的数据淹没。接收方会在确认应答中告知发送方当前可用的接收窗口大小,发送方根据这个信息调整自己的发送速率
- 拥塞控制:TCP通过多种算法(如慢启动、拥塞避免、快速重传和快速恢复)来动态调整发送方的发送速率,避免网络拥塞。这些算法旨在在网络负载较高时减小发送速率,在网络条件改善时逐渐增加发送速率
TCP通信实现流程
涉及到的库方法
-
创建套接字(Socket)
#include <sys/socket.h> int socket(int domain, int type, int protocol);
-
绑定(Bind)套接字到指定的IP地址和端口
#include <sys/socket.h> int bind(int socket, const struct sockaddr *address, socklen_t address_len);
-
监听(Listen)客户端的连接请求
#include <sys/socket.h> int listen(int socket, int backlog)
-
接受(Accept)客户端的连接
#include <sys/socket.h> int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
-
连接、发送和接收数据(Send/Recv)
#include <sys/socket.h> int connect(int socket, const struct sockaddr *address, socklen_t address_len); ssize_t send(int socket, const void *buffer, size_t length, int flags); ssize_t recv(int socket, void *buffer, size_t length, int flags);
-
关闭连接(Close)
#include <unistd.h> int close(int fildes);
代码实现
服务器代码(Linux)
tcp_server.h
#ifndef TCP_SERVER_H
#define TCP_SERVER_H
#include <pthread.h>
#include <netinet/in.h>
#include "cJson.h"
#include "common_base.h"
// 定义常量
#define PORT 18888
#define BUFFER_SIZE 1024
#define TCP_IP "127.0.0.1"
// 外部函数声明
extern void getSerialNoStr(char *buf);
/**
* 启动服务器主循环,等待客户端连接
*/
void start_serve_tcp(void *arg);
/**
* 解析接收到的消息
*/
void parse_message(char *data, size_t data_size);
#endif // TCP_SERVER_H
tcp_server.c
#include "tcp_server.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
/**
* 启动TCP服务器,等待客户端连接并处理接收到的数据。
*
* @param arg 传递给线程的参数,通常为NULL。
*/
void start_serve_tcp(void *arg)
{
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE] = {0};
// 创建 Socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("Socket 创建失败");
return;
}
// 配置服务器地址
memset_s(&server_addr, sizeof(server_addr), 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
// 配置端口号和IP
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(TCP_IP);
// 绑定 Socket
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
printf("绑定失败\n");
close(server_fd);
return;
}
// 开始监听
if (listen(server_fd, 5) == -1)
{
printf("监听失败\n");
close(server_fd);
return;
}
printf("服务器已启动,监听地址:%s:%d\n", inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port));
// 等待客户端连接
while (1)
{
printf("等待客户端连接...\n");
if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len)) == -1)
{
printf("接受客户端连接失败");
continue;
}
printf("客户端已连接:%s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 接收数据
int received_size;
printf("receive before client_fd: %d\n", client_fd);
while ((received_size = recv(client_fd, buffer, BUFFER_SIZE, 0)) > 0)
{
printf("收到数据,大小: %d 字节\n", received_size);
parse_message(buffer, received_size);
printf("jsonString: %s\n", jsonString);
sleep(1);
if(jsonString)
{
printf("receive after client_fd: %d\n", client_fd);
// 发送 JSON 数据到客户端
ssize_t sent_size = send(client_fd, jsonString, strlen(jsonString), 0);
if (sent_size == -1)
{
printf("发送数据失败\n");
}
else
{
printf("成功发送 %zd 字节到客户端\n", sent_size);
}
free(jsonString);
jsonString = NULL;
}
else
{
printf("生成 JSON 数据失败\n");
}
}
if (received_size == 0)
{
printf("客户端已断开连接\n");
}
else if (received_size == -1)
{
printf("接收数据失败");
}
close(client_fd);
}
close(server_fd);
}
void parse_message(char *data, size_t data_size)
{
cJSON *rootMsg = NULL;
cJSON *serialNo = NULL; // 序列号
cJSON *netCmd = NULL; // 操作行为
MANUAL_TRIG_PARAM manualTrParam; // 手动触发抓拍接口传参结构体
// 确保消息头部完整性(消息类型:4字节,数据长度:4字节)
if (data_size < 8)
{
printf("数据长度不足,无法解析\n");
return;
}
// 解析消息类型和数据长度
int message_type = ntohl(*(int *)data);
int data_length = ntohl(*(int *)(data + 4));
printf("消息类型: %d\n", message_type);
printf("数据长度: %d\n", data_length);
// 检查数据长度是否匹配
if (data_size < 8 + data_length)
{
printf("数据长度与实际内容不匹配\n");
return;
}
// 解析消息内容
char *message_content = (char *)malloc(data_length + 1);
if (!message_content)
{
printf("内存分配失败\n");
return;
}
memcpy(message_content, data + 8, data_length);
message_content[data_length] = '\0'; // 确保字符串以 \0 结尾
printf("消息内容: %s\n", message_content);
rootMsg = cJSON_Parse(message_content);
if (NULL == rootMsg)
{
printf("rootMsg is not json tpye\n");
free(message_content);
return;
}
printf("解析成功\n");
serialNo = cJSON_GetObjectItem(rootMsg, "serialNo");
netCmd = cJSON_GetObjectItem(rootMsg, "netCmd");
if (serialNo && netCmd)
{
printf("serialNo: %s\n", serialNo->valuestring);
printf("netCmd: %s\n", netCmd->valuestring);
}
else
{
printf("缺少必要的 JSON 字段\n");
cJSON_Delete(rootMsg);
free(message_content);
return;
}
char serial[64] = {0};
getSerialNoStr(serial);
printf("getSerialNoStr: %s\n", serial);
printf("serialNo: %s\n", serialNo->valuestring);
if(strncmp(serial, serialNo->valuestring, 9) == 0)
{
// TODO:业务处理
}
else
{
printf("serialNo is not equal\n");
}
cJSON_Delete(rootMsg);
free(message_content);
}
客户端代码(Windows)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
void send_message(const char *ip, int port, int message_type, const char *content) {
WSADATA wsa;
SOCKET sock;
struct sockaddr_in server_addr;
char buffer[1024];
int recv_size;
char recv_buffer[1024];
// 初始化 Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
printf("Winsock 初始化失败,错误代码: %d\n", WSAGetLastError());
exit(EXIT_FAILURE);
}
// 创建 Socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
printf("Socket 创建失败,错误代码: %d\n", WSAGetLastError());
WSACleanup();
exit(EXIT_FAILURE);
}
// 配置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ip);
// 连接服务器
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
printf("连接服务器失败,错误代码: %d\n", WSAGetLastError());
closesocket(sock);
WSACleanup();
exit(EXIT_FAILURE);
}
// 准备消息
int data_length = strlen(content);
int net_message_type = htonl(message_type);
int net_data_length = htonl(data_length);
memcpy(buffer, &net_message_type, 4);
memcpy(buffer + 4, &net_data_length, 4);
memcpy(buffer + 8, content, data_length);
// 发送消息
send(sock, buffer, 8 + data_length, 0);
// 接收服务器返回的数据
if ((recv_size = recv(sock, recv_buffer, sizeof(recv_buffer) - 1, 0)) == SOCKET_ERROR) {
printf("接收数据失败,错误代码: %d\n", WSAGetLastError());
} else {
recv_buffer[recv_size] = '\0'; // 确保以 null 结尾
printf("服务器返回: %s\n", recv_buffer);
}
closesocket(sock);
WSACleanup();
}
int main() {
send_message("127.0.0.1", 18888, 1, "{id: 'FS123456', command: 'NET_CAP'}");
return 0;
}
原文地址:https://blog.csdn.net/qq_40949254/article/details/145158938
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!