0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【网络编程】用于echo回显测试的TCP服务器的设计

嵌入式物联网开发 来源:嵌入式物联网开发 作者:嵌入式物联网开发 2022-08-31 13:25 次阅读
笔者在工作中,常常接触到网络通讯相关的内容,经常需要着手解决一些网络通讯相关的疑难杂症。排查网络问题的时候,往往需要借助一些工具,而很多时候自己想要的功能,网上又未能找到匹配度高的exe工具。无奈之下,有的时候就不能不自己码代码,写一些【为我所用】的测试代码,来帮助自己完成问题的排查。

​ 本文主要介绍一个TCP服务器端的测试程序,它的主要功能是:接收TCP客户端的连接,当收到客户端发送的消息后,立刻给客户端回复收到的消息;这个功能,通俗来讲,就叫【回显】。别看它很简单,但是在实际排查网络问题时,确实非常地有效。

​ 通过本文的阅读,你将了解到以下内容:

  • TCP客户端/服务器代码逻辑的剖析
  • TCP服务器端如何获取客户端的IP地址和端口信息
  • TCP回显测试服务器的使用和验证

​ 鉴于笔者主要集中在Linux环境编程,以下所有讲解都是基于Linux环境;如在Windows环境下编程,可能需要更改相应的网络编程API,修改后的功能读者自行验证。


TCP客户端/服务器代码逻辑的剖析


​ 在Linux环境下,要实现网络通讯,我们一般采用的都是socket编程;但是,Linux环境下的socket编程是一个大类,并不仅仅只有网络编程才是socket编程,有一种叫Unix Domain Socket编程,它也叫socket编程。只不过它一般不用于远程的网络通讯,而是用于本地(当前主机环境内)进程之间的通讯。曾经就因为这个问题,笔者在一次面试中,就被见多识广的面试官DISS了一番,希望大家也补补这方面的知识。以下部分讲述的主要是基于局域网或广域网的网络socket编程。

​ 在网络socket编程中,会有2种不同的【身份】:客户端和服务器。【客户端】指的是,网络连接的发起方,作为网络处理的请求方,向对端请求某种服务。【服务器】指的是,网络连接的被动连接方,一般它不能主动连接别人,只能监听客户端的连接,待它收到客户端的服务请求后,会对客户端的服务请求做出响应;通常服务器的运行模式是一个服务器可对应N个客户端。

​ 在TCP socket 网络编程中,客户端的代码逻辑一般是:

【 socket -> bind -> connect -> send -> recv -> close 】
socket:创建一个socket套接字,用于执行此次网络连接
bind:将服务器的信息(主要是ip和端口)与创建的socket绑定
connect: 向服务器发起网络连接请求
send: 将客户端的数据发送到服务器端
recv: 接收服务器回应的处理数据
close: 关闭socket套接字,释放对应的系统资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6R5slz09-1661923478821)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 对应的,TCP服务器的代码逻辑一般是:

【 socket -> bind -> listen -> accept -> recv -> send -> close 】
socket:创建一个socket套接字,用于执行此次服务器的网络服务
bind:将当前需要创建的服务器的信息(主要是ip和端口)与创建的socket绑定,该ip和端口就是客户端bind操作时需要用到的ip和端口
listen: 设置socket套接字执行监听,此处可以设置服务器最多能同时接收多少个客户端的连接
accept: 接受客户端的连接请求,此处对应的就是客户端的connect操作
recv: 接收客户端发送的请求数据
send: 将处理完的请求数据发送到客户端
close: 关闭socket套接字,释放对应的系统资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-erGqy5UU-1661923478827)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 了解了TCP客户端和服务器的基本代码逻辑后,我们直接附上tcp-echo-服务器的测试代码:tcp_server_echo.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_CLINET_NUM 10 /** 最大客户端连接数,可根据实际情况增减 */

/** 使用hexdump格式打印数据的利器 */
static void hexdump(const char *title, const void *data, unsigned int len)
{
    char str[160], octet[10];
    int ofs, i, k, d;
    const unsigned char *buf = (const unsigned char *)data;
    const char dimm[] = "+------------------------------------------------------------------------------+";

    printf("%s (%d bytes)\n", title, len);
    printf("%s\r\n", dimm);
    printf("| Offset  : 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   0123456789ABCDEF |\r\n");
    printf("%s\r\n", dimm);

    for (ofs = 0; ofs < (int)len; ofs += 16) {
        d = snprintf( str, sizeof(str), "| %08x: ", ofs );
        for (i = 0; i < 16; i++) {
            if ((i + ofs) < (int)len)
                snprintf( octet, sizeof(octet), "%02x ", buf[ofs + i] );
            else
                snprintf( octet, sizeof(octet), "   " );

            d += snprintf( &str[d], sizeof(str) - d, "%s", octet );
        }
        d += snprintf( &str[d], sizeof(str) - d, "  " );
        k = d;

        for (i = 0; i < 16; i++) {
            if ((i + ofs) < (int)len)
                str[k++] = (0x20 <= (buf[ofs + i]) &&  (buf[ofs + i]) <= 0x7E) ? buf[ofs + i] : '.';
            else
                str[k++] = ' ';
        }

        str[k] = '\0';
        printf("%s |\r\n", str);
    }

    printf("%s\r\n", dimm);
}

/** 获取客户端的ip和端口信息 */
static int get_clinet_ip_port(int sock, char *ip_port, int len, int *port)
{
    struct sockaddr_in sa;
    int sa_len;
	
    sa_len = sizeof(sa);
    if(!getpeername(sock, (struct sockaddr *)&sa, &sa_len)) {
        *port = ntohs(sa.sin_port);
        snprintf(ip_port, len, "%s", inet_ntoa(sa.sin_addr));
    }
    return 0;
}


/** 服务器端处理客户端请求数据的线程入口函数 */
static void *client_deal_func(void* arg)
{
    nt client_sock = *(int *)arg;
	
    while(1) {  
        char buf[4096];
        int ret;
		
        memset(buf,'\0',sizeof(buf));
        ret = read(client_sock,buf,sizeof(buf)); /* 读取客户端发送的请求数据 */
        if (ret <= 0) {
            break; /* 接收出错,跳出循环 */
        }

        hexdump("server recv:", buf, ret);
        ret = write(client_sock, buf, ret); /* 将收到的客户端请求数据发送回客户端,实现echo的功能 */
        if( ret < 0) {
            break; /* 发送出错,跳出循环 */
        }
    }
	
    close(client_sock);
}

/** 服务器主函数入口,接受命令参数输入,指定服务器监听的端口号 */
int main(int argc, char **argv)
{
    int ret;
    int ser_port = 0;
    int ser_sock = -1;
    int client_sock = -1;
    struct sockaddr_in server_socket;
    struct sockaddr_in socket_in;
    pthread_t thread_id;  
    int val = 1;
	
    /* 命令行参数的简单判断和help提示 */
    if(argc != 2) {
        printf("usage: ./client [port]\n");
        ret = -1;
        goto exit_entry;
    }
	
    /* 读取命令行输入的服务器监听的端口 */
    ser_port = atoi(argv[1]);
    if (ser_port <=0 || ser_port >= 65536) {
        printf("server port error: %d\n", ser_port);
        ret = -2;
        goto exit_entry;
    }
	
    /* 创建socket套接字 */
    ser_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(ser_sock < 0) {
        perror("socket error");
        return -3;
    }
		
    /* 设置socket属性,使得服务器使用的端口,释放后,别的进程立即可重复使用该端口 */
    ret = setsockopt(ser_sock, SOL_SOCKET,SO_REUSEADDR, (void *)&val, sizeof(val));
    if(ret == -1) {
        perror("setsockopt");
        return -4;
    }
	
    bzero(&server_socket, sizeof(server_socket));
    server_socket.sin_family = AF_INET;
    server_socket.sin_addr.s_addr = htonl(INADDR_ANY); //表示本机的任意ip地址都处于监听
    server_socket.sin_port = htons(ser_port);
	
    /* 绑定服务器信息 */
    if(bind(ser_sock, (struct sockaddr*)&server_socket, sizeof(struct sockaddr_in)) < 0) {
        perror("bind error");
        ret = -5;
        goto exit_entry;
    }
	
    /* 设置服务器监听客户端的最大数目 */
    if(listen(ser_sock, MAX_CLINET_NUM) < 0) { 
        perror("listen error");
        ret = -6;
        goto exit_entry;
    }
	
    printf("TCP server create success, accepting clients ...\n");
    for(;;) { /* 循环等待客户端的连接 */
        char buf_ip[INET_ADDRSTRLEN];		
        socklen_t len = 0;
        client_sock = accept(ser_sock, (struct sockaddr*)&socket_in, &len);
        if(client_sock < 0) {
            perror("accept error");
            ret = -7;
            continue;
        }		
        
        {
            char client_ip[128];
            int client_port;
            get_clinet_ip_port(client_sock, client_ip, sizeof(client_ip), &client_port);
            /* 打印客户端的ip和端口信息 */
            printf("client connected [ip: %s, port :%d]\n", client_ip, client_port);
        }
		
        /* 使用多线程的方式处理客户端的请求,每接收一个客户端连接,启动一个线程处理对应的数据 */
        pthread_create(&thread_id, NULL, (void *)client_deal_func, (void *)&client_sock);  
        pthread_detach(thread_id); 
    }
	
exit_entry:
    if (ser_sock >= 0) {
        close(ser_sock); /* 程序退出前,释放socket资源 */
    }
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PtxtqEBB-1661923478828)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]


TCP服务器端如何获取客户端的IP地址和端口信息


​ 如上的测试代码中,有这么一个函数:

/** 获取客户端的ip和端口信息 */
static int get_clinet_ip_port(int sock, char *ip_port, int len, int *port)
{
    struct sockaddr_in sa;
    int sa_len;
	
    sa_len = sizeof(sa);
    if(!getpeername(sock, (struct sockaddr *)&sa, &sa_len)) {
        *port = ntohs(sa.sin_port);
        snprintf(ip_port, len, "%s", inet_ntoa(sa.sin_addr));
    }
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s92JFClt-1661923478839)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ get_clinet_ip_port函数是在服务器成功接受了客户端的连接之后被调用,sock是该通讯链路对应的socket通道,函数内部通过getpeername接口,取得对方(客户端)的地址信息,存放在结构体sa中;接着使用ntohs将sa中的端口信息转成int类型,通过函数的入参port传递出去;使用inet_ntoa将sa中的ip地址信息转成字符串类型,通过函数的入参ip传递出去。这样,函数的调用者,通过ip和port变量就取得了客户端的ip和端口信息了。下面会给出,这个函数成功调用后,打印出的客户端信息范例。


TCP回显测试服务器的使用和验证


​ 有了tcp-server-echo的代码,我们就可以执行编译、测试了。编译程序,在Linux控制台如下输入:

gcc tcp_server_echo.c -o tcp_server_echo -lpthread

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgCENij3-1661923478841)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 加上-lpthread表示链接多线程库,因为程序中用到了多线程操作。正常编译成功后,就可以在当前工程目录看到tcp_server_echo文件的存在,这个就是我们编译出来的可执行文件。

​ 编译成功后,使用以下命令启动服务器,其中6210表示启动服务器需要监听的端口号;注意,启动服务器时一定要输入监听的端口号,否则启动会报错。

./tcp_server_echo 6210

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RpaaPcOC-1661923478843)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 以下是笔者使用该测试服务器对客户端的连接做echo测试,记录如下:

​ 服务器端的输出:

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3rgsFgSy-1661923478845)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]编辑

​ 以下是客户端对应的接收的3组echo请求数据:

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZv1RaVq-1661923478849)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]编辑

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k63YVfFw-1661923478850)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]编辑

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVqFINIm-1661923478852)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]编辑

​ 经对比可以发现,echo的数据与客户端发送的原始请求数据是一致的,证明echo-server运行是完全没有问题的。


​ 综述,灵活使用好这个echo服务器可以高效地对客户端的网络做一些排查工作,比如通过客户端去连接这个echo服务器,就可以很快知道客户端当前的网络环境是不是畅通的?数据发送和数据接收功能是否是正常的?还可以大致分析出客户端网络通讯的瓶颈,究竟是连接耗时还是数据发送耗时,还是数据接收耗时,具体的耗时大致是什么级别,等等。

​ 话又说回来,文中的echo服务器代码毕竟仅仅是测试代码,仅用于应对一些网络测试功能;如果真要应用在正式的生产环境,那其中的个别代码还需要进一步斟酌、优化,这部分的工作就交给有心的读者吧。如果读者在阅读文本的过程中,发现有纰漏之处,可以随时与笔者联系,欢迎您的指正。谢谢。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 服务器
    +关注

    关注

    12

    文章

    9123

    浏览量

    85329
  • TCP
    TCP
    +关注

    关注

    8

    文章

    1353

    浏览量

    79055
  • ECHO
    +关注

    关注

    1

    文章

    73

    浏览量

    27166
  • 网络编程
    +关注

    关注

    0

    文章

    71

    浏览量

    10074
收藏 人收藏

    评论

    相关推荐

    串口服务器——TCP Server

    。在实际应用中,我们经常会遇到需要将串口数据转发到TCP服务器或将TCP服务器数据转发到串口的情况,所以本文将介绍如何实现串口接入TCP
    的头像 发表于 07-31 17:58 1633次阅读
    串口<b class='flag-5'>服务器</b>——<b class='flag-5'>TCP</b> Server

    三分钟使用HMI Board完成TCP ECHO服务器的搭建

    本文将使用 HMI Board 完成 TCP ECHO 服务器的搭建。得益于 RT - Thread 完善的设备驱动与其强大的软件包生态,此次开发完全做到0代码,并且可以在极短的时间内完成。
    的头像 发表于 11-13 17:18 1017次阅读
    三分钟使用HMI Board完成<b class='flag-5'>TCP</b> <b class='flag-5'>ECHO</b><b class='flag-5'>服务器</b>的搭建

    测试echo服务器lwip时出现问题的解决办法?

    嗨,我想利用sdk测试echo服务器lwip,fpga程序并运行configration。但是,在控制台中,有一些行让我感到困惑。----- lwIP TCP
    发表于 05-12 07:58

    如何使用Socket实现TCP服务器

    TCP 服务器。我们先将 socket 编程的流程列出来,然后给出具体的实例。  TCP 服务器的 socket
    发表于 03-30 06:07

    4412开发板Qt网络编程-TCP实现服务器和客户端

    网络编程TCP 和 UDP,TCP 编程需要用到俩个类:QTcpServer 和 QTcpSocket。1
    发表于 04-28 15:33

    TCP服务器创建过程

    用过正点原子LWIP服务器例程开发的朋友可能知道,例程的设计是只支持一个客户端连接的,但实际应用中往往需要用到多客户端连接。下面是在正点原子扩展例程网络实验14 NETCONN_TCP 服务器
    发表于 08-24 08:03

    使用网络调试助手模拟TCP服务器

    上一篇分享的:AT指令测试WIFI通信模组并获取天气数据,我们在测试AT+CIPSTART这条指令(连接服务端)时给出了三种方法,其中第三种方法是使用一些网络调试助手模拟
    发表于 01-13 06:50

    echo什么意思_@echo off的作用

    本文介绍了echo在Linux和dos中的含义及应用,以及@echo的作用。英文原义:EchoProtocol中文释义:应答协议注解,主要用于调试和检测中。它可以基于TCP协议,
    发表于 11-21 23:36 3.7w次阅读
    <b class='flag-5'>echo</b>什么意思_@<b class='flag-5'>echo</b> off的作用

    RAW API 接口的TCP服务器

    RAW Tcp回响服务器
    的头像 发表于 07-05 00:10 3837次阅读
    RAW API 接口的<b class='flag-5'>TCP</b><b class='flag-5'>服务器</b>

    网络调试和串口调试集合UDP TCP客户端和TCP服务器端应用程序免费下载

    本文档的主要内容详细介绍的是网络调试和串口调试集合UDP TCP客户端和TCP服务器端应用程序免费下载。
    发表于 08-30 08:00 16次下载
    <b class='flag-5'>网络</b>调试和串口调试集合UDP <b class='flag-5'>TCP</b>客户端和<b class='flag-5'>TCP</b><b class='flag-5'>服务器</b>端应用程序免费下载

    ESP8266作为TCP客户端连接TCP服务器测试的实例资料说明

    本文档的主要内容详细介绍的是ESP8266作为TCP客户端连接TCP服务器测试的实例资料说明。
    发表于 06-06 17:51 8次下载
    ESP8266作为<b class='flag-5'>TCP</b>客户端连接<b class='flag-5'>TCP</b><b class='flag-5'>服务器</b>和<b class='flag-5'>测试</b>的实例资料说明

    Linux下网络编程TCP并发服务器TCP客户端程序免费下载

    本文档的主要内容详细介绍的是Linux下网络编程TCP并发服务器TCP客户端程序免费下载
    发表于 01-08 15:12 9次下载
    Linux下<b class='flag-5'>网络</b><b class='flag-5'>编程</b><b class='flag-5'>TCP</b>并发<b class='flag-5'>服务器</b>和<b class='flag-5'>TCP</b>客户端程序免费下载

    Linux下TCP网络编程-创建服务器与客户端

    这篇文章介绍在Linux下的socket编程,完成TCP服务器、客户端的创建,实现数据通信。
    的头像 发表于 08-14 09:26 2483次阅读
    Linux下<b class='flag-5'>TCP</b><b class='flag-5'>网络</b><b class='flag-5'>编程</b>-创建<b class='flag-5'>服务器</b>与客户端

    基于LwIP的TCP服务器设计

    前面我们实现了UDP服务器及客户端以及基于其上的TFTP应用服务器。接下来我们将实现同样广泛应用的TCP协议各类应用。
    的头像 发表于 12-14 15:09 1660次阅读
    基于LwIP的<b class='flag-5'>TCP</b><b class='flag-5'>服务器</b>设计

    基于TCP的Telnet服务器设计

    前面我们已经实现了基于RAW API的TCP服务器和客户端,也在此基础上实现了HTTP应用。接下来我们实现一个基于TCP的Telnet服务器应用。
    的头像 发表于 12-14 15:30 1337次阅读
    基于<b class='flag-5'>TCP</b>的Telnet<b class='flag-5'>服务器</b>设计