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

tcp和udp協(xié)議的異同

基于Socket的UDP和TCP編程解析 1

基于Socket的UDP和TCP編程解析 2

TCP/UDP網(wǎng)絡(luò)編程的基礎(chǔ)知識(shí)合集2
TCP與UDP的基本區(qū)別

評(píng)論