本文主要記錄TCP/UDP網(wǎng)絡(luò)編程的基礎(chǔ)知識,采用TCP/UDP實現(xiàn)宿主機和目標(biāo)機之間的網(wǎng)絡(luò)通信。
內(nèi)容目錄
-
目標(biāo)
2.Linux網(wǎng)絡(luò)編程基礎(chǔ)
2.1 嵌套字2.2 端口
2.3 網(wǎng)絡(luò)地址
2.3.1 網(wǎng)絡(luò)地址的格式
2.3.2 網(wǎng)絡(luò)地址的轉(zhuǎn)換
2.4 字節(jié)序
3.TCP
3.1 TCP流程圖
3.2 TCP步驟分析
3.3 TCP完整代碼
3.4 測試結(jié)果
4.UDP
4.1 UDP流程圖
4.2 UDP步驟分析
4.3 UDP完整代碼
4.4 測試結(jié)果
1. 目標(biāo)
實現(xiàn)讓兩個設(shè)備通過網(wǎng)絡(luò)傳輸數(shù)據(jù),比如開發(fā)板和Linux主機之間傳數(shù)據(jù),
以后就可以實現(xiàn)開發(fā)板通過網(wǎng)絡(luò)上報數(shù)據(jù)或者 主機通過網(wǎng)絡(luò)控制開發(fā)板 。
此外,暫時不想關(guān)心具體的網(wǎng)絡(luò)模型,更注重于網(wǎng)絡(luò)相關(guān)函數(shù)的直接使用。
2.Linux網(wǎng)絡(luò)編程基礎(chǔ)
2.1 嵌套字
多個TCP連接或者多個應(yīng)用程序進程 可能需要同一個TCP端口傳輸數(shù)據(jù)。
為了區(qū)分不同應(yīng)用程序進程和連接,許多計算機操作系統(tǒng)為應(yīng)用程序與TCP/IP交互提供了稱為 嵌套字(Socket) 的接口。
Linux中的網(wǎng)絡(luò)編程正是通過Socket接口實現(xiàn)的,Socket是一種文件描述符。
常用的TCP/IP有以下三種類型的嵌套字:
-
流式嵌套字(SOCK_STREAM)
用于提供面向連接的、可靠的數(shù)據(jù)傳輸服務(wù),即使用TCP進行傳輸。
-
數(shù)據(jù)報嵌套字(SOCK_DGRAM)
用于提供無連接的服務(wù),即使用UDP進行傳輸。
-
原始嵌套字(SOCK_RAW)
可以讀寫內(nèi)核沒有處理的IP數(shù)據(jù)報,而流式嵌套字只能讀取TCP的數(shù)據(jù),數(shù)據(jù)報嵌套字只能讀取UDP的數(shù)據(jù)。
因此,如果要訪問其它協(xié)議發(fā)送的數(shù)據(jù)必須使用原始嵌套字,它允許對底層協(xié)議(如IP或ICMP)直接訪問。
2.2 端口
TCP/IP協(xié)議中的端口,端口號的范圍從0~65535。
一類是由互聯(lián)網(wǎng)指派名字和號碼公司ICANN負(fù)責(zé)分配給一些常用的應(yīng)用程序固定使用的“周知的端口”,其值一般為0~1023。例如http的端口號是80,F(xiàn)TP為21,SSH為22,Telnet為23等。
還有一類是用戶自己定義的,通常是大于1024的整型值。
2.3 網(wǎng)絡(luò)地址
網(wǎng)絡(luò)通信,歸根到底還是進程間的通信(不同計算機上的進程間通信)。
在網(wǎng)絡(luò)中,每一個節(jié)點(計算機或路由)都有一個網(wǎng)絡(luò)地址,如192.168.1.4,也就是IP地址。
兩個進程通信時,首先要確定各自所在的網(wǎng)絡(luò)節(jié)點的網(wǎng)絡(luò)地址。
但是,網(wǎng)絡(luò)地址只能確定進程所在的計算機,而一臺計算機上很可能同時運行著多個進程,所以僅憑網(wǎng)絡(luò)地址還不能確定到底是和網(wǎng)絡(luò)中的哪一個進程進行通信,因此套接口中還需要包括其他的信息,也就是端口號(PORT)。
在一臺計算機中,一個端口號一次只能分配給一個進程,也就是說,在一臺計算機中,端口號和進程之間是一一對應(yīng)關(guān)系。
所以, 使用端口號和網(wǎng)絡(luò)地址的組合可以唯一的確定整個網(wǎng)絡(luò)中的一個網(wǎng)絡(luò)進程 。
例如,如網(wǎng)絡(luò)中某一臺計算機的IP為192.168.1.4,操作系統(tǒng)分配給計算機中某一應(yīng)用程序進程的端口號為1500,則此時192.168.1.4 1500
就構(gòu)成了一個套接口。
2.3.1 網(wǎng)絡(luò)地址的格式
在Socket程序設(shè)計中,struct sockaddr
用于記錄網(wǎng)絡(luò)地址,其格式如下:
1struct sockaddr
2{
3 unsigned short sa_family; /*協(xié)議族,采用AF_XXX的形式,例如AF_INET(IPv4協(xié)議族)*/
4 char sa_data[14]; /*14字節(jié)的協(xié)議地址,包含該socket的IP地址和端口號。*/
5};
但在實際編程中,并不針對sockaddr
數(shù)據(jù)結(jié)構(gòu)進行操作,而是用與其等價的sockaddr_in
數(shù)據(jù)結(jié)構(gòu):
1struct sockaddr_in
2{
3 short int sa_family; /*地址族*/
4 unsigned short int sin_port; /*端口號*/
5 struct in_addr sin_addr; /*IP地址*/
6 unsigned char sin_zero[8]; /*填充0 以保持與struct sockaddr同樣大小*/
7};
2.3.2 網(wǎng)絡(luò)地址的轉(zhuǎn)換
IP地址通常用數(shù)字加點(如192.168.1.4)表示,而在struct in_addr
中使用的式32位整數(shù)表示。因此,Linux提供如下函數(shù)進行兩者之間的轉(zhuǎn)換:
- inet_aton()函數(shù):
所需要頭文件 :
#include
#include
#include
函數(shù)格式 :
int inet_aton(const char *cp, struct in_addr *inp);
函數(shù)功能 :
將a.b.c.d字符串形式的IP地址轉(zhuǎn)換成32位網(wǎng)絡(luò)序號IP地址;
*cp:存放字符串形式的IP地址的指針
*inp:存放32位的網(wǎng)絡(luò)序號IP地址
返回值 :
轉(zhuǎn)換成功,返回非0,否則返回0;
- inet_ntoa()函數(shù):客戶機端:
所需要頭文件 :
#include
#include
#include
函數(shù)格式 :
char *inet_ntoa(struct in_addr in);
函數(shù)功能 :
將32位網(wǎng)絡(luò)序號IP地址轉(zhuǎn)換成a.b.c.d字符串形式的IP地址;
in:Internet主機地址的結(jié)構(gòu)
返回值 :
轉(zhuǎn)換成功,返回一個字符指針,否則返回NULL;
2.4 字節(jié)序
不同的CPU采用對變量的字節(jié)存儲順序可能不同。
常用的X86結(jié)構(gòu)是小端模式,很多的ARM,DSP都為小端模式,即內(nèi)存的低地址存儲數(shù)據(jù)的低字節(jié),高地址存儲數(shù)據(jù)的高字節(jié)。
而KEIL C51則為大端模式,內(nèi)存的高地址存儲數(shù)據(jù)的低字節(jié),低地址存儲數(shù)據(jù)高字節(jié)。
對于網(wǎng)絡(luò)傳輸來說,數(shù)據(jù)順序必須是一致的,網(wǎng)絡(luò)字節(jié)順序采用大端字節(jié)序方式。
下面是四個常用的轉(zhuǎn)換函數(shù):
主機轉(zhuǎn)網(wǎng)絡(luò):
- htons()函數(shù):
所需要頭文件 :
#include
函數(shù)格式 :
unsigned short int htons(unsigned short int hostshort)
函數(shù)功能 :
將參數(shù)指定的16位主機(host)字符順序轉(zhuǎn)換成網(wǎng)絡(luò)(net)字符順序;
hostshort:待轉(zhuǎn)換的16位主機字符順序數(shù)
返回值 :
返回對應(yīng)的網(wǎng)絡(luò)字符順序數(shù);
- htonl()函數(shù):
所需要頭文件 :
#include
函數(shù)格式 :
unsigned long int htons(unsigned long int hostlong)
函數(shù)功能 :
將參數(shù)指定的32位主機(host)字符順序轉(zhuǎn)換成網(wǎng)絡(luò)(net)字符順序;
hostlong:待轉(zhuǎn)換的32位主機字符順序數(shù)
返回值 :
返回對應(yīng)的網(wǎng)絡(luò)字符順序數(shù);
網(wǎng)絡(luò)轉(zhuǎn)主機:
- ntohs()函數(shù):
所需要頭文件 :
#include
函數(shù)格式 :
unsigned short int ntohs(unsigned short int netshort)
函數(shù)功能 :
將參數(shù)指定的16位網(wǎng)絡(luò)(net)字符順序轉(zhuǎn)換成主機(host)字符順序;
netshort:待轉(zhuǎn)換的16位網(wǎng)絡(luò)字符順序數(shù)
返回值 :
返回對應(yīng)的主機字符順序數(shù);
- ntohl()函數(shù):
所需要頭文件 :
#include
函數(shù)格式 :
unsigned long int ntohl(unsigned long int netlong)
函數(shù)功能 :
將參數(shù)指定的32位網(wǎng)絡(luò)(net)字符順序轉(zhuǎn)換成主機(host)字符順序;
netshort:待轉(zhuǎn)換的32位網(wǎng)絡(luò)字符順序數(shù)
返回值 :
返回對應(yīng)的主機字符順序數(shù);
3.TCP
TCP有專門的傳遞保證機制,收到數(shù)據(jù)時會自動發(fā)送確認(rèn)消息,發(fā)送方收到確認(rèn)消息后才會繼續(xù)發(fā)送消息,否則繼續(xù)等待。
這樣的好處是傳輸?shù)臄?shù)據(jù)是可靠的,此外它是有連接的傳輸,大多數(shù)網(wǎng)絡(luò)傳輸都是用的TCP。
3.1 TCP流程圖
3.2 TCP步驟分析
程序分為服務(wù)器端和客戶機端,先從服務(wù)器端開始分析。
-
服務(wù)器端:
a. 創(chuàng)建socket
1 sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
2 if (-1 == sock_fd)
3 {
4 fprintf(stderr,"socket error:%s\\n\\a", strerror(errno));
5 exit(1);
6 }
所需要頭文件 :
#include
#include
函數(shù)格式 :
int socket(int domain, int type, int protocol);
函數(shù)功能 :
創(chuàng)建一個套接字;
domain:協(xié)議域(族), 決定了套接字的地址類型 ,例如AF_INET決定了要用IPv4地址(32位)與端口號(16位)的組合。常見的協(xié)議族有: AF_INET 、AF_INET6、AF_LOCAL(或稱AF_UNIX)、AF_ROUTE等;
type: 指定套接字類型 , SOCK_STREAM (TCP)、 SOCK_DGRAM (UDP)、SOCK_RAW
protocol:指定socket所使用的傳輸協(xié)議編號,通常為0
返回值 :
若成功,返回一個套接字描述符,否則返回-1;
Socket就是一種文件描述符,和普通的打開文件一樣,需要檢測其返回結(jié)果。
b. 設(shè)置socket
1 memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
2 server_addr.sin_family = AF_INET;
3 server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IP
4 server_addr.sin_port = htons(PORT_NUMBER);
設(shè)置何種協(xié)議族,設(shè)置本機IP和端口,也就有了唯一性。
c. 綁定socket
1 ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
2 if(-1 == ret)
3 {
4 fprintf(stderr,"bind error:%s\\n\\a", strerror(errno));
5 close(sock_fd);
6 exit(1);
7 }
所需要頭文件 :
#include
#include
函數(shù)格式 :
int bind(int sockfd, struct sockaddr *addr, int addrlen);
函數(shù)功能 :
把套接字綁定到本地計算機的某一個端口上;
sockfd:待綁定的套接字描述符
addr:一個struct sockaddr *指針,指定要綁定給sockfd的協(xié)議地址。內(nèi)容結(jié)構(gòu)由前面的協(xié)議族決定。
addrlen:地址的長度
返回值 :
若成功,返回0,否則返回-1,錯誤信息存在errno中;
d. 開始監(jiān)聽
1 ret = listen(sock_fd, BACKLOG);
2 if (-1 == ret)
3 {
4 fprintf(stderr,"listen error:%s\\n\\a", strerror(errno));
5 close(sock_fd);
6 exit(1);
7 }
所需要頭文件 :
#include
#include
函數(shù)格式 :
int listen(int sockfd, int backlog);
函數(shù)功能 :
使服務(wù)器的這個端口和IP處于監(jiān)聽狀態(tài),等待網(wǎng)絡(luò)中某一客戶機的連接請求,最大連接數(shù)量為backlog≤128;
sockfd:待監(jiān)聽的套接字描述符
backlog:最大可監(jiān)聽和連接的客戶端數(shù)量
返回值 :
若成功,返回0,否則返回-1;
e. 阻塞,等待連接
1 addr_len = sizeof(struct sockaddr);
2 new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len);
3 if (-1 == new_fd)
4 {
5 fprintf(stderr,"accept error:%s\\n\\a", strerror(errno));
6 close(sock_fd);
7 exit(1);
8 }
1
所需要頭文件 :
#include
#include
函數(shù)格式 :
int accept(int sockfd, struct sockaddr *addr, int *addrlen);
函數(shù)功能 :
接受連接請求,建立起與客戶機之間的通信連接。服務(wù)器處于監(jiān)聽狀態(tài)時,如果某時刻獲得客戶機的連接請求,此時并不是立即處理這個請求,而是將這個請求放在等待隊列中,當(dāng)系統(tǒng)空閑時再處理客戶機的連接請求;
當(dāng)accept函數(shù)接受一個連接時,會返回一個新的socket標(biāo)識符,以后的數(shù)據(jù)傳輸和讀取就要通過這個新的socket編號來處理,原來參數(shù)中的socket也可以繼續(xù)使用,繼續(xù)監(jiān)聽其它客戶機的連接請求;
accept連接成功時,參數(shù)addr所指的結(jié)構(gòu)體會填入所連接機器的地址數(shù)據(jù);
sockfd:待監(jiān)聽的套接字描述符
addr:指向struct sockaddr的指針,用于返回客戶端的協(xié)議地址
addrlen:協(xié)議地址的長度
返回值 :
若成功,返回一個由內(nèi)核自動生成的一個全新描述字,代表與返回客戶的TCP連接,否則返回-1,錯誤信息存在errno中;
f. 接收數(shù)據(jù)
1 recv_len = recv(new_fd, recv_buf, 999, 0);
2 if (recv_len <= 0)
3 {
4 fprintf(stderr, "recv error:%s\\n\\a", strerror(errno));
5 close(new_fd);
6 exit(1);
7 }
8 else
9 {
10 recv_buf[recv_len] = '\\0';
11 printf("Get msg from client%d: %s\\n", client_num, recv_buf);
12 }
所需要頭文件 :
#include
#include
函數(shù)格式 :
int recv(int sockfd, void *buf, size_t len, int flags);
函數(shù)功能 :
用新的套接字來接收遠(yuǎn)端主機傳來的數(shù)據(jù),并把數(shù)據(jù)存到由參數(shù)buf指向的內(nèi)存空間;
sockfd:sockfd為前面accept的返回值,即new_fd,也就是新的套接字
buf:指明一個緩沖區(qū)
len:指明緩沖區(qū)的長度
flags:通常為0
返回值 :
若成功,返回接收到的字節(jié)數(shù),另一端已關(guān)閉則返回0,否則返回-1,錯誤信息存在errno中;
g. 關(guān)閉socket
1 close(sock_fd);
2 exit(0);
為了應(yīng)對多個連接,并保證它們之間相互獨立,實際編程中往往還要加入多進程fork()。
讓子進程接收數(shù)據(jù),父進程繼續(xù)監(jiān)聽新的連接。
- 客戶機端:
a. 創(chuàng)建socket
1 sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
2 if (-1 == sock_fd)
3 {
4 fprintf(stderr,"socket error:%s\\n\\a", strerror(errno));
5 exit(1);
6 }
b. 設(shè)置socket
1 memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
2 server_addr.sin_family = AF_INET;
3 server_addr.sin_port = htons(PORT_NUMBER);
其中注意的是,這里設(shè)置的socket內(nèi)容是指 希望連接的服務(wù)器IP和端口號信息,IP地址來自用戶的輸入,并轉(zhuǎn)換格式得到。因此,這里的設(shè)置和服務(wù)器的設(shè)置,要保持內(nèi)容上的一致。
1 ret = inet_aton(argv[1], &server_addr.sin_addr);
2 if(0 == ret)
3 {
4 fprintf(stderr,"server_ip error.\\n");
5 close(sock_fd);
6 exit(1);
7 }
c. 連接
1 ret = connect(sock_fd, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr));
2 if (-1 == ret)
3 {
4 fprintf(stderr,"connect error:%s\\n\\a", strerror(errno));
5 close(sock_fd);
6 exit(1);
7 }
-
主機
+關(guān)注
關(guān)注
0文章
998瀏覽量
35178 -
TCP
+關(guān)注
關(guān)注
8文章
1372瀏覽量
79142 -
UDP
+關(guān)注
關(guān)注
0文章
326瀏覽量
33993 -
網(wǎng)絡(luò)通信
+關(guān)注
關(guān)注
4文章
808瀏覽量
29848 -
網(wǎng)絡(luò)編程
+關(guān)注
關(guān)注
0文章
72瀏覽量
10085
發(fā)布評論請先 登錄
相關(guān)推薦
評論