服务器端代码
/*2 - TC服务器 */
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库函数
#include <unistd.h> // Unix标准函数
#include <sys/socket.h> // 套接字相关函数
#include <sys/types.h> // 系统数据类型
#include <fcntl.h> // 文件控制
#include <sys/stat.h> // 文件状态
#include <netinet/in.h> // 网络地址结构
#include <arpa/inet.h> // IP地址转换函数
int main(int argc,char *argv[])
{
if(argc!=3){ // 检查参数数量是否正确
printf("Usage:%s ip port\n",argv[0]); // 打印使用方法
exit(0); // 退出程序
}
//1.创建socket
int sockfd = socket(AF_INET,SOCK_STREAM,0); // 创建IPv4 TCP套接字
if(sockfd==-1){ // 检查socket创建是否成功
perror("socket"); // 打印错误信息
exit(-1); // 异常退出
}
//2.绑定ip和端口号(自己)
struct sockaddr_in addr; // 定义IPv4地址结构
addr.sin_family = AF_INET; // 设置地址族为IPv4
addr.sin_port = htons(atoi(argv[2])); // 将端口号转换为网络字节序
addr.sin_addr.s_addr = inet_addr(argv[1]); // 将IP地址字符串转换为网络字节序
int res = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)); // 绑定套接字到指定地址
if(res==-1){ // 检查绑定是否成功
perror("bind"); // 打印错误信息
exit(-1); // 异常退出
}
//3.监听
listen(sockfd,10); // 开始监听,设置最大连接队列为10
//4.等待客户端连接
while(1){ // 无限循环,持续接受客户端连接
struct sockaddr_in cilent_addr; // 存储客户端地址信息
socklen_t len = sizeof(cilent_addr); // 客户端地址结构长度
// 接受客户端连接
int newfd = accept(sockfd,(struct sockaddr *)&cilent_addr,&len);
if(newfd==-1){ // 检查连接是否成功
perror("accept"); // 打印错误信息
exit(-1); // 异常退出
}
// 打印客户端IP地址
printf("%s到此一游!\n",inet_ntoa(cilent_addr.sin_addr));
//5.和客户端通信
write(newfd,"hello",6); // 向客户端发送"hello"消息
//不再通信关闭
close(newfd); // 关闭与客户端的连接
}
close(sockfd); // 关闭服务器套接字(实际上永远不会执行到这里)
return 0;
}
主要流程说明:
参数检查:检查程序启动参数是否正确(需要IP和端口两个参数)
创建套接字:创建一个IPv4的TCP套接字
绑定地址:将套接字绑定到指定的IP地址和端口
开始监听:设置套接字为监听状态,准备接受客户端连接
接受连接:循环等待并接受客户端连接
处理连接:对于每个连接,打印客户端IP并发送"hello"消息
关闭连接:完成通信后关闭与客户端的连接
注意事项:
服务器会一直运行,直到被手动终止
每次只处理一个客户端连接,发送完消息后立即关闭连接
实际应用中可能需要更复杂的错误处理和并发处理机制
客户端代码
#include <stdio.h> // 标准输入输出函数
#include <stdlib.h> // 标准库函数(如exit)
#include <unistd.h> // Unix标准函数(如read, write, close)
#include <string.h> // 字符串处理函数
#include <sys/socket.h> // 套接字相关函数和结构
#include <netinet/in.h> // 互联网地址族定义
#include <arpa/inet.h> // IP地址转换函数
// 定义常量
#define SERVER_IP "192.168.53.1" // 服务器IP地址
#define SERVER_PORT 5555 // 服务器端口号
#define BUFFER_SIZE 1024 // 接收缓冲区大小
int main() {
// 1. 创建socket
// AF_INET: IPv4协议, SOCK_STREAM: 流式套接字(TCP), 0: 默认协议
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) { // 检查socket是否创建成功
perror("socket creation failed"); // 打印错误信息
exit(EXIT_FAILURE); // 退出程序并返回失败状态
}
// 2. 准备服务器地址结构
struct sockaddr_in server_addr; // 定义IPv4地址结构
memset(&server_addr, 0, sizeof(server_addr)); // 清空结构体
server_addr.sin_family = AF_INET; // 设置地址族为IPv4
server_addr.sin_port = htons(SERVER_PORT); // 设置端口号(转换为网络字节序)
// 将点分十进制IP转换为网络字节序的二进制形式
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 3. 连接到服务器
// 参数:套接字描述符,服务器地址结构,结构体大小
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
perror("connection failed"); // 连接失败时打印错误
close(sockfd); // 关闭套接字
exit(EXIT_FAILURE); // 退出程序
}
// 连接成功提示
printf("Connected to server %s:%d\n", SERVER_IP, SERVER_PORT);
// 4. 接收服务器消息
char buffer[BUFFER_SIZE]; // 定义接收缓冲区
// 从套接字读取数据,最大读取BUFFER_SIZE-1字节(保留位置给\0)
ssize_t bytes_received = read(sockfd, buffer, BUFFER_SIZE - 1);
if (bytes_received == -1) { // 读取失败
perror("read failed");
} else if (bytes_received == 0) { // 对端关闭连接
printf("Server closed the connection\n");
} else { // 成功读取数据
buffer[bytes_received] = '\0'; // 确保字符串以\0结尾
printf("Received from server: %s\n", buffer); // 打印接收到的消息
}
// 5. 关闭连接
close(sockfd); // 关闭套接字
return 0; // 正常退出程序
}
关键点详细说明:
socket创建:
AF_INET
表示使用IPv4协议SOCK_STREAM
表示使用面向连接的TCP协议第三个参数0表示使用默认协议
地址结构准备:
memset
清空结构体是良好的编程习惯htons
将主机字节序的端口号转换为网络字节序inet_addr
将点分十进制的IP字符串转换为网络字节序的二进制形式
连接过程:
connect
是阻塞调用,会等待直到连接建立或失败连接失败时需要先关闭已创建的套接字再退出
数据接收:
read
返回读取的字节数,-1表示错误,0表示连接关闭手动添加字符串终止符
\0
是安全的做法
资源清理:
程序退出前必须关闭套接字描述符
这是良好的资源管理习惯
这个客户端实现了最基本的TCP连接功能,可以扩展添加发送数据、循环通信等功能。