Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
网络学习营
【LwIP学习营】【第一周】网络通信基础及实现TCP 聊天客户端
发布于 2018-05-16 13:59:35 浏览:3223
订阅该版
[tocm] # LWIP学习笔记 # 2018/5/16 ## 预备知识 ## ### TCP与UDP区别 ### **TCP:** (Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。 **UDP:** (User Datagram Protocol 用户数据报协议)是OSI(Open System Interconnection开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。 1. TCP提供的是面向连接的、可靠的数据流传输;UDP提供的是非面向连接的、不可靠的数据流传输。 2. TCP提供可靠的服务,通过TCP连接传送的数据,无差错、不丢失,不重复,按序到达;UDP尽大努力交付,即不保证可靠交付。 3. TCP面向字节流;UDP面向报文。 4. TCP连接只能是点到点的;UDP支持一对一、一对多、多对一和多对多的交互通信。 5. TCP首部开销20字节;UDP的首部开销小,只有8个字节。 6. TCP的逻辑通信信道是全双工的可靠信道;UDP的逻辑通信信道是不可靠信道。 ### TCP编程的服务器端一般步骤 ### 1. 创建一个socket,用函数socket(); 2. 设置socket属性,用函数setsockopt(); * 可选 3. 绑定IP地址、端口等信息到socket上,用函数bind(); 4. 开启监听,用函数listen(); 5. 接收客户端上来的连接,用函数accept(); 6. 收发数据,用函数send()和recv(),或者read()和write(); 7. 关闭网络连接; 8. 关闭监听; ### TCP编程的客户端一般步骤 ### 1. 创建一个socket,用函数socket(); 2. 设置socket属性,用函数setsockopt();* 可选 3. 绑定IP地址、端口等信息到socket上,用函数bind();* 可选 4. 设置要连接的对方的IP地址和端口等属性; 5. 连接服务器,用函数connect(); 6. 收发数据,用函数send()和recv(),或者read()和write(); 7. 关闭网络连接; ### UDP编程的服务器端一般步骤 ### 1. 创建一个socket,用函数socket(); 2. 设置socket属性,用函数setsockopt();* 可选 3. 绑定IP地址、端口等信息到socket上,用函数bind(); 4. 循环接收数据,用函数recvfrom(); 5. 关闭网络连接; ### UDP编程的客户端一般步骤 ### 1. 创建一个socket,用函数socket(); 2. 设置socket属性,用函数setsockopt();* 可选 3. 绑定IP地址、端口等信息到socket上,用函数bind();* 可选 4. 设置对方的IP地址和端口等属性; 5. 发送数据,用函数sendto(); 6. 关闭网络连接; ### API函数 ### ---------- 为通信创造一个端点并返回一个文件描述符。 ``` int socket(int domain, int type, int protocol); ``` * 入口参数: |参数 | 描述 | ---------------|-------------------------------- |domain|确定协议族;| |type | 数据类型;| |protocol | 协议;| ---------- ---------- 给套接字分配一个地址。当使用 socket()创造一个套接字时, 只是给定了协议族,并没有分配地址。在套接字能够接受来自其他主机的连接前,必须用bind()给它绑定一个地址。 ``` int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen); ``` * 入口参数: |参数 | 描述 | ---------------|-------------------------------- |sockfd|代表socket的文件描述符。| |my_addr |指向 sockaddr 结构体的指针,代表要绑定的地址 ;| |addrlen | 是sockaddr结构体的大小。| ---------- ---------- 一旦一个套接字和一个地址联系之后,listen() 监听到来的连接。但是这只适用于对面向连接的模式,例如 套接字类型是 (SOCK_STREAM, SOCK_SEQPACKET)。 ``` int listen(int sockfd, int backlog); ``` * 入口参数: |参数 | 描述 | ---------------|-------------------------------- |sockfd|代表socket的文件描述符。| |backlog |一个整数,表示一次能够等待的最大连接数目。操作系统通常会对这个值设置上限。| ---------- ---------- 当应用程序监听来自其他主机的面对数据流的连接时,通过事件(比如Unix select()系统调用)通知它。必须用 accept()函数初始化连接。 Accept() 为每个连接创立新的套接字并从监听队列中移除这个连接。 ``` int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); ``` * 入口参数: |参数 | 描述 | ---------------|-------------------------------- |sockfd|代表socket的文件描述符。| |cliaddr | 指向sockaddr 结构体的指针,客户机地址信息。| |addrlen | 指向 socklen_t的指针,确定客户机地址结构体的大小 。| ---------- ---------- connect()系统调用为一个套接字设置连接,参数有文件描述符和主机地址。 ``` int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); ``` * 入口参数: |参数 | 描述 | ---------------|-------------------------------- |sockfd|监听的套接字描述符| |serv_addr | 指向sockaddr 结构体的指针,服务器地址信息。| |addrlen | sockaddr结构体的大小。| ---------- ---------- gethostbyname() 和 gethostbyaddr()函数是用来解析主机名和地址的。可能会使用DNS服务或者本地主机上的其他解析机制(例如查询/etc/hosts)。返回一个指向 struct hostent的指针,这个结构体描述一个IP主机。 ``` struct hostent *gethostbyname(const char *name); struct hostent *gethostbyaddr(const void *addr, int len, int type); ``` ---------- ## 实现一个简单的 TCP 聊天客户端 ## ---------- 在RT-Thread的MSH下connectchat命令将会创建一个线程.这个线程里,主要调用了tcpclient("112.124.34.90",5005)函数.tcpclinent()会创建一个TCP客户端,将接收到的信息打印出来.如果这个TCP客户端没有成功创建的话,那么是无法使用其他的聊天功能的. ``` void tcp_chat_thread(void *parameter) { rt_kprintf("
please waiting connect.
"); tcpclient("112.124.34.90",5005); rt_kprintf("
disconnect.
"); } int chat_init() { rt_thread_t tid; /* 创建test线程 */ tid = rt_thread_create("chat", tcp_chat_thread, RT_NULL, 1024, 10, 20); /* 创建成功则启动线程 */ if (tid != RT_NULL) rt_thread_startup(tid); return 0; } void connectchat(int argc, char **argv) { if(argc == 1) { chat_init(); } else { rt_kprintf("ERROR command 3 !
"); } } MSH_CMD_EXPORT(connectchat, connectchat); ``` ---------- **tcpclient("112.124.34.90",5005);进行如下操作:** 1. 创建一个socket,socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 2. 设置要连接的对方的IP地址和端口等属性; 3. 连接服务器,connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); 4. 接收数据,recv(sock, recv_data, BUFSZ - 1, 0); 5. 以上任意一步出错,则closesocket(sock);sock = -1; (可以参考前面内容 ***TCP编程的客户端一般步骤*** ) ``` void tcpclient(const char *url, int port) { struct sockaddr_in server_addr; /* 分配用于存放接收数据的缓冲 */ recv_data = rt_malloc(BUFSZ); if (recv_data == RT_NULL) { rt_kprintf("No memory
"); return; } /* 创建一个socket,类型是SOCKET_STREAM,TCP类型 */ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { /* 创建socket失败 */ rt_kprintf("Socket error
"); /* 释放接收缓冲 */ rt_free(recv_data); return; } /* 初始化预连接的服务端地址 */ server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); ip4addr_aton(url, (ip4_addr_t *)&server_addr.sin_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); sock = -1; /*释放接收缓冲 */ rt_free(recv_data); return; } while (1) { /* 从sock连接中接收最大BUFSZ - 1字节数据 */ bytes_received = recv(sock, recv_data, BUFSZ - 1, 0); /* 如果返回的<=0这说明出错,此时需要断开连接. */ if (bytes_received <= 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); sock = -1; rt_kprintf("
close the socket.
"); /* 释放接收缓冲 */ rt_free(recv_data); break; } /* 有接收到数据,把末端清零 */ recv_data[bytes_received] = '\0'; /* 在控制终端显示收到的数据 */ rt_kprintf("
%s ", recv_data); } return; } ``` ---------- (其它的命令原理都一样,就拿这个做例子.) 在msh下使用这个命令的时候,这个函数首先会判断sock不为-1,然后判断命令参数的数量是否符合要求,由于msh下是以空格分隔各个命令参数的,所以需要人为的给各个参数之间加上send(sock, " ", strlen(" "), 0);**(额,经学长提醒,这个操作如果在输入的参数空格太多的情况下会带来一个问题,就是如果say xxxxx 如果发送的消息空格太多的话,会造成一部分xxxxx发送不全.调大RT_FINSH_ARG_MAX的值算是一种不优雅的方法.应该有更合理的方法解决这个问题.)** ``` void say(int argc, char **argv) { if (sock != -1) { if (argc > 1) { send(sock, "say ", strlen("say "), 0); for (rt_uint32_t i = 1; i <= argc; i++) { send(sock, " ", strlen(" "), 0); send(sock, argv*, strlen(argv*), 0); } send(sock, "
", strlen("
"), 0); } else { rt_kprintf("warning !
"); } } else { rt_kprintf("warning: Please use connectchat first !
"); } } MSH_CMD_EXPORT(say, say hello!); ``` ** 最终效果: ![xuexi.png](https://oss-club.rt-thread.org/uploads/201805/17/161008s1afrj68yom1rd87.png)
查看更多
7
个回答
默认排序
按发布时间排序
来一颗糖
2018-05-16
这家伙很懒,什么也没写!
[i=s] 本帖最后由 来一颗糖 于 2018-5-17 16:01 编辑 [/i] 学习的过程中 查阅的资料
shenlan
2018-05-16
这个人很勤快,没空留下点什么
>学习的过程中 查阅的资料 --- 糖糖果然无处不在
yufangxi
2018-05-16
这家伙很懒,什么也没写!
写的太好了。
我夏了夏天
认证专家
2018-05-16
Life isn't about finding yourself, life is about creating yourself.
这个编程用的是 POSIX 接口吧?
来一颗糖
2018-05-16
这家伙很懒,什么也没写!
>这个编程用的是 POSIX 接口吧? --- 这些函数名称是sockets.h里封装后的.应该是吧,并不是很了解 POSIX 标准:o
armink
2018-05-16
这家伙很懒,什么也没写!
赞,逻辑清晰,文档整齐明了
来一颗糖
2018-05-17
这家伙很懒,什么也没写!
[i=s] 本帖最后由 来一颗糖 于 2018-5-17 14:45 编辑 [/i] TCP客户端聊天程序 ``` #include
#include
/* 使用BSD socket,需要包含socket.h头文件 */ #include "netdb.h" #include "lwip/sockets.h" #define BUFSZ 1024 static char *recv_data; static int sock = -1, bytes_received; void tcpclient(const char *url, int port) { struct sockaddr_in server_addr; /* 分配用于存放接收数据的缓冲 */ recv_data = rt_malloc(BUFSZ); if (recv_data == RT_NULL) { rt_kprintf("No memory\n"); return; } /* 创建一个socket,类型是SOCKET_STREAM,TCP类型 */ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { /* 创建socket失败 */ rt_kprintf("Socket error\n"); /* 释放接收缓冲 */ rt_free(recv_data); return; } /* 初始化预连接的服务端地址 */ server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); ip4addr_aton(url, (ip4_addr_t *)&server_addr.sin_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("\n Connect fail!\n"); closesocket(sock); sock = -1; /*释放接收缓冲 */ rt_free(recv_data); return; } while (1) { /* 从sock连接中接收最大BUFSZ - 1字节数据 */ bytes_received = recv(sock, recv_data, BUFSZ - 1, 0); /* 如果返回的<=0这说明出错,此时需要断开连接. */ if (bytes_received <= 0) { /* 接收失败,关闭这个连接 */ closesocket(sock); sock = -1; rt_kprintf("\nclose the socket.\r\n"); /* 释放接收缓冲 */ rt_free(recv_data); break; } /* 有接收到数据,把末端清零 */ recv_data[bytes_received] = '\0'; /* 在控制终端显示收到的数据 */ rt_kprintf("\n%s ", recv_data); } return; } void tcp_chat_thread(void *parameter) { rt_kprintf("\nplease waiting connect.\n"); tcpclient("112.124.34.90", 5005); rt_kprintf("\ndisconnect.\n"); } int chat_init() { rt_thread_t tid; /* 创建test线程 */ tid = rt_thread_create("chat", tcp_chat_thread, RT_NULL, 1024, 10, 20); /* 创建成功则启动线程 */ if (tid != RT_NULL) rt_thread_startup(tid); return 0; } void connectchat(int argc, char **argv) { if (argc == 1) { chat_init(); } else { rt_kprintf("ERROR command !\n"); } } MSH_CMD_EXPORT(connectchat, connectchat); void login(int argc, char **argv) { if (sock != -1) { if (argc == 2) { send(sock, "login ", strlen("login "), 0); send(sock, argv[1], strlen(argv[1]), 0); send(sock, "\r\n", strlen("\r\n"), 0); } else { rt_kprintf("ERROR command !\n"); } } else { rt_kprintf("warning: Please use connectchat first !\n"); } } MSH_CMD_EXPORT(login, login xxx); void say(int argc, char **argv) { if (sock != -1) { if (argc > 1) { send(sock, "say ", strlen("say "), 0); for (rt_uint32_t i = 1; i <= argc; i++) { send(sock, " ", strlen(" "), 0); send(sock, argv[i], strlen(argv[i]), 0); } send(sock, "\r\n", strlen("\r\n"), 0); } else { rt_kprintf("warning !\n"); } } else { rt_kprintf("warning: Please use connectchat first !\n"); } } MSH_CMD_EXPORT(say, say hello!); void look(int argc, char **argv) { if (sock != -1) { if (argc == 1) { send(sock, "look ", strlen("look "), 0); send(sock, "\r\n", strlen("\r\n"), 0); } else { rt_kprintf("ERROR command !\n"); } } else { rt_kprintf("warning: Please use connectchat first !\n"); } } MSH_CMD_EXPORT(look, look); void who(int argc, char **argv) { if (sock != -1) { if (argc == 1) { send(sock, "who ", strlen("who "), 0); send(sock, "\r\n", strlen("\r\n"), 0); } else { rt_kprintf("ERROR command !\n"); } } else { rt_kprintf("warning: Please use connectchat first !\n"); } } MSH_CMD_EXPORT(who, who); void logout(int argc, char **argv) { if (sock != -1) { if (argc == 1) { send(sock, "logout ", strlen("logout "), 0); send(sock, "\r\n", strlen("\r\n"), 0); } else { rt_kprintf("ERROR command !\n"); } } else { rt_kprintf("warning: Please use connectchat first !\n"); } } MSH_CMD_EXPORT(logout, logout); ```
撰写答案
登录
注册新账号
关注者
0
被浏览
3.2k
关于作者
来一颗糖
这家伙很懒,什么也没写!
提问
7
回答
231
被采纳
1
关注TA
发私信
相关问题
1
【LWIP学习营】第一关开发环境搭建
2
LWIP学习营第一周入门移植问题汇总贴
3
【LWIP学习营】f407+lan8720A小结
4
【LwIP学习营】【第一周】仅零散记录,无主题
5
【LWIP学习营】正点原子探索者F407+LAN8720第一周小结
6
【LwIP学习营】【第一周】LWIP移植
7
【LwIP学习营】【第一周】LWIP移植
8
【LwIP学习营】【第一周】开发板适配
9
【LwIP学习营】【第一周】环境搭建和配置验证
10
【LwIP学习营】【第一周】移植中遇到phy芯片不一致及其解...
推荐文章
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
次被采纳
张世争
8
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
KunYi
6
个答案
1
次被采纳
本月文章贡献
程序员阿伟
6
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部