Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
网络学习营
【网络编程学习】+skawu+tcp聊天室客户端实现
发布于 2018-08-01 15:28:38 浏览:1603
订阅该版
一、开发环境 1、github最新RT-Thread源码:[https://github.com/RT-Thread/rt-thread](https://github.com/RT-Thread/rt-thread) 2、最新env工具(0.7.0版本):[https://pan.baidu.com/s/1cg28rk#list/path=%2F](https://pan.baidu.com/s/1cg28rk#list/path=%2F) 3、虚拟网卡模拟软件:tap-windows-9.21.2.exe 4、RT-Thread提供的聊天服务器 二、实验目的 通过编程实现: 1. 使用 login 命令登录上服务器聊天室,并将服务器返回的信息通过串口打印出来 2. 使用 say 命令通知服务器你的个人姓名和作业时间 “My name is Yqiu, I'm doing this job at 20180730”,并 将服务器返回的信息打印出来 3. 使用 look 命令查看聊天室人员,并将服务器返回的信息打印出来 4. 使用 who 命令查看我是谁,并将服务器返回的信息打印出来 5. 使用 say 命令告诉服务器我看到有哪些人在聊天室 6. 使用 say 命令告诉服务器我准备退出了 7. 使用 logout 退出聊天室 参考 Log 信息: Welcome to TestChat
Please log in Use "login "
yqiu: What are you doing?
The following are in this room: yqiu
The following are logged in: yqiu
yqiu: I can see yqiu in the room
yqiu: I am quiting 三、实现思路 1、创建两个线程,一个接收处理数据,一个发送指令 2、分析服务器返回数据的格式和特点,以便于解析数据,特别注意look命令的返回数据,这个需要解析出来 3、发送完数据后做下延时,让出线程,放给接收线程去处理,特别在处理look命令时,延时还有一点好处在于服务器返回的数据速度没那么快,延时可以在显示上做优化 4、从数据接收线程获取到look的返回数据后,需要解析数据,把用户名后面的‘
’改为空格,否则在发送时会有问题,服务器以'
'为一条命令的结束 ![1.png](/uploads/201808/01/152651uzgg0cc8t91c02gg.png) 从上图中的打印信息可以看到look返回的数据是两次打印,所以整体思路是在第二次接收到数据时进行处理。 其余的都是细节的问题,比如buf的memset等,数据处理包括命令的生成和接收数据的解析很多用了字符串操作函数,比如strcat,strncmp等,具体这些函数的用法可以度娘查看。 后面会有两个程序清单,第一个是每个服务器命令单独实现,可以在RT-Thread的命令行使用,第二个是这次的课题实现,代码实现自动登录、发送消息等。 四、实验结果 ![2.png](/uploads/201808/01/152658n1xwmmmnm3ng811n.png) 五、源代码 程序清单1:实现聊天室服务器命令,在msh命令行中单独使用 ```/* * 程序清单:tcp 聊天室客户端 * * 这是一个 tcp 客户端的例程 * 导出 tcpclient 命令到控制终端 * 命令调用格式:tcpclient URL PORT * URL:服务器地址 PORT::端口号 * 程序功能:实现聊天室命令 * tcpclient:连接聊天室服务器 * usage:tcpclient IP PORT * login:登录聊天室 * usage:login 用户名 * say: 说话 * usage: say 内容 * look: 查看聊天室人员 * usage: look * who: 查看我是谁 * usage: who * logout: 退出聊天室 * usage: logout */ #include
#include
/* 使用BSD socket,需要包含socket.h头文件 */ #include "netdb.h" #include
#define BUFSZ 1024 /* 定义全局变量 */ int sock; int conn_ok = 0; char *recv_data; /* 创建一个任务,专门用来输出显示socket接收到的消息 */ void tcpclient_handle_entry(void *parameter) { int bytes_received; /* 申请内存用于存储接收到的数据 */ /* 分配用于存放接收数据的缓冲 */ recv_data = rt_malloc(BUFSZ); if (recv_data == RT_NULL) { rt_kprintf("No memory
"); return; } rt_memset(recv_data, 0, BUFSZ); while (1) { /* 从sock连接中接收最大BUFSZ - 1字节数据 */ bytes_received = recv(sock, recv_data, BUFSZ - 1, 0); if (bytes_received < 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
received error,close the socket.
"); /* 释放接收缓冲 */ rt_free(recv_data); break; } else if (bytes_received == 0) { /* 默认 recv 为阻塞模式,此时收到0认为连接出错,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
received error,close the socket.
"); /* 释放接收缓冲 */ rt_free(recv_data); break; } { /* 在控制终端显示收到的数据 */ rt_kprintf("
%s ", recv_data); rt_memset(recv_data, 0, BUFSZ); } } } //static const char send_data[] = "This is TCP Client from RT-Thread."; /* 发送用到的数据 */ void tcpclient(int argc, char **argv) { // int ret; struct hostent *host; struct sockaddr_in server_addr; const char *url; int port; rt_thread_t tid; if (argc < 3) { rt_kprintf("Usage: tcpclient URL PORT
"); rt_kprintf("Like: tcpclient 183.111.111.111 11222
"); return ; } url = argv[1]; port = strtoul(argv[2], 0, 10); /* 通过函数入口参数url获得host地址(如果是域名,会做域名解析) */ host = gethostbyname(url); /* 创建一个socket,类型是SOCKET_STREAM,TCP类型 */ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { /* 创建socket失败 */ rt_kprintf("Socket error
"); return; } /* 初始化预连接的服务端地址 */ server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); server_addr.sin_addr = *((struct in_addr *)host->h_addr); rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); /* 连接到服务端 */ if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) { /* 连接失败 */ rt_kprintf("Connect fail!
"); closesocket(sock); return; } conn_ok = 1; /* 创建线程 */ #ifdef RT_USING_HEAP tid = rt_thread_create("tcpdata", tcpclient_handle_entry, RT_NULL, RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3 - 2, 20); RT_ASSERT(tid != RT_NULL); #else rt_err_t result; tid = &main_thread; result = rt_thread_init(tid, "tcpdata", tcpclient_handle_entry, RT_NULL, main_stack, sizeof(main_stack), RT_THREAD_PRIORITY_MAX / 3 - 2, 20); RT_ASSERT(result == RT_EOK); /* if not define RT_USING_HEAP, using to eliminate the warning */ (void)result; #endif rt_thread_startup(tid); return; } MSH_CMD_EXPORT(tcpclient, a tcp client sample); /* 用户登录 */ void login(int argc, char **argv) { char send_data[1024] = "login "; int ret, dat_len; if (argc < 2) { rt_kprintf("Usage: longin ID
"); rt_kprintf("Like: login skawu
"); return ; } strcat(&send_data[6], argv[1]); dat_len = rt_strlen(send_data); send_data[dat_len] = ' '; send_data[dat_len + 1] = '
'; send_data[dat_len + 2] = '\0'; rt_kprintf("send data : %s
", send_data); ret = send(sock, send_data, dat_len + 2, 0); if (ret < 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
login error,close the socket.
"); return; } else if (ret == 0) { /* 打印send函数返回值为0的警告信息 */ rt_kprintf("
Send warning,send function return 0.
"); } } MSH_CMD_EXPORT(login, a tcp client cmmand login); /* 说话 */ void say(int argc, char **argv) { char send_data[1024] = "say "; int ret, dat_len = 4, i; if (argc < 2) { rt_kprintf("Usage: say content
"); rt_kprintf("Like: say \"I am skawu\"
"); return ; } if(rt_strlen(argv[1]) > 1000){ rt_kprintf("The content is to long, and it should be less than 1000 byte
"); return; } for(i = 1; i < argc; i++) { strcat(&send_data[dat_len], argv*); dat_len += rt_strlen(argv*); send_data[dat_len] = ' '; dat_len++; } dat_len = rt_strlen(send_data); send_data[dat_len] = ' '; send_data[dat_len + 1] = '
'; send_data[dat_len + 2] = '\0'; rt_kprintf("send data : %s
", send_data); ret = send(sock, send_data, dat_len + 2, 0); if (ret < 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
login error,close the socket.
"); return; } else if (ret == 0) { /* 打印send函数返回值为0的警告信息 */ rt_kprintf("
Send warning,send function return 0.
"); } } MSH_CMD_EXPORT(say, a tcp client cmmand say); /* 查看聊天室都有那些人 */ void look(int argc, char **argv) { char send_data[1024] = "look"; int ret, dat_len; dat_len = rt_strlen(send_data); send_data[dat_len] = ' '; send_data[dat_len + 1] = '
'; send_data[dat_len + 2] = '\0'; rt_kprintf("send data : %s
", send_data); ret = send(sock, send_data, dat_len + 2, 0); if (ret < 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
login error,close the socket.
"); return; } else if (ret == 0) { /* 打印send函数返回值为0的警告信息 */ rt_kprintf("
Send warning,send function return 0.
"); } } MSH_CMD_EXPORT(look, a tcp client cmmand look); /* 我是谁 */ void who(int argc, char **argv) { char send_data[1024] = "who"; int ret, dat_len; dat_len = rt_strlen(send_data); send_data[dat_len] = ' '; send_data[dat_len + 1] = '
'; send_data[dat_len + 2] = '\0'; rt_kprintf("send data : %s
", send_data); ret = send(sock, send_data, dat_len + 2, 0); if (ret < 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
login error,close the socket.
"); return; } else if (ret == 0) { /* 打印send函数返回值为0的警告信息 */ rt_kprintf("
Send warning,send function return 0.
"); } } MSH_CMD_EXPORT(who, a tcp client cmmand who); /* 退出 */ void logout(int argc, char **argv) { char send_data[1024] = "logout"; int ret, dat_len; dat_len = rt_strlen(send_data); send_data[dat_len] = ' '; send_data[dat_len + 1] = '
'; send_data[dat_len + 2] = '\0'; rt_kprintf("send data : %s
", send_data); ret = send(sock, send_data, dat_len + 2, 0); if (ret < 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
login error,close the socket.
"); return; } else if (ret == 0) { /* 打印send函数返回值为0的警告信息 */ rt_kprintf("
Send warning,send function return 0.
"); } } MSH_CMD_EXPORT(logout, a tcp client cmmand logout);```程序清单2:代码实现自动登录和发送命令 ```/* * 程序清单:tcp 聊天室客户端 * * 这是一个 tcp 客户端的例程 * 导出 tcpclient 命令到控制终端 * 命令调用格式:tcpclient URL PORT * URL:服务器地址 PORT::端口号 * 程序功能:实现聊天室命令 */ #include
#include
/* 使用BSD socket,需要包含socket.h头文件 */ #include "netdb.h" #include
#define BUFSZ 512 /* 定义命令常量 */ const char cmd_login[] = "login "; const char cmd_say[] = "say "; const char cmd_look[] = "look"; const char cmd_who[] = "who"; const char cmd_logout[] = "logout"; const char user_name[] = "skawu"; const char say_hello[] = "My name is skawu, I'm doing this job at 20180731"; const char look_back[] = "The following are in this room:"; const char who_back[] = "The following are logged in:"; /* 宏定义 */ #define FLAG_LOGIN 0x01 // user login #define FLAG_SAYHELLO 0x02 // Say Hello #define FLAG_LOOK 0x03 // look #define FLAG_WHO 0x04 // who #define FLAG_SAYSEE 0x05 // Say who i can see #define FLAG_SAYQUIT 0x06 // Say i am quiting #define FLAG_LOGOUT 0x07 // logout #define FLAG_IDLE 0xFF // IDLE /* 定义全局变量 */ int sock; int conn_ok = 0; char *recv_data; int debug = 0; int flag = -2; // 命令执行进度标志 char user_buf[BUFSZ] = " "; /* 创建一个任务,专门用来输出显示socket接收到的消息 */ void tcpclient_handle_entry(void *parameter) { int bytes_received; int flag_look_back = 0; int i = 0, j = 0, k = 0; /* 申请内存用于存储接收到的数据 */ /* 分配用于存放接收数据的缓冲 */ recv_data = rt_malloc(BUFSZ); if (recv_data == RT_NULL) { rt_kprintf("No memory
"); return; } rt_memset(recv_data, 0, BUFSZ); while (1) { /* 从sock连接中接收最大BUFSZ - 1字节数据 */ bytes_received = recv(sock, recv_data, BUFSZ - 1, 0); if (bytes_received < 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
received error,close the socket.
"); /* 释放接收缓冲 */ rt_free(recv_data); break; } else if (bytes_received == 0) { /* 默认 recv 为阻塞模式,此时收到0认为连接出错,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
received error,close the socket.
"); /* 释放接收缓冲 */ rt_free(recv_data); break; } { /* 在控制终端显示收到的数据 */ rt_kprintf("
%s
", debug++, recv_data); /* 判断look返回数据的数据头 */ if(!strncmp(recv_data, look_back, strlen(look_back))){ // rt_kprintf("recv_data len: %d
", strlen(recv_data)); flag_look_back = 1; memset(user_buf, 0, BUFSZ); continue; } /* 抓取服务器端look的返回数据 */ // rt_kprintf("flag_look_back: %d
", flag_look_back); if(flag_look_back){ i = strlen(recv_data); // rt_kprintf("i: %d
", i); for(j = 0; j < i; j++) { if ((recv_data[j] != 0x0D) && (recv_data[j] != 0x0A)) { user_buf[k] = recv_data[j]; k++; } if (recv_data[j] == 0x0A) { user_buf[k] = ' '; k++; } } flag_look_back = 0; flag = FLAG_WHO; // rt_kprintf("user_buf: %s
", user_buf); i = 0, j = 0, k = 0; } rt_memset(recv_data, 0, BUFSZ); } } } void tcpclient_send_entry(void *parameter) { int ret, dat_len; char send_data[BUFSZ] = " "; // 发送数据缓冲区 /* 申请内存用于存储接收到的数据 */ /* 分配用于存放接收数据的缓冲 */ recv_data = rt_malloc(BUFSZ); if (recv_data == RT_NULL) { rt_kprintf("No memory
"); return; } rt_memset(recv_data, 0, BUFSZ); while (1) { switch(flag) { case FLAG_LOGIN: // 登录 { memset(send_data, 0, BUFSZ); memcpy(send_data, cmd_login, strlen(cmd_login)); strcat(send_data, user_name); flag = FLAG_SAYHELLO; } break; case FLAG_SAYHELLO: // say hello and time { memset(send_data, 0, BUFSZ); memcpy(send_data, cmd_say, strlen(cmd_say)); strcat(send_data, say_hello); flag = FLAG_LOOK; } break; case FLAG_LOOK: // look 查看聊天室人员 { memset(send_data, 0, BUFSZ); memcpy(send_data, cmd_look, strlen(cmd_look)); flag = FLAG_IDLE; // 等待接收函数处理完成 } break; case FLAG_WHO: // who 查看我是谁 { memset(send_data, 0, BUFSZ); memcpy(send_data, cmd_who, strlen(cmd_who)); flag = FLAG_SAYSEE; } break; case FLAG_SAYSEE: // say 告诉服务器我看到哪些人在聊天室了 { memset(send_data, 0, BUFSZ); memcpy(send_data, cmd_say, strlen(cmd_say)); strcat(send_data, "I can see "); strcat(send_data, user_buf); strcat(send_data, "in th room"); flag = FLAG_SAYQUIT; } break; case FLAG_SAYQUIT: // say 告诉服务器我准备退出了 { memset(send_data, 0, BUFSZ); memcpy(send_data, cmd_say, strlen(cmd_say)); strcat(send_data, "I am quiting"); flag = FLAG_LOGOUT; } break; case FLAG_LOGOUT: // 退出聊天室 { memset(send_data, 0, BUFSZ); memcpy(send_data, cmd_logout, strlen(cmd_logout)); flag = FLAG_IDLE; } break; default : { memset(send_data, 0, BUFSZ); } break; } dat_len = strlen(send_data); if(dat_len <= 0) { // 如果send_data为空,则不发送命令 rt_thread_delay(100); continue; } send_data[dat_len] = ' '; send_data[dat_len + 1] = '
'; rt_kprintf("
Command: %s
", send_data); ret = send(sock, send_data, dat_len + 2, 0); if (ret < 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); conn_ok = 0; rt_kprintf("
login error,close the socket.
"); return; } else if (ret == 0) { /* 打印send函数返回值为0的警告信息 */ rt_kprintf("
Send warning,send function return 0.
"); } memset(send_data, 0, BUFSZ); rt_thread_delay(100); } } //static const char send_data[] = "This is TCP Client from RT-Thread."; /* 发送用到的数据 */ void tcpclient(int argc, char **argv) { struct hostent *host; struct sockaddr_in server_addr; const char *url; int port; rt_thread_t tid; if (argc < 3) { rt_kprintf("Usage: tcpclient URL PORT
"); rt_kprintf("Like: tcpclient 183.111.111.111 11222
"); return ; } url = argv[1]; port = strtoul(argv[2], 0, 10); /* 通过函数入口参数url获得host地址(如果是域名,会做域名解析) */ host = gethostbyname(url); /* 创建一个socket,类型是SOCKET_STREAM,TCP类型 */ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { /* 创建socket失败 */ rt_kprintf("Socket error
"); return; } /* 初始化预连接的服务端地址 */ server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); server_addr.sin_addr = *((struct in_addr *)host->h_addr); rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); /* 连接到服务端 */ if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) { /* 连接失败 */ rt_kprintf("Connect fail!
"); closesocket(sock); return; } conn_ok = 1; flag = FLAG_LOGIN; /* 创建线程 1 处理服务器数据 */ tid = rt_thread_create("tcpdata", tcpclient_handle_entry, RT_NULL, RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3 - 2, 20); RT_ASSERT(tid != RT_NULL); rt_thread_startup(tid); /* 创建线程 2 客户端命令 */ tid = rt_thread_create("tcpsend", tcpclient_send_entry, RT_NULL, RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3 - 3, 20); RT_ASSERT(tid != RT_NULL); rt_thread_startup(tid); return; } MSH_CMD_EXPORT(tcpclient, a tcp client sample); ```
查看更多
0
个回答
默认排序
按发布时间排序
暂无答案,快来添加答案吧
撰写答案
登录
注册新账号
关注者
0
被浏览
1.6k
关于作者
skawu
这家伙很懒,什么也没写!
提问
11
回答
11
被采纳
0
关注TA
发私信
相关问题
1
【LWIP学习营】第一关开发环境搭建
2
LWIP学习营第一周入门移植问题汇总贴
3
【LWIP学习营】f407+lan8720A小结
4
【LwIP学习营】【第一周】仅零散记录,无主题
5
【LWIP学习营】正点原子探索者F407+LAN8720第一周小结
6
【LwIP学习营】【第一周】网络通信基础及实现TCP 聊天客户端
7
【LwIP学习营】【第一周】LWIP移植
8
【LwIP学习营】【第一周】LWIP移植
9
【LwIP学习营】【第一周】开发板适配
10
【LwIP学习营】【第一周】环境搭建和配置验证
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
使用百度AI助手辅助编写一个rt-thread下的ONVIF设备发现功能的功能代码
2
RT-Thread 发布 EtherKit开源以太网硬件!
3
rt-thread使用cherryusb实现虚拟串口
4
《C++20 图形界面程序:速度与渲染效率的双重优化秘籍》
5
《原子操作:程序世界里的“最小魔法单位”解析》
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
a1012112796
13
个答案
1
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
本月文章贡献
程序员阿伟
6
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部