网络编程 期中_动手编个小程序

本文介绍了一个网络编程实战案例,通过实现客户端与服务器的交互,支持pwd、cd、ls等命令,展示了如何使用C语言进行网络编程,包括套接字创建、连接、数据读写及命令执行结果返回等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、题目

请分别写一个客户端程序和服务器程序,客户端程序连接上服务器之后,通过敲命令和服务器进行交互,支持的交互命令包括:

  • pwd:显示服务器应用程序启动时的当前路径
  • cd:改变服务器应用程序的当前路径
  • ls:显示服务器应用程序当前路径下的文件列表
  • quit:客户端进程退出,但是服务器端不能退出,第二个客户可以再次连接上服务器端

客户端程序要求

  • 可以指定待连接的服务器端 IP 地址和端口
  • 在输入一个命令之后,回车结束,之后等待服务器端将执行结果返回,客户端程序需要将结果显示在屏幕上

服务器程序要求

  • 暂时不需要考虑多个客户并发连接的情形,只考虑每次服务一个客户连接
  • 要把命令执行的结果返回给已连接的客户端
  • 服务器端不能因为客户端退出就直接退出

二、开始

客户端

telnet-client.c

#include "common.h"

int main(int argc, char **argv) {
	// 客户端执行命令格式:./telnet-client 127.0.0.1 43211
	// 看格式就知道有 3 个参数,后两个依次是 IP 地址、端口号(服务器定义的),满足题目关于客户端的第一个要求
	if (argc != 3) {
		error(1, 0, "usage:  telnet-client IPaddress port");
	}

	// 获取端口号(将 ASCII 转换为整数)
	int port = atoi(argv[2]);

	// 创建套接字(TCP:SOCK_STREAM)
	int sockfd = socket(PF_INET, SOCK_STREAM, 0);

	// 创建 IPV4 套接字地址格式(含 IP 地址、端口号)
	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

	// 当前是 TCP,调用 connect 函数将激发 TCP 三次握手
	int connect_rt = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr);
	if (connect_rt < 0) {
		error(1, errno, "connect failed");
	}

	char send_line[MAXLINE];
	char recv_line[MAXLINE];

	fd_set allreads;
	fd_set readmask;

	FD_ZERO(&allreads);
	FD_SET(0, &allreads);
	FD_SET(sockfd, &allreads);

	for (;;) {
		readmask = allreads;
		// 使用 select 同时处理标准输入和套接字
		int rt = select(sockfd + 1, &readmask, NULL, NULL, NULL);
		if (rt <= 0) {
			error(1, errno, "select failed");
		}

		// 套接字可读事件
		if (FD_ISSET(sockfd, &readmask)) {
			// 读数据
			int n = read(sockfd, recv_line, MAXLINE);
			if (n < 0) {
				error(1, errno, "read failed");
			} else if (n == 0) {
				printf("server closed\n");
				break; // 跳出 for 循环,然后通过 exit(0) 退出进程
			}

			recv_line[n] = 0;
			fputs(recv_line, stdout);
			fputs("\n", stdout);
		}

		// 标准输入事件(STDIN_FILENO:标准输入文件描述符,值为 0)
		if (FD_ISSET(STDIN_FILENO, &readmask)) {
			if (fgets(send_line, MAXLINE, stdin) != NULL) {
				int i = strlen(send_line);
				if (send_line[i - 1] == '\n') {
					send_line[i - 1] = 0;
				}

				// 读到 quit,调用 shutdown 函数关闭发送方向
				if (strncmp(send_line, "quit", strlen(send_line)) == 0) {
					if (shutdown(sockfd, 1)) {
						error(1, errno, "shutdown failed");
					}
				}

				// 发送读到的内容
				if (write(sockfd, send_line, strlen(send_line)) < 0) {
					error(1, errno, "write failed");
				}
			}
		}
	}
	exit(0);
}

服务端

telnet-server.c

#include "common.h"

static int count;

static void sig_int(int singo) {
	printf("\nreceived %d datagrams\n", count);
	exit(0);
}

char *run_cmd(char *cmd) {
	char *data = malloc(16 * 1024);
	bzero(data, sizeof(data));
	char *data_index = data; // 通过移动指针 data_index 间接操作 data,最终可以直接返回 data,因为 data 的指针没有移动

	const int max_buffer = 256;
	char buffer[max_buffer];

	// 这里使用 popen,别搞错了,不是 fopen 或 open
	FILE *fp = popen(cmd, "r"); // 若成功打开,则必须调用 pclose 关闭
	if (fp) {
		while (!feof(fp)) {
			if (fgets(buffer, sizeof(max_buffer), fp) != NULL) {
				int len = strlen(buffer);
				memcpy(data_index, buffer, len);
				data_index += len;
			}
		}
		pclose(fp);
		fp = 0;
	}
	return data;
}

int main(int argc, char **argv) {
	int listenfd = socket(PF_INET, SOCK_STREAM, 0);

	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	// 在 bind 之前设置 SO_REUSEADDR 套接字选项才有效
	int on = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	
	int bind_rt = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	if (bind < 0) {
		error(1, errno, "bind failed");
	}

	int listen_rt = listen(listenfd, LISTENQ);
	if (listen_rt < 0) {
		error(1, errno, "listen failed");
	}

	signal(SIGPIPE, SIG_IGN);

	int connfd;
	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);

	char buf[256];
	count = 0;
	
	// accept 在 for(;;) 循环中,阻塞的,一个一个响应
	while (1) {
		if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) {
			error(1, errno, "accept failed");
		}

		while (1) {
			bzero(buf, sizeof(buf));
			int n = read(connfd, buf, sizeof(buf));
			if (n < 0) {
				error(1, errno, "read failed");
			} else if (n == 0) {
				printf("client closed\n");
				close(connfd);
				break;
			}

			count++;
			buf[n] = 0;
			
			if (strncmp(buf, "ls", 2) == 0) {
				char *result = run_cmd("ls");
				if (send(connfd, result, strlen(result), 0) < 0) {
					return 1;
				}
				free(result);
			} else if (strncmp(buf, "pwd", 3) == 0) {
				char buf[256];
				char *result = getcwd(buf, 256);
				if (send(connfd, result, strlen(result), 0) < 0) {
					return 1;
				}

			} else if (strncmp(buf, "cd ", 3) == 0) { // 注意:cd 后面有一个空格
				char target[256];
				bzero(target, sizeof(target));
				memcpy(target, buf + 3, strlen(buf) - 3);
				if (chdir(target) == -1) {
					printf("change dir failed, %s\n", target);
				}
			} else {
				char *error = "error: unknow input type";
				if (send(connfd, error, strlen(error), 0) < 0) {
					return 1;
				}
			}
		}
	}
	exit(0);
}

头文件 common.h

#ifndef MID_TEST_COMMON_H
#define MID_TEST_COMMON_H

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <strings.h>
#include    <sys/socket.h>    /* basic socket definitions */
#include    <netinet/in.h>    /* sockaddr_in{} and other Internet defns */
#include    <arpa/inet.h>    /* inet(3) functions */
#include    <errno.h>

#include    <unistd.h>
#include    <signal.h>

#include <sys/select.h>

void error(int status, int err, char *fmt, ...);

#define    SERV_PORT      43211
#define    MAXLINE        4096

#define    LISTENQ        1024

#endif //MID_TEST_COMMON_H

三、CMake 管理当前项目

① 代码组成

-CMakeLists.txt
-include:存放头文件
-src:存放源代码
代码组成

CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 3.1)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include)
ADD_SUBDIRECTORY(src)

include 目录:include/common.h(common.h 上面有)
include 目录

src 目录(telnet-client.c、telnet-server.c 上面有)
src目录

src/CmakeLists.txt

ADD_EXECUTABLE(telnet-client telnet-client.c)
TARGET_LINK_LIBRARIES(telnet-client)

ADD_EXECUTABLE(telnet-server telnet-server.c)
TARGET_LINK_LIBRARIES(telnet-server)

② 创建并进入 build 目录

mkdir build && cd build

创建并进入 build 目录

③ 外部编译

cmake .. && make

外部编译

四、测试

测试步骤
① 打开两个命令行窗口
② 其中一个窗口先执行服务器命令,输入命令 ./telnet-server 后回车
③ 另一个窗口再执行客户端命令,输入命令 ./telnet-client 127.0.0.1 43211 后回车
在客户端所在命令行窗口
输入 ls、pwd、cd xxx(xxx 代表目录)命令,服务器正常返回结果
输入 quit,客户端退出,服务器打印 client closed

左:客户端;右:服务端
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值