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