TCP/IP网络编程之基于TCP的服务端/客户端(二)
回聲客戶(hù)端問(wèn)題
上一章TCP/IP網(wǎng)絡(luò)編程之基于TCP的服務(wù)端/客戶(hù)端(一)中,我們解釋了回聲客戶(hù)端所存在的問(wèn)題,那么單單是客戶(hù)端的問(wèn)題,服務(wù)端沒(méi)有任何問(wèn)題?是的,服務(wù)端沒(méi)有問(wèn)題,現(xiàn)在先讓我們回顧下服務(wù)端的I/O代碼
echo_server.c
…… while ((str_len = read(clnt_sock, messag, 1024)) != 0)write(clnt_sock, messag, str_len); ……
接著,我們回顧客戶(hù)端的代碼
echo_client.c
…… write(sock, message, strlen(message)); str_len = read(sock, message, 1024 - 1); ……
二者都在循環(huán)調(diào)用read或write函數(shù),實(shí)際上之前的回聲客戶(hù)端將100%接收自己傳輸?shù)臄?shù)據(jù),只不過(guò)接收數(shù)據(jù)時(shí)的單位有些問(wèn)題
觀察下面的代碼,我們可以知道回聲客戶(hù)端傳輸?shù)氖亲址?#xff0c;而且是通過(guò)調(diào)用write函數(shù)一次性發(fā)送,之后還調(diào)用了一次read函數(shù),期待著接收自己傳輸?shù)淖址?#xff0c;這就是問(wèn)題所在
echo_client.c
…… while (1) {fputs("Input message(Q to quit):", stdout);fgets(message, 1024, stdin);if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))break;write(sock, message, strlen(message));str_len = read(sock, message, 1024 - 1);message[str_len] = 0;printf("Message from server:%s", message); } ……
既然回聲客戶(hù)端會(huì)收到所有字符串?dāng)?shù)據(jù),是否只需多等一會(huì)?過(guò)一段時(shí)間再調(diào)用read函數(shù)是否可以一次性讀取所有的字符串內(nèi)容?的確,過(guò)一段時(shí)間后即可接收,但需要多久?1秒還是1分鐘?沒(méi)人知道。而且這也不符合常理,正常應(yīng)該客戶(hù)端在收到字符串?dāng)?shù)據(jù)時(shí)立即讀取并輸出
其實(shí)問(wèn)題很容易解決,因?yàn)榭梢蕴崆按_定接收數(shù)據(jù)的長(zhǎng)度,若之前傳輸了20個(gè)字節(jié)長(zhǎng)的字符串,則在接收時(shí)循環(huán)調(diào)用read函數(shù)讀取20個(gè)字節(jié)即可,下面,我們解決方案的代碼
echo_client2.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 void error_handling(char *message);int main(int argc, char *argv[]) {int sock;char message[BUF_SIZE];int str_len, recv_len, recv_cnt;struct sockaddr_in serv_adr;if (argc != 3){printf("Usage:%s<IP><port>\n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);if (sock == -1)error_handling("socket()error");memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)error_handling("connect()error");elseputs("Connected..........");while (1){fputs("Input message(Q to quit):", stdout);fgets(message, BUF_SIZE, stdin);if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))break;str_len = write(sock, message, strlen(message));recv_len = 0;while (recv_len < str_len){recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1);if (recv_cnt == -1)error_handling("read()error!");recv_len += recv_cnt;}message[recv_len] = 0;printf("Message from server:%s", message);}close(sock);return 0; }void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(1); }
在45~56行是變更及添加部分,echo_client.c僅調(diào)用了一次read函數(shù),上述示例為了接收所有傳輸數(shù)據(jù)而循環(huán)調(diào)用read函數(shù)。另外代碼48行還可以寫(xiě)成如下形式:
while (recv_len != str_len) {…… }
接收的數(shù)據(jù)大小應(yīng)和傳輸?shù)南嗤?#xff0c;因此recv_len中保存的值等于str_len保存的值時(shí),即可跳出while循環(huán)
回聲客戶(hù)端可以提前知道接收數(shù)據(jù)的長(zhǎng)度,但這只是特例,多數(shù)情況下,我們是不知道接收數(shù)據(jù)的長(zhǎng)度,那么我們應(yīng)該如何收發(fā)數(shù)據(jù)?此時(shí)需要的就是應(yīng)用層協(xié)議的定義,之前回聲服務(wù)端/客戶(hù)端中曾經(jīng)定義:收到Q就立即終止連接
同樣,收發(fā)數(shù)據(jù)過(guò)程中也需要定好規(guī)則(協(xié)議)以表示數(shù)據(jù)的邊界,或提前告知收發(fā)數(shù)據(jù)的大小。服務(wù)端/客戶(hù)端實(shí)現(xiàn)過(guò)程中逐步定義的這些規(guī)則集合就是應(yīng)用層協(xié)議,可以看出應(yīng)用層協(xié)議并不是什么高深的技術(shù),僅僅是為了特定程序而制定的規(guī)則
下面我們就來(lái)編寫(xiě)一個(gè)應(yīng)用層協(xié)議的程序,該程序中服務(wù)端從客戶(hù)端獲得多個(gè)數(shù)字和運(yùn)算符信息。服務(wù)器端收到這些信息后根據(jù)運(yùn)算符對(duì)數(shù)字做處理再返回給客戶(hù)端,例如:客戶(hù)端向服務(wù)端傳遞3、5、9的同時(shí)請(qǐng)求加法運(yùn)算,那么服務(wù)端做完3+5+9=17的結(jié)果后會(huì)返回給客戶(hù)端
op_client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 #define RLT_SIZE 4 #define OPSZ 4 void error_handling(char *message); int main(int argc, char *argv[]) {int sock;char opmsg[BUF_SIZE];int result, opnd_cnt, i;struct sockaddr_in serv_adr;if (argc != 3){printf("Usage:%s <IP><port>\n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);if (sock == -1){error_handling("socket() error");}memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)error_handling("connect() error!");elseputs("Connected......");fputs("Operand count:", stdout);scanf("%d", &opnd_cnt);opmsg[0] = (char)opnd_cnt;for (i = 0; i < opnd_cnt; i++){printf("Operand %d:", i + 1);scanf("%d", (int *)&opmsg[i * OPSZ + 1]);}fgetc(stdin);fputs("Operator:", stdout);scanf("%c", &opmsg[opnd_cnt * OPSZ + 1]);write(sock, opmsg, opnd_cnt * OPSZ + 2);read(sock, &result, RLT_SIZE);printf("Operation result:%d\n", result);close(sock);return 0; } void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(1); }
- 第8、9行:將待計(jì)算的數(shù)字的字節(jié)數(shù)和運(yùn)算結(jié)果的字節(jié)數(shù)設(shè)為常數(shù)
- 第14行:為收發(fā)數(shù)據(jù)準(zhǔn)備的內(nèi)存空間,需要數(shù)據(jù)積累到一定程度后再收發(fā),因此通過(guò)數(shù)組創(chuàng)建
- 第37、38行:從用戶(hù)的輸入中得到待算數(shù)個(gè)數(shù)后,保存至數(shù)組opmsg。強(qiáng)制轉(zhuǎn)換成char類(lèi)型,因?yàn)閰f(xié)議規(guī)定待算數(shù)個(gè)數(shù)應(yīng)通過(guò)一個(gè)字節(jié)整數(shù)型傳遞,因此不能超過(guò)一個(gè)字節(jié)整數(shù)型能夠表示的范圍。示例中用的是有符號(hào)整數(shù)型,但待算數(shù)個(gè)數(shù)不能是負(fù)數(shù),因此使用無(wú)符號(hào)整數(shù)型更合理
- 第40~44行:從用戶(hù)的輸入中得到待算整數(shù),保存到數(shù)組opmsg。4字節(jié)int型數(shù)據(jù)要保存到保存到char數(shù)組,因而在轉(zhuǎn)換成int指針類(lèi)型
- 第45行:第47行中輸入字符,在此之前調(diào)用fgetc函數(shù)刪掉緩沖中的字符'\n'
- 第47行:最后輸入運(yùn)算符信息,保存到opmsg數(shù)組
- 第48行:調(diào)用write函數(shù)一次性傳輸opmsg數(shù)組中的運(yùn)算相關(guān)信息,可以調(diào)用一次write函數(shù)進(jìn)行傳輸,也可以分多次調(diào)用
- 第49行:保存服務(wù)端傳輸?shù)倪\(yùn)算結(jié)果,待接收的數(shù)據(jù)長(zhǎng)度為4字節(jié),因此調(diào)用一次read函數(shù)即可接收
?
?
圖1-1? ?客戶(hù)端op_client.c的數(shù)據(jù)傳送格式
從圖1-1可以看出,若想在同一數(shù)組中保存并傳輸多種數(shù)據(jù)類(lèi)型,應(yīng)把數(shù)組聲明為char類(lèi)型。而且需要額外做一些指針及數(shù)組運(yùn)算。接下來(lái)給出服務(wù)端代碼:
op_server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 #define OPSZ 4 void error_handling(char *message); int calculate(int opnum, int opnds[], char operator);int main(int argc, char *argv[]) {int serv_sock, clnt_sock;char opinfo[BUF_SIZE];int result, opnd_cnt, i;int recv_cnt, recv_len;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;if (argc != 2){printf("Usage:%s<port>\n", argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);if (serv_sock == -1)error_handling("socket() error");memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)error_handling("bind() error");if (listen(serv_sock, 5) == -1)error_handling("listen() error");clnt_adr_sz = sizeof(clnt_adr);for (i = 0; i < 5; i++){opnd_cnt = 0;clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);read(clnt_sock, &opnd_cnt, 1);recv_len = 0;while ((opnd_cnt * OPSZ + 1) > recv_len){recv_cnt = read(clnt_sock, &opinfo[recv_len], BUF_SIZE - 1);recv_len += recv_cnt;}result = calculate(opnd_cnt, (int *)opinfo, opinfo[recv_len - 1]);write(clnt_sock, (char *)&result, sizeof(result));close(clnt_sock);}close(serv_sock);return 0; } int calculate(int opnum, int opnds[], char op) {int result = opnds[0], i;switch (op){case '+':for (i = 1; i < opnum; i++)result += opnds[i];break;case '-':for (i = 1; i < opnum; i++)result -= opnds[i];break;case '*':for (i = 1; i < opnum; i++)result *= opnds[i];break;}return result; } void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(1); }
- 第38行:為了接收5個(gè)客戶(hù)端的連接請(qǐng)求而編寫(xiě)的for語(yǔ)句
- 第42行:首先接收待算數(shù)個(gè)數(shù)
- 第45~49行:根據(jù)第42行中的待算數(shù)個(gè)數(shù)接收待算數(shù)
- 第50行:調(diào)用calculate函數(shù)的同時(shí)傳遞待算數(shù)和運(yùn)算符信息參數(shù)
- 第51行:向客戶(hù)端傳輸calculate函數(shù)返回的運(yùn)算結(jié)果
編譯op_server.c并運(yùn)行
# gcc op_server.c -o op_server # ./op_server 8500
編譯op_client.c并運(yùn)行
# gcc op_client.c -o op_client # ./op_client 127.0.0.1 8500 Connected...... Operand count:3 Operand 1:1 Operand 2:2 Operand 3:3 Operator:+ Operation result:6 # ./op_client 127.0.0.1 8500 Connected...... Operand count:2 Operand 1:2 Operand 2:15 Operator:* Operation result:30
TCP原理
之前我們說(shuō)過(guò),TCP套接字的數(shù)據(jù)收發(fā)無(wú)邊界,服務(wù)端即使調(diào)用一次write函數(shù)傳輸60個(gè)字節(jié)的數(shù)據(jù),客戶(hù)端也有可能分3次調(diào)用read函數(shù),每次讀取20個(gè)字節(jié)的數(shù)據(jù)。但此處也有些疑問(wèn),服務(wù)端一次性傳輸60個(gè)字節(jié)的數(shù)據(jù),而客戶(hù)端分批讀取。客戶(hù)端在讀取20個(gè)字節(jié)后,剩下的40個(gè)字節(jié)在何處等候呢?實(shí)際上,write函數(shù)調(diào)用后并非立即傳輸數(shù)據(jù),read函數(shù)調(diào)用后也并非馬上接收數(shù)據(jù)。更準(zhǔn)確地說(shuō),圖1-2所示,write函數(shù)調(diào)用瞬間,數(shù)據(jù)將移至輸出緩沖;read函數(shù)調(diào)用瞬間,從輸入緩沖讀取數(shù)據(jù)
圖1-2? ?TCP套接字的I/O緩沖
如圖1-2所示,調(diào)用write函數(shù)時(shí),數(shù)據(jù)將移到輸出緩沖,在適當(dāng)?shù)臅r(shí)候(不管是分別傳送還是一次性傳送)傳向?qū)Ψ降妮斎刖彌_。這時(shí)對(duì)方將調(diào)用read函數(shù)從輸入緩沖讀取數(shù)據(jù)。這些I/O緩沖特性如下:
- I/O緩沖在每個(gè)TCP套接字中單獨(dú)存在
- I/O緩沖在創(chuàng)建套接字時(shí)自動(dòng)生成
- 即使關(guān)閉套接字也會(huì)繼續(xù)傳遞輸出緩沖中遺留的數(shù)據(jù)
- 關(guān)閉套接字將丟失輸入緩沖中的數(shù)據(jù)
如果客戶(hù)端輸入緩沖有50個(gè)字節(jié),但服務(wù)端卻有100個(gè)字節(jié)需要傳輸,是否會(huì)造成數(shù)據(jù)丟失?之前說(shuō)過(guò)TCP是可靠的,不會(huì)發(fā)生超過(guò)輸入緩沖大小的數(shù)據(jù)傳輸,因?yàn)門(mén)CP會(huì)控制數(shù)據(jù)流,其中有滑動(dòng)窗口協(xié)議,接收數(shù)據(jù)的套接字每次會(huì)告訴發(fā)送數(shù)據(jù)的套接字可傳遞的最大字節(jié)數(shù),于是發(fā)送數(shù)據(jù)的套接字收到這個(gè)數(shù)字后傳遞等長(zhǎng)度大小的數(shù)據(jù),待接收數(shù)據(jù)的套接字發(fā)現(xiàn)緩沖中騰出更多位置,會(huì)告訴發(fā)送數(shù)據(jù)的套接字,接收更多的數(shù)據(jù)。因此,TCP不會(huì)因?yàn)榫彌_滿了而丟失數(shù)據(jù)
TCP內(nèi)部工作原理1:與對(duì)方套接字的連接
TCP套接字從創(chuàng)建到消失所經(jīng)過(guò)程分為如下三步:
TCP在實(shí)際通信過(guò)程中也會(huì)經(jīng)歷三次對(duì)話過(guò)程,因此,該過(guò)程又稱(chēng)為T(mén)hree-way handshake(三次握手),如圖1-3所示:
圖1-3? ?TCP套接字的連接設(shè)置過(guò)程
套接字是以雙全工方式工作的,也就是說(shuō)它可以雙向傳遞數(shù)據(jù)。因此,收發(fā)數(shù)據(jù)前需要做一些準(zhǔn)備,首先,請(qǐng)求連接的主機(jī)A向主機(jī)B傳遞如下消息:
[SYN] SEQ:1000, ACK:-
該消息中SEQ為1000,ACK為空,而SEQ為1000的含義是:主機(jī)A現(xiàn)傳遞的數(shù)據(jù)包序號(hào)為1000給主機(jī)B,如果接收無(wú)誤,請(qǐng)通知主機(jī)A向主機(jī)B傳遞1001號(hào)數(shù)據(jù)包。這是首次請(qǐng)求連接時(shí)使用的消息,又稱(chēng)SYN。SYN是Synchronize的簡(jiǎn)寫(xiě),表示收發(fā)數(shù)據(jù)前傳輸?shù)耐较ⅰ=酉聛?lái),主機(jī)B向主機(jī)A傳遞消息:[SYN+ACK] SEQ:2000, ACK:1001。此時(shí)SEQ為2000,ACK為1001,而SEQ具體含義和之前一樣:主機(jī)B現(xiàn)傳遞的數(shù)據(jù)包序號(hào)為2000給主機(jī)A,如果接收無(wú)誤,請(qǐng)通知主機(jī)B向主機(jī)A傳遞2001號(hào)數(shù)據(jù)包
而ACK 1001的含義為:剛才傳輸?shù)腟EQ為1000的數(shù)據(jù)包接收無(wú)誤,請(qǐng)傳遞SEQ為1001的數(shù)據(jù)包。對(duì)于主機(jī)A首次傳輸?shù)臄?shù)據(jù)包的確認(rèn)消息(ACK 1001)和主機(jī)B傳輸數(shù)據(jù)做準(zhǔn)備的同步消息(SEQ 2000)捆綁發(fā)送,因此,此種類(lèi)型的消息又稱(chēng)SYN+ACK
收發(fā)數(shù)據(jù)前向數(shù)據(jù)包分配序號(hào),并向?qū)Ψ酵▓?bào)此序號(hào),這都是為防止數(shù)據(jù)丟失所做的準(zhǔn)備。通過(guò)向數(shù)據(jù)包分配序號(hào)并確認(rèn),可以在數(shù)據(jù)丟失時(shí)馬上查看并重傳丟失的數(shù)據(jù)包。因此,TCP可以保證可靠的數(shù)據(jù)傳輸。最后觀察主機(jī)A向主機(jī)B傳輸消息:[ACK] SEQ: 1001, ACK: 2001。我們都明白SEQ和ACK的含義了,主機(jī)A現(xiàn)在向主機(jī)B傳遞1001號(hào)數(shù)據(jù)包,并表示2001號(hào)數(shù)據(jù)包接收無(wú)誤。至此,主機(jī)A和主機(jī)B確認(rèn)了彼此均就緒
TCP內(nèi)部工作原理2:與對(duì)方主機(jī)的數(shù)據(jù)交換
通過(guò)第一步三次握手過(guò)程完成了數(shù)據(jù)交換準(zhǔn)備,下面就開(kāi)始正式收發(fā)收據(jù)。其默認(rèn)方式如圖1-4所示:
圖1-4? ?TCP套接字的數(shù)據(jù)交換過(guò)程
圖1-4給出了主機(jī)A分兩次向主機(jī)B傳輸各100字節(jié)的過(guò)程。首先主機(jī)A通過(guò)一個(gè)數(shù)據(jù)包發(fā)送100個(gè)字節(jié)的數(shù)據(jù),數(shù)據(jù)包的SEQ為1200。主機(jī)B為了確認(rèn)這一點(diǎn),向主機(jī)A發(fā)送ACK1301消息。此時(shí)的ACK號(hào)為1301而非1201,原因在于ACK號(hào)的增量為傳輸?shù)臄?shù)據(jù)字節(jié)數(shù)。假設(shè)每次ACK號(hào)不加傳輸?shù)淖止?jié)數(shù),這樣雖然可以確認(rèn)數(shù)據(jù)包到達(dá)目標(biāo)主機(jī),但無(wú)法明確100字節(jié)全部正確到達(dá)還是丟失了一部分。因此ACK消息公式為:ACK號(hào) = SEQ號(hào) + 傳遞字節(jié)數(shù) + 1
與三次握手協(xié)議相同,最后加一是為了告知對(duì)方下次要傳遞的SEQ號(hào)。下面分析傳輸過(guò)程中數(shù)據(jù)包消失的情況,如圖1-5:
如圖1-5? ?TCP套接字?jǐn)?shù)據(jù)傳輸過(guò)程中發(fā)生錯(cuò)誤
圖1-5表示通過(guò)SEQ 1301數(shù)據(jù)包向主機(jī)B傳遞100字節(jié)數(shù)據(jù)。但中間發(fā)生了錯(cuò)誤,主機(jī)B未收到,經(jīng)過(guò)一段時(shí)間,主機(jī)A仍為收到對(duì)于SEQ 1301的ACK確認(rèn),因此試著重傳該數(shù)據(jù)包。為了完成數(shù)據(jù)包的重傳,TCP套接字啟動(dòng)計(jì)時(shí)器以等待ACK應(yīng)答。若計(jì)時(shí)器發(fā)生超時(shí)則重傳
TCP的內(nèi)部工作原理3:斷開(kāi)與套接字的連接
TCP套接字的結(jié)束過(guò)程也與之前相似,如果對(duì)方還有數(shù)據(jù)需要傳輸時(shí)直接斷掉連接會(huì)出現(xiàn)問(wèn)題,所以斷開(kāi)連接時(shí)需要雙方協(xié)商,先由套接字A向套接字B傳遞斷開(kāi)連接的消息,套接字B發(fā)出確認(rèn)收到的消息,然后向套接字A傳遞可以斷開(kāi)連接的消息,套接字A同樣發(fā)出確認(rèn)的消息,如圖1-6所示:
圖1-6? ?TCP套接字?jǐn)嚅_(kāi)連接過(guò)程
圖1-6數(shù)據(jù)包內(nèi)的FIN表示斷開(kāi)連接。也就是說(shuō),雙方各發(fā)送一次FIN消息后斷開(kāi)連接,此過(guò)程經(jīng)歷四個(gè)階段,因此稱(chēng)為四次握手(Four-way handshaking)。圖1-6向主機(jī)A傳遞兩次ACK 5001,也許這會(huì)讓大家感到困惑,這里做一下解答:主機(jī)A向主機(jī)B發(fā)送FIN消息,告訴主機(jī)B自己沒(méi)有數(shù)據(jù)可以發(fā)送,但如果主機(jī)B還有數(shù)據(jù)沒(méi)發(fā)送完,可以不必急著關(guān)閉套接字,可以繼續(xù)發(fā)送數(shù)據(jù)。于是,主機(jī)B發(fā)送ACK。當(dāng)主機(jī)B確認(rèn)數(shù)據(jù)發(fā)送完畢,則向主機(jī)A發(fā)送FIN消息,告訴主機(jī)A數(shù)據(jù)全部發(fā)送關(guān)閉,準(zhǔn)備關(guān)閉連接。主機(jī)A收到FIN消息,還是不相信網(wǎng)絡(luò),怕主機(jī)B不知道要關(guān)閉,所以發(fā)送ACK,如果主機(jī)B沒(méi)有收到可以重傳。主機(jī)B收到ACK后,就知道可以斷開(kāi)連接了,于是主機(jī)A等待在超時(shí)時(shí)間內(nèi)沒(méi)有收到回復(fù), 則證明主機(jī)B已關(guān)閉連接,于是主機(jī)A也跟著關(guān)閉連接
轉(zhuǎn)載于:https://www.cnblogs.com/beiluowuzheng/p/9656284.html
總結(jié)
以上是生活随笔為你收集整理的TCP/IP网络编程之基于TCP的服务端/客户端(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: dnf吞噬愤怒套装怎么样?
- 下一篇: 2018.09.16 loj#10243