计算机网络课设之基于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)目的:
- 培养理论联系实际的设计思想,训练综合运用所学的基础理论知识,结合生产实际分析和解决网络应用中问题的能力,从而使基础理论知识得到巩固和加深。
- 学习掌握网络应用工程的一般设计过程和方法。
- 通过 基于udp协议的socket编程加深对与udp协议的理解以及与tcp协议的区分、
- 掌握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
计算机网络课设之基于UDP协议的简易聊天机器人的更多相关文章
- 基于UDP协议的控制台聊天
这几天学了java的网络编程弄出一个基于UDP协议的聊天工具 功能 添加并且备注好友(输入对方的ip) 删除好友 查看好友列表 用java写的控制台程序导出可执行程序后不能双击打开 还需要些一个脚本文 ...
- 基于UDP协议的控制台聊天程序(c++版)
本博客由Rcchio原创,转载请告知作者 ------------------------------------------------------------------------------- ...
- 网络编程应用:基于UDP协议【实现聊天程序】--练习
要求: 使用UDP协议实现一个聊天程序 代码: 发送端: package UDP聊天程序; import java.io.IOException; import java.net.DatagramPa ...
- TCPIP协议编程:基于UDP协议的局域网聊天工具的研发
任务目标 聊天器采用客户端/服务器(C/S)模式: 客户端利用UDP与服务器相连,客户端与客户端之间通过UDP相互通信: 服务器端具有服务器端口设置维护客户端个人信息,记录客户端状态,分配账号等: 客 ...
- 网络编程初探--使用UDP协议的简易聊天室
UDP是一种无连接的传输层协议,提供快速不可靠的服务. 一.发送端 * 创建UDP发送端 * 步骤: * 1.建立UDP的Socket服务 * 2.将要发送的数据封装到数据包中 * 3.通过UDP的s ...
- 基于UDP协议模拟的一个TCP协议传输系统
TCP协议以可靠性出名,这其中包括三次握手建立连接,流控制和拥塞控制等技术.详细介绍如下: 1. TCP协议将需要发送的数据分割成数据块.数据块大小是通过MSS(maximum segment siz ...
- 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程
Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...
- JAVA基础知识之网络编程——-基于UDP协议的通信例子
UDP是一种不可靠的协议,它在通信两端各建立一个socket,这两个socket不会建立持久的通信连接,只会单方面向对方发送数据,不检查发送结果. java中基于UDP协议的通信使用DatagramS ...
- 基于UDP协议的网络编程
UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送.接收数据报的对象. Java使用DatagramSock ...
随机推荐
- BZOJ 1040 骑士(环套树DP)
如果m=n-1,显然这就是一个经典的树形dp. 现在是m=n,这是一个环套树森林,破掉这个环后,就成了一个树,那么这条破开的边连接的两个顶点不能同时选择.我们可以对这两个点进行两次树形DP根不选的情况 ...
- 【bzoj5094】硬盘检测 乱搞
题目描述 已知从 $n$ 个不同的32位无符号整数中随机选 $m=10000$ 次所得的结果,求可能性最大的 $n$ ,其中 $n=10^k,1\le k\le 7$. 输入 第一行包含一个正整数m( ...
- 【bzoj2699】更新 dp
题目描述 对于一个数列A[1..N],一种寻找最大值的方法是:依次枚举A[2]到A[N],如果A[i]比当前的A[1]值要大,那么就令A[1]=A[i],最后A[1]为所求最大值.假设所有数都在范围[ ...
- BZOJ4811 Ynoi2017由乃的OJ(树链剖分+线段树)
先考虑NOI2014的水题,显然从高位到低位贪心,算一下该位取0和1分别得到什么即可. 加强这个水题,变成询问区间.那么线段树维护该位取0和1从左到右和从右到左走完这个节点表示的区间会变成什么即可,也 ...
- hdu 1690 Bus System (最短路径)
Bus System Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- BZOJ2286:[SDOI2011]消耗战——题解
+++++++++++++++++++++++++++++++++++++++++++ +本文作者:luyouqi233. + +欢迎访问我的博客:http://www.cnblogs.com/luy ...
- BZOJ1101 & 洛谷3455:[POI2007]ZAP——题解
https://www.luogu.org/problemnew/show/3455#sub http://www.lydsy.com/JudgeOnline/problem.php?id=1101 ...
- 函数strcat实现
1.函数原型 extern char *strcat(char *dest,const char *src); 注:在C++中,则存在于<cstring>头文件中. 2.函数功能: str ...
- HDOJ(HDU).1015 Safecracker (DFS)
HDOJ(HDU).1015 Safecracker [从零开始DFS(2)] 从零开始DFS HDOJ.1342 Lotto [从零开始DFS(0)] - DFS思想与框架/双重DFS HDOJ.1 ...
- mybaties实体的 Mapper.xml文件中自定义sql时模糊查询的写法
<select id=selectByNameLike" parameterType="string" resultMap="BaseResultMap ...