點擊上方「嵌入式大雜燴」,選擇「置頂公眾號」第一時間查看嵌入式筆記!
前言
關(guān)于socket的筆記,之前已經(jīng)有分享過兩篇相關(guān)的文章:
【socket應(yīng)用】基于C語言的天氣客戶端的實現(xiàn)
本篇筆記我們再來一起回顧一下socket相關(guān)的知識:我們的開發(fā)板作為TCP客戶端,與TCP服務(wù)端程序進(jìn)行通信
。
準(zhǔn)備相關(guān)工程
-
硬件:小熊派開發(fā)板。 -
軟件:STM32+RT-Thread -
開發(fā)工具:RT-Thread Studio V1.1.0。
實驗前提是我們的開發(fā)板與我們的PC所處的網(wǎng)絡(luò)環(huán)境在同一網(wǎng)段內(nèi)。
我們的開發(fā)板聯(lián)網(wǎng)模塊時ESP8266。這里需要使用RTT的at_device軟件包,這在之前的筆記中已經(jīng)有介紹:【RT-Thread筆記】onenet軟件包的使用。
RT-Thread的網(wǎng)絡(luò)框架
在編寫代碼之前有必要先了解一下RT-Thread的網(wǎng)絡(luò)框架結(jié)構(gòu)(圖片來源:RT-Thread官網(wǎng)):
從下往上看:
第 1 層:與硬件相關(guān)的一些網(wǎng)絡(luò)模塊,這里我們用的是ESP8266
。
第 2~4 層:一些中間層。本次實驗中我們可以不用深究,我們把這幾層看做一個黑盒子,先不用管里面的實現(xiàn)。有精力的朋友可以去研究,初學(xué)朋友暫時先別去碰,碰就是勸退。。。
不過也可以稍微了解一些這幾層是什么。
第 2 層是協(xié)議棧層。這些是一些輕量型的、用于嵌入式中的TCP/IP 協(xié)議棧 。
第 3 層是網(wǎng)卡層。通過 netdev 網(wǎng)卡層用戶可以統(tǒng)一管理各個網(wǎng)卡信息和網(wǎng)絡(luò)連接狀態(tài),并且可以使用統(tǒng)一的網(wǎng)卡調(diào)試命令接口。
第 4 層是SAL 套接字抽象層。通過它 RT-Thread 系統(tǒng)能夠適配下層不同的網(wǎng)絡(luò)協(xié)議棧,并提供給上層統(tǒng)一的網(wǎng)絡(luò)編程接口,方便不同協(xié)議棧的接入。
第 5 層應(yīng)用層標(biāo)準(zhǔn)socket接口。其提供一套標(biāo)準(zhǔn) BSD Socket API。所謂標(biāo)準(zhǔn)就是我們在RT-Thread應(yīng)用編程中用的網(wǎng)絡(luò)接口與在PC上進(jìn)行網(wǎng)絡(luò)編程所用的接口函數(shù)是一樣的,如:
有了這樣的一套標(biāo)準(zhǔn) BSD Socket API,我們的程序就可以在 PC 上編寫、調(diào)試:
然后再移植相關(guān)代碼到 RT-Thread 操作系統(tǒng)上,這給我們提供了很大的便利。
其中,第4層和第5層在在代碼中是用宏來關(guān)聯(lián)起來的:
更多的關(guān)于RT-Thread的網(wǎng)絡(luò)框架介紹可以查看官網(wǎng)文檔:
https://www.rt-thread.org/document/site/programming-manual/sal/sal/#
下面開始編寫測試代碼,首先我們需要清楚一個TCP客戶端-服務(wù)端模型
:
編寫代碼
(1)編寫TCP客戶端代碼(開發(fā)板代碼)
我們這里編寫的客戶端測試代碼就是按照上面那個圖來一步一步的編寫的:
1、創(chuàng)建一個socket
2、連接服務(wù)端
3、發(fā)送數(shù)據(jù)
4、阻塞等待接收數(shù)據(jù)
5、關(guān)閉連接
①創(chuàng)建一個socket
用到的接口:
int socket(int domain, int type, int protocol);
我們創(chuàng)建socket相關(guān)的代碼如下:
/* 創(chuàng)建一個socket,類型是SOCKET_STREAM, TCP類型 */
if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
/* 創(chuàng)建socket失敗 */
rt_kprintf("Socket error\n");
return -1;
}
domain / 協(xié)議族類型:
-
AF_INET:IPv4 -
AF_INET6:IPv6
type / 協(xié)議類型:
-
SOCK_STREAM:流套接字 -
SOCK_DGRAM:數(shù)據(jù)報套接字 -
SOCK_RAW:原始套接字
protocol / 傳輸協(xié)議
-
IPPROTO_TCP -
IPPROTO_UDP -
......
②連接服務(wù)端
用到的接口:
int connect(int s, const struct sockaddr *name, socklen_t namelen);
我們連接服務(wù)端相關(guān)的代碼如下:
左右滑動查看全部代碼>>>
/* 從終端獲取URL */
url = argv[1];
/* 從終端獲取端口并轉(zhuǎn)為無符號數(shù)據(jù) */
port = strtoul(argv[2], 0, 10);
/* 通過函數(shù)入口參數(shù)url獲得host地址(如果是域名,會做域名解析) */
host = gethostbyname(url);
/* 初始化預(yù)連接的服務(wù)端地址 */
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));
/* 連接到服務(wù)端 */
if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
/* 連接失敗 */
rt_kprintf("Connect fail!\n");
closesocket(sock_fd);
return -1;
}
else
{
/* 連接成功 */
rt_kprintf(">>>>>>>>>>>>Connect server(%s %d) success!\n", url, port);
}
③發(fā)送數(shù)據(jù)
用到的接口:
int send(int s, const void *dataptr, size_t size, int flags);
我們發(fā)送數(shù)據(jù)相關(guān)的代碼如下:
/* 發(fā)送數(shù)據(jù) */
if (send(sock_fd, argv[3], strlen(argv[3]), 0) < 0)
{
/* 發(fā)送失敗,關(guān)閉這個連接 */
closesocket(sock_fd);
rt_kprintf("\nsend error,close the socket.\r\n");
}
else
{
/* 發(fā)送成功 */
rt_kprintf(">>>>>>>>>>>>Send data(%s) to server success!\n", argv[3]);
}
④接收數(shù)據(jù)
用到的接口:
int recv(int s, void *mem, size_t len, int flags);
我們接收數(shù)據(jù)的相關(guān)代碼如下:
/* 等待服務(wù)端發(fā)送過來的數(shù)據(jù) */
if (recv(sock_fd, recv_buf, 100, 0) < 0)
{
/* 接收失敗,關(guān)閉這個連接 */
closesocket(sock_fd);
rt_kprintf("\nreceived error,close the socket.\r\n");
}
else
{
/* 接收成功,打印收到的數(shù)據(jù) */
rt_kprintf(">>>>>>>>>>>>Recv data from server: %s\n",recv_buf);
}
⑤關(guān)閉連接
用到的接口:
int closesocket(int s);
(2)編寫TCP服務(wù)端代碼(PC機(jī))
這里提供的是Windows環(huán)境下的TCP服務(wù)端程序代碼,編寫思路也是按照上面的TCP客戶端-服務(wù)端模型
來的,相關(guān)接口就不詳細(xì)列舉了,直接貼代碼吧:
左右滑動查看全部代碼>>>
/* 程序:Windows環(huán)境下的TCP服務(wù)端程序
gcc編譯命令:gcc tcp_server.c -lwsock32 -o tcp_server.exe
微信公眾號:嵌入式大雜燴
作者:ZhengN
*/
#include <stdio.h>
#include <winsock2.h>
#define BUF_LEN 100
int main(void)
{
WSADATA wd;
SOCKET ServerSock, ClientSock;
char Buf[BUF_LEN] = {0};
SOCKADDR ClientAddr;
SOCKADDR_IN ServerSockAddr;
int addr_size = 0, recv_len = 0;
/* sock需要 */
WSAStartup(MAKEWORD(2,2),&wd);
printf("===============這是一個TCP服務(wù)端程序==============\n");
/* 創(chuàng)建服務(wù)端socket */
if (-1 == (ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
{
printf("socket error!\n");
exit(1);
}
/* 設(shè)置服務(wù)端信息 */
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結(jié)構(gòu)體ServerSockAddr清零
ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
ServerSockAddr.sin_addr.s_addr = inet_addr("192.168.1.101");// 本機(jī)IP地址
ServerSockAddr.sin_port = htons(1314); // 端口
/* 綁定套接字 */
if (-1 == bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))
{
printf("bind error!\n");
exit(1);
}
/* 進(jìn)入監(jiān)聽狀態(tài) */
if (-1 == listen(ServerSock, 10))
{
printf("listen error!\n");
exit(1);
}
addr_size = sizeof(SOCKADDR);
while (1)
{
/* 監(jiān)聽客戶端請求,accept函數(shù)返回一個新的套接字,發(fā)送和接收都是用這個套接字 */
if (-1 == (ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &addr_size)))
{
printf("socket error!\n");
exit(1);
}
/* 接受客戶端的返回數(shù)據(jù) */
int recv_len = recv(ClientSock, Buf, BUF_LEN, 0);
printf("客戶端發(fā)送過來的數(shù)據(jù)為:%s\n", Buf);
/* 發(fā)送數(shù)據(jù)到客戶端 */
send(ClientSock, Buf, recv_len, 0);
/* 關(guān)閉客戶端套接字 */
closesocket(ClientSock);
/* 清空緩沖區(qū) */
memset(Buf, 0, BUF_LEN);
}
/*如果有退出循環(huán)的條件,這里還需要清除對socket庫的使用*/
/* 關(guān)閉服務(wù)端套接字 */
//closesocket(ServerSock);
/* WSACleanup();*/
return 0;
}
驗證、分析
1、PC端自驗證
我們使用我們自己用C語言編寫的客戶端、服務(wù)端程序進(jìn)行驗證:
2、STM32<-->PC
(1)STM32作為客戶端,與PC端我們自己編寫的服務(wù)端程序進(jìn)行通信。
tcp_client命令是我們使用MSH_CMD_EXPORT
宏導(dǎo)出的命令,如:
MSH_CMD_EXPORT(tcp_client, tcp_client sample);
我們可在終端按下TAB鍵
或者輸入help
來查看有沒有導(dǎo)出成功:
我們的測試命令格式為:
tcp_client URL PORT DATA
其中,URL 參數(shù)代表網(wǎng)址或IP地址,這里是局域網(wǎng)內(nèi)的TCP通信測試,所以這個參數(shù)其實就是我們電腦的IP地址,可以在cmd下輸入ipconfig
命令進(jìn)行查看:
PORT 參數(shù)代表端口。這里要輸入的是服務(wù)端程序綁定的端口號。端口使用16bit進(jìn)行編號,即其范圍為:0~65536
。
但 0~1023
的端口一般由系統(tǒng)分配給特定的服務(wù)程序,例如 Web 服務(wù)的端口號為 80,F(xiàn)TP 服務(wù)的端口號為 21等。
我們這里的服務(wù)端程序端口號可以設(shè)置為1024~65535
范圍內(nèi)的隨意一個數(shù)。但要注意的是我們輸入的測試命令中的PORT參數(shù)要與服務(wù)端程序綁定的端口一樣,否則客戶端就連接不上服務(wù)端:
DATA參數(shù)代表我們要發(fā)送給服務(wù)端的數(shù)據(jù)。
需要注意的是,我們在進(jìn)行測試時需要先啟動服務(wù)端程序
。如果服務(wù)端程序還未啟動就運行我們的客戶端程序,就會出現(xiàn)連接失敗:
(2)STM32作為客戶端,PC端網(wǎng)絡(luò)調(diào)試助手作為服務(wù)端。
從這個網(wǎng)絡(luò)助手中可以看到在收到數(shù)據(jù)的同時可以顯示出客戶端的IP及端口號。客戶端的端口號是系統(tǒng)隨機(jī)分配的
(范圍為:1024~65535):
所以我們不關(guān)心端口號,但是我們可以查看客戶端的IP地址。如:
除了這個串口調(diào)試助手之外,之前也有分享過一個很好用的socket編程調(diào)試工具,有興趣的朋友可移步至:《網(wǎng)絡(luò)調(diào)試助手的簡單使用》進(jìn)行查看。
(3)Python實現(xiàn)服務(wù)端
服務(wù)端程序可以用C、C++、Python等語言來編寫,上面我們用的是C語言。這里我們也來過一把Python癮:
Python代碼:
以下Python代碼來自CSDN博客。博客鏈接:
https://blog.csdn.net/liao392781/article/details/80116600?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
#coding=utf-8
#創(chuàng)建TCP服務(wù)器
from socket import *
from time import ctime
HOST='192.168.1.101' #這個是我的服務(wù)器ip,根據(jù)情況改動
PORT=1314 #我的端口號
BUFSIZ=1024
ADDR=(HOST,PORT)
tcpSerSock=socket(AF_INET,SOCK_STREAM) #創(chuàng)服務(wù)器套接字
tcpSerSock.bind(ADDR) #套接字與地址綁定
tcpSerSock.listen(5) #監(jiān)聽連接,傳入連接請求的最大數(shù),一般為5就可以了
while True:
print('waiting for connection...')
tcpCliSock,addr =tcpSerSock.accept()
print('...connected from:',addr)
while True:
stock_codes = tcpCliSock.recv(BUFSIZ).decode() #收到的客戶端的數(shù)據(jù)需要解碼(python3特性)
print('stock_codes = ',stock_codes) #傳入?yún)?shù)stock_codes
if not stock_codes:
break
tcpCliSock.send(('[%s] %s' %(ctime(),stock_codes)).encode()) #發(fā)送給客戶端的數(shù)據(jù)需要編碼(python3特性)
after_close_simulation = tcpCliSock.recv(BUFSIZ).decode() #收到的客戶端的數(shù)據(jù)需要解碼(python3特性)
print('after_close_simulation = ',after_close_simulation) #傳入?yún)?shù)after_close_simulation
if not after_close_simulation:
break
tcpCliSock.send(('[%s] %s' %(ctime(),after_close_simulation)).encode()) #發(fā)送給客戶端的數(shù)據(jù)需要編碼(python3特性)
tcpCliSock.close()
tcpSerSock.close()
猜你喜歡
最后
若覺得文章不錯,轉(zhuǎn)發(fā)分享、在看,也是我們繼續(xù)更新的動力。
在公眾號內(nèi)回復(fù)更多資源,可免費獲取嵌入式資料。期待你的關(guān)注~
加好友,回暗號【嵌入式大雜燴】,進(jìn)微信群
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!