### 服务端代码详解及注释
```c
#define DEFAULT_PORT 5050 // 服务端的默认端口号为 5050
#define BUFFER_SIZE 1024 // 缓冲区大小
#define _WINSOCK_DEPRECATED_NO_WARNINGS // 忽略 Winsock 过时警告
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <process.h> // 添加这个头文件以使用 _beginthreadex
#pragma comment(lib, "ws2_32.lib") // 告诉编译器链接 Winsock 库
// 处理客户端连接的线程函数
unsigned __stdcall thread_func(void* lpThreadParameter)
{
SOCKET s_client = *(SOCKET*)lpThreadParameter; // 从参数中获取客户端套接字
free(lpThreadParameter); // 释放分配的内存
while (1){
// 5. 开始通讯
char mode[BUFFER_SIZE] = {0};
int ret = recv(s_client, mode, BUFFER_SIZE, 0); // 接收模式('d' 或 'f')
if (ret <= 0) break; // 如果接收失败或客户端断开连接,退出循环
if (strcmp(mode, "d") == 0) {
// 接收普通文本数据
char buffer[BUFFER_SIZE] = {0};
ret = recv(s_client, buffer, BUFFER_SIZE, 0); // 接收实际数据
if (ret <= 0) break; // 如果接收失败或客户端断开连接,退出循环
printf("--客户端%llu 的数据传输结果如下所示:\n", s_client);
printf("TCP Server Receive: %s\n", buffer); // 打印接收到的数据
send(s_client, buffer, (int)strlen(buffer), 0); // 将接收到的数据回传给客户端
printf("TCP Server Send: %s\n", buffer); // 打印发送的数据
} else if (strcmp(mode, "f") == 0) {
// 接收文件
char filename[BUFFER_SIZE] = {0};
int ret = recv(s_client, filename, BUFFER_SIZE, 0); // 接收文件名
if (ret <= 0) break; // 如果接收失败或客户端断开连接,退出循环
FILE *fp = fopen(filename, "wb"); // 以二进制写入模式打开文件
if (!fp) {
printf("Failed to create file.\n");
continue;
}
while (1) {
char buffer[BUFFER_SIZE] = {0};
ret = recv(s_client, buffer, BUFFER_SIZE, 0); // 接收文件内容
if (ret <= 0) break; // 如果接收失败或客户端断开连接,退出循环
fwrite(buffer, 1, ret, fp); // 写入文件
if (ret < BUFFER_SIZE) break; // 文件传输结束
}
fclose(fp); // 关闭文件
printf("--来自客户端%llu的文件%s成功接收并保存\n", s_client, filename); // 打印接收成功的消息
}
// else {
// printf("Unknown mode: %s\n", mode);
// }
}
printf("**客户端: %llu 断开连接.\n", s_client); // 打印客户端断开连接的信息
closesocket(s_client); // 关闭客户端套接字
_endthreadex(0); // 结束线程
return 0;
}
int main(int argc, char *argv[])
{
int iPort = DEFAULT_PORT;
// 初始化 Winsock 库,版本为 2.2
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("Failed to load Winsock.\n");
return -1;
}
// 1. 创建一个 IPv4 的流式套接字
SOCKET s_listen = socket(AF_INET, SOCK_STREAM, 0);
if (s_listen == INVALID_SOCKET) {
printf("socket() Failed: %d\n", WSAGetLastError());
return -1;
}
printf("--监听套接字为 %d\n", s_listen);
// 2. 绑定端口号和 IP 地址
struct sockaddr_in local = {0}; // 定义本地地址结构体
local.sin_family = AF_INET; // 设置地址族为 IPv4
local.sin_port = htons(iPort); // 设置端口号,并将其转换为网络字节序
local.sin_addr.s_addr = htonl(INADDR_ANY); // 设置 IP 地址为任意可用地址和接口
if (bind(s_listen, (struct sockaddr*)&local, sizeof(local)) == SOCKET_ERROR) {
printf("bind() Failed!!! errcode: %d\n", WSAGetLastError());
return -1;
}
// 3. 监听属性
if (listen(s_listen, 10) == SOCKET_ERROR) { // 开始监听连接请求,最大排队数量为 10
printf("start listen failed!!! errcode: %d\n", WSAGetLastError());
return -1;
}
// 4. 等待客户端连接
while (1) {
SOCKET s_client = accept(s_listen, NULL, NULL); // 接受客户端连接
if (s_client == INVALID_SOCKET) { // 检查连接是否成功
continue;
}
printf("**端口号: %llu 成为新连接\n", s_client);
SOCKET* sockfd = (SOCKET*)malloc(sizeof(SOCKET)); // 分配内存存储客户端套接字
*sockfd = s_client; // 将客户端套接字赋值给新分配的内存
unsigned threadId; // 定义线程 ID
_beginthreadex(NULL, 0, thread_func, sockfd, 0, &threadId); // 创建新线程处理客户端连接
}
// 清理资源
WSACleanup();
return 0;
}
```
### 客户端代码详解及注释
```c
#define DEFAULT_PORT 5050 // 默认端口号
#define BUFFER_SIZE 1024 // 缓冲区大小
#define _WINSOCK_DEPRECATED_NO_WARNINGS // 忽略 Winsock 过时警告
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib") // 告诉编译器链接 Winsock 库
int main()
{
int iPort = DEFAULT_PORT;
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData); // 初始化 Winsock 库,版本为 2.2
// 1. 创建一个 IPv4 流式套接字
SOCKET s_client = socket(AF_INET, SOCK_STREAM, 0);
if (s_client == INVALID_SOCKET) {
printf("socket() Failed: %d\n", WSAGetLastError());
return -1;
}
// 2. 连接服务器
struct sockaddr_in target = {0}; // 定义目标地址结构体
target.sin_family = AF_INET; // 设置地址族为 IPv4
target.sin_port = htons(iPort); // 设置端口号,并将其转换为网络字节序
target.sin_addr.s_addr = inet_addr("127.0.0.1"); // 将主机数转换成无符号长整形的网络字节顺序
if (-1 == connect(s_client, (struct sockaddr*)&target, sizeof(target))) {
printf("connect server failed!!!\n");
closesocket(s_client);
return -1;
}
while (1) {
char choice[10];
printf("请选择传输普通数据还是文件 (d/f): ");
scanf("%s", choice);
if (strcmp(choice, "d") == 0) {
// 发送普通文本数据
send(s_client, "d", strlen("d"), 0); // 发送模式标志 'd'
char sbuffer[BUFFER_SIZE] = {0}; // 定义发送数据的缓冲区
printf("请输入数据: ");
scanf("%s", sbuffer);
send(s_client, sbuffer, strlen(sbuffer), 0); // 将数据发送到服务器
char rbuffer[BUFFER_SIZE] = {0}; // 定义接收数据的缓冲区
int ret = recv(s_client, rbuffer, BUFFER_SIZE, 0); // 接收服务器返回的数据
if (ret <= 0) break; // 如果接收失败或服务器断开连接,退出循环
printf("--服务器成功接收到数据:%s\n", rbuffer); // 打印接收到的数据
} else if (strcmp(choice, "f") == 0) {
// 发送文件
send(s_client, "f", strlen("f"), 0); // 发送模式标志 'f'
char filename[BUFFER_SIZE] = {0};
printf("请输入文件名: ");
scanf("%s", filename);
FILE *fp = fopen(filename, "rb"); // 以二进制读取模式打开文件
if (!fp) {
printf("该文件不存在,请重新输入.\n");
continue;
}
send(s_client, filename, strlen(filename), 0); // 发送文件名
while (1) {
char buffer[BUFFER_SIZE] = {0};
size_t bytes_read = fread(buffer, 1, BUFFER_SIZE, fp); // 读取文件内容
if (bytes_read > 0) {
send(s_client, buffer, bytes_read, 0); // 发送文件内容
}
if (bytes_read < BUFFER_SIZE) break; // 文件传输结束
}
fclose(fp); // 关闭文件
printf("--文件已发送.\n");
} else {
printf("输入无效,请重新选择\n");
}
}
// 4. 关闭连接
closesocket(s_client);
WSACleanup(); // 清理 Winsock 资源
return 0;
}
```
### 总结
- **服务端**:
- 初始化 Winsock 库。
- 创建并绑定一个监听套接字。
- 监听客户端连接请求。
- 接受客户端连接后,创建新线程处理每个客户端。
- 在线程中,根据客户端发送的模式(`d` 表示普通数据,`f` 表示文件)进行相应的处理。
- 处理完客户端请求后,关闭套接字并结束线程。
- **客户端**:
- 初始化 Winsock 库。
- 创建并连接到服务端。
- 根据用户选择(`d` 表示普通数据,`f` 表示文件),向服务端发送相应数据。
- 对于普通数据,发送并接收回传的数据。
- 对于文件,先发送文件名,再逐块发送文件内容。
- 最后关闭连接并清理 Winsock 资源。