<think>我们使用C语言和BSD套接字API来实现TCP双向通信。以下代码示例包括两个文件:server.c(服务器端)和client.c(客户端)。服务器将监听指定端口,客户端连接后,双方可以互相发送消息。
注意:此代码在Linux环境下编译运行(Windows下需稍作修改,如使用Winsock2)。
### 1. 服务器端代码 (server.c)
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
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};
// 1. 创建TCP socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket创建失败");
exit(EXIT_FAILURE);
}
// 2. 绑定IP和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 接受任意IP的连接
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("绑定失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 监听连接
if (listen(server_fd, 5) < 0) { // 最大等待队列为5
perror("监听失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("服务器正在监听端口 %d...\n", PORT);
// 4. 接受客户端连接
if ((client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len)) < 0) {
perror("接受连接失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("客户端已连接: %s\n", inet_ntoa(client_addr.sin_addr));
// 5. 发送欢迎消息给客户端
const char *welcome_msg = "欢迎连接服务器!请输入消息:";
send(client_fd, welcome_msg, strlen(welcome_msg), 0);
// 6. 循环进行双向通信
while (1) {
// 接收客户端消息
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(client_fd, buffer, BUFFER_SIZE, 0);
if (bytes_received <= 0) {
perror("接收失败或连接关闭");
break;
}
printf("客户端: %s", buffer);
// 检查是否收到退出指令
if (strncmp(buffer, "exit", 4) == 0) {
printf("客户端请求退出。\n");
break;
}
// 发送响应消息
printf("请输入回复消息 (输入exit退出): ");
fgets(buffer, BUFFER_SIZE, stdin);
send(client_fd, buffer, strlen(buffer), 0);
if (strncmp(buffer, "exit", 4) == 0) {
printf("服务器退出。\n");
break;
}
}
// 7. 关闭连接
close(client_fd);
close(server_fd);
return 0;
}
```
### 2. 客户端代码 (client.c)
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define SERVER_IP "127.0.0.1"
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
int sock;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE] = {0};
// 1. 创建TCP socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket创建失败");
exit(EXIT_FAILURE);
}
// 2. 配置服务器地址
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("地址转换失败");
close(sock);
exit(EXIT_FAILURE);
}
// 3. 连接服务器
if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("连接服务器失败");
close(sock);
exit(EXIT_FAILURE);
}
printf("已连接到服务器 %s:%d\n", SERVER_IP, PORT);
// 4. 接收服务器欢迎消息
memset(buffer, 0, BUFFER_SIZE);
recv(sock, buffer, BUFFER_SIZE, 0);
printf("服务器: %s\n", buffer);
// 5. 循环进行双向通信
while (1) {
// 发送消息给服务器
printf("请输入消息 (输入exit退出): ");
fgets(buffer, BUFFER_SIZE, stdin);
send(sock, buffer, strlen(buffer), 0);
if (strncmp(buffer, "exit", 4) == 0) {
printf("客户端退出。\n");
break;
}
// 接收服务器响应
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(sock, buffer, BUFFER_SIZE, 0);
if (bytes_received <= 0) {
perror("接收失败或连接关闭");
break;
}
printf("服务器: %s", buffer);
// 检查服务器是否退出
if (strncmp(buffer, "exit", 4) == 0) {
break;
}
}
// 6. 关闭连接
close(sock);
return 0;
}
```
### 3. 编译与运行方法
1. **编译服务器端**:
```bash
gcc server.c -o server
```
2. **编译客户端**:
```bash
gcc client.c -o client
```
3. **运行**:
```bash
# 先启动服务器
./server
# 再启动客户端(新终端)
./client
```
### 4. 通信流程说明
1. **连接建立**:
- 服务器启动监听8888端口
- 客户端连接服务器
- 服务器发送欢迎消息
2. **双向通信**:
- 客户端输入消息 -> 服务器接收并显示
- 服务器输入回复 -> 客户端接收并显示
- 任意一方输入"exit"可终止连接
### 5. 关键函数说明
1. **服务器端**:
- `socket()`:创建TCP套接字
- `bind()`:绑定IP和端口
- `listen()`:开始监听
- `accept()`:接受客户端连接
- `recv()`/`send()`:接收/发送数据
2. **客户端**:
- `connect()`:连接服务器
- `recv()`/`send()`:接收/发送数据
### 6. 注意事项
1. **阻塞调用**:
- `recv()`和`accept()`是阻塞调用,程序会等待直到有数据或连接到达
2. **错误处理**:
- 每个系统调用后都进行了错误检查,确保程序健壮性
3. **多客户端支持**:
- 当前服务器仅支持单客户端,如需多客户端需使用多线程/多进程或IO复用
4. **数据边界**:
- TCP是流式协议,无消息边界,实际应用中需设计协议(如长度前缀)
### 7. 扩展建议
1. **多线程服务器**:
```c
// 在accept后创建线程
pthread_t thread_id;
pthread_create(&thread_id, NULL, handle_client, (void*)&client_fd);
```
2. **非阻塞IO**:
- 使用`fcntl()`设置非阻塞模式,配合`select()`/`poll()`实现高并发
3. **数据序列化**:
- 使用JSON或Protocol Buffers进行结构化数据传输
4. **加密通信**:
- 集成OpenSSL实现TLS加密(参考引用[^2])
§§ 相关问题 §§
1. 如何实现服务器同时处理多个客户端连接?
2. 在C语言网络编程中如何处理TCP粘包问题?
3. 如何用select()实现非阻塞IO的TCP服务器?
4. 如何将上述TCP通信示例改为UDP通信?
5. 如何在Windows平台使用Winsock实现相同的双向通信?[^1]</think>下面我将提供完整的C语言实现TCP双向通信的代码示例,包含服务器端(server.c)和客户端(client.c)代码。这个实现允许双方互相发送消息,解决了您提到的服务器无法向客户端发送消息的问题。
### 服务器端代码 (server.c)
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#define PORT 8888
#define BUFFER_SIZE 1024
// 客户端处理线程
void *handle_client(void *arg) {
int client_socket = *((int *)arg);
char buffer[BUFFER_SIZE] = {0};
// 向客户端发送欢迎消息
const char *welcome = "服务器: 欢迎连接! 输入消息开始对话(输入'exit'退出)\n";
send(client_socket, welcome, strlen(welcome), 0);
while (1) {
// 接收客户端消息
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(client_socket, buffer, BUFFER_SIZE, 0);
if (bytes_received <= 0) {
perror("接收失败或连接关闭");
break;
}
// 打印客户端消息
printf("客户端: %s", buffer);
// 检查退出指令
if (strncmp(buffer, "exit", 4) == 0) {
printf("客户端请求退出\n");
break;
}
// 准备服务器回复
printf("请输入回复消息: ");
fgets(buffer, BUFFER_SIZE, stdin);
// 发送回复
ssize_t bytes_sent = send(client_socket, buffer, strlen(buffer), 0);
if (bytes_sent < 0) {
perror("发送失败");
break;
}
}
close(client_socket);
pthread_exit(NULL);
}
int main() {
int server_fd, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// 创建TCP socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket创建失败");
exit(EXIT_FAILURE);
}
// 配置服务器地址
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("绑定失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("监听失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("服务器启动,监听端口 %d...\n", PORT);
while (1) {
// 接受客户端连接
if ((client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len)) < 0) {
perror("接受连接失败");
continue;
}
printf("客户端已连接: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 创建线程处理客户端
pthread_t thread_id;
if (pthread_create(&thread_id, NULL, handle_client, (void *)&client_socket) < 0) {
perror("线程创建失败");
close(client_socket);
}
// 分离线程,使其结束后自动释放资源
pthread_detach(thread_id);
}
close(server_fd);
return 0;
}
```
### 客户端代码 (client.c)
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#define SERVER_IP "127.0.0.1" // 服务器IP
#define PORT 8888
#define BUFFER_SIZE 1024
// 接收消息线程
void *receive_messages(void *arg) {
int sock = *((int *)arg);
char buffer[BUFFER_SIZE] = {0};
while (1) {
// 接收服务器消息
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(sock, buffer, BUFFER_SIZE, 0);
if (bytes_received <= 0) {
perror("接收失败或连接关闭");
break;
}
// 打印服务器消息
printf("%s", buffer);
}
pthread_exit(NULL);
}
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
pthread_t recv_thread;
// 创建TCP socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket创建失败");
exit(EXIT_FAILURE);
}
// 配置服务器地址
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 转换IP地址
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
perror("地址转换失败");
close(sock);
exit(EXIT_FAILURE);
}
// 连接服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("连接服务器失败");
close(sock);
exit(EXIT_FAILURE);
}
printf("已连接到服务器 %s:%d\n", SERVER_IP, PORT);
// 创建接收消息线程
if (pthread_create(&recv_thread, NULL, receive_messages, (void *)&sock) < 0) {
perror("线程创建失败");
close(sock);
exit(EXIT_FAILURE);
}
// 主线程处理用户输入
char buffer[BUFFER_SIZE] = {0};
while (1) {
// 获取用户输入
printf("请输入消息: ");
fgets(buffer, BUFFER_SIZE, stdin);
// 发送消息到服务器
ssize_t bytes_sent = send(sock, buffer, strlen(buffer), 0);
if (bytes_sent < 0) {
perror("发送失败");
break;
}
// 检查退出指令
if (strncmp(buffer, "exit", 4) == 0) {
printf("退出程序...\n");
break;
}
}
// 清理资源
close(sock);
pthread_cancel(recv_thread); // 取消接收线程
pthread_join(recv_thread, NULL); // 等待线程结束
return 0;
}
```
### 编译与运行方法
1. **编译服务器端**:
```bash
gcc server.c -o server -lpthread
```
2. **编译客户端**:
```bash
gcc client.c -o client -lpthread
```
3. **运行**:
```bash
# 先启动服务器
./server
# 再启动客户端(新终端)
./client
```
### 通信流程说明
1. **服务器启动**:
- 创建TCP socket并绑定端口
- 监听客户端连接
- 为每个客户端创建独立线程处理通信
2. **客户端连接**:
- 创建TCP socket
- 连接服务器
- 创建独立线程接收服务器消息
3. **双向通信**:
- 客户端主线程处理用户输入并发送
- 客户端接收线程显示服务器消息
- 服务器线程接收客户端消息并发送回复
### 关键功能实现
1. **服务器主动发送消息**:
```c
// 服务器发送消息
send(client_socket, buffer, strlen(buffer), 0);
```
2. **客户端接收消息**:
```c
// 客户端接收线程
bytes_received = recv(sock, buffer, BUFFER_SIZE, 0);
printf("%s", buffer);
```
3. **多线程处理**:
```c
// 服务器为每个客户端创建线程
pthread_create(&thread_id, NULL, handle_client, (void *)&client_socket);
// 客户端创建接收线程
pthread_create(&recv_thread, NULL, receive_messages, (void *)&sock);
```
### 常见问题解决
1. **连接失败**:
- 检查服务器IP是否正确
- 确保防火墙允许端口8888通信
- 验证服务器是否已启动
2. **消息乱码**:
- 确保双方使用相同的字符编码
- 检查网络字节序转换(htons/ntohs)
3. **资源泄漏**:
- 使用close()关闭所有socket
- 使用pthread_detach()或pthread_join()管理线程
4. **端口占用**:
```bash
# 查看端口占用情况
netstat -tuln | grep 8888
```
### 扩展功能建议
1. **多客户端支持**:
```c
// 使用线程池管理多个客户端
#define MAX_CLIENTS 10
int client_sockets[MAX_CLIENTS] = {0};
```
2. **消息广播**:
```c
// 向所有客户端广播消息
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] > 0) {
send(client_sockets[i], buffer, strlen(buffer), 0);
}
}
```
3. **加密通信**:
```c
// 使用OpenSSL添加TLS加密(参考引用[^2])
#include <openssl/ssl.h>
#include <openssl/err.h>
```
4. **协议设计**:
```c
// 添加消息头标识
struct MessageHeader {
uint32_t length;
uint8_t type;
};
```