ソケットとは
ソケットとは、アプリケーションやファームウェアから見たTCP/IPのデータの出入り口みたいなものです。
ソケットを利用するアプリケーションやファームウェアは、ソケット用のAPIを利用してTCP/IPの通信を行います。
LightWeihgt IPでもソケット用のAPIが用意されており、暗号化通信のプロトコルスタックmbedTLSではLightWeight IPのソケット通信を利用して暗号化通信を行うことが可能です。
本記事はLwIPを使った際のソケット通信の流れを説明するため、
ソケットとソケット通信そのものの具体的な説明に関しては、下記の記事などに詳しく説明されているので省きます。ソケット通信を詳しく知りたい方はこちらを読んでいただくと良いかと思います。
LightWeightIPのソケット通信
LightWeightIPには、ソケット通信用のAPIが用意されているのでそれを利用します。宣言はsockets.hにあるので、このファイルをインクルードして使用します。
APIのソケット通信用のAPIのLightWeightIPの実装としては、前回の記事で示したTCP,UDPのAPIを、必要に応じて呼び出す形になります。下記の図のイメージです。
LightweightIPのソケット通信の実装
TCPとUDPでは、実装の際に違いが出るので、分けて説明します。
初期化
LightweightIPの初期化を行います。ソケット処理用のAPIを使うための初期化処理は無いので、LightweightIPの初期化のみ行います。
下記では静的IPアドレスを初期化時に設定しています。DHCPでIPアドレスを取得したい場合は、こちらの記事を参考にAPI「dhcp_start()」を呼び出すことでIPアドレスを取得するようにしてください。
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/tcpip.h"
#include "lwip/tcp.h"
#include "lwip/udp.h"
#include "netif/ethernet.h"
struct netif gnetif;
// ここでイーサネットドライバ用初期化関数宣言 (target依存)
err_t ethernetif_init(struct netif *netif);
void ethernet_link_status_updated(struct netif *netif);
void lwip_init_network(void) {
ip_addr_t ipaddr, netmask, gw;
tcpip_init(NULL, NULL); // lwIP全体初期化
IP4_ADDR(&ipaddr, 192, 168, 1, 100); // IPアドレス
IP4_ADDR(&netmask, 255, 255, 255, 0); // サブネットマスク
IP4_ADDR(&gw, 192, 168, 1, 1); // デフォルトゲートウェイ
netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ethernetif_init, tcpip_input);
netif_set_default(&gnetif);
netif_set_up(&gnetif);
netif_set_link_callback(&gnetif, ethernet_link_status_updated);
}
TCP
TCPの通信は通信相手とのコネクションが必要になり、サーバ側とクライアント側で実装が異なるため、分けて示します。
サーバ側の実装
サーバー側の実装を下記に示します。
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/tcpip.h"
#include "lwip/sockets.h"
#include <string.h>
void lwip_init_network(void); // 前述の初期化関数を想定
void tcp_server_sample(void) {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buf[128];
// 初期化
lwip_init_network();
// ソケット生成
server_fd = socket(AF_INET, SOCK_STREAM, 0);
// アドレスを紐づけ
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = INADDR_ANY;
bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
// サーバーを接続待ちに設定(接続最大台数:1)
listen(server_fd, 1);
// 接続受け入れ
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
// 送受信(エコー)
int recv_len = recv(client_fd, buf, sizeof(buf) - 1, 0);
if (recv_len > 0) {
send(client_fd, buf, recv_len, 0); // エコー
}
// 接続終了(クローズ)
close(client_fd);
close(server_fd);
}
LightWeightIPの初期化後は「socket()」でソケット生成し、「bind()」で設定したIPアドレスをサーバーに紐づけ、「Listen()」でTCPクライアントからの接続待ちに入ります。
TCPクライアントから接続要求があれば、「accept()」によってそれを受け入れます。その後は「recv()」と「send()」で送受信が可能になります。ソケットをクローズしたい(通信を終了したい)場合は「close()」を使用します。
クライアント側の実装
クライアントの場合の処理を下記に示します。下記は引数にて文字列でIPアドレスを渡すことを想定されています。
#include "lwip/init.h"
#include "lwip/sockets.h"
#include <string.h>
void lwip_init_network(void); // 前述の初期化関数を想定
void tcp_client_sample(const char* server_ip) {
int sockfd;
struct sockaddr_in servaddr;
char buf[128] = "Hello, lwIP TCP Server!";
// 初期化
lwip_init_network();
// ソケット生成
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 接続
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8080);
servaddr.sin_addr.s_addr = inet_addr(server_ip); // 文字列のIPアドレスをLwIPが使用する形に変換
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 送受信
send(sockfd, buf, strlen(buf), 0);
int len = recv(sockfd, buf, sizeof(buf) - 1, 0);
// 接続終了(クローズ)
closes(sockfd);
}
UDP
UDPはコネクションを必要としないため、通信に使用するソケットを生成を行い、送受信用のAPIを呼び出すだけになります。
コネクションレス通信になるので、TCPのように「accept()」や「listen()」, 「connect()」を呼び出さなくても通信可能ですが、受信したデータに取りこぼしが無いかや、送信データが正しく送られているかはわかりません。
サーバ側の実装
UDPのサーバーの実装を下記に示します。受信には「recvfrom()」送信には「sendto()」を使用します。
先ほども説明した通り、コネクションレス通信のため「listen()」と「accept()」は必要なくなります。
#include "lwip/init.h"
#include "lwip/sockets.h"
#include <string.h>
void lwip_init_network(void);
void udp_server_sample(void) {
int sockfd;
struct sockaddr_in servaddr, cliaddr;
socklen_t addrlen = sizeof(cliaddr);
char buf[128];
// 初期化
lwip_init_network();
// ソケット生成
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// IPアドレスを割り当て
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5005);
servaddr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
// 送受信
int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &addrlen);
if (len > 0) {
sendto(sockfd, buf, len, 0, (struct sockaddr*)&cliaddr, addrlen); // エコー
}
// 接続終了(クローズ)
close(sockfd);
}
クライアント側の実装
UDPクライアントの処理を下記に示します。
こちらもコネクションレス通信のため、「connect()」は必要なくなります。
#include "lwip/init.h"
#include "lwip/sockets.h"
#include <string.h>
void lwip_init_network(void);
void udp_client_sample(const char* server_ip) {
int sockfd;
struct sockaddr_in servaddr;
char buf[128] = "Hello UDP!";
// 初期化
lwip_init_network();
// ソケット生成
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 送信
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5005);
servaddr.sin_addr.s_addr = inet_addr(server_ip);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
// 接続終了(クローズ)
close(sockfd);
}
参考資料