项目需求:
实现回声服务器的客户端/服务器程序,客户端通过网络连接到服务器,并发送任意一串英文信息,服务器端接收信息后,将每个字符转换为大写并回送给客户端显示。
Socket通信模型
服务器端
#include<stdio.h> // 标准输入输出库
#include<unistd.h> // 提供对 POSIX 操作系统 API 的访问功能,包含许多 POSIX 标准的函数原型
#include<sys/types.h> // 包含一些基本的系统数据类型的定义
#include<string.h> // 提供字符串操作函数
#include<ctype.h> // 提供一些字符分类函数
#include<arpa/inet.h> // 提供对IPv4地址转换函数的支持
#define SERVER_PORT 8888
int main(void)
{
int sock;
struct sockaddr_in server_addr;
// 1. 创建套接字
sock = socket(AF_INET, SOCK_STREAM, 0);
// 2. 清空标签,写上地址和端口号
bzero(&server_addr, sizeof(server_addr));
// 3. 设置地址族为IPv4,监听本地所有IP地址,绑定端口号
server_addr.sin_family = AF_INET; // 选择协议族IPV4
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本地所有IP地址
server_addr.sin_port = htons(SERVER_PORT); // 绑定端口号
// 4. 将套接字绑定到指定地址和端口
bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
// 5. 监听套接字,允许同时连接的最大客户端数为128
listen(sock, 128);
// 6. 打印等待客户端连接的提示信息
printf("等待客户端的连接...\n");
int done = 1;
while (done)
{
struct sockaddr_in client;
int client_sock;
int len = 0;
char client_ip[64];
char buf[256];
socklen_t client_addr_len;
client_addr_len = sizeof(client);
// 接受客户端连接,返回客户端套接字描述符
client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
// 打印客户端地址和端口号
printf("客户端IP: %s\t端口号: %d\n",
inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)),
ntohs(client.sin_port));
//读取客户端发送的数据
len = read(client_sock, buf, sizeof(buf)-1);
buf[len] = '\0';
printf("receive[%d]: %s\n", len, buf);
//转换成大写
for(i=0; i<len; i++)
{
if(buf[i]>='a' && buf[i]<='z'){
buf[i] = buf[i] - 32;
}
buf[i] = toupper(buf[i]);
}
len = write(client_sock, buf, len);
printf("\nfinished. len: %d\n", len);
// 关闭客户端套接字
close(client_sock);
}
// 关闭服务器套接字
close(sock);
return 0;
}
struct sockaddr_in
是用于表示 IPv4 地址结构的结构体,在网络编程中经常用于指定套接字的地址信息。这个结构体在 <netinet/in.h>
头文件中定义。
struct sockaddr_in {
short sin_family; // 地址族 (Address Family),通常设置为 AF_INET
unsigned short sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IPv4 地址结构
char sin_zero[8]; // 为了保持和 struct sockaddr 一样的大小,一般设置为 0
};
sin_family
:地址族,通常设置为AF_INET
表示使用 IPv4 地址。sin_port
:端口号,使用unsigned short
类型表示,需要使用网络字节序(通过htons
函数转换)。sin_addr
:IPv4 地址结构,包含一个字段s_addr
表示32位的IPv4地址,需要使用htonl
函数进行转换。sin_zero
:为了保持和struct sockaddr
结构一样的大小而添加的字段,一般设置为0。
-
创建套接字,返回套接字描述符,并将其保存在sock = socket(AF_INET, SOCK_STREAM, 0);
sock
变量中。使用IPv4地址族(AF_INET
),流式套接字(SOCK_STREAM
,即TCP),协议为默认(0
)。 -
清空bzero(&server_addr, sizeof(server_addr));
server_addr
结构体,确保其中的数据全部为零。 -
将套接字bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
sock
绑定到指定的地址和端口,使用server_addr
结构体来指定。 -
开始监听套接字,允许同时连接的最大客户端数为128。listen(sock, 128);
-
打印等待客户端连接的提示信息。printf("等待客户端的连接...\n");
客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_PORT 8888
#define SERVER_IP "192.168.65.129"
int main(int argc, char **argv) {
int sockfd;
char *message;
struct sockaddr_in servaddr;
int n;
char buf[64];
// 检查命令行参数
if (argc != 2) {
fputs("Usage: ./echo_client message \n", stderr);
exit(1);
}
// 获取要发送的消息
message = argv[1];
printf("message: %s\n", message);
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 初始化服务器地址结构
memset(&servaddr, '\0', sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
servaddr.sin_port = htons(SERVER_PORT);
// 连接到服务器
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 发送消息到服务器
write(sockfd, message, strlen(message));
// 从服务器接收响应
n = read(sockfd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("receive: %s\n", buf);
} else {
perror("error!!!");
}
printf("finished.\n");
// 关闭套接字
close(sockfd);
return 0;
}
初步测试
1.运行服务器端
2.运行客户端
在最新版本的Windows中,默认情况下并没有安装telnet
客户端。
可以按照以下步骤来启用telnet
客户端:
-
打开控制面板: 在Windows搜索栏中输入"Control Panel",然后打开控制面板。
-
进入程序: 在控制面板中,选择"程序",然后选择"启用或关闭Windows功能"。
-
启用Telnet客户端: 在弹出的窗口中,找到"Telnet客户端",勾选,然后确定。
-
等待安装完成: Windows会开始安装
telnet
客户端。安装完成,就可以再次尝试使用telnet
命令。
客户端发送信息