前回から引き続き,たまっていた改造を一気にアップ してしまおう.
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の出展でのデモは前回あたりのソースコードで やっていたのだけど,こころなしか以前よりも安定して 動作しているように感じる.
これで再送処理をするための準備はできた.次はタイマを利用できるようにして, いよいよ再送処理の実装だ.