日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

基于Linux的socket编程模板

發布時間:2025/4/5 linux 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于Linux的socket编程模板 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??? 在網絡編程編程中,我們經常會遇到這樣一種C/S架構,服務器端(Server)監聽客戶端(Client)發送過來的命令,然后解析該命令,并做對應的處理,最后返回處理結果(例如成功或者失敗及原因)給客戶端。

??? 最近,在Linux下做網絡編程,涉及的就是上面的這種需求,簡單地整理了下自己的代碼,分享在這里吧,供初學者參考。

??? 首先說一下編程思路吧。

??? 在這種情況客戶端必須實現的的接口有:連接服務器、發送、斷開連接。

??? 服務器端,有一個主線程,用于監聽客戶端的連接請求,一旦有新客戶端連接,則創建一個新的socket及線程專門服務這個客戶端。這個服務線程專門監聽該客戶端的命令,并且解析命令進行服務器,直到客戶端斷開連接或者發送關閉連接的命令。

??? 另外,需要涉及一個通信協議,約定命令的包頭、命令的識別碼、命令的參數。

??? 思路就說到這兒了,下面的相關代碼,附件中有完整的代碼,包含了Makefile文件。

一、通信協議設計

  • // ?
  • //??COPYRIGHT?NOTICE ?
  • //??Copyright?(c)?2011,?華中科技大學?ticktick(版權聲明) ?
  • //??All?rights?reserved. ?
  • //? ?
  • ///?@file????Command.h?? ?
  • ///?@brief???命令包聲明文件 ?
  • /// ?
  • ///?定義各種TCP命令包以及相關結構體 ?
  • /// ?
  • ///?@version?1.0??? ?
  • ///?@author??lujun? ?
  • ///?@E-mail??lujun.hust@gmail.com ?
  • ///?@date????2011/08/21 ?
  • // ?
  • // ?
  • //??修訂說明: ?
  • // ?
  • ?
  • #ifndef?COMMAND_H_ ?
  • #define?COMMAND_H_ ?
  • ?
  • typedef?unsigned?char?uint8_t; ?
  • ?
  • //?TCP?CMD?header?len ?
  • #define?TCP_CMD_HEADER_LEN?8 ?
  • ?
  • //?CMD?header ?
  • static?uint8_t?TCP_CMD_HEADER_STR[TCP_CMD_HEADER_LEN]?=?{?0xAA,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xFF?}; ?
  • ?
  • //?max?user?name?len? ?
  • #define?MAX_USER_NAME_LEN?20 ?
  • ?
  • //?server?cmd?struct ?
  • typedef?enum?_ServerCMD ?
  • { ?
  • ????CMD_SAVE_USER_NAME,?????????//?save?user?name ?
  • ????CMD_SAVE_USER_AGE,??????????//?save?user?age ?
  • ?
  • }ServerCMD; ?
  • ?
  • //?return?cmd? ?
  • typedef?enum?_ReturnCMD ?
  • { ?
  • ????DVS_RETURN_SUCCESS?=?0,?????? ?
  • ????DVS_RETURN_FAIL,????????????? ?
  • ????DVS_RETURN_TIMEOUT,?????????? ?
  • ????DVS_RETURN_INVLID_HEADER,???? ?
  • ????DVS_RETURN_INVLID_CMD,??????? ?
  • ????DVS_RETURN_INVLID_PRM,??????? ?
  • ?
  • }ReturnCMD; ?
  • ?
  • //?1bytes?aligning ?
  • #pragma?pack(?push,?1?) ?
  • ?
  • //?server?pack?from?client ?
  • typedef?struct?_ServerPack? ?
  • { ?
  • ????//?cmd?header ?
  • ????uint8_t?cmdHeader[TCP_CMD_HEADER_LEN]; ?
  • ?
  • ????//?command?id ?
  • ????ServerCMD?serverCMD; ?
  • ?
  • ????//?cmd?param ?
  • ????union?
  • ????{ ?
  • ????????//?save?user?name ?
  • ??????????struct?
  • ??????????{ ?
  • ??????????????//?user?name ?
  • ????????????char?username[MAX_USER_NAME_LEN]; ?
  • ?
  • ??????????}UserName; ?
  • ?
  • ??????????//?save?user?age ?
  • ??????????struct?
  • ??????????{ ?
  • ??????????????//?user?age ?
  • ????????????int?userage; ?
  • ?
  • ??????????}UserAge; ?
  • ?
  • ????}Parameters; ?
  • ?
  • }ServerPack; ?
  • ?
  • //?return?pack?from?server ?
  • typedef?struct?_ReturnPack ?
  • { ?
  • ????//?cmd?header ?
  • ????uint8_t?cmdHeader[TCP_CMD_HEADER_LEN]; ?
  • ?
  • ????//?return?cmd? ?
  • ????ReturnCMD?returnCMD; ?
  • ?
  • }ReturnPack; ?
  • ?
  • #pragma?pack(?pop?) ?
  • ?
  • #define?SERVER_PACK_LEN?sizeof(ServerPack) ?
  • #define?RETURN_PACK_LEN?sizeof(ReturnPack) ?
  • ?
  • #endif?//?COMMAND_H_ ?
  • 二、客戶端代碼

  • // ?
  • //??COPYRIGHT?NOTICE ?
  • //??Copyright?(c)?2011,?華中科技大學?ticktick(版權聲明) ?
  • //??All?rights?reserved. ?
  • //? ?
  • ///?@file????client.c?? ?
  • ///?@brief???tcp客戶端代碼 ?
  • /// ?
  • ///?實現tcp客戶端的相關接口 ?
  • /// ?
  • ///?@version?1.0??? ?
  • ///?@author??lujun? ?
  • ///?@E-mail??lujun.hust@gmail.com ?
  • ///?@date????2011/08/21 ?
  • // ?
  • // ?
  • //??修訂說明: ?
  • // ?
  • ?
  • #include?<stdio.h> ?
  • #include?<sys/types.h> ?
  • #include?<sys/socket.h> ?
  • #include?<netinet/in.h> ?
  • #include?<unistd.h> ?
  • #include?<errno.h> ?
  • #include?<string.h> ?
  • #include?"client.h" ?
  • ?
  • //?socket?handle ?
  • int?g_hSocket; ?
  • ?
  • int?connect_server(?char?*destIp,?int?destPort?) ?
  • { ?
  • ????int?result; ?
  • ????struct?sockaddr_in?address; ?
  • ?
  • ????//?create?a?socket ?
  • ????g_hSocket?=?socket(AF_INET,SOCK_STREAM,0); ?
  • ?
  • ????//?set?server?addr ?
  • ????address.sin_family?=?AF_INET; ?
  • ?
  • ????//?use?server?ip?and?listen?port?to?connect ?
  • ????address.sin_addr.s_addr?=?inet_addr(?destIp?); ?
  • ????address.sin_port????????=?htons(destPort); ?
  • ???? ?
  • ????//?connect?tcp?server ?
  • ????result?=?connect(g_hSocket,(struct?sockaddr?*)&address,sizeof(address)?); ?
  • ????if(?result?==?-1?) ?
  • ????{ ?
  • ????????printf("[tcp?client]?can't?connect?server?!\n"); ?
  • ??????????return?-1; ?
  • ????} ?
  • ???? ?
  • ????return?0; ?
  • } ?
  • ?
  • int?close_connect() ?
  • { ?
  • ????printf("close?connect?with?server?!\n?"); ?
  • ?
  • ????close(g_hSocket); ?
  • ?
  • ????return?0; ?
  • } ?
  • ?
  • int?send_cmd(?ServerPack?sPack?) ?
  • { ?
  • ????int?recvBytes?=?0; ?
  • ????int?sendBytes?=?0; ?
  • ????ReturnPack?rPack; ?
  • ?
  • ????//?add?cmd?header ?
  • ????memcpy(sPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN); ?
  • ?
  • ????//?send?cmd ?
  • ????while(1) ?
  • ????{ ?
  • ????????sendBytes?=?send(g_hSocket,(uint8_t?*)&sPack,SERVER_PACK_LEN,0); ?
  • ???????? ?
  • ????????if(?sendBytes?==?SERVER_PACK_LEN?) ?
  • ????????{ ?
  • ????????????printf("successfully?send?bytes?%d\n",SERVER_PACK_LEN); ?
  • ????????????break; ?
  • ????????} ?
  • ????????else?if(?sendBytes?<=?0?&&?errno?!=?EINTR?&&?errno?!=?EWOULDBLOCK?&&?errno?!=?EAGAIN) ?
  • ????????{ ?
  • ????????????printf("disconnected?or?other?errors!\n"); ?
  • ??????????????return?-1; ?
  • ????????} ?
  • ????????else?
  • ????????{ ?
  • ????????????continue; ?
  • ????????} ?
  • ????} ?
  • ?
  • ????//?recv?process?result?from?server ?
  • ????while(1) ?
  • ????{ ?
  • ????????recvBytes?=?recv(g_hSocket,(uint8_t?*)&rPack,RETURN_PACK_LEN,0); ?
  • ?
  • ??????????if(?recvBytes?==?RETURN_PACK_LEN?) ?
  • ??????????{ ?
  • ??????????????break; ?
  • ??????????} ?
  • ??????????else?if(?recvBytes?<=0?&&?errno?!=?EINTR?&&?errno?!=?EWOULDBLOCK?&&?errno?!=?EAGAIN?) ?
  • ??????????{ ?
  • ??????????????printf("disconnected?or?error?occur!\n?close?the?socket!\n"); ?
  • ??????????????return?-1; ?
  • ??????????} ?
  • ??????????else?
  • ??????????{? ?
  • ??????????????continue; ?
  • ??????????} ?
  • ????} ?
  • ?
  • ????//?check?header ?
  • ????if?(?memcmp(?rPack.cmdHeader,?TCP_CMD_HEADER_STR,?TCP_CMD_HEADER_LEN?)?!=?0?) ?
  • ????{ ?
  • ????????printf("return?pack?header?errror!\n"); ?
  • ????????return?-2; ?
  • ????} ?
  • ?
  • ????//?get?return?status ?
  • ????if(?rPack.returnCMD?!=?DVS_RETURN_SUCCESS?) ?
  • ????{ ?
  • ????????printf("return?status?:?fail!\n"); ?
  • ????????return?-3; ?
  • ????} ?
  • ???? ?
  • ????return?0;???? ?
  • } ?
  • ?
  • ?
  • 三、服務器主線程代碼

  • // ?
  • //??COPYRIGHT?NOTICE ?
  • //??Copyright?(c)?2011,?華中科技大學?ticktick(版權聲明) ?
  • //??All?rights?reserved. ?
  • //? ?
  • ///?@file????server.c? ?
  • ///?@brief???tcp服務器主線程代碼 ?
  • /// ?
  • ///?實現tcp服務端監聽線程相關函數 ?
  • /// ?
  • ///?@version?1.0??? ?
  • ///?@author??lujun? ?
  • ///?@E-mail??lujun.hust@gmail.com ?
  • ///?@date????2011/08/21 ?
  • // ?
  • // ?
  • //??修訂說明: ?
  • // ?
  • ?
  • #include?<sys/types.h> ?
  • #include?<sys/socket.h> ?
  • #include?<stdio.h> ?
  • ?
  • #include?<netinet/in.h> ?
  • #include?<unistd.h> ?
  • ?
  • #include?"serverIf.h" ?
  • ?
  • int?g_hServerSocket; ?
  • ?
  • int?open_port(?int?localport?) ?
  • { ?
  • ????int?result; ?
  • ????int?clientSocket,client_len; ?
  • ?
  • ????struct?sockaddr_in?server_addr; ?
  • ????struct?sockaddr_in?client_addr; ?
  • ?
  • ????//?create?a?socket?obj?for?server ?
  • ????g_hServerSocket?=?socket(AF_INET,SOCK_STREAM,0); ?
  • ?
  • ????//?bind?tcp?port ?
  • ????server_addr.sin_family?=?AF_INET; ?
  • ????server_addr.sin_addr.s_addr?=?htonl(INADDR_ANY); ?
  • ????server_addr.sin_port?=?htons(localport); ?
  • ?
  • ????result?=?bind(g_hServerSocket,(struct?sockaddr?*)&server_addr,sizeof(server_addr)?); ?
  • ????if(?result?!=?0?)? ?
  • ????{ ?
  • ???????????printf("[tcp?server]?bind?error!\n?");???? ?
  • ???????????return?-1; ?
  • ????} ?
  • ?
  • ????//?begin?to?listen ?
  • ????result?=?listen(g_hServerSocket,5); ?
  • ????if(?result?!=?0?) ?
  • ????{ ?
  • ????????printf("[tcp?server]?listen?error!\n?"); ?
  • ??????????return?-1; ?
  • ????} ?
  • ?
  • // 注: ServerEnv用于給服務線程傳參,定義于serverIf.h中
  • ????ServerEnv?env; ?
  • ????while(1) ?
  • ????{ ?
  • ??????????client_len?=?sizeof(client_addr); ?
  • ??????????clientSocket?=?accept(g_hServerSocket,(struct?sockaddr?*)&client_addr,&client_len?); ?
  • ?
  • ??????????if(?clientSocket?<?0?) ?
  • ??????????{ ?
  • ??????????????printf("[tcp?server]?accept?error!\n"?); ?
  • ??????????????return?-1; ?
  • ??????????} ?
  • ??????????? ?
  • ??????????env.m_hSocket?=?clientSocket; ?
  • ?????????? ?
  • ??????????//?add?new?tcp?server?thread ?
  • ??????????add_new_tcp_process_thr(&env); ?
  • ????} ?
  • ?
  • ????return?0; ?
  • } ?
  • ?
  • int?close_port() ?
  • { ?
  • ????printf("close?server?port,?stop?listen!\n"); ?
  • ?
  • ????close(g_hServerSocket); ?
  • ?
  • ????return?0; ?
  • } ?
  • 四、服務器端服務線程代碼

  • // ?
  • //??COPYRIGHT?NOTICE ?
  • //??Copyright?(c)?2011,?華中科技大學?ticktick(版權聲明) ?
  • //??All?rights?reserved. ?
  • //? ?
  • ///?@file????serverIf.c?? ?
  • ///?@brief???tcp服務線程代碼 ?
  • /// ?
  • ///?實現tcp服務線程相關接口 ?
  • /// ?
  • ///?@version?1.0??? ?
  • ///?@author??lujun? ?
  • ///?@E-mail??lujun.hust@gmail.com ?
  • ///?@date????2011/08/21 ?
  • // ?
  • // ?
  • //??修訂說明: ?
  • // ?
  • ?
  • #include?"serverIf.h" ?
  • #include?"../include/Command.h" ?
  • #include?<sys/socket.h> ?
  • #include?<stdio.h> ?
  • #include?<string.h> ?
  • #include?<errno.h> ?
  • ?
  • int?add_new_tcp_process_thr(?ServerEnv?*envp?) ?
  • { ?
  • ????pthread_t?tcpThr; ?
  • ?
  • ????if(?pthread_create(?&tcpThr,NULL,tcpServerThrFxn,envp?)?) ?
  • ????{ ?
  • ????????printf("tcp?thread?create?fail!\n"); ?
  • ????????return?-1; ?
  • ????} ?
  • ?
  • ????printf("tcp?thread?has?been?created!\n"); ?
  • ?
  • ????return?0; ?
  • } ?
  • ?
  • int?save_user_name(?char?*?pUsername?) ?
  • { ?
  • ????printf("ok,user?name?saved,username=%s\n",pUsername); ?
  • ?
  • ????return?0; ?
  • } ?
  • ?
  • int?save_user_age(?int?age?) ?
  • { ?
  • ????printf("ok,user?age?saved,userage=%d\n",age); ?
  • ?
  • ????return?0; ?
  • } ?
  • ?
  • void?*?tcpServerThrFxn(?void?*?arg?) ?
  • { ?
  • ????ServerEnv?*?envp?=?(ServerEnv?*)arg; ?
  • ????int?socketfd?=?envp->m_hSocket; ?
  • ????int?returnBytes; ?
  • ?
  • ????ServerPack?sPack; ?
  • ????ReturnPack?rPack; ?
  • ????memcpy(rPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN); ?
  • ?
  • ????while(1) ?
  • ????{ ?
  • ??????????//?read?cmd?from?client ?
  • ??????????returnBytes?=?recv(socketfd,(uint8_t?*)&sPack,SERVER_PACK_LEN,0); ?
  • ???????? if(?returnBytes?==?SERVER_PACK_LEN?) ?
  • ??????????{ ?
  • ??????????????//printf("ok,recv?%d?bytes!?\n",SERVER_PACK_LEN); ?
  • ??????????} ?
  • ??????????else?if(?returnBytes?<=?0?&&?errno?!=?EINTR?&&?errno?!=?EWOULDBLOCK?&&?errno?!=?EAGAIN?) ?
  • ??????????{ ?
  • ??????????????printf("disconnected?or?error?occur!?errno=%d\n?",errno); ?
  • ??????????????break; ?
  • ??????????} ?
  • ??????????else?
  • ??????????{ ?
  • ??????????????continue; ?
  • ??????????} ?
  • ?
  • ??????? ?//?check?the?header ?
  • ??????????if?(?memcmp(?sPack.cmdHeader,?TCP_CMD_HEADER_STR,?TCP_CMD_HEADER_LEN?)?!=?0?) ?
  • ??????????{ ?
  • ??????????????rPack.returnCMD?=?DVS_RETURN_INVLID_HEADER; ?
  • ?
  • ??????????????//?return?error?info?to?client ?
  • ??????????????returnBytes?=?send(socketfd,(uint8_t?*)&rPack,RETURN_PACK_LEN,0?)?; ?
  • ??????????????if(?returnBytes?<?RETURN_PACK_LEN) ?
  • ??????????????{ ?
  • ??????????????????printf("send?error!\n"); ?
  • ????????????????????continue; ?
  • ??????????????} ?
  • ??????????} ?
  • ?
  • ??????????//?analyse?cmd ?
  • ??????????rPack.returnCMD?=?DVS_RETURN_SUCCESS; ?
  • ???????? switch(?sPack.serverCMD?) ?
  • ??????????{ ?
  • ??????????case?CMD_SAVE_USER_NAME: ?
  • ???????????????{?? ?
  • ?????????????????????if(?save_user_name(sPack.Parameters.UserName.username)?!=?0) ?
  • ?????????????????????{ ?
  • ?????????????????????????rPack.returnCMD?=?DVS_RETURN_FAIL; ?
  • ?????????????????????} ?
  • ???????????????} ?
  • ??????????break; ?
  • ??????????case?CMD_SAVE_USER_AGE: ?
  • ??????????????{ ?
  • ???????????????????if(?save_user_age(sPack.Parameters.UserAge.userage)?!=?0) ?
  • ?????????????????????{? ?
  • ?????????????????????????rPack.returnCMD?=?DVS_RETURN_FAIL; ?
  • ?????????????????????} ?
  • ??????????????} ?
  • ??????????break; ?
  • ??????????default: ?
  • ?????????????{?? ?
  • ???????????????????rPack.returnCMD?=?DVS_RETURN_INVLID_CMD; ?
  • ?????????????} ?
  • ??????????break; ?
  • ??????????}? ?
  • ???? ?
  • ??????????//?return?result?info?to?client ?
  • ??????????returnBytes?=?send(socketfd,(uint8_t?*)&rPack,RETURN_PACK_LEN,0?); ?
  • ??????????if(?returnBytes?<?RETURN_PACK_LEN?) ?
  • ??????????{ ?
  • ????????????printf("send?error!\n"); ?
  • ????????????continue;??????? ?
  • ??????????} ?
  • ????} ?
  • ?
  • ????printf("close?session?socket!"); ?
  • ?
  • ????//?close?socket ?
  • ????close(socketfd); ?
  • ?
  • ????return?(void*)0; ?
  • } ?
  • 五、客戶端測試代碼

  • // ?
  • //??COPYRIGHT?NOTICE ?
  • //??Copyright?(c)?2011,?華中科技大學?ticktick(版權聲明) ?
  • //??All?rights?reserved. ?
  • //? ?
  • ///?@file????main.c?? ?
  • ///?@brief???tcp客戶端測試代碼 ?
  • /// ?
  • ///?實現?tcp客戶端測試 ?
  • /// ?
  • ///?@version?1.0??? ?
  • ///?@author??lujun? ?
  • ///?@E-mail??lujun.hust@gmail.com ?
  • ///?@date????2011/08/21 ?
  • // ?
  • // ?
  • //??修訂說明: ?
  • // ?
  • ?
  • #include?<stdio.h> ?
  • ?
  • #include?"client.h" ?
  • ?
  • #define?LOCAL_IP_STR?"127.0.0.1" ?
  • #define?DEST_IP_STR??"192.201.0.8" ?
  • #define?DEST_PORT????8000 ?
  • ?
  • int?main(int?argc,?char?**argv) ?
  • { ?
  • ????int?i?=0; ?
  • ?
  • ????printf("tcp?test?start!\n"); ?
  • ?
  • ????if(?connect_server(DEST_IP_STR,DEST_PORT)?!=?0) ?
  • ????{ ?
  • ????????return?-1; ?
  • ????} ?
  • ?
  • ????ServerPack?sPack; ?
  • ????sPack.serverCMD?=?CMD_SAVE_USER_AGE; ?
  • ????sPack.Parameters.UserAge.userage?=?20; ?
  • ?
  • ????if(?send_cmd(sPack)?==?-1?) ?
  • ??????{ ?
  • ??????????printf("send?cmd?fail!\n"); ?
  • ??????} ?
  • ?
  • ????getchar(); ?
  • ????getchar(); ?
  • ?
  • ????close_connect(); ?
  • ?
  • ????printf("tcp?test?pass!\n"); ?
  • ?
  • ????return?0; ?
  • } ?
  • 六、服務器端測試代碼

  • // ?
  • //??COPYRIGHT?NOTICE ?
  • //??Copyright?(c)?2011,?華中科技大學?ticktick(版權聲明) ?
  • //??All?rights?reserved. ?
  • //? ?
  • ///?@file????main.c?? ?
  • ///?@brief???tcp客戶端代碼 ?
  • /// ?
  • ///?實現tcp服務器端測試的代碼 ?
  • /// ?
  • ///?@version?1.0??? ?
  • ///?@author??lujun? ?
  • ///?@E-mail??lujun.hust@gmail.com ?
  • ///?@date????2011/08/21 ?
  • // ?
  • // ?
  • //??修訂說明: ?
  • // ?
  • ?
  • #include?<stdio.h> ?
  • ?
  • #include?"server.h" ?
  • #include?"serverIf.h" ?
  • ?
  • #define?LOCAL_PORT?8000 ?
  • ?
  • int?main(int?argc,?char?**argv) ?
  • { ?
  • ????printf("tcp?test?start!\n"); ?
  • ?
  • ????if(?open_port(LOCAL_PORT)?!=?0) ?
  • ????{ ?
  • ????????return?-1; ?
  • ????} ?
  • ?
  • ????close_port(); ?
  • ?
  • ????printf("?close?port?!\n"); ?
  • ?
  • ????return?0; ?
  • } ?
  • 七、總結和說明

    ??? 本文后面的附件中有完整的代碼,歡迎下載使用。編譯方法,把代碼文件夾都拷貝到linux下,在本代碼文件夾的根目錄下,運行make,即可生成對應的可執行文件。在運行測試程序的時候,請先執行server.out,然后執行client.out

    ??? 另外,如果需要轉載本文或者代碼,請注明出處,本代碼來自ticktick的博客:http://ticktick.blog.51cto.com?謝謝。

    ??? 如果發現本代碼的任何bug或者有任何建議,歡迎留言或者來信交流。

    轉載于:https://blog.51cto.com/ticktick/645523

    總結

    以上是生活随笔為你收集整理的基于Linux的socket编程模板的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。