网络编程实战
以下內(nèi)容源于朱有鵬嵌入式課程的學習整理,如有侵權,請告知刪除。
目錄
一、Linux網(wǎng)絡編程框架
1、OSI參考模型
2、TCP/IP協(xié)議引入
3、BS和CS
二、TCP協(xié)議的學習1
1、關于TCP理解的重點
2、TCP如何保證可靠傳輸
三、TCP協(xié)議的學習2
1、TCP建立連接時的三次握手
2、TCP釋放連接時的四次握手
3、基于TCP通信的服務模式(編程時也是按這個模式編寫的)
4、常見的使用TCP協(xié)議的網(wǎng)絡應用
四、socket編程接口(與網(wǎng)絡通信相關的API)介紹
1、建立連接
2、發(fā)送和接收
3、輔助性函數(shù)
4、表示IP地址相關數(shù)據(jù)結(jié)構(gòu)
五、IP地址格式轉(zhuǎn)換函數(shù)實踐
六、soekct實踐編程1_2
1、服務器端程序編寫
2、客戶端程序編寫
七、socket實踐編程3
八、socket編程實踐4
九、代碼
?
一、Linux網(wǎng)絡編程框架
1、OSI參考模型
(1)OSI 7層模型;
(2)網(wǎng)絡為什么要分層?
(3)網(wǎng)絡分層的具體表現(xiàn)。
2、TCP/IP協(xié)議引入
參閱博客:http://www.cnblogs.com/BlueTzar/articles/811160.html
(1)TCP/IP協(xié)議是用的最多的網(wǎng)絡協(xié)議實現(xiàn);
(2)TCP/IP分為4層,對應OSI的7層;
(3)編程時最關注應用層,了解傳輸層,網(wǎng)際互聯(lián)層和網(wǎng)絡接入層不用管。
3、BS和CS
(1)CS架構(gòu)介紹(client server,客戶端服務器架構(gòu));
(2)BS架構(gòu)介紹(broswer server,瀏覽器服務器架構(gòu));
?
二、TCP協(xié)議的學習1
1、關于TCP理解的重點
(1)TCP協(xié)議工作在傳輸層,對上服務socket接口(與網(wǎng)絡通信有關的API),對下調(diào)用IP層;
(2)TCP協(xié)議面向連接,通信前必須先3次握手建立連接關系后才能開始通信;
(3)TCP協(xié)議提供可靠傳輸,有反饋機制,不怕丟包、亂序等。
2、TCP如何保證可靠傳輸
(1)TCP在傳輸有效信息前,要求通信雙方必須先握手,建立連接才能通信;
(2)TCP的接收方收到數(shù)據(jù)包后會ack給發(fā)送方,若發(fā)送方未收到ack會丟包重傳;
(3)TCP的有效數(shù)據(jù)內(nèi)容會附帶校驗,以防止內(nèi)容在傳遞過程中損壞;
(4)TCP會根據(jù)網(wǎng)絡帶寬來自動調(diào)節(jié)適配速率(滑動窗口技術);
(5)發(fā)送方會給各分割報文編號,接收方會校驗編號,一旦順序錯誤即會重傳。
(7)這些都是別人已經(jīng)實現(xiàn)的功能,我們只需知道這些特性就可以了,編程時不會在意這些細節(jié)。
?
三、TCP協(xié)議的學習2
1、TCP建立連接時的三次握手
(1)建立連接需要三次握手;
(2)建立連接的條件:服務器處于listen狀態(tài)時(比如服務器在維護時,就不處在監(jiān)聽狀態(tài)),客戶端主動發(fā)起connect;
2、TCP釋放連接時的四次握手
(1)關閉連接需要四次握手;
(2)服務器或者客戶端都可以主動發(fā)起關閉;
(4)這些握手協(xié)議已經(jīng)封裝在TCP協(xié)議內(nèi)部,socket編程接口平時不用管。
?
3、基于TCP通信的服務模式(編程時也是按這個模式編寫的)
(1)具有公網(wǎng)IP地址的服務器(或者使用動態(tài)IP地址映射技術);
(2)服務器端socket、bind、listen后處于監(jiān)聽狀態(tài);
(3)客戶端socket后,直接connect去發(fā)起連接。
(4)服務器收到并同意客戶端接入后,會建立TCP連接,然后雙方開始收發(fā)數(shù)據(jù),收發(fā)時是雙向的,而且雙方均可發(fā)起。
(5)雙方均可發(fā)起關閉連接。
?
4、常見的使用TCP協(xié)議的網(wǎng)絡應用
(1)http、ftp;
(2)QQ服務器;
(3)mail服務器;
?
四、socket編程接口(與網(wǎng)絡通信相關的API)介紹
1、建立連接
(1)socket
- socket函數(shù)類似于open,用來打開一個網(wǎng)絡連接,如果成功則返回一個網(wǎng)絡文件描述符(int類型)。
- 對網(wǎng)絡連接進行操作,都是通過這個網(wǎng)絡文件描述符進行:發(fā)送相當于寫文件,接收相當于讀文件。
- 函數(shù)參數(shù)見上圖標注,編程中的一個實例:socket(AF_INET,SOCK_STREAM,0)
(2)bind
(3)listen
(4)connect
?
2、發(fā)送和接收
(1)send和write:發(fā)送的時候可以使用write,也可以使用send,只是send多了一個flag。
(2)recv和read:和上述一樣。
?
3、輔助性函數(shù)
用來進行ip地址轉(zhuǎn)換的,點分十進制和二進制之間轉(zhuǎn)換
(1)inet_aton、inet_addr、inet_ntoa(以前使用的,不支持IPV6)
(2)inet_ntop、inet_pton(現(xiàn)在推薦使用的,支持IPV6/4)
?
4、表示IP地址相關數(shù)據(jù)結(jié)構(gòu)
(1)都定義在 netinet/in.h;
(2)struct sockaddr
- 是linux的網(wǎng)絡編程接口中用來表示IP地址的標準結(jié)構(gòu)體,bind、connect等函數(shù)中都需要這個結(jié)構(gòu)體,兼容IPV4和IPV6的。
- 在實際編程中,會被一個struct sockaddr_in或者一個struct sockaddr_in6所填充。即它是一個形參,傳ruct sockaddr_in或者struct sockaddr_in6都可以。
(3)struct sockaddr_in
- 注意里面包含了ip地址和端口號
(4)struct in_addr
struct in_addr {in_addr_t s_addr; };(5)typedef uint32_t in_addr_t
- 網(wǎng)絡內(nèi)部用來表示IP地址;
?
五、IP地址格式轉(zhuǎn)換函數(shù)實踐
1、inet_addr、inet_ntoa、inet_aton;(只能IPV4)
?
?
- 從上面可知,存在大小端問題。解決方法:網(wǎng)絡字節(jié)序,其實就是統(tǒng)一使用大端模式。
- 如果電腦使用的是小端模式,先把ip地址轉(zhuǎn)換為大端模式;如果本來就是大端模式,那就不用改。
- 這些API會自動完成這樣的功能,即會檢測所用的電腦的模式,然后在代碼內(nèi)部進行處理,最后輸出一定是大端模式的。
2、inet_pton、inet_ntop(可以用于IPV6)
- 例一
?
- 例二
?
六、soekct實踐編程1_2
結(jié)合代碼來看過程、參數(shù)含義
1、服務器端程序編寫
(1)socket
(2)bind
(3)listen
(4)accept,返回值是一個fd
- 如果accept正確返回,則表示服務器和客戶端成功建立一個TCP連接
- 可以通過該TCP連接與客戶端進行讀寫操作。
- 而讀寫操作需要一個fd,此fd由accept返回。
- socket返回的fd叫做監(jiān)聽fd,是用來監(jiān)聽客戶端的,不能用來和任何客戶端進行讀寫;
- accept返回的fd叫做連接fd,用來和客戶端程序進行讀寫。
2、客戶端程序編寫
(1)socket
(2)connect
- 端口號,實質(zhì)就是一個數(shù)字編號,用來唯一地標識一個能上網(wǎng)的進程。
- 端口號和IP地址,會被打包到當前進程發(fā)出的或者接收到的每個數(shù)據(jù)包。
- 每個數(shù)據(jù)包將來在網(wǎng)絡上傳遞的時候,內(nèi)部都包含了發(fā)送方和接收方的信息(就是IP地址和端口號)。
?
七、socket實踐編程3
1、客戶端發(fā)送&服務器接收
2、服務器發(fā)送&客戶端接收
3、探討:如何讓服務器和客戶端好好溝通
(1)客戶端和服務器原則上都可以任意的發(fā)和收,但是實際上雙方必須配合:client發(fā)的時候server就收,而server發(fā)的時候client就收;
(2)必須了解到的一點:client和server之間的通信是異步的,這就是問題的根源
(3)解決方案:依靠應用層協(xié)議來解決,即server和client事先做好一系列的通信約定。
?
八、socket編程實踐4
1、自定義應用層協(xié)議第一步:規(guī)定發(fā)送和接收方法
(1)規(guī)定連接建立后,由客戶端主動向服務器發(fā)出1個請求數(shù)據(jù)包,然后服務器收到數(shù)據(jù)包后回復客戶端一個回應數(shù)據(jù)包,這就是一個通信回合;
(2)整個連接的通信就是由N多個回合組成的。
2、自定義應用層協(xié)議第二步:定義數(shù)據(jù)包格式。
3、常用應用層協(xié)議:http、ftp……
4、UDP簡介
?
九、代碼
1、運行步驟
(1)將下面的這兩份代碼復制到linux下,形成兩份文檔client.c和server.c,然后gcc編譯
(2)修改linux的ip地址為192.168.1.141
(3)先運行server.c生成的可執(zhí)行文件,再運行client.c生成的可執(zhí)行文件。
2、代碼與邏輯
服務器
socket函數(shù),獲取網(wǎng)絡連接的文件描述符
bind函數(shù),將服務器的端口、ip地址與socket函數(shù)創(chuàng)建的文件描述符綁定
listen函數(shù),監(jiān)聽服務器的當前端口(其他端口不監(jiān)聽)
accept函數(shù),阻塞以等待用戶連接
客服端
socket函數(shù),獲取網(wǎng)絡連接的文件描述符
connect函數(shù),連接服務器
---連接上之后--
send函數(shù),客服端給服務器發(fā)送數(shù)據(jù)
recv函數(shù),客服端接收服務器的回復
client
#include<stdio.h> #include<sys/socket.h> #include<sys/types.h> #include<arpa/inet.h> #include<string.h>#define SERPORT 6013 #define SERADDR "192.168.1.141"//要設置為服務器的IP地址 #define CMD_REGISTER 1#define STAT_OK 0 #define STAT_ERR 1typedef struct FORMAT {char name[20];int age;int cmd;int stat; }info;char sendbuf[100]; char recvbuf[100];int main(void) {//第一步:socket函數(shù),獲取網(wǎng)絡連接的文件描述符int sockfd=-1;int ret=-1;struct sockaddr_in seraddr={0};sockfd=socket(AF_INET,SOCK_STREAM,0);if(-1==sockfd){perror("socket error:");return -1;}printf("socket success.socket=%d.\n",sockfd);//第二步:connect函數(shù),連接服務器seraddr.sin_family=AF_INET;//定義ip類型,IPV4還是IPV6seraddr.sin_port=htons(SERPORT);seraddr.sin_addr.s_addr=inet_addr(SERADDR);ret=connect(sockfd,(const struct sockaddr*)&seraddr,sizeof(seraddr));printf("連接成功\n");/* while(1){ //第三步:send函數(shù),客戶端給服務器發(fā)送信息printf("請輸入你想發(fā)給服務器的內(nèi)容:\n");scanf("%s",sendbuf);ret=send(sockfd,sendbuf,strlen(sendbuff),0);//第四步:客戶端接收服務器端的回復memset(recvbuf,0,sizeof(recvbuf));ret=recv(sockfd,recvbuf,sizeof(recvbuf),0);printf("服務器回復內(nèi)容是:%s\n",recvbuf);//第五步:客戶端解釋服務器的回復,再做下一步定奪} */ info st1;while(1){ printf("\n請輸入學生姓名:\n");scanf("%s",st1.name);printf("\n請輸入學生年齡:\n");scanf("%d",&st1.age);st1.cmd=CMD_REGISTER;ret=send(sockfd,&st1,sizeof(info),0);//第四步:客戶端接收服務器端的回復memset(&st1,0,sizeof(st1));ret=recv(sockfd,&st1,sizeof(st1),0);if(STAT_OK==st1.stat){printf("注冊學生信息成功!");}elseprintf("注冊學生信息失敗!");}return 0; }server:
#include <stdio.h> #include <arpa/inet.h> #include <string.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h>#define MYPORT 6013 #define SERADDR "192.168.1.141"//要設置為服務器的IP地址 #define BLEN 100 #define STAT_OK 0 #define STAT_ERR 1 #define CMD_REGISTER 1 typedef struct FORMAT {char name[20];int age;int cmd;int stat; }info;char recvbuf[100];int main(void) {int sockfd=-1;int ret=-1;int clifd=-1;socklen_t len=0;struct sockaddr_in seraddr={0};//也可以使用memset函數(shù)進行初始化struct sockaddr_in clientaddr={0};//第一步:socket函數(shù),獲取網(wǎng)絡連接的文件描述符sockfd=socket(AF_INET,SOCK_STREAM,0);if(-1==sockfd){perror("socket error:");return -1;}printf("socket success!socket=%d.\n",sockfd);//第二步:bind函數(shù),綁定sockfd與服務器的ip地址、端口號//填充seraddr這個結(jié)構(gòu)體seraddr.sin_family=AF_INET;//定義ip類型,IPV6還是IPV4seraddr.sin_port=htons(MYPORT);seraddr.sin_addr.s_addr=inet_addr(SERADDR);ret=bind(sockfd,(const struct sockaddr*)&seraddr,sizeof(seraddr));//類型不一樣,會警告if(ret==-1){perror("bind error:");//函數(shù)本身有錯誤號時可以用perror來顯示錯誤信息}printf("bind success!\n");//第三步:listen函數(shù),監(jiān)聽服務器的當前端口(其他端口不監(jiān)聽)ret=listen(sockfd,BLEN);//第二個參數(shù)是服務器允許的隊列長度,比如最多100號if(ret==-1){perror("listen error:");}printf("listen success!\n");//第四步:accept函數(shù),阻塞等待用戶連接 //注意accept返回值是網(wǎng)絡文件描述符,和sockfd不同,它才是真正的用于發(fā)送數(shù)據(jù)的fd,而sockfd只是用于偵聽的。clifd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);printf("用戶連接成功!\n");/* while(1){//第五步:recv函數(shù),服務器開始接收數(shù)據(jù)ret=recv(clifd,recvbuf,sizeof(recvbuf),0);printf("client發(fā)送的內(nèi)容是:%s\n",recvbuf);memset(recvbuf,0,sizeof(recvbuf));//第六步:服務器解釋數(shù)據(jù)包//第七步:回復客戶端OKret=send(clifd,"OK",2,0);} */while(1){info st;ret=recv(clifd,&st,sizeof(recvbuf),0);if(CMD_REGISTER==st.cmd){printf("用戶要注冊學生信息\n");printf("姓名:%s,年齡:%d\n",st.name,st.age);st.stat=STAT_OK;ret=send(clifd,&st,sizeof(info),0);}}return 0; }?
總結(jié)
- 上一篇: 精讲RestTemplate第6篇-文件
- 下一篇: Dev-cpp调试教程