计算机网络课设之基于UDP协议的简易聊天机器人

  前言:2017年6月份计算机网络的课设任务,在同学的帮助和自学下基本搞懂了,基于UDP协议的基本聊天的实现方法。实现起来很简单,原理也很简单,主要是由于老师必须要求使用C语言来写,所以特别麻烦,而且C语言的socket编程我基本没有接触过,顶多对java网络编程有一点涉猎。下面我将自己所学的知识做了一个总结,希望可以对想要去接触socket(网络)编程的同学有一个帮助,当然想要学好网络编程肯定是离不开几本书的支撑的,这篇文章主要通过一个机器人聊天的案例帮大家入下门。
  注意:想要成功运行的前提条件是你别忘记,把我的代码的ip地址改一下(查看自己ip地址用ipconfig命令,详细查看《常见网络命令之traceroute命令一起其他常用命令 - 不忘初心 - 博客频道 - CSDN.NET 》http://blog.csdn.net/qq_34337272/article/details/72910417),以及问答文本文档创建一下,一定要按照我的格式。任何一个问题都会导致程序无法运行
项目地址:基于UDP协议的简易聊天机器人 - 下载频道 - CSDN.NET http://download.csdn.net/detail/qq_34337272/9879091

一,先上效果图

  1,半智能聊天+服务器可自定义内容(输入匹配 的内容自动回复,输入不匹配的内容服务器可以自定义回复):
  
  2,服务器自动回复没有自定义回复
  
  附加(自动回复问题内容文档):
  

二,目的和意义

(1)意义:

将理论运用于实践,更深入地掌握计算机网络的核心内容。用具体的实践成果,体现对理论知识的掌握程度,提高计算机网络的实践能力,加深对计算机网络理论知识的理解,特别是网络通信这一块的理解。

(2)目的:

  1. 培养理论联系实际的设计思想,训练综合运用所学的基础理论知识,结合生产实际分析和解决网络应用中问题的能力,从而使基础理论知识得到巩固和加深。
  2. 学习掌握网络应用工程的一般设计过程和方法。
  3. 通过 基于udp协议的socket编程加深对与udp协议的理解以及与tcp协议的区分、
  4. 掌握c语言socket编程的基本方法,简单网络编程的编程思想。

二,要求和涉及的知识点


三,具体实现过程

(1)基本的知识点:

1,UDP基本介绍以及聊天程序选用UDP的好处
在我们学习UDP的时候就知道,UDP不需要建立连接,而且没有数据确认和数据重传的机制,所以实时性较高而且花费开销特别小。在聊天时即使丢失一些数据也不会影响信息的交流,我们可以通过上下文语义知道对方所要表达的意思,或者根据对方的信息重新发送我们要说的话;对于TCP来说,在通讯前要经过三次握手协议建立连接,而建立连接的过程往往是比较耗费时间的,连接建立之后,我们在聊天时候可能过很长时间才说一句话,那么连接是保持呢还是先断开,等对方说话时再建立连接呢?所以在聊天中TCP面向连接、数据确认与重传的机制将会影响聊天的效率。所以一般聊天类的程序一般都会选择UDP而不是TCP。

2,客户服务器模式


3,数据报式套接字(SOCK_DGRAM)
提供了一个无连接服务(UDP)。数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。 下图为无连接协议(UDP)的套接字调用:

4,重要函数
recvfrom(int sockfd,void* buff,size nbytes,int flags,struct sockaddr* from,socklen_t *addrlen);
参数:sockfd : 套接字描述符, buff : 用于存放数据的缓冲区,nbytes : 缓冲区大小,flags : 暂时总设置为0,from : 用于存放UDP对端的套接字协议地址(输出参数)addrlen : UDP对端的套接字协议地址字节大小(输出参数)
注意:最后两个参数from和addrlen可以得知该UDP数据是谁发送过来的。
如果设置from和addrlen为NULL,表示我们忽略对端信息。
返回值:
成功返回读取到的字节数,出错返回-1。返回值0是被允许的,不同于TCP中read返回0表示对端已经关闭。
sendto(int sockfd,const void* buff,size nbytes,int flags,const struct sockaddr* to,socklen_t *addrlen);
参数:sockfd : 套接字描述符;buff : 要发送的数据内存;nbytes : 数据大小;flags : 一般设置为0;to : 指向接收者的套接字地址结构(输入参数);addrlen : 上述套接字地址结构to的字节大小(输入参数)
注意:最后两个参数to和addrlen告知该数据要发给谁。
返回值:
成功返回写入的字节数,出错返回-1
写入字节长度为0是被允许的,在UDP下,会形成一个只包含20字节的IP首部和一个8字节的UDP首部,没有数据的IP数据报。

(2)代码清单(含详细注解):

客户端:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<Winsock2.h>
#include<stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main()
{
    //windows操作系统下的初始化工作,加载套接字库
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    char    addr[20] = { 0 };
    wVersionRequested = MAKEWORD(1, 1);//第一个参数为低位字节;第二个参数为高位字节
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        return;
    }
    if (LOBYTE(wsaData.wVersion) != 1 ||
        HIBYTE(wsaData.wVersion) != 1) {
        WSACleanup();
        return;
    }
    //创建套接字

    SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);
    //基于UDP的客户端来说,它不需要去绑定,但是要设置信息将要发送到对方机器的地址信息,也就是服务器端的地址信息
    SOCKADDR_IN addrSrv; //定义一个地址结构体的变量,
    printf("************如遇到发送消息,机器人不回复的情况,请稍等片刻,机器人正在处理你的消息************\n");
    printf("************                          输入 q 即可结束对话                         ************\n");
    //获取服务器地址
    addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.116");
    addrSrv.sin_family = AF_INET;//地址族,,代表TCP/IP协议族
    addrSrv.sin_port = htons(6000);//接收端口号
    char recvBuf[100];//接收数据
    char sendBuf[100];//发送数据
    char tempBuf[200];//临时数据存储
    int len = sizeof(SOCKADDR);//用于返回接收数据的地址结构的长度,必须先经过初始化
    while (true)
    {
        printf("please input data:\n");
        gets_s(sendBuf);//获取用户输入的数据
        //UDP不需要建立连接直接通过sendto()函数发送数据
        sendto(sockClient, sendBuf, strlen(sendBuf) + 1, 0,
            (SOCKADDR*)&addrSrv, len);//发送数据
        recvfrom(sockClient, recvBuf, 100, 0,
            (SOCKADDR*)&addrSrv, &len);//接收数据
                                       //判断是否结束对话
        if ('q' == recvBuf[0] )
        {
            sendto(sockClient, "q", strlen("q") + 1, 0,
                (SOCKADDR*)&addrSrv, len);
            //一下语句实现倒计时功能
            printf_s("对话结束,还有五秒即将关闭窗口\n");
            int i;
            for (i = 5; i >= 0; i--)
            {
                printf("%d", i);
                Sleep(1000);
                printf("\b");//退格 即将当前光标位置回退一列
            }
            printf(" ");
            printf("\n");
            //程序退出
            break;
        }
        //将接收到的数据格式化到tempBuf中
        //inet_ntoa参数:一个网络上的IP地址;功能:将一个十进制网络字节序转换为点分十进制IP格式的字符串。
        sprintf_s(tempBuf, "%s say:%s", inet_ntoa(addrSrv.sin_addr), recvBuf);

        printf("%s\n", tempBuf);
    }
    //关闭套接字
    closesocket(sockClient);
    //终止对套接字库的使用
    WSACleanup();
}

服务器端:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<Winsock2.h>
#include<stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main()
{
    FILE    *f;
    char    szLine[MAX_PATH];
    char    buffer[MAX_PATH];

    fopen_s(&f, "D:\\info.txt", "r");
    if (f == NULL)
    {
        printf("无法打开文件\n");
        return;
    }
    //windows操作系统下的初始化工作,加载套接字库
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        return;
    }
    if (LOBYTE(wsaData.wVersion) != 1 ||
        HIBYTE(wsaData.wVersion) != 1) {
        WSACleanup();
        return;
    }
    //创建套接字,因为是基于UDP的,所以用SOCK_DGRAM.
    SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
    //对于服务器端,接着应该进行绑定
    SOCKADDR_IN addrSrv;//定义一个地址结构体的变量,
    addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.116");
    //地址族,AF这个前缀表示地址族(address family)
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);//端口号
    bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
    char recvBuf[100];//字符数组,用来接收信息
    char sendBuf[100];//用来发送信息
    char tempBuf[200];//用来存放中间数据
    //定义一个地址结构体的变量,在通讯的时候,
    /*我们需要获取和我们通讯的这一方的地址信息,
    这一获取是我们通过调用recvfrom来获得的,但是我们需要提供一个地址结构体的变量*/
    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);
    printf("服务器开启成功等待客户连接\n");

    //while循环,保证通讯过程能够不断的循环下去
    while (1)
    {
        //接收数据
        recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
        //判断是否结束对话,q表示结束
        if ('q' == recvBuf[0])
        {
            sendto(sockSrv, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrClient, len);
            printf("对话结束!\n");
            break;
        }
        //将数据格式化到tempBuf中
        //addrClient.sin_addr表示对方的IP地址,
        //inet_ntoa将IP转换为点分十进制表示的形式,如172.0.0.1
        sprintf_s(tempBuf, "%s say:%s", inet_ntoa(addrClient.sin_addr), recvBuf);
        //将数据打印输出
        printf("%s\n", tempBuf);
        //把文件指针指向文件的开头
        fseek(f, 0, SEEK_SET);
        //对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘ ’或‘/0’;例:char a[100];memset(a, '/0', sizeof(a));
        memset(szLine, 0, MAX_PATH);
        //szLine: 字符型指针,指向存储读入数据的缓冲区的地址。
        //MAX_PATH: 从流中读入MAX_PATH - 1个字符
        //f : 指向读取的流。
        //取的数据保存在szLine指向的字符数组中
        fgets(szLine, MAX_PATH, f);
        //printf("%s\n", szLine);
        while (szLine[0] != '#')
        {
            if (szLine[0] == 'Q')
            {
                char szTemp[MAX_PATH] = { 0 };
                //复制字符串szLine + 2到缓冲区szTemp
                lstrcpyA(szTemp, szLine + 2);
                szTemp[lstrlenA(szTemp) - 1] = '\0';
                //匹配成功找到答案
                if (lstrcmpA(szTemp, recvBuf) == 0)
                {

                    memset(szLine, 0, MAX_PATH);
                    fgets(szLine, MAX_PATH, f);
                    //向客户端发送udp数据报,即回答客户端的消息
                    //参数sockSrv为已建好连线的socket,如果利用UDP协议则不需经过连线操作。
                    //参数 szLine+2欲连线的数据内容,参数flags 一般设0,
                    //szLine加2的原因是从读取的字符的第三个字符开始输出,因为前两个字符为A:
                    sendto(sockSrv, szLine + 2, strlen(szLine) - 1, 0, (SOCKADDR*)&addrClient, len);
                    printf("服务器对话框输出内容:\n");
                    printf("%s", szLine+2);
                    break;
                }

            }
            memset(szLine, 0, MAX_PATH);
            fgets(szLine, MAX_PATH, f);
            //printf("%s\n", szLine);
        }
        if (szLine[0]== '#')
        {
           //注意把前三行代码注释后三行代码取消注释就是自动回复的
            memset(buffer, 0, MAX_PATH);
            sprintf_s(buffer, "听不懂,");
            sendto(sockSrv, buffer + 2, strlen(buffer) - 1, 0, (SOCKADDR*)&addrClient, len);
            //printf("Please input data:\n");
            //gets_s(sendBuf);//从键盘输入数据
            //sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrClient, len);//发送数据
        }

    }
    //当循环结束的时候,关闭套接字
    closesocket(sockSrv);
    //终止对套接字的使用
    WSACleanup();
    fclose(f);
}

四,心得

  在做这次课程设计的过程中遇到了很多问题,比如自己不是很了解c语言soket编程的一些具体函数,所以刚开始时不知道如何下手。花了几天时间把老师给的代码里的每一个函数好好查了一遍,并且对每个函数都有了一个认识。这也是以后工作的一个基础。做一个双方通信的程序很简单,但是如何做带一个自动回复的当时还是有点不理解,通过询问我们班一个网络编程学的还挺好的一位同学知道可以通过文本存放相关问题和答案然后在程序中读取搜索用户的输入与那个问题相匹配,然后再把相应的内容输出。、
   我觉的这次课程设计的题目还挺好的,让我对网络的客户/服务器有了更深的理解以及对于UDP协议有了更加深入的认识和对于网络编程有了初步认识与了解及巩固了对文件读取的内容。
  后面这段时间我会好好把java网络编程再深入学习一遍,相信有了基础我能很快上手做一些的小项目。
  附加:通过上面的内容需要掌握还是很难,给大家推荐几篇博客:
  socket编程原理 - guisu,程序人生。 逆水行舟,不进则退。 - 博客频道 - CSDN.NET http://blog.csdn.net/hguisu/article/details/7444092
UDP套接字编程 - 蔷薇岛少年的博客 - 博客频道 - CSDN.NET http://blog.csdn.net/qq_17416741/article/details/51921001
套接字socket 的地址族和类型、工作原理、创建过程 - 推酷 http://www.tuicool.com/articles/26BJ7f
memset函数使用详解 - 追逐. - 博客园 http://www.cnblogs.com/xiaolongchase/archive/2011/10/22/2221326.html
SOCKADDR_IN_百度百科 http://baike.baidu.com/item/SOCKADDR_IN

时间: 2024-10-24 08:54:20

计算机网络课设之基于UDP协议的简易聊天机器人的相关文章

udp-需要一个java基于UDP协议的文件传输程序

问题描述 需要一个java基于UDP协议的文件传输程序 文件是一个实验数据的文件,是txt个格式的,需要将它传输到指定的IP上 解决方案 server package com.way.server;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundEx

基于UDP协议的语音聊天系统的设计与实现

问题描述 基于UDP协议的语音聊天系统的设计与实现 系统架构为C/S模式.能登陆.查找.添加好友,先后与2个以上好友语音聊天.服务端存储信息. 解决方案 你可以去学习一下 XMPP 解决方案二: 你这个模型,要是局域网就好说,一个收一个发,开个UDP持续监听就可以,但是要是通外网,那你就要好好学习下,以下几个软件,mqtt,pjsip.不然你做出来的东西可能很难用.

Java基于UDP协议实现简单的聊天室程序_java

最近比较闲,一直在抽空回顾一些Java方面的技术应用. 今天没什么事做,基于UDP协议,写了一个非常简单的聊天室程序. 现在的工作,很少用到socket,也算是对Java网络编程方面的一个简单回忆.  先看一下效果:   实现的效果可以说是非常非常简单,但还是可以简单的看到一个实现原理.  "聊天室001"的用户,小红和小绿相互聊了两句,"聊天室002"的小黑无人理会,在一旁寂寞着.  看一下代码实现:  1.首先是消息服务器的实现,功能很简单:•将客户端的信息(进

golang基于websocket实现的简易聊天室程序_Golang

本文实例讲述了golang基于websocket实现的简易聊天室.分享给大家供大家参考,具体如下: 先说点无关的,最近忙于工作没有更新博客,今天休息顺便把golang websocket研究了一下,挺好玩的,写了一个聊天室,分享给大家. websocket包 : code.google.com/p/go.net/websocket 文档 : http://go.pkgdoc.org/code.google.com/p/go.net/websocket 首先安装websocket包 复制代码 代码

解析C语言基于UDP协议进行Socket编程的要点_C 语言

两种协议 TCP 和 UDP前者可以理解为有保证的连接,后者是追求快速的连接. 当然最后一点有些 太过绝对 ,但是现在不需熬考虑太多,因为初入套接字编程,一切从简. 稍微试想便能够大致理解, TCP 追求的是可靠的传输数据, UDP 追求的则是快速的传输数据. 前者有繁琐的连接过程,后者则是根本不建立可靠连接(不是绝对),只是将数据发送而不考虑是否到达. 以下例子以 *nix 平台的便准为例,因为 Windows平台需要考虑额外的加载问题,稍作添加就能在 Windows 平台上运行UDP. UD

Android中基于XMPP协议实现IM聊天程序与多人聊天室_Android

简单的IM聊天程序由于项目需要做一个基于XMPP协议的Android通讯软件.故开始研究XMPP. XMPP协议采用的是客户端-服务器架构,所有从一个客户端发到另一个客户端的消息和数据都必须经过XMPP服务器转发,而且支持服务器间DNS的路由,也就是说可以构建服务器集群,使不同的 服务器下的客户端也可以通信,XMPP的前身是一个开源组织制定的网络通信协议--Jabber,XMPP的核心是在网络上分片段发送XML流的协议,这个协议是XMPP的即时通讯指令的传递手段.       为了防止服务器间发

Android中基于XMPP协议实现IM聊天程序与多人聊天室

简单的IM聊天程序 由于项目需要做一个基于XMPP协议的Android通讯软件.故开始研究XMPP. XMPP协议采用的是客户端-服务器架构,所有从一个客户端发到另一个客户端的消息和数据都必须经过XMPP服务器转发,而且支持服务器间DNS的路由,也就是说可以构建服务器集群,使不同的 服务器下的客户端也可以通信,XMPP的前身是一个开源组织制定的网络通信协议--Jabber,XMPP的核心是在网络上分片段发送XML流的协议,这个协议是XMPP的即时通讯指令的传递手段.       为了防止服务器间

linux网络编程之socket(十四) 基于UDP协议的网络程序

一.下图是典型的UDP客户端/服务器通讯过程 下面依照通信流程,我们来实现一个UDP回射客户/服务器 #include <sys/types.h> #include <sys/socket.h>  ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struc

基于udp协议的文件传输代码

问题描述 C#版的谢谢各位 解决方案 解决方案二:还带这样要的啊,google能搜到的,我搜过也下载过,不过下载下来的东西,你要转成自己能用的,没有一天时间,搞不定LZ好运