TCP(Transmission Control Protocol,传输控制协议) 是一种面向连接、可靠的字节流传输协议。
| 特性 | 说明 |
|---|---|
| 面向连接 | 通信前必须建立连接(三次握手) |
| 可靠传输 | 确认应答、超时重传、校验和 |
| 字节流 | 无边界保护,需要应用层处理粘包 |
| 全双工 | 双向同时通信 |
| 流量控制 | 滑动窗口机制 |
| 拥塞控制 | 慢启动、拥塞避免、快重传、快恢复 |
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠 | 不可靠 |
| 传输方式 | 字节流 | 数据报 |
| 效率 | 较低 | 较高 |
| 适用场景 | 文件传输、Web | 视频流、DNS |
socket() → bind() → listen() → accept() → read()/write() → close()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#define PORT 8080
#define BUF_SIZE 1024
volatile sig_atomic_t running = 1;
void sigint_handler(int sig) {
running = 0;
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buf[BUF_SIZE];
signal(SIGINT, sigint_handler);
// 1. 创建socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 设置地址复用
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 绑定地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
close(server_fd);
exit(EXIT_FAILURE);
}
// 4. 监听
if (listen(server_fd, 128) < 0) {
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("TCP Server started on port %d\n", PORT);
// 5. 接受连接(单客户端版本)
while (running) {
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
if (running) perror("accept");
continue;
}
printf("Client connected: %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 6. 通信循环
ssize_t n;
while ((n = read(client_fd, buf, BUF_SIZE)) > 0) {
buf[n] = '\0';
printf("Received %zd bytes: %s", n, buf);
// Echo back
if (write(client_fd, buf, n) < 0) {
perror("write");
break;
}
}
if (n == 0) {
printf("Client disconnected\n");
} else {
perror("read");
}
close(client_fd);
}
close(server_fd);
printf("Server shutdown\n");
return 0;
}
编译命令:
gcc -Wall -o server server.c
socket() → connect() → write()/read() → close()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUF_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buf[BUF_SIZE];
// 1. 创建socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 连接服务器
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("inet_pton");
close(sockfd);
exit(EXIT_FAILURE);
}
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Connected to server %s:%d\n", SERVER_IP, PORT);
// 3. 通信循环
while (1) {
printf("Enter message (or 'quit'): ");
if (fgets(buf, BUF_SIZE, stdin) == NULL) break;
if (strncmp(buf, "quit", 4) == 0) break;
if (write(sockfd, buf, strlen(buf)) < 0) {
perror("write");
break;
}
ssize_t n = read(sockfd, buf, BUF_SIZE);
if (n <= 0) {
if (n == 0) printf("Server closed connection\n");
else perror("read");
break;
}
buf[n] = '\0';
printf("Echo: %s", buf);
}
// 4. 关闭
close(sockfd);
printf("Disconnected\n");
return 0;
}
编译命令:
gcc -Wall -o client client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#define PORT 8080
#define BUF_SIZE 1024
void handle_client(int client_fd, struct sockaddr_in *client_addr) {
char buf[BUF_SIZE];
ssize_t n;
printf("[%d] Client connected: %s:%d\n",
getpid(),
inet_ntoa(client_addr->sin_addr),
ntohs(client_addr->sin_port));
while ((n = read(client_fd, buf, BUF_SIZE)) > 0) {
buf[n] = '\0';
printf("[%d] Echo: %s", getpid(), buf);
if (write(client_fd, buf, n) < 0) {
perror("write");
break;
}
}
printf("[%d] Client disconnected\n", getpid());
close(client_fd);
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
close(server_fd);
exit(EXIT_FAILURE);
}
if (listen(server_fd, 128) < 0) {
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Echo Server listening on port %d\n", PORT);
while (1) {
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("accept");
continue;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
close(client_fd);
continue;
}
if (pid == 0) {
// 子进程处理客户端
close(server_fd);
handle_client(client_fd, &client_addr);
exit(EXIT_SUCCESS);
} else {
// 父进程继续监听
close(client_fd);
// 避免僵尸进程
signal(SIGCHLD, SIG_IGN);
}
}
close(server_fd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUF_SIZE 1024
int main(int argc, char *argv[]) {
const char *server_ip = (argc > 1) ? argv[1] : SERVER_IP;
int port = (argc > 2) ? atoi(argv[2]) : PORT;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
printf("Connecting to %s:%d...\n", server_ip, port);
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Connected! Type messages (Ctrl+D to quit):\n");
char buf[BUF_SIZE];
while (fgets(buf, BUF_SIZE, stdin) != NULL) {
if (write(sockfd, buf, strlen(buf)) < 0) {
perror("write");
break;
}
ssize_t n = read(sockfd, buf, BUF_SIZE);
if (n <= 0) {
if (n == 0) printf("Server closed\n");
else perror("read");
break;
}
buf[n] = '\0';
printf("Echo: %s", buf);
}
close(sockfd);
return 0;
}
编译和运行:
# 编译
gcc -Wall -o echo_server echo_server.c
gcc -Wall -o echo_client echo_client.c
# 终端1:启动服务器
./echo_server
# 终端2:启动客户端
./echo_client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define PORT 8080
#define BUF_SIZE 1024
void sigchld_handler(int sig) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
void handle_client(int client_fd) {
char buf[BUF_SIZE];
ssize_t n;
while ((n = read(client_fd, buf, BUF_SIZE)) > 0) {
buf[n] = '\0';
printf("[%d] Received: %s", getpid(), buf);
write(client_fd, buf, n);
}
printf("[%d] Client disconnected\n", getpid());
close(client_fd);
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 128) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
signal(SIGCHLD, sigchld_handler);
printf("Multi-process server on port %d\n", PORT);
while (1) {
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("accept");
continue;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
close(client_fd);
continue;
}
if (pid == 0) {
close(server_fd);
handle_client(client_fd);
exit(0);
} else {
close(client_fd);
}
}
close(server_fd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#define PORT 8080
#define BUF_SIZE 1024
#define MAX_CLIENTS 100
typedef struct {
int sockfd;
struct sockaddr_in addr;
} client_info_t;
void *client_handler(void *arg) {
client_info_t *info = (client_info_t *)arg;
int client_fd = info->sockfd;
char buf[BUF_SIZE];
ssize_t n;
printf("[Thread %lu] Client connected: %s:%d\n",
pthread_self(),
inet_ntoa(info->addr.sin_addr),
ntohs(info->addr.sin_port));
free(info);
while ((n = read(client_fd, buf, BUF_SIZE)) > 0) {
buf[n] = '\0';
printf("[Thread %lu] Echo: %s", pthread_self(), buf);
write(client_fd, buf, n);
}
printf("[Thread %lu] Client disconnected\n", pthread_self());
close(client_fd);
return NULL;
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 128) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Multi-threaded server on port %d\n", PORT);
while (1) {
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("accept");
continue;
}
client_info_t *info = malloc(sizeof(client_info_t));
if (!info) {
perror("malloc");
close(client_fd);
continue;
}
info->sockfd = client_fd;
info->addr = client_addr;
pthread_t tid;
if (pthread_create(&tid, NULL, client_handler, info) != 0) {
perror("pthread_create");
free(info);
close(client_fd);
continue;
}
pthread_detach(tid);
}
close(server_fd);
return 0;
}
编译命令:
# 多进程版
gcc -Wall -o server_mp server_mp.c
# 多线程版(需要链接pthread)
gcc -Wall -o server_mt server_mt.c -lpthread
TCP是字节流协议,没有消息边界。发送端的多次 write() 可能被合并成一次发送,接收端的 read() 可能一次读取多条消息。
示例:
发送端:write("Hello") → write("World")
接收端:read() → "HelloWorld" (一次读取)
或 read() → "Hello",read() → "World" (两次读取)
#define MSG_LEN 100
// 发送端
char msg[MSG_LEN] = {0};
strncpy(msg, data, MSG_LEN - 1);
write(sockfd, msg, MSG_LEN);
// 接收端
char msg[MSG_LEN];
read(sockfd, msg, MSG_LEN);
// 发送端:以\n结尾
write(sockfd, "Hello\n", 6);
// 接收端:逐字符读取直到遇到分隔符
void recv_line(int sockfd, char *buf, int max_len) {
int i = 0;
char c;
while (i < max_len - 1) {
read(sockfd, &c, 1);
if (c == '\n') break;
buf[i++] = c;
}
buf[i] = '\0';
}
// 消息格式:[4字节长度][实际数据]
// 发送端
void send_msg(int sockfd, const char *data, uint32_t len) {
uint32_t net_len = htonl(len);
write(sockfd, &net_len, 4);
write(sockfd, data, len);
}
// 接收端
int recv_msg(int sockfd, char *buf, int max_len) {
uint32_t net_len;
if (read(sockfd, &net_len, 4) != 4) return -1;
uint32_t len = ntohl(net_len);
if (len > max_len) return -1;
int total = 0;
while (total < len) {
int n = read(sockfd, buf + total, len - total);
if (n <= 0) return -1;
total += n;
}
return len;
}
TCP支持发送 紧急数据(Out-of-Band Data),也称为带外数据,用于发送需要优先处理的控制信息。
// 发送端:使用MSG_OOB标志
const char *urgent = "URGENT!";
send(sockfd, urgent, strlen(urgent), MSG_OOB);
// 接收端
char buf[1024];
ssize_t n = recv(sockfd, buf, sizeof(buf), MSG_OOB); // 只接收紧急数据
if (n > 0) {
printf("Urgent data: %.*s\n", (int)n, buf);
}
// 嵌入式设备作为TCP客户端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_IP "192.168.1.100"
#define SERVER_PORT 8080
int send_sensor_data(float temperature, float humidity) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) return -1;
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
close(sockfd);
return -1;
}
// 构造JSON格式数据
char data[128];
snprintf(data, sizeof(data),
"{\"temp\":%.1f,\"humi\":%.1f}",
temperature, humidity);
// 发送数据
uint32_t len = htonl(strlen(data));
write(sockfd, &len, 4);
write(sockfd, data, strlen(data));
close(sockfd);
return 0;
}
// 嵌入式设备作为TCP服务器,接受远程控制命令
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 9090
#define CMD_LEN 64
void execute_command(const char *cmd) {
if (strcmp(cmd, "LED_ON") == 0) {
// 打开LED
printf("LED turned ON\n");
} else if (strcmp(cmd, "LED_OFF") == 0) {
// 关闭LED
printf("LED turned OFF\n");
} else if (strcmp(cmd, "REBOOT") == 0) {
// 重启设备
printf("Rebooting...\n");
}
}
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return -1;
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
close(server_fd);
return -1;
}
if (listen(server_fd, 5) < 0) {
perror("listen");
close(server_fd);
return -1;
}
printf("Control server listening on port %d\n", PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) continue;
char cmd[CMD_LEN];
ssize_t n = read(client_fd, cmd, CMD_LEN - 1);
if (n > 0) {
cmd[n] = '\0';
execute_command(cmd);
}
close(client_fd);
}
close(server_fd);
return 0;
}
read()/write() 可能只读写部分数据read() 返回0)select()/poll()/epoll 实现并发SO_REUSEADDR 避免地址占用// 完整的错误处理模板
ssize_t writen(int fd, const void *buf, size_t n) {
size_t nleft = n;
ssize_t nwritten;
const char *ptr = buf;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) < 0) {
if (errno == EINTR) continue; // 信号中断,重试
return -1;
} else if (nwritten == 0) {
break; // 对端关闭
}
nleft -= nwritten;
ptr += nwritten;
}
return n - nleft;
}
SO_REUSEADDRaccept() 返回新fd的原因:支持并发listen() 的 backlog 参数:已完成连接队列长度SO_REUSEADDR 的作用:允许绑定TIME_WAIT状态的地址