期中大作业丨动手编写一个自己的程序吧!

你好,我们之前已经学习了网络编程的基础篇和提高篇。经过近两个月的学习,不知道你对这些内容的掌握程度如何呢?

我之前说过,网络编程是一个既重视理论,又重视实战的内容模块。一味地消化理论并不足以让你掌握网络编程,只有自己亲自动手写代码,编写程序,才能对TCP、UDP、套接字这些内容有更深切的体会,才能切实感受到它们是如何帮助我们的程序进行互联互通的。

网络编程就像一个魔法棒,我们之前已经学习了一些“咒语”,但上手操纵才能真实地施展魔法。所以我在专栏中安排了一个期中作业,借由这个作业让你上手编写代码,相信你在这个过程中也会更有成就感。

我在这里再提供一些“咒语”提示,方便你回顾之前的内容,以便在解题的时候更加胸有成竹。

客户端程序可以以第11篇文章的程序例子为原型,这里主要考察使用select多路复用,一方面从标准输入接收字节流,另一方面通过套接字读写,以及使用shutdown关闭半连接的能力。

服务器端程序则考察套接字读写的能力,以及对端连接关闭情况下的异常处理等能力。

题目不难,相信你可以做好。

题干

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

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

客户端程序要求

  1. 可以指定待连接的服务器端IP地址和端口。
  2. 在输入一个命令之后,回车结束,之后等待服务器端将执行结果返回,客户端程序需要将结果显示在屏幕上。
  3. 样例输出如下所示。
第一次连接服务器
$./telnet-client 127.0.0.1 43211
pwd
/home/vagrant/shared/Code/network/yolanda/build/bin
cd ..
pwd
/home/vagrant/shared/Code/network/yolanda/build
cd ..
pwd
/home/vagrant/shared/Code/network/yolanda
ls
build
chap-11
chap-12
chap-13
chap-14
chap-15
chap-16
chap-17
chap-18
chap-20
chap-21
chap-22
chap-23
chap-25
chap-26
chap-27
chap-28
chap-4
chap-5
chap-6
chap-7
clean.sh
cmake-build-debug
CMakeLists.txt
lib
mid-homework
README.md


cd -
pwd
/home/vagrant/shared/Code/network/yolanda
cd /home
pwd
/home
ls
ubuntu
vagrant
quit

//再次连接服务器
$./telnet-client 127.0.0.1 43211
pwd
/home/vagrant/shared/Code/network/yolanda/build
ls
bin
chap-11
chap-12
chap-13
chap-15
chap-16
chap-17
chap-18
chap-20
chap-21
chap-22
chap-23
chap-25
chap-26
chap-28
chap-4
chap-5
chap-6
chap-7
CMakeCache.txt
CMakeFiles
cmake_install.cmake
lib
Makefile
mid-homework

quit

服务器程序要求

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

你可以把自己编写的程序代码放到GitHub上,并在评论里留下链接。我会认真查看这些代码,并在周五给出自己的反馈意见以及题目分析。由于时间有限,无法尽数查看,后续我会以答疑或者加餐的形式再做补充。

期待你的成果!

精选留言

  • 传说中的成大大

    2019-09-17 23:34:06

    https://github.com/xiecheng1991/network-practise-code-by-myself/tree/master/mid_test
    实现了如下功能
    1. 自定义协议用于服务器端发送到客户端过后是否有需要打印的内容如ls和pwd的返回
    2. 设置了心跳检测 如果客户端一定时间不发消息过后 服务器会断开它的连接并且退出,退出的原因在于因为只有一个客户端的连接 所以如果不退出会重复的select超时
    3. 设置了重用so_reuseaddr 避免time_wait状态
    4. 实现了ls命令
    5. 实现了pwd命令
    代码缺陷如下
    1. 未完整的实现cd命令确实理解不到
    2. 因为只能实现1对1的服务器客户端逻辑 所以很多地方写的不全面
    今天晚上花了三个多小时把代码写好 并且通过gdb调试完成
    作者回复

    写得不错,精神可嘉

    2019-09-19 23:38:31

  • Steiner

    2019-09-20 00:52:24

    第一次写这种比较大的交互程序,用c语言写了几次,感觉一堆东西都写在一起太难维护了,于是用c++做了点类设计.发现的程序bug太多了,目前打算简单的交互以下,服务端一次与一个客户端交互,服务端老是段错误,用gdb发现这个path一直都是0x0,不知道什么原因,求老师指正
    https://github.com/Ne-Steiner/socket
    作者回复

    char * path;
    path=getcwd(path,128);

    这段改下:
    char path[128];
    char * result = getcwd(path, 128);

    因为需要分配一段内存给getcwd调用才可以。

    2019-09-20 14:18:10

  • 沉淀的梦想

    2019-09-19 01:53:07

    https://github.com/DQinYuan/homework-telnet-test

    我主要是写Java的,没怎么写过C,边百度边写好不容易写出一版,代码可能比较简陋,老师不要嫌弃
    作者回复

    写的挺好的呀

    2019-09-19 17:09:29

  • sdjdd

    2019-09-18 00:05:41

    https://github.com/sdjdd/net-programming-midterm
    作者回复

    写得很不错啊

    2019-09-19 23:38:09

  • ray

    2020-04-09 23:11:28

    感谢老师的指导,虽然写的有点晕,但还是收获良多。
    现在还不敢说能徒手写出良好的socket程式,但若是要除错,使用linux工具找问题,已经顺畅许多了!
    再次感谢老师的指导^^

    平常用php开发,就用php写啦XD
    https://github.com/ChanJuiHuang/php_socket_program
    作者回复

    👍

    2020-04-12 16:41:58

  • 传说中的成大大

    2019-09-16 15:25:43

    这个期中大作业的难点不在网络方面 在于 如何在程序中如何cd ../ pwd ls等等o(╥﹏╥)o
    作者回复

    被你看出来了 :)

    2019-09-19 17:39:47

  • 陈狄

    2023-09-18 15:09:28

    不好意思老师,作业交晚了
    int main(int argc, char *argv[])
    {
    int sockfd = 0, n = 0;
    char recvBuff[1024];
    char sendBuff[1024];
    struct sockaddr_in serv_addr;


    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    return 1;
    }

    memset(&serv_addr, 0, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000);

    if (inet_pton(AF_INET, argv[1], &serv_addr.sin_addr) <= 0) {
    printf("inet_pton error occured\n");
    return 1;
    }

    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
    return 1;
    }

    while (scanf("%s", sendBuff) > 0) {
    memset(recvBuff, 0, sizeof(recvBuff));
    write(sockfd, sendBuff, strlen(sendBuff));
    if ((n = read(sockfd, recvBuff, sizeof(recvBuff) - 1)) > 0) {
    printf("%s \n", recvBuff);
    }
    memset(sendBuff, 0, sizeof(sendBuff));
    }

    return 0;
    }

    // 服务器端
    int main(int argc, char *argv[]) {
    int listenfd = 0, connfd = 0;
    struct sockaddr_in serv_addr;

    char recvBuff[1024];
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, '0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000);

    bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    listen(listenfd, 10);

    while (1) {
    connfd = accept(listenfd, (struct sockaddr *)NULL, NULL);
    printf("accept client fd:%d \n", connfd);

    while (read(connfd, recvBuff, sizeof(recvBuff) - 1) > 0) {
    FILE *fp;
    char buffer[80];
    fp = popen(recvBuff, "r");
    fgets(buffer, sizeof(buffer), fp);
    write(connfd, buffer, strlen(buffer));
    pclose(fp);
    memset(recvBuff, 0, sizeof(recvBuff));
    }
    printf("client exit \n");
    sleep(1);
    }
  • 孙升

    2021-12-22 23:04:36

    https://github.com/mandone/net-code/tree/master/mid-test
    补作业了,同Java开发,确实有点吃劲
    作者回复

    多写写就好了。

    2021-12-25 11:20:17

  • supermouse

    2020-02-20 22:20:06

    https://github.com/YoungYo/MidtermAssignment/tree/master
    主要是参考了老师的第11讲的代码,老师说的要求基本都实现了,唯一有点缺陷的是执行cd命令的时候不能执行“cd -”、“cd ~”这类命令,只能指定确切的目录名,比如“/home”
  • chs

    2019-11-10 01:45:09

    https://github.com/chenghengs/network
    请多指教
    作者回复

    写得很好。

    2019-11-17 19:40:58

  • YidWang

    2019-09-24 23:55:57

    消息的去重 没有设计
    作者回复

    具体是指?

    2019-09-25 18:48:54

  • coffee0218

    2019-09-19 10:18:41

    https://github.com/coffee0218/c_client_server
    cd ~还是有问题
    作者回复

    可以不用考虑这个特殊情况

    2019-09-19 17:17:45

  • Geek_007

    2019-09-16 23:52:29

    我有一个问题,高级语言如go,它就没有epoll或者select(不是select那个关键字)。它的epoll只能是syscall,那是不是意味着协程和单进程的epoll多路复用是一样高效的呢???lua似乎也没有epoll的实现。但是他们也都很高效
    作者回复

    首先,golang里面的go routine(协程)是golang语言层面的设计,它具体的执行也是依赖操作系统线程语义的,它的优点是帮我们管理了线程切换,同时对线程切换进行了优化,它们的高效也是利用了操作系统提供的能力,两者并不矛盾。

    2019-09-19 17:38:46

  • ( ̄_ ̄ )

    2019-09-16 23:34:13

    https://github.com/hongningexpro/Linux_network_program/tree/master/middle_exam
    作者回复

    给努力的同学点赞

    2019-09-19 23:38:50

  • yusuf

    2019-09-16 21:16:35

    https://github.com/linuxxiaoyu/tcp
    作者回复

    2019-09-19 23:38:38

  • 向东

    2019-09-16 08:44:47

    服务器应用程序的当前路径怎么获取。
    cd ..,怎么和当前路径进行关联起来?
    作者回复

    我用的是chdir系统调用。

    2019-09-19 17:42:15