前回から引き続き,たまっていた改造を一気にアップ してしまおう.
TCPの再送を行うためには,パケットを送信したらすぐに削除してしまうのでなく 保存しておいて,ACKが来たらそこで削除して次のパケットを送信する,という 手順に書き換える必要がある.つまり送信用の一時バッファが必要になる.
実は,このバッファリング処理の設置にはちょっと抵抗がある.というのは, バッファリングしてパケットデータを残すことで再送に備えるということは, 送信時にはパケットをコピーして送信するということになるからだ. これは性能的にも影響があるのだが,なんといってもメモリを余計に使ってしまう. 従来はhttpdから受け取ったデータをそのまま加工して送信していたため, まあ頑張ればDRAMを利用せずに内蔵RAMだけの範囲でTCPの処理も行えるような 気もしていたのだが,このようなコピー処理が入ってしまっては, 内蔵RAMだけの動作はいよいよ難しくなってくるだろう.
しかし前回も書いたように,TCPの安定通信のためには 再送処理は必須だと思うし,タイマの実用的な使いかたの見本にもなる. ということで,まああまり深く考えずに実装してみようと思う.
で,そんなかんじでTCPの処理を書き換えてみた.
主な修正点を説明しよう.
まず接続管理用の connection 構造体に,送信バッファとして sending という メンバを追加する.
struct connection {
int number;
kz_msgbox_id_t id;
uint32 src_ipaddr;
uint32 dst_ipaddr;
uint16 src_port;
uint16 dst_port;
- uint32 snd_number;
- uint32 seq_number;
- uint32 ack_number;
+ uint32 snd_number; /* 自分がどこまで送信したか */
+ uint32 seq_number; /* 相手がどこまで受信したか */
+ uint32 ack_number; /* 自分がどこまで受信したか */
enum {
TCP_CONNECTION_STATUS_CLOSED,
TCP_CONNECTION_STATUS_LISTEN,
- TCP_CONNECTION_STATUS_SYNSENT,
+ TCP_CONNECTION_STATUS_SYNSENT1,
+ TCP_CONNECTION_STATUS_SYNSENT2,
TCP_CONNECTION_STATUS_SYNRECV,
TCP_CONNECTION_STATUS_ESTAB,
TCP_CONNECTION_STATUS_FINWAIT1,
TCP_CONNECTION_STATUS_FINWAIT2,
TCP_CONNECTION_STATUS_CLOSEWAIT,
TCP_CONNECTION_STATUS_LASTACK,
} status;
+ struct netbuf *sending;
struct netbuf *send_queue;
struct netbuf **send_queue_end;
struct netbuf *recv_queue;
struct netbuf **recv_queue_end;
};
あとついでにステータスの管理のために SYNSENT をSYNSENT1とSYNSENT2の2つに
分けている.これは,そうしたほうがなんかすっきり書けたから.
コネクションの削除時に,送信バッファも解放する処理を追加.
static int tcp_free_connection(struct connection *con)
{
struct netbuf *buf, *next;
+ if (con->sending)
+ kz_kmfree(con->sending);
for (buf = con->send_queue; buf; buf = next) {
next = buf->next;
kz_kmfree(buf);
}
for (buf = con->recv_queue; buf; buf = next) {
next = buf->next;
kz_kmfree(buf);
}
memset(con, 0, sizeof(*con));
kz_kmfree(con);
return 0;
}
再送のためには送信バッファを設置して,ACKが来たら送信パケットを削除する
という処理にする必要がある.つまり,パケットはそのまま送信するのでなく,
送信バッファにあるものをコピーして送信することになる.このためパケットの
コピー用のサービス関数を追加.
+static struct netbuf *tcp_copypkt(struct netbuf *pkt)
+{
+ struct netbuf *copypkt;
+
+ copypkt = kz_kmalloc(DEFAULT_NETBUF_SIZE);
+ memcpy(copypkt, pkt, DEFAULT_NETBUF_SIZE);
+
+ copypkt->next = NULL;
+ copypkt->top = (char *)copypkt + (pkt->top - (char *)pkt);
+
+ return copypkt;
+}
パケットの送信関数では,パケットを送信バッファに格納して,さらにコピーして
送信するように修正する.これにより送信パケットはバッファに残るので,
必要に応じて再送することが可能になる.
static int tcp_send_flush(struct connection *con)
{
struct netbuf *pkt;
if (con->snd_number == con->seq_number) {
pkt = con->send_queue;
if (pkt) {
con->send_queue = pkt->next;
if (!con->send_queue)
con->send_queue_end = &(con->send_queue);
pkt->next = NULL;
+
+ if (con->sending)
+ kz_kmfree(con->sending);
+ con->sending = pkt;
+ pkt = tcp_copypkt(pkt);
+
tcp_sendpkt(pkt, con);
}
}
return 0;
}
なお tcp_recv_open(),tcp_recv_close(),tcp_recv_data() に関してはとくに差分は
掲載しないが,前回は #if 0 でくくるだけだったが,もう利用しないので,
今回ごっそり削除した.
次に TCP の受信処理の改造.
static int tcp_recv(struct netbuf *pkt)
{
struct netbuf *buf;
struct connection *con;
struct tcp_header *tcphdr;
- int new_status;
- int closed = 0, ret = 0;
+ int opened = 0, closed = 0, ret = 0;
tcphdr = (struct tcp_header *)pkt->top;
con = tcp_search_connection_from_addr(0,
pkt->option.common.ipaddr.addr,
tcphdr->dst_port,
tcphdr->src_port);
if (!con)
con = tcp_search_connection_from_addr(0, 0, tcphdr->dst_port, 0);
if (!con) return 0;
- new_status = con->status;
-
if (tcphdr->flags & TCP_HEADER_FLAG_RST) {
closed++;
}
if (tcphdr->flags & TCP_HEADER_FLAG_ACK) {
- /* ここで送信キューからパケット削除する */
+ con->seq_number = tcphdr->ack_number;
+
+ /* 送信バッファからパケットを削除する */
+ if (con->snd_number == con->seq_number) {
+ if (con->sending) {
+ kz_kmfree(con->sending);
+ con->sending = NULL;
+ }
+ }
/* データ送信に対してACKが返ってきたので,次のデータを送信する */
if (con->status == TCP_CONNECTION_STATUS_ESTAB) {
- con->seq_number = tcphdr->ack_number;
tcp_send_flush(con);
}
- /* SYNSENT,SYNRECVならばESTABに */
- if ((con->status == TCP_CONNECTION_STATUS_SYNSENT) ||
- (con->status == TCP_CONNECTION_STATUS_SYNRECV)) {
- con->seq_number = tcphdr->ack_number;
-
- /* セッション確立を上位タスクに通知 */
- buf = kz_kmalloc(sizeof(*buf));
- buf->cmd = TCP_CMD_ESTAB;
- buf->option.tcp.establish.number = con->number;
- kz_send(con->id, 0, (char *)buf);
+ if (con->status == TCP_CONNECTION_STATUS_SYNSENT1) {
+ con->status = TCP_CONNECTION_STATUS_SYNSENT2;
+ }
- new_status = TCP_CONNECTION_STATUS_ESTAB;
+ if (con->status == TCP_CONNECTION_STATUS_SYNRECV) {
+ con->status = TCP_CONNECTION_STATUS_ESTAB;
+ opened++;
}
if (con->status == TCP_CONNECTION_STATUS_FINWAIT1) {
- con->seq_number = tcphdr->ack_number;
- new_status = TCP_CONNECTION_STATUS_FINWAIT2;
+ con->status = TCP_CONNECTION_STATUS_FINWAIT2;
}
if (con->status == TCP_CONNECTION_STATUS_LASTACK) {
- con->seq_number = tcphdr->ack_number;
con->status = TCP_CONNECTION_STATUS_CLOSED;
closed++;
}
}
if (tcphdr->flags & TCP_HEADER_FLAG_SYN) {
/* LISTENならばSYN+ACKを返す */
if (con->status == TCP_CONNECTION_STATUS_LISTEN) {
con->snd_number = con->seq_number = 1;
con->dst_ipaddr = pkt->option.common.ipaddr.addr;
con->dst_port = tcphdr->src_port;
con->ack_number = tcphdr->seq_number + 1;
- tcp_makesendpkt(con, TCP_HEADER_FLAG_SYNACK, 1460, 1460, 1, 0, NULL);
- new_status = TCP_CONNECTION_STATUS_SYNRECV;
+ tcp_send_enqueue(con, TCP_HEADER_FLAG_SYNACK, 1460, 1460, 1, 0, NULL);
+ con->status = TCP_CONNECTION_STATUS_SYNRECV;
}
/* SYNSENTならばACKを返す(たぶんSYN+ACKが来ている) */
- if (con->status == TCP_CONNECTION_STATUS_SYNSENT) {
+ if (con->status == TCP_CONNECTION_STATUS_SYNSENT2) {
con->ack_number = tcphdr->seq_number + 1;
tcp_makesendpkt(con, TCP_HEADER_FLAG_ACK, 1460, 0, 0, 0, NULL);
+ opened++;
+ con->status = TCP_CONNECTION_STATUS_ESTAB;
}
}
if (tcphdr->flags & TCP_HEADER_FLAG_FIN) {
/* FINWAIT2なら,ACKを返してCLOSEDに遷移 */
if (con->status == TCP_CONNECTION_STATUS_FINWAIT2) {
con->ack_number = tcphdr->seq_number + 1;
tcp_makesendpkt(con, TCP_HEADER_FLAG_ACK, 1460, 0, 0, 0, NULL);
- new_status = TCP_CONNECTION_STATUS_CLOSED;
+ con->status = TCP_CONNECTION_STATUS_CLOSED;
closed++;
}
/* ESTABなら,ACK, FIN+ACK を返してLASTACKに遷移 */
if (con->status == TCP_CONNECTION_STATUS_ESTAB) {
con->ack_number = tcphdr->seq_number + 1;
tcp_makesendpkt(con, TCP_HEADER_FLAG_ACK, 1460, 0, 0, 0, NULL);
- /* new_status = TCP_CONNECTION_STATUS_CLOSEWAIT; */
- tcp_makesendpkt(con,
+ /* con->status = TCP_CONNECTION_STATUS_CLOSEWAIT; */
+ tcp_send_enqueue(con,
#if 0
- TCP_HEADER_FLAG_FIN,
+ TCP_HEADER_FLAG_FIN,
#else
- TCP_HEADER_FLAG_FINACK,
+ TCP_HEADER_FLAG_FINACK,
#endif
- 1460, 0, 0, 0, NULL);
- new_status = TCP_CONNECTION_STATUS_LASTACK;
+ 1460, 0, 0, 0, NULL);
+ con->status = TCP_CONNECTION_STATUS_LASTACK;
}
}
if (tcphdr->flags & TCP_HEADER_FLAG_PSH) {
/* データを受信 */
if (con->status == TCP_CONNECTION_STATUS_ESTAB) {
pkt->next = NULL;
*(con->recv_queue_end) = pkt;
con->recv_queue_end = &(pkt->next);
tcp_recv_flush(con); /* データを上位タスクに通知してACKを返す */
ret = 1;
}
}
-#if 0
- switch (con->status) {
- case TCP_CONNECTION_STATUS_LISTEN:
- case TCP_CONNECTION_STATUS_SYNSENT:
- case TCP_CONNECTION_STATUS_SYNRECV:
- return tcp_recv_open(pkt, con, tcphdr);
-
- case TCP_CONNECTION_STATUS_ESTAB:
- if (tcphdr->flags & TCP_HEADER_FLAG_FIN)
- if (tcp_recv_close(pkt, con, tcphdr))
- return 1; /* conが削除されたので処理継続できないので返る.要修正 */
- return tcp_recv_data(pkt, con, tcphdr);
-
- case TCP_CONNECTION_STATUS_FINWAIT1:
- case TCP_CONNECTION_STATUS_FINWAIT2:
- case TCP_CONNECTION_STATUS_CLOSEWAIT:
- case TCP_CONNECTION_STATUS_LASTACK:
- return tcp_recv_close(pkt, con, tcphdr);
-
- case TCP_CONNECTION_STATUS_CLOSED:
- default:
- break;
+ if (opened) {
+ /* セッション確立を上位タスクに通知 */
+ buf = kz_kmalloc(sizeof(*buf));
+ buf->cmd = TCP_CMD_ESTAB;
+ buf->option.tcp.establish.number = con->number;
+ kz_send(con->id, 0, (char *)buf);
}
-#endif
- con->status = new_status;
if (closed) {
/* セッション終了を上位タスクに通知 */
buf = kz_kmalloc(sizeof(*buf));
buf->cmd = TCP_CMD_CLOSE;
buf->option.tcp.close.number = con->number;
kz_send(con->id, 0, (char *)buf);
con = tcp_delete_connection(con->number);
tcp_free_connection(con);
}
return ret;
}
tcp_recv()は送信バッファの対処を追加して,さらに整理しなおした.
ACK以外の制御パケット(SYNACKやFIN)は tcp_makesendpkt() で直接送信するのでなく,
tcp_send_enqueue() を利用することで,送信バッファ経由で送信するようにした.
またSYNSENTはSYNSENT1,SYNSENT2に分けて遷移するようにするようにした.
これにより変数 new_status の処理が不要になってすっきりする.
最後に tcp_proc() の修正.これも今まで同様,送信バッファ設置と SYNSENT1,SYNSENT2に関する修正だ.
static int tcp_proc(struct netbuf *buf)
{
struct connection *con;
int ret = 0;
switch (buf->cmd) {
case TCP_CMD_IPADDR:
my_ipaddr = buf->option.common.ipaddr.addr;
break;
case TCP_CMD_ACCEPT:
con = kz_kmalloc(sizeof(*con));
memset(con, 0, sizeof(*con));
con->number = ++number;
con->id = buf->option.tcp.accept.id;
con->src_ipaddr = my_ipaddr;
con->src_port = buf->option.tcp.accept.port;
con->snd_number = 1;
con->seq_number = 1;
con->ack_number = 0;
con->status = TCP_CONNECTION_STATUS_LISTEN;
+ con->sending = NULL;
con->send_queue = NULL;
con->recv_queue = NULL;
con->send_queue_end = &con->send_queue;
con->recv_queue_end = &con->recv_queue;
kz_send(MSGBOX_ID_TCPCONLIST, 0, (char *)con);
tcp_search_connection_from_number(0); /* 頭出し */
break;
case TCP_CMD_CONNECT:
con = kz_kmalloc(sizeof(*con));
memset(con, 0, sizeof(*con));
con->number = ++number;
con->id = buf->option.tcp.connect.id;
con->src_ipaddr = my_ipaddr;
con->dst_ipaddr = buf->option.tcp.connect.ipaddr;
con->src_port = port++;
con->dst_port = buf->option.tcp.connect.port;
con->snd_number = 1;
con->seq_number = 1;
con->ack_number = 0;
- con->status = TCP_CONNECTION_STATUS_SYNSENT;
+ con->status = TCP_CONNECTION_STATUS_SYNSENT1;
+ con->sending = NULL;
con->send_queue = NULL;
con->recv_queue = NULL;
con->send_queue_end = &con->send_queue;
con->recv_queue_end = &con->recv_queue;
kz_send(MSGBOX_ID_TCPCONLIST, 0, (char *)con);
tcp_search_connection_from_number(0); /* 頭出し */
/* SYNを送信 */
- tcp_makesendpkt(con, TCP_HEADER_FLAG_SYN, 1460, 1460, 1, 0, NULL);
+ tcp_send_enqueue(con, TCP_HEADER_FLAG_SYN, 1460, 1460, 1, 0, NULL);
break;
case TCP_CMD_CLOSE:
con = tcp_search_connection_from_number(buf->option.tcp.close.number);
if (!con) break;
/* FINを送信 */
tcp_send_enqueue(con,
#if 0
TCP_HEADER_FLAG_FIN,
#else
TCP_HEADER_FLAG_FINACK,
#endif
1460, 0, 0, 0, NULL);
#if 0
con->status = TCP_CONNECTION_STATUS_FINWAIT1;
#endif
break;
case TCP_CMD_RECV:
ret = tcp_recv(buf);
break;
case TCP_CMD_SEND:
ret = tcp_send(buf);
break;
default:
break;
}
動作はシミュレータで確認した.ちゃんとwebサーバが動いている.
実はMTM06の出展でのデモは前回あたりのソースコードで
やっていたのだけど,こころなしか以前よりも安定して
動作しているように感じる.
これで再送処理をするための準備はできた.次はタイマを利用できるようにして, いよいよ再送処理の実装だ.