概要
实现一个简单的客户端-服务器通信模型,基于TCP协议。
-
服务器端程序:
- 服务器在指定的端口(
SERVER_PORT
8888)上监听客户端的连接请求。 - 每当有客户端连接到服务器时,服务器通过
fork
创建一个子进程来处理该客户端的通信。 - 子进程接收客户端发送的数据,并将其打印到服务器的控制台上。
- 服务器在指定的端口(
-
客户端程序:
- 客户端程序通过命令行参数指定服务器的IP地址。
- 客户端尝试连接到服务器,并在连接成功后,从标准输入读取数据,将其发送到服务器。
- 客户端在发送数据后继续等待用户输入,直到连接断开或发送失败。
实验目的:
这两个程序配合使用,展示了TCP通信的基本原理。服务器端程序负责监听和处理多个客户端的连接,而客户端程序负责向服务器发送数据。
实验
环境
Linunx
服务器代码
#include <sys/types.h> /* 包含基本的数据类型定义 */
#include <sys/socket.h> /* 包含套接字相关的函数和数据结构定义 */
#include <string.h>
#include <netinet/in.h> /* 包含Internet地址族相关的定义 */
#include <arpa/inet.h> /* 包含IP地址转换的函数定义 */
#include <unistd.h> /* 包含Unix标准函数定义,如close */
#include <stdio.h>
#include <signal.h> /* 包含信号处理相关的定义 */
#define SERVER_PORT 8888 /* 定义服务器监听的端口号 */
#define BACKLOG 10 /* 定义listen函数的监听队列长度 */
int main(int argc, char **argv)
{
int iSocketServer; /* 服务器套接字描述符 */
int iSocketClient; /* 客户端套接字描述符 */
struct sockaddr_in tSocketServerAddr; /* 服务器地址结构 */
struct sockaddr_in tSocketClientAddr; /* 客户端地址结构 */
int iRet; /* 用于保存函数返回值 */
int iAddrLen; /* 地址结构长度 */
int iRecvLen; /* 接收到的数据长度 */
unsigned char ucRecvBuf[1000]; /* 数据接收缓冲区 */
int iClientNum = -1; /* 客户端计数器 */
/* 忽略子进程的终止信号,防止僵尸进程 */
signal(SIGCHLD,SIG_IGN);
/* 创建服务器套接字,使用IPv4地址族(AF_INET)和面向连接的TCP协议(SOCK_STREAM) */
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}
/* 初始化服务器地址结构 */
tSocketServerAddr.sin_family = AF_INET; /* 设置地址族为IPv4 */
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* 设置端口号,并将其转换为网络字节序 */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* 绑定到所有可用的网络接口 */
memset(tSocketServerAddr.sin_zero, 0, 8); /* 清空sin_zero字段,保持结构体对齐 */
/* 将套接字绑定到指定的IP地址和端口 */
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}
/* 监听套接字,准备接受连接请求 */
iRet = listen(iSocketServer, BACKLOG);
if (-1 == iRet)
{
printf("listen error!\n");
return -1;
}
while (1) /* 无限循环,持续接受并处理客户端连接 */
{
iAddrLen = sizeof(struct sockaddr);
/* 接受客户端连接,返回客户端套接字描述符 */
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient)
{
iClientNum++; /* 增加客户端计数器 */
/* 打印客户端IP地址 */
printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork()) /* 创建子进程处理客户端连接 */
{
/* 子进程中的代码 */
while (1)
{
/* 接收客户端发送的数据并显示 */
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0) /* 连接关闭或出错 */
{
close(iSocketClient); /* 关闭客户端套接字 */
return -1; /* 结束子进程 */
}
else
{
ucRecvBuf[iRecvLen] = '\0'; /* 添加字符串结束符 */
/* 打印接收到的客户端消息 */
printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
}
}
}
}
}
/* 关闭服务器套接字 */
close(iSocketServer);
return 0;
}
客户端代码
#include <sys/types.h> /* 包含基本的数据类型定义 */
#include <sys/socket.h> /* 包含套接字相关的函数和数据结构定义 */
#include <string.h>
#include <netinet/in.h> /* 包含Internet地址族相关的定义 */
#include <arpa/inet.h> /* 包含IP地址转换的函数定义 */
#include <unistd.h> /* 包含Unix标准函数定义,如close */
#include <stdio.h>
#define SERVER_PORT 8888 /* 定义服务器监听的端口号 */
int main(int argc, char **argv)
{
int iSocketClient; /* 客户端套接字描述符 */
struct sockaddr_in tSocketServerAddr; /* 服务器地址结构 */
int iRet; /* 用于保存函数返回值 */
unsigned char ucSendBuf[1000]; /* 数据发送缓冲区 */
int iSendLen; /* 发送的数据长度 */
/* 检查命令行参数,必须提供服务器IP地址 */
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
/* 创建客户端套接字,使用IPv4地址族(AF_INET)和面向连接的TCP协议(SOCK_STREAM) */
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
/* 初始化服务器地址结构 */
tSocketServerAddr.sin_family = AF_INET; /* 设置地址族为IPv4 */
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* 设置端口号,并将其转换为网络字节序 */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* 使用INADDR_ANY时,将绑定到所有可用的接口 */
/* 将命令行提供的IP地址字符串转换为网络字节序的二进制形式 */
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n"); /* 如果转换失败,提示IP地址无效 */
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8); /* 清空sin_zero字段,保持结构体对齐 */
/* 连接到服务器 */
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n"); /* 如果连接失败,打印错误信息并退出 */
return -1;
}
/* 循环从标准输入读取数据并发送到服务器 */
while (1)
{
if (fgets(ucSendBuf, 999, stdin)) /* 从标准输入读取一行数据 */
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0); /* 发送数据到服务器 */
if (iSendLen <= 0) /* 如果发送失败或连接断开 */
{
close(iSocketClient); /* 关闭客户端套接字 */
return -1; /* 退出程序 */
}
}
}
return 0;
}
实验结果
//客户端消息
ktj:~/tcp$ ./client.out 127.0.0.1
hello world ktj
//服务器端消息
ktj:~/tcp$ ./server.out
Get connect from client 0 : 127.0.0.1
Get Msg From Client 0: hello world ktj