服务器代码
cpp
/*
* ============================================================================================
* 文件名称:server.c
* 创建者: mf
* 创建日期:2024年05月20日
* 描述:此程序实现了一个简易的TCP服务器,监听8888端口,接受客户端连接后,每隔一秒向客户端发送当前时间,
* 直到写入操作失败或客户端断开连接。服务器通过忽略SIGPIPE信号来处理潜在的管道错误。
* ============================================================================================
*/
#include <stdio.h> // 标准输入输出定义
#include <sys/types.h> // 基本系统数据类型定义
#include <sys/socket.h> // 套接字编程相关函数声明
#include <netinet/in.h> // 网络地址结构体定义
#include <netinet/ip.h> // IP协议头文件
#include <arpa/inet.h> // 地址转换函数声明
#include <unistd.h> // 通用Unix函数声明,如close()
#include <signal.h> // 信号处理函数声明
#include <time.h> // 时间函数声明
#define PORT 8888 // 监听端口号
int main(int argc, char *argv[])
{
int ret = -1; // 返回值变量
int sfd = -1; // 服务器套接字描述符
int cfd = -1; // 客户端连接套接字描述符
int opt = 1; // 选项值,用于设置套接字选项
// 创建TCP套接字
sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1)
{
perror("socket"); // 错误处理
return -1;
}
// 设置套接字选项,允许端口重用
ret = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (ret == -1)
{
perror("setsockopt");
return -1;
}
// 服务器地址结构体初始化
struct sockaddr_in adds, addc;
memset(&adds, 0, sizeof(adds)); // 清零
adds.sin_family = AF_INET; // 协议族为IPv4
adds.sin_port = htons(PORT); // 设置监听端口
adds.sin_addr.s_addr = INADDR_ANY; // 监听所有IP地址
// 绑定套接字到指定地址和端口
ret = bind(sfd, (struct sockaddr*)&adds, sizeof(adds));
if (ret == -1)
{
perror("bind");
return -1;
}
// 开始监听,最多挂起10个连接请求
ret = listen(sfd, 10);
if (ret == -1)
{
perror("listen");
return -1;
}
// 忽略SIGPIPE信号,避免因写入已关闭的连接而退出
signal(SIGPIPE, SIG_IGN);
printf("等待客户端在端口 %d 上的连接...\n", PORT);
// 循环等待并接受客户端连接
while (1)
{
socklen_t addrlen = sizeof(addc);
cfd = accept(sfd, (struct sockaddr*)&addc, &addrlen);
if (cfd == -1)
{
perror("accept");
continue; // 出错则继续等待下一个连接
}
// 打印客户端连接信息
printf("连接来自 IP: %s 端口: %d\n", inet_ntoa(addc.sin_addr), ntohs(addc.sin_port));
// 向客户端发送当前时间,每秒一次
while (1)
{
time_t tm = time(NULL); // 获取当前时间
sprintf(buf, "%s", ctime(&tm)); // 将时间转换为字符串
ret = write(cfd, buf, strlen(buf)); // 发送给客户端
if (ret == -1)
{
perror("write"); // 写入错误处理
break; // 发生错误则跳出循环
}
sleep(1); // 暂停一秒
}
// 客户端连接结束,关闭连接套接字
close(cfd);
printf("客户端已断开连接。等待新连接...\n");
}
// 关闭服务器套接字,尽管这里可能永远不会执行到
close(sfd);
return 0;
}
客户端代码
cpp
/*
* ============================================================================================
* 文件名称:client.c
* 创建者: mf
* 创建日期:2024年05月20日
* 描述:此程序实现了一个简易的TCP客户端,用于连接指定IP地址和端口的服务器(此处为192.168.110.6:8888),
* 并不断从服务器接收数据,打印至标准输出。若连接失败或读取数据时发生错误,则程序将终止。
* ============================================================================================
*/
#include <stdio.h> // 标准输入输出函数库
#include <sys/types.h> // 基本系统数据类型定义
#include <sys/socket.h> // 套接字编程所需函数声明
#include <netinet/in.h> // 网络地址结构体定义
#include <arpa/inet.h> // 地址转换函数声明
#include <unistd.h> // 提供了POSIX操作系统API中的函数声明,如close()
#define IP "192.168.110.6" // 服务器IP地址
#define PORT 8888 // 服务器端口号
int main(int argc, char *argv[])
{
int fd = -1; // 客户端套接字描述符
int ret = -1; // 函数返回值
char buf[32] = {0}; // 用于接收服务器数据的缓冲区
int opt = 1; // 设置套接字选项的参数
// 创建TCP套接字
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
perror("socket"); // 错误处理
return -1;
}
// 设置套接字选项,允许端口重用(注意:此选项在客户端通常不是必须的)
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (ret == -1)
{
perror("setsockopt");
return -1;
}
// 服务器地址结构体初始化
struct sockaddr_in adds;
memset(&adds, 0, sizeof(adds)); // 清零
adds.sin_family = AF_INET; // 协议族为IPv4
adds.sin_port = htons(PORT); // 设置服务器端口
adds.sin_addr.s_addr = inet_addr(IP); // 设置服务器IP地址
// 尝试连接服务器
ret = connect(fd, (struct sockaddr*)&adds, sizeof(adds));
if (ret == -1)
{
perror("connect");
return -1;
}
// 连接成功,开始循环读取服务器发送的数据
printf("已连接到服务器,开始接收数据...\n");
while(1)
{
// 读取服务器发送的数据
ret = read(fd, buf, sizeof(buf));
if (ret == -1)
{
perror("read"); // 读取错误处理
return -1;
} else if (ret == 0)
{
// 对端关闭连接,read返回0
printf("服务器已关闭连接。\n");
return -1;
} else
{
// 打印接收到的数据
printf("%s", buf);
}
}
// 正常情况下不会执行到此处,但为了规范依然关闭套接字
close(fd);
return 0;
}