網絡編程 期中_動手編個小程序

一、題目

請分別寫一個客戶端程序和服務器程序,客戶端程序連接上服務器之後,通過敲命令和服務器進行交互,支持的交互命令包括:

  • 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

左:客戶端;右:服務端
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章