android6.0 adbd深い分析(四)adbd usb線を抜いて再接続する過程
24474 ワード
このブログでは、adbdがusb線で抜いてから接続するコードの流れを主に分析します.
まず、自分でデバッグしたコード印刷を見てみましょう.
表示の問題でlogの時間を除いてpidとtidだけを表示しました
私たちはlogに従って、まずoutput_です.thread adb駆動データの読み出しエラー
反対側のsocketpair関連関数はtransport_socket_events関数
transport_socket_events関数、handleを呼び出しました.packet関数
handle_packet関数受信A_SYNCコマンド、offline処理
hand_packetはsyncメッセージを処理します.まずhandle_を見てみましょう.offline関数は、実は状態を処理します
offlineを処理する私たちはsend_を見ますpacketもsocketpairの反対側にデータを書き、inputThreadがデータ受信を行う
Input_thread受信handle_packetからofflineメッセージが送られてきて、スレッドを直接終了し、kick_を呼び出しました.transport関数
ここでkickを呼び出したのはremote_kick関数
remote_kickはusbを呼び出したkick関数
USb_adb_Init初期化時kickはusb_adb_kick
この関数を見てみましょう.最終的にsignalを送信しました.
このsignalはusb_adb_open_thread待機状態が破られ、adbドライバが再開され、input_が再開されます.thread output_thread
そしてoutput_threadが開いたらhandle_に向かいますpacketはA_を送信しますSYNCコマンド
そしてhandle_へpacketはこのsyncコマンドをどのように処理しますか?
そしてinput_threadこのsyncコマンドを受信した後の処理は以下の通りです.
input_threadはsyncコマンドを受信してactiveを1にし、次回adbドライバにデータを書くことができます.
しかし、疑問があるのではないでしょうか.この時usbが先に抜けていて、挿入されていないので、なぜこの時input_threadはadb駆動でデータを書くことができますか?答えはここです.
次に、usb線を細かく挿入するコードフローを分析します.
まずlogを見て、usb線を抜いてread_を印刷しました.from_remote afterというロゴは、この時点でoutput_threadはadb駆動からデータを読み出してブロックしなくなり、handle_にデータを送信しました.packet.
handle_packetはこの時adb駆動のA_を直接受け取りましたCNXNコマンド:
最初からtransportのconnection_state = CS_OFFLINEなので、先にhandle_を処理しましたonline、send_を呼び出しました接続関数:
send_接続はinput_へthreadは接続情報を送信し、最後にadb駆動に送信し、pcはadbdと接続されていることを知っています.
そしてparse_bannerという関数でconnection_stateが処理しました
だから私はA_を処理していますCNXNの時、parse_bannerは前後にロゴを打った
最後にusb線が差し込まれたときにconnection_が始まることに気づきましたstate:0つまりCS_OFFLINE、後は3つまりCS_になりましたHOST
後ろのロゴについては、shell:dumpsys iphonesubinfo'shell:dumpsys battery'は別のコマンドです.
最後にもう一つのコマンドがhandleを先に処理します.offline、状態はまず0で、それから3に変更します
最後になぜ2回接続したのか、よくわかりませんが、これは協議で規定されているはずです.
一、log印刷プロセス
まず、自分でデバッグしたコード印刷を見てみましょう.
表示の問題でlogの時間を除いてpidとtidだけを表示しました
// usb
185 188 I adbd : output_thread:(null): remote read failed for transport
185 188 I adbd : output_thread:(null) SYNC offline for transport
185 185 I adbd : handle_packet: A_SYNC
185 185 I adbd : handle_packet: A_SYNC CS_OFFLINE
185 187 I adbd : input_thread:(null): transport SYNC offline
185 187 I adbd : input_thread:(null): transport input thread is exiting, fd 13
185 186 I usb_adb_open_thread: adbd usb_thread - opening device
185 186 I usb_adb_open_thread: adbd opening device succeeded
185 185 I adbd : handle_packet: A_SYNC
185 185 I adbd : handle_packet: A_SYNC send_packet
185 2164 I adbd : input_thread:(null): transport SYNC online
// usb
185 2165 I adbd : output_thread: read_from_remote after
185 185 I adbd : handle_packet: A_CNXN
185 185 I adbd : handle_packet: A_CNXN handle_online
185 185 I adbd : handle_packet: A_CNXN send_connect
185 2165 I adbd : output_thread: read_from_remote after
185 185 I adbd : adb command: 'shell:dumpsys iphonesubinfo'
185 2165 I adbd : output_thread: read_from_remote after
185 2165 I adbd : output_thread: read_from_remote after
185 185 I adbd : adb command: 'shell:dumpsys battery'
185 2165 I adbd : output_thread: read_from_remote after
185 2165 I adbd : output_thread: read_from_remote after
185 2165 I adbd : output_thread: read_from_remote after
185 185 I adbd : handle_packet: A_CNXN
185 185 I adbd : handle_packet: A_CNXN handle_offline
185 185 I adbd : handle_packet: A_CNXN handle_online
185 185 I adbd : handle_packet: A_CNXN send_connect
二、usb線を抜く流れ
2.1 output_thread adb駆動データ読み出しエラー
私たちはlogに従って、まずoutput_です.thread adb駆動データの読み出しエラー
static void *output_thread(void *_t)
{
atransport *t = reinterpret_cast<atransport*>(_t);
apacket *p;
D("%s: starting transport output thread on fd %d, SYNC online (%d)
",
t->serial, t->fd, t->sync_token + 1);
p = get_apacket();
p->msg.command = A_SYNC;
p->msg.arg0 = 1;
p->msg.arg1 = ++(t->sync_token);
p->msg.magic = A_SYNC ^ 0xffffffff;
if(write_packet(t->fd, t->serial, &p)) {
put_apacket(p);
D("%s: failed to write SYNC packet
", t->serial);
goto oops;
}
D("%s: data pump started
", t->serial);
for(;;) {
p = get_apacket();
if(t->read_from_remote(p, t) == 0){
D("%s: received remote packet, sending to transport
",
t->serial);
if(write_packet(t->fd, t->serial, &p)){
put_apacket(p);
D("%s: failed to write apacket to transport
", t->serial);
LOG("%s:%s: failed to write apacket to transport
", __FUNCTION__, t->serial);
goto oops;
}
} else {
D("%s: remote read failed for transport
", t->serial);
LOG("%s:%s: remote read failed for transport
", __FUNCTION__, t->serial);// adb
put_apacket(p);
break;
}
}
D("%s: SYNC offline for transport
", t->serial);
LOG("%s:%s SYNC offline for transport
", __FUNCTION__, t->serial);//
p = get_apacket();
p->msg.command = A_SYNC;
p->msg.arg0 = 0;
p->msg.arg1 = 0;
p->msg.magic = A_SYNC ^ 0xffffffff;
if(write_packet(t->fd, t->serial, &p)) {// sockpair
put_apacket(p);
D("%s: failed to write SYNC apacket to transport", t->serial);
}
output_thread adb駆動データの読み出しエラーが発生し、スレッドを終了しsockpairの反対側のデータを送信反対側のsocketpair関連関数はtransport_socket_events関数
fdevent_install(&(t->transport_fde),
t->transport_socket,
transport_socket_events,
t);
transport_socket_events関数、handleを呼び出しました.packet関数
static void transport_socket_events(int fd, unsigned events, void *_t)
{
atransport *t = reinterpret_cast<atransport*>(_t);
D("transport_socket_events(fd=%d, events=%04x,...)
", fd, events);
if(events & FDE_READ){
apacket *p = 0;
if(read_packet(fd, t->serial, &p)){
D("%s: failed to read packet from transport socket on fd %d
", t->serial, fd);
} else {
handle_packet(p, (atransport *) _t);
}
}
}
2.2 handle_义齿
handle_packet関数受信A_SYNCコマンド、offline処理
void handle_packet(apacket *p, atransport *t)
{
asocket *s;
D("handle_packet() %c%c%c%c
", ((char*) (&(p->msg.command)))[0],
((char*) (&(p->msg.command)))[1],
((char*) (&(p->msg.command)))[2],
((char*) (&(p->msg.command)))[3]);
print_packet("recv", p);
switch(p->msg.command){
case A_SYNC:
LOG("%s: A_SYNC
", __FUNCTION__);// sync
if(p->msg.arg0){
send_packet(p, t);
LOG("%s: A_SYNC send_packet
", __FUNCTION__);
if(HOST) send_connect(t);
} else {
t->connection_state = CS_OFFLINE;
LOG("%s: A_SYNC CS_OFFLINE
", __FUNCTION__);//offline
handle_offline(t);
send_packet(p, t);
}
return;
hand_packetはsyncメッセージを処理します.まずhandle_を見てみましょう.offline関数は、実は状態を処理します
void handle_offline(atransport *t)
{
D("adb: offline
");
//Close the associated usb
t->online = 0;
run_transport_disconnects(t);
}
2.3 Input_thread処理offline
offlineを処理する私たちはsend_を見ますpacketもsocketpairの反対側にデータを書き、inputThreadがデータ受信を行う
static void *input_thread(void *_t)
{
atransport *t = reinterpret_cast<atransport*>(_t);
apacket *p;
int active = 0;
D("%s: starting transport input thread, reading from fd %d
",
t->serial, t->fd);
for(;;){
if(read_packet(t->fd, t->serial, &p)) {
D("%s: failed to read apacket from transport on fd %d
",
t->serial, t->fd );
LOG("%s:%s: failed to read apacket from transport on fd %d
", __FUNCTION__, t->serial, t->fd );
break;
}
if(p->msg.command == A_SYNC){
if(p->msg.arg0 == 0) {
D("%s: transport SYNC offline
", t->serial);
put_apacket(p);
LOG("%s:%s: transport SYNC offline
", __FUNCTION__, t->serial);// handle_packet offline ,
break;
} else {
if(p->msg.arg1 == t->sync_token) {
LOG("%s:%s: transport SYNC online
", __FUNCTION__, t->serial);
active = 1;
} else {
D("%s: transport ignoring SYNC %d != %d
",
t->serial, p->msg.arg1, t->sync_token);
}
}
} else {
if(active) {
D("%s: transport got packet, sending to remote
", t->serial);
t->write_to_remote(p, t);
} else {
D("%s: transport ignoring packet while offline
", t->serial);
}
}
put_apacket(p);
}
// this is necessary to avoid a race condition that occured when a transport closes
// while a client socket is still active.
close_all_sockets(t);
D("%s: transport input thread is exiting, fd %d
", t->serial, t->fd);
LOG("%s:%s: transport input thread is exiting, fd %d
", __FUNCTION__, t->serial, t->fd);//
kick_transport(t);// kick_transport
transport_unref(t);
return 0;
Input_thread受信handle_packetからofflineメッセージが送られてきて、スレッドを直接終了し、kick_を呼び出しました.transport関数
void kick_transport(atransport* t)
{
if (t && !t->kicked)
{
int kicked;
adb_mutex_lock(&transport_lock);
kicked = t->kicked;
if (!kicked)
t->kicked = 1;
adb_mutex_unlock(&transport_lock);
if (!kicked)
t->kick(t);
}
}
ここでkickを呼び出したのはremote_kick関数
void init_usb_transport(atransport *t, usb_handle *h, int state)
{
D("transport: usb
");
t->close = remote_close;
t->kick = remote_kick;
t->read_from_remote = remote_read;
t->write_to_remote = remote_write;
t->sync_token = 1;
t->connection_state = state;
t->type = kTransportUsb;
t->usb = h;
#if ADB_HOST
HOST = 1;
#else
HOST = 0;
#endif
}
remote_kickはusbを呼び出したkick関数
static void remote_kick(atransport *t)
{
usb_kick(t->usb);
}
でusbが呼び出されましたhandleのkick関数void usb_kick(usb_handle *h)
{
h->kick(h);
}
USb_adb_Init初期化時kickはusb_adb_kick
static void usb_adb_init()
{
usb_handle* h = reinterpret_cast<usb_handle*>(calloc(1, sizeof(usb_handle)));
if (h == nullptr) fatal("couldn't allocate usb_handle");
h->write = usb_adb_write;
h->read = usb_adb_read;
h->kick = usb_adb_kick;
この関数を見てみましょう.最終的にsignalを送信しました.
static void usb_adb_kick(usb_handle *h)
{
D("usb_kick
");
adb_mutex_lock(&h->lock);
adb_close(h->fd);
h->fd = -1;
// notify usb_adb_open_thread that we are disconnected
adb_cond_signal(&h->notify);
adb_mutex_unlock(&h->lock);
}
このsignalはusb_adb_open_thread待機状態が破られ、adbドライバが再開され、input_が再開されます.thread output_thread
static void *usb_adb_open_thread(void *x)
{
struct usb_handle *usb = (struct usb_handle *)x;
int fd;
while (true) {
// wait until the USB device needs opening
adb_mutex_lock(&usb->lock);
while (usb->fd != -1)
adb_cond_wait(&usb->notify, &usb->lock);//
adb_mutex_unlock(&usb->lock);
D("[ usb_thread - opening device ]
");// adb
__android_log_print(ANDROID_LOG_INFO, __FUNCTION__,
"adbd usb_thread - opening device
");
do {
/* XXX use inotify? */
fd = unix_open("/dev/android_adb", O_RDWR);
if (fd < 0) {
// to support older kernels
fd = unix_open("/dev/android", O_RDWR);
}
if (fd < 0) {
adb_sleep_ms(1000);
}
} while (fd < 0);
D("[ opening device succeeded ]
");
__android_log_print(ANDROID_LOG_INFO, __FUNCTION__,
"adbd opening device succeeded
");
close_on_exec(fd);
usb->fd = fd;
D("[ usb_thread - registering device ]
");
register_usb_transport(usb, 0, 0, 1);// usb , input_thread output_thread
}
// never gets here
return 0;
}
2.3再開オープンアウトプット_thread input_threadスレッド
そしてoutput_threadが開いたらhandle_に向かいますpacketはA_を送信しますSYNCコマンド
static void *output_thread(void *_t)
{
atransport *t = reinterpret_cast<atransport*>(_t);
apacket *p;
D("%s: starting transport output thread on fd %d, SYNC online (%d)
",
t->serial, t->fd, t->sync_token + 1);
p = get_apacket();
p->msg.command = A_SYNC;
p->msg.arg0 = 1;
p->msg.arg1 = ++(t->sync_token);
p->msg.magic = A_SYNC ^ 0xffffffff;
if(write_packet(t->fd, t->serial, &p)) {
put_apacket(p);
D("%s: failed to write SYNC packet
", t->serial);
goto oops;
}
そしてhandle_へpacketはこのsyncコマンドをどのように処理しますか?
void handle_packet(apacket *p, atransport *t)
{
asocket *s;
D("handle_packet() %c%c%c%c
", ((char*) (&(p->msg.command)))[0],
((char*) (&(p->msg.command)))[1],
((char*) (&(p->msg.command)))[2],
((char*) (&(p->msg.command)))[3]);
print_packet("recv", p);
switch(p->msg.command){
case A_SYNC:
LOG("%s: A_SYNC
", __FUNCTION__);
if(p->msg.arg0){// 1
send_packet(p, t);// input_thread
LOG("%s: A_SYNC send_packet
", __FUNCTION__);
if(HOST) send_connect(t);
} else {
t->connection_state = CS_OFFLINE;
LOG("%s: A_SYNC CS_OFFLINE
", __FUNCTION__);
handle_offline(t);
send_packet(p, t);
}
return;
そしてinput_threadこのsyncコマンドを受信した後の処理は以下の通りです.
static void *input_thread(void *_t)
{
atransport *t = reinterpret_cast<atransport*>(_t);
apacket *p;
int active = 0;
D("%s: starting transport input thread, reading from fd %d
",
t->serial, t->fd);
for(;;){
if(read_packet(t->fd, t->serial, &p)) {
D("%s: failed to read apacket from transport on fd %d
",
t->serial, t->fd );
LOG("%s:%s: failed to read apacket from transport on fd %d
", __FUNCTION__, t->serial, t->fd );
break;
}
if(p->msg.command == A_SYNC){
if(p->msg.arg0 == 0) {
D("%s: transport SYNC offline
", t->serial);
put_apacket(p);
LOG("%s:%s: transport SYNC offline
", __FUNCTION__, t->serial);
break;
} else {
if(p->msg.arg1 == t->sync_token) {// sync , active 1, adb
D("%s: transport SYNC online
", t->serial);
LOG("%s:%s: transport SYNC online
", __FUNCTION__, t->serial);
active = 1;
} else {
D("%s: transport ignoring SYNC %d != %d
",
t->serial, p->msg.arg1, t->sync_token);
LOG("%s:%s: transport ignoring SYNC
", __FUNCTION__, t->serial);
}
}
} else {
if(active) {
D("%s: transport got packet, sending to remote
", t->serial);
t->write_to_remote(p, t);
} else {
D("%s: transport ignoring packet while offline
", t->serial);
}
}
put_apacket(p);
}
input_threadはsyncコマンドを受信してactiveを1にし、次回adbドライバにデータを書くことができます.
しかし、疑問があるのではないでしょうか.この時usbが先に抜けていて、挿入されていないので、なぜこの時input_threadはadb駆動でデータを書くことができますか?答えはここです.
static void *output_thread(void *_t)
{
atransport *t = reinterpret_cast<atransport*>(_t);
apacket *p;
D("%s: starting transport output thread on fd %d, SYNC online (%d)
",
t->serial, t->fd, t->sync_token + 1);
p = get_apacket();
p->msg.command = A_SYNC;
p->msg.arg0 = 1;
p->msg.arg1 = ++(t->sync_token);
p->msg.magic = A_SYNC ^ 0xffffffff;
if(write_packet(t->fd, t->serial, &p)) {
put_apacket(p);
D("%s: failed to write SYNC packet
", t->serial);
goto oops;
}
D("%s: data pump started
", t->serial);
for(;;) {
p = get_apacket();
// , result , if , , log,
int result = t->read_from_remote(p, t);// usb ,
LOG("%s: read_from_remote after
", __FUNCTION__);
if(result == 0){
D("%s: received remote packet, sending to transport
",
t->serial);
if(write_packet(t->fd, t->serial, &p)){
put_apacket(p);
D("%s: failed to write apacket to transport
", t->serial);
LOG("%s:%s: failed to write apacket to transport
", __FUNCTION__, t->serial);
goto oops;
}
} else {
D("%s: remote read failed for transport
", t->serial);
LOG("%s:%s: remote read failed for transport
", __FUNCTION__, t->serial);
put_apacket(p);
break;
}
}
この時私たちはoutputでthreadでコードを少し修正してlogを増やして、usb線が差し込まれていないことに気づいたとき、このときoutput_threadはadb駆動データを読み出す際にブロックされ、input_にデータが伝達されないthreadはadbドライバに書きましたが、もちろん問題はありません.三、usb線を差し込む流れ
次に、usb線を細かく挿入するコードフローを分析します.
まずlogを見て、usb線を抜いてread_を印刷しました.from_remote afterというロゴは、この時点でoutput_threadはadb駆動からデータを読み出してブロックしなくなり、handle_にデータを送信しました.packet.
handle_packetはこの時adb駆動のA_を直接受け取りましたCNXNコマンド:
case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
/* XXX verify version, etc */
LOG("%s: A_CNXN
", __FUNCTION__);
if(t->connection_state != CS_OFFLINE) {
t->connection_state = CS_OFFLINE;
handle_offline(t);
LOG("%s: A_CNXN handle_offline
", __FUNCTION__);
}
parse_banner(reinterpret_cast<const char*>(p->data), t);
if (HOST || !auth_required) {
handle_online(t);
LOG("%s: A_CNXN handle_online
", __FUNCTION__);
if (!HOST) {
send_connect(t);
LOG("%s: A_CNXN send_connect
", __FUNCTION__);
}
} else {
send_auth_request(t);
LOG("%s: A_CNXN send_auth_request
", __FUNCTION__);
}
break;
最初からtransportのconnection_state = CS_OFFLINEなので、先にhandle_を処理しましたonline、send_を呼び出しました接続関数:
void handle_online(atransport *t)
{
D("adb: online
");
t->online = 1;
}
send_接続はinput_へthreadは接続情報を送信し、最後にadb駆動に送信し、pcはadbdと接続されていることを知っています.
void send_connect(atransport *t)
{
D("Calling send_connect
");
apacket *cp = get_apacket();
cp->msg.command = A_CNXN;
cp->msg.arg0 = A_VERSION;
cp->msg.arg1 = MAX_PAYLOAD;
cp->msg.data_length = fill_connect_data((char *)cp->data,
sizeof(cp->data));
send_packet(cp, t);
}
そしてparse_bannerという関数でconnection_stateが処理しました
void parse_banner(const char* banner, atransport* t) {
D("parse_banner: %s
", banner);
// The format is something like:
// "device::ro.product.name=x;ro.product.model=y;ro.product.device=z;".
std::vector<std::string> pieces = android::base::Split(banner, ":");
if (pieces.size() > 2) {
const std::string& props = pieces[2];
for (auto& prop : android::base::Split(props, ";")) {
// The list of properties was traditionally ;-terminated rather than ;-separated.
if (prop.empty()) continue;
std::vector<std::string> key_value = android::base::Split(prop, "=");
if (key_value.size() != 2) continue;
const std::string& key = key_value[0];
const std::string& value = key_value[1];
if (key == "ro.product.name") {
qual_overwrite(&t->product, value);
} else if (key == "ro.product.model") {
qual_overwrite(&t->model, value);
} else if (key == "ro.product.device") {
qual_overwrite(&t->device, value);
}
}
}
const std::string& type = pieces[0];
if (type == "bootloader") {
D("setting connection_state to CS_BOOTLOADER
");
t->connection_state = CS_BOOTLOADER;
update_transports();
} else if (type == "device") {
D("setting connection_state to CS_DEVICE
");
t->connection_state = CS_DEVICE;
update_transports();
} else if (type == "recovery") {
D("setting connection_state to CS_RECOVERY
");
t->connection_state = CS_RECOVERY;
update_transports();
} else if (type == "sideload") {
D("setting connection_state to CS_SIDELOAD
");
t->connection_state = CS_SIDELOAD;
update_transports();
} else {
D("setting connection_state to CS_HOST
");
t->connection_state = CS_HOST;
}
}
だから私はA_を処理していますCNXNの時、parse_bannerは前後にロゴを打った
case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
/* XXX verify version, etc */
LOG("%s: A_CNXN
", __FUNCTION__);
if(t->connection_state != CS_OFFLINE) {
t->connection_state = CS_OFFLINE;
handle_offline(t);
LOG("%s: A_CNXN handle_offline
", __FUNCTION__);
}
LOG("%s: A_CNXN parse_banner before: connection_state:%d
", __FUNCTION__, t->connection_state);
parse_banner(reinterpret_cast<const char*>(p->data), t);
LOG("%s: A_CNXN parse_banner after: connection_state:%d
", __FUNCTION__, t->connection_state);
最後にusb線が差し込まれたときにconnection_が始まることに気づきましたstate:0つまりCS_OFFLINE、後は3つまりCS_になりましたHOST
185 185 I adbd : handle_packet: A_CNXN
185 185 I adbd : handle_packet: A_CNXN parse_banner before: connection_state:0
185 185 I adbd : handle_packet: A_CNXN parse_banner after: connection_state:3
185 185 I adbd : handle_packet: A_CNXN handle_online
185 185 I adbd : handle_packet: A_CNXN send_connect
後ろのロゴについては、shell:dumpsys iphonesubinfo'shell:dumpsys battery'は別のコマンドです.
// usb
185 185 I adbd : handle_packet: A_CNXN
185 185 I adbd : handle_packet: A_CNXN parse_banner before: connection_state:0
185 185 I adbd : handle_packet: A_CNXN parse_banner after: connection_state:3
185 185 I adbd : handle_packet: A_CNXN handle_online
185 185 I adbd : handle_packet: A_CNXN send_connect
185 185 I adbd : adb command: 'shell:dumpsys iphonesubinfo'
185 185 I adbd : adb command: 'shell:dumpsys battery'
185 185 I adbd : handle_packet: A_CNXN
185 185 I adbd : handle_packet: A_CNXN handle_offline
185 185 I adbd : handle_packet: A_CNXN parse_banner before: connection_state:0
185 185 I adbd : handle_packet: A_CNXN parse_banner after: connection_state:3
185 185 I adbd : handle_packet: A_CNXN handle_online
185 185 I adbd : handle_packet: A_CNXN send_connect
最後にもう一つのコマンドがhandleを先に処理します.offline、状態はまず0で、それから3に変更します
case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
/* XXX verify version, etc */
LOG("%s: A_CNXN
", __FUNCTION__);
if(t->connection_state != CS_OFFLINE) {
t->connection_state = CS_OFFLINE;// handleoffline connection_state = CS_OFFLINE 0
handle_offline(t);
LOG("%s: A_CNXN handle_offline
", __FUNCTION__);
}
LOG("%s: A_CNXN parse_banner before: connection_state:%d
", __FUNCTION__, t->connection_state);
parse_banner(reinterpret_cast<const char*>(p->data), t);
LOG("%s: A_CNXN parse_banner after: connection_state:%d
", __FUNCTION__, t->connection_state);
......
最後になぜ2回接続したのか、よくわかりませんが、これは協議で規定されているはずです.