標籤:linux 多線程 網路
使用Socket建立TCP伺服器
1首先瞭解一下TCP
1TCP是連線導向的,必須是三向交握之後
2TCP提供可靠串連,實現丟失重傳,RTT的估算物理 網卡 網線都會影響 這個丟包
3TCP通過給所發資料的每一個段管理一個序號進行排序. 沒一個包都有一個序號,由底層按照序號發送給你
4TCP提供流量控制和擁塞控制:通過視窗擁塞視窗可以限制流量的,1000個客戶TCP可以限定他能夠是以多塊的速度來接收你的資料TCP的串連是全雙工系統的. 互相不干擾發送和接收是同時進行的
首先我們配置一下Linux環境非常簡單哦
地址是:http://12158490.blog.51cto.com/12148490/1947803
Linux需要安裝g++ gcc makefile
1開始寫代碼在windows上編碼,但是我們的工程是在linux目錄下的
650) this.width=650;" src="https://s3.51cto.com/wyfs02/M00/9B/F3/wKioL1lpfh3hHBReAAAQ6OptOho664.png-wh_500x0-wm_3-wmp_4-s_2623743358.png" title="QOV$15~LWB~XNN5HP`WIU@T.png " alt="wKioL1lpfh3hHBReAAAQ6OptOho664.png-wh_50" />
1初始化
/初始化windows網路程式庫,這裡就會載入動態庫檔案所以我們要載入lib檔案,可以在vs載入也可以代碼#pragma comment(lib,"ws2_32.lib")WSADATA ws;WSAStartup(MAKEWORD(2,2),&ws);
2 初始化完成就是建立一個socket了,socket反回的
是一個int型,是與客戶建立建立的socket,第一個參數是使用的
協議是tcp/ip,應為socket還可以用來藍芽的通訊協定
第二個參數是使用TCP還是UDP這裡SOCK_STREAM是TCP
第三個參數是原始通訊端的時候用到的為0就行
int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock == -1){printf("create err\n");return -1;}printf("[%d]", sock);
3綁定
sockaddr_in是一個結構體,他記錄連接埠和ip地址以及協議族.
1需要指定連接埠。
2綁定本地的ip地址,如果綁定127.0.0.1就只能本地訪問
這個網路,如果綁定外網ip那隻能從外部存取,內部不能訪問
一台機器可以有兩個網卡,如果要任意都可以訪問那就直接htonl(0),將ip和杜連接埠綁定到哪個socket,
bind()如果不等於0會失敗,而且很容易失敗,最好判斷一下。
enum{PORT=8017} sockaddr_in sa;sa.sin_family = AF_INET;sa.sin_port = htons(PORT);sa.sin_addr.s_addr = htonl(0);if (bind(sock, (sockaddr*)&sa, sizeof(sa)) != 0){printf("bind port err\n");return -2;}printf("bind port ok!!!\n");
4偵聽
綁定只是做了一個標識,還沒有等待客戶的串連.
這個函數一調用開始接收使用者的串連了。第二個參數
是該通訊端使用的隊列長度,指定在請求隊列總允許的最大請求
數,意思就是如果你接收10個串連還沒有用Accept進行接待就會把新的串連扔掉。
listen(socket,10);
4獲得串連使用者資訊
這個函數是獲得獲得使用者的串連資訊,他反回一個int是用來真正
和使用者進行收發資料的,第一個參數之前建立的sock是和使用者建立串連的,第二個參數是輸出的參數,就是你穿進去之後,在裡面進行賦值,
然後輸出出來, 在訪問結構體的成員就可以獲得ip和連接埠了。
如果不需要ip地址和連接埠,只需要直接傳人0就行。
這個時候就可以建立線程和使用者進行通訊了,
sockaddr_in sadd;socklen_t len = sizeof(sadd);int client = accept(sock, (sockaddr*)&sadd, &len);if (client > 0){printf("client connect ok\n");printf("sockeid: %d\n", client);//輸出連接埠號碼和資訊char *ip = inet_ntoa(sadd.sin_addr);//網路位元組序轉本地位元組序哦unsigned short port = ntohs(sadd.sin_port);printf("IP: %s\n", ip);printf("port: %d\n", port);closesocket(client);}
一般情況下每建立一個串連,會把他傳到一個新的線程當中,簡單的方案是把他來的時候就建立一個新的線程複雜的方案是建立一個線程池,每來一個串連,把他扔給閑置的線程.
我們在linux進行編譯首先打卡這個工程路勁
650) this.width=650;" src="https://s1.51cto.com/wyfs02/M01/9B/F4/wKiom1lphByAcaDzAAAI6F4dDMs617.png-wh_500x0-wm_3-wmp_4-s_3093762570.png" title="RQ{EK])SXNQVAACETMV0H9S.png" alt="wKiom1lphByAcaDzAAAI6F4dDMs617.png-wh_50" />
編寫一個makefile
650) this.width=650;" src="https://s4.51cto.com/wyfs02/M02/9B/F4/wKioL1lphDSikCNcAAAFOp-XjBE140.png-wh_500x0-wm_3-wmp_4-s_2277294869.png" title="WBS}JSFITC90E]Y%_26~X1F.png" alt="wKioL1lphDSikCNcAAAFOp-XjBE140.png-wh_50" />
簡單說下makefile
上面的4個類似預定義
CC是編譯器
SRC是源檔案
OBJ是編譯的檔案
EXEC編譯串連可執行檔
$(OBJ)代表簡單點就是使用這個預定義一樣類似
start: $(OBJ)
$(CC) -o $(EXEC) $(OBJ)
上面依賴OBJ他會先執行這個
$(OBJ):
$(CC) -o $(OBJ) -c $(SRC)//這裡只編譯
//刪除只編譯的檔案
clean:
rm -f $(OBJ)
//執行 可執行檔
run:
./$(EXEC)
650) this.width=650;" src="https://s1.51cto.com/wyfs02/M00/9B/F4/wKiom1lphEmiAx0wAABCRjOB8XY461.png-wh_500x0-wm_3-wmp_4-s_1035654349.png" title="N(VB)LV9CCUZY]MB)7Q(RQ6.png" alt="wKiom1lphEmiAx0wAABCRjOB8XY461.png-wh_50" />
啟動make start
650) this.width=650;" src="https://s5.51cto.com/wyfs02/M01/9B/F5/wKiom1lphavQ32lyAABDivf7H78455.png-wh_500x0-wm_3-wmp_4-s_315776401.png" title="C~8GFVM06M%4YO16K$)[HJ0.png" alt="wKiom1lphavQ32lyAABDivf7H78455.png-wh_50" />
這時候會出現很多錯誤,
650) this.width=650;" src="https://s4.51cto.com/wyfs02/M00/9B/F5/wKiom1lphd2wMjiLAABB9Y4rAnM276.png-wh_500x0-wm_3-wmp_4-s_938615570.png" title="`%3Y)3~701@L~Z19D3V5FH3.png " alt="wKiom1lphd2wMjiLAABB9Y4rAnM276.png-wh_50" />
原因:
linux,用不到windows的庫檔案,也不需要初始化
,還有一些api不一致。
解決方案:
#ifdef WIN32 如果是 WIN32情況下調用哪些
#else 就是不再windows環境下用到的
#else
man socket 查看需要的標頭檔
650) this.width=650;" src="https://s3.51cto.com/wyfs02/M00/9B/F4/wKioL1lphnaj5oHvAAAL8OatXSU238.png-wh_500x0-wm_3-wmp_4-s_1527445518.png" title="C`E5J4KXKJJWRJ%1~57~L0A.png " alt="wKioL1lphnaj5oHvAAAL8OatXSU238.png-wh_50" />
用起來很方便
650) this.width=650;" src="https://s2.51cto.com/wyfs02/M01/9B/F5/wKiom1lphqawuhyJAABXbU3EvU0657.png-wh_500x0-wm_3-wmp_4-s_3526728051.png" title="U))PJ~1OTVS{YMNTX$PXAS8.png" alt="wKiom1lphqawuhyJAABXbU3EvU0657.png-wh_50" />
在紅色部分,填寫linux需要的東西,這個東西會
在編譯的時候就會執行,他發現不是windows就是會執行#else
裡的代碼.
650) this.width=650;" src="https://s5.51cto.com/wyfs02/M01/9B/F5/wKiom1lphsSw_lkPAAAdyDnuAUM606.png-wh_500x0-wm_3-wmp_4-s_819094601.png" title="MO@~`RHOVMZSS{CXHW_4V`T.png " alt="wKiom1lphsSw_lkPAAAdyDnuAUM606.png-wh_50" />
其他的也是一樣的。 clossocket 緩衝 close就行
然後就可以編譯成功了。
make start編譯產生一下
然後make run執行一下
650) this.width=650;" src="https://s1.51cto.com/wyfs02/M00/9B/F5/wKiom1lph2bx88nRAAA5HspBT0s739.png-wh_500x0-wm_3-wmp_4-s_1979248057.png" title=")PHODIDAU9N_98Y%6LCJ2EG.png" alt="wKiom1lph2bx88nRAAA5HspBT0s739.png-wh_50" />
啟動另外一個linux測試一下
telnet 192.168.1.125 8013 測試連接一些
然後左邊我們看到把
串連的使用者的 IP和連接埠都列印了。
650) this.width=650;" src="https://s2.51cto.com/wyfs02/M00/9B/F4/wKioL1lph6fgsbCVAAGONiicwFY745.png-wh_500x0-wm_3-wmp_4-s_152942287.png" title="`XDU}$ILJ~DQ2E(APO]E{G0.png " alt="wKioL1lph6fgsbCVAAGONiicwFY745.png-wh_50" />
基本流程都有了,後面的話,只需要照著編譯就行。
我們開始recv接收用戶端發送資料
第一個參數是使用者和你串連之後,返回的
那個socket哦,第二個參數是儲存資料的緩衝,
第三個參數是緩衝大小-1是為了留意味放/0結束符
第四個0就行了,跟系統有關的東西
傳回值就是收到資料的大小
實際傳回值有時候會大於1024 但是發送方會
有一些資料的切割,所以很容易造成錯誤
char buf[1024];memset(buf, 0, sizeof(buf));int len = recv(client, buf, sizeof(buf) - 1, 0);print("recv %s\n",buf);
在Linux編譯執行一下
在發送端串連後發送資料
650) this.width=650;" src="https://s2.51cto.com/wyfs02/M02/9B/F5/wKiom1lpi7DxK6dJAABpq1EDpx8815.png-wh_500x0-wm_3-wmp_4-s_763954218.png" title="F5BPRJ1BRV0UHQ_L(J}SQYA.png" alt="wKiom1lpi7DxK6dJAABpq1EDpx8815.png-wh_50" />
伺服器收到資料通訊成功
650) this.width=650;" src="https://s4.51cto.com/wyfs02/M02/9B/F5/wKiom1lpi93D8TciAADeoDa2OAE328.png-wh_500x0-wm_3-wmp_4-s_546600093.png" title="WOI$@L$7667FXH$LV8]6]UY.png" alt="wKiom1lpi93D8TciAADeoDa2OAE328.png-wh_50" />
迴圈接收使用者的資訊,在死迴圈裡接收,如果收到的資料<=就退出
如果使用者發送的裡面有 "quit"就會退出
for (;;){memset(buf, 0, sizeof(buf));int recv_len = recv(client, buf, sizeof(buf) - 1, 0);if (recv_len <= 0)break;if(strstr(buf, "quit")!= NULL) break;printf("recv %s\n", buf);}
在Linux編譯執行一下
在發送端串連後發送資料
650) this.width=650;" src="https://s3.51cto.com/wyfs02/M02/9B/F5/wKioL1lpjPOjWlzeAADMPaPMpag632.png-wh_500x0-wm_3-wmp_4-s_3584078235.png" title="%0(_ONF~4NI3167_X%J}C3Y.png" alt="wKioL1lpjPOjWlzeAADMPaPMpag632.png-wh_50" />
多線程並發處理 直接用c++11的線程庫。不需要考慮跨平台
的問題.
建立線程的地方就是,accept然後會返回的一個socket
給這個線程用
#include <thread>using namespace std;class TcpThread{public: //線程入口函數 建立一個 void Main() { char buf[1024]; for (;;) {memset(buf, 0, sizeof(buf));int recv_len = recv(client, buf, sizeof(buf) - 1, 0);if (recv_len <= 0)break;if (strstr(buf, "quit") != NULL){char re[] = "quit success\n";int sendlen = send(client, re, strlen(re)+1, 0);}int sendlen = send(client, "ok\n",4, 0);printf("recv %s\n", buf); } closesocket(client); } //使用者的socket int client = 0; };
如何建立線程呢,獲得使用者資訊accept應該放到死迴圈裡
應為線程需要他的傳回值.
建立一個線程 需要考慮什麼時候清理
幾種方案 1對象複用 2自己清理
650) this.width=650;" src="https://s5.51cto.com/wyfs02/M01/9B/F6/wKiom1lplQbAEFq8AAAt_2cuuJM690.png-wh_500x0-wm_3-wmp_4-s_3006341758.png" title="T}DO([8HM448CK8V7UKQZL6.png" alt="wKiom1lplQbAEFq8AAAt_2cuuJM690.png-wh_50" />
for (;;){//這裡會阻塞 只有等待有新的串連 才會執行下面的代碼int client = accept(sock, (sockaddr*)&sadd, &len);if (client > 0){printf("client connect ok\n");printf("sockeid: %d\n", client);//輸出連接埠號碼和資訊char *ip = inet_ntoa(sadd.sin_addr);//網路位元組序轉本地位元組序哦unsigned short port = ntohs(sadd.sin_port);printf("IP: %s\n", ip);printf("port: %d\n", port);}else{break;}//建立一個線程 需要考慮什麼時候清理//幾種方案 1對象複用 2自己清理TcpThread *th = new TcpThread();th->client = client; //獲得socket//啟動線程 第一個參數是入口函數的地址(函數指標)//第二個參數調用的對象std::thread sth(&TcpThread::Main,th);//上面的調用完後 對象 會被銷毀掉//銷毀不受影響 但是本線程還有一些資源沒有被釋放掉// 我們可以直接讓他直接釋放調用,主線程不要控制子線程的處理,比如掛起啊 或者關閉//這種操作是很危險的,因為 主線程不知道子線程運行到什麼階段 //正常情況我們不去處理 detach() 釋放主線程擁有的子線程的資源sth.detach();}
然後移植到linux makefile需添加 std c++11 才可以編譯成功
650) this.width=650;" src="https://s4.51cto.com/wyfs02/M01/9B/F6/wKioL1lpnAjQh2oiAAAe6pcoS18046.png-wh_500x0-wm_3-wmp_4-s_1071493355.png" title="0~W~1OI6@XUB8239}T68MWY.png" alt="wKioL1lpnAjQh2oiAAAe6pcoS18046.png-wh_50" />
還需要添加 -lpthread
650) this.width=650;" src="https://s2.51cto.com/wyfs02/M00/9B/F7/wKiom1lpnbmiMxuLAAAYUTZY-IA850.png-wh_500x0-wm_3-wmp_4-s_680741025.png" title="U0`(R[EHNCQRAIS)0BY6WZA.png " alt="wKiom1lpnbmiMxuLAAAYUTZY-IA850.png-wh_50" />
上面的有點問題
$(CC) $(OBJ) -o $(EXEC) -std=c++11 -lthread
本文出自 “12148490” 部落格,請務必保留此出處http://12158490.blog.51cto.com/12148490/1947841
C++socket網路編程(跨平台)實戰HTTP伺服器(二)