一、什么是socket?
Socket的英文原義是“孔”或“插座”。在編程中,Socket被稱做套接字,是網(wǎng)絡(luò)通信中的一種約定。Socket編程的應(yīng)用無處不在,我們平時用的QQ、微信、瀏覽器等程序,都與Socket編程有關(guān)。我們平時使用瀏覽器查資料,這個過程的技術(shù)原理是怎樣的呢?
我們平時使用瀏覽器,大致就是這樣的一個過程。這里有兩個重要的名詞:服務(wù)端與客戶端。
Socket編程的目的就是如何實現(xiàn)這兩端之間的通信。
1、Socket編程在嵌入式中也很重要
Socket編程不僅僅在互聯(lián)網(wǎng)方面很重要,在我們的嵌入式方面也是非常的重要,因為現(xiàn)在很多電子設(shè)備都趨向于聯(lián)網(wǎng)。比如很多嵌入式工作的招聘要求都會有這一條要求:
說一點(diǎn)題外話,還在學(xué)校的朋友,如果感覺到很迷茫,不知道學(xué)什么的時候,可以上招聘網(wǎng)站上看看自己未來工作相關(guān)的職位的任職要求,這樣就可以總結(jié)自己的一些不足、比較有針對性的去學(xué)習(xí)。
二、Socket編程中的幾個重要概念
Socket編程用于解決我們客戶端與服務(wù)端之間通信的問題。我們平時多多少少都有聽過IP地址、端口、TCP協(xié)議、UDP協(xié)議等概念,這些都與Socket編程中相關(guān),想要知道怎么用起來,當(dāng)然得先了解它們的一些介紹。下面看一下這些專業(yè)術(shù)語的一些要點(diǎn)介紹:
1、什么是IP地址?
IP地址(Internet Protocol Address)是指互聯(lián)網(wǎng)協(xié)議地址,又譯為網(wǎng)際協(xié)議地址。IP地址被用來給Internet上的電腦一個編號。我們可以把“個人電腦”比作“一臺電話”,那么“IP地址”就相當(dāng)于“電話號碼”。若計算機(jī)1知道計算機(jī)2的IP地址,則計算機(jī)1就能訪問計算機(jī)2。
IP地址是一個32位的二進(jìn)制數(shù),通常被分割為4個“8位二進(jìn)制數(shù)”(也就是4個字節(jié))。IP地址通常用點(diǎn)分十進(jìn)制表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之間的十進(jìn)制整數(shù)。例:點(diǎn)分十進(jìn)IP地址(100.4.5.6),實際上是32位二進(jìn)制數(shù)(01100100.00000100.00000101.00000110)。
IP地址有IPv4與IPv6之分,現(xiàn)在用得較多的是IPv4。其中,有一個特殊的IP地址需要我們記?。?27.0.0.1,這是回送地址,即本地機(jī),一般用來測試使用。后邊我們的實例中會用到。
關(guān)于IP地址還有很多知識要點(diǎn),但是對于在Socket編程中的應(yīng)用,我們暫且知道這么多就可以。
2、什么是TCP/IP端口?
上一點(diǎn)中我們提到,若計算機(jī)1知道計算機(jī)2的IP地址,則計算機(jī)1就能訪問計算機(jī)2。但是,我們要訪問計算機(jī)2中的不同的應(yīng)用軟件,則還得需要一個信息:端口。端口使用16bit進(jìn)行編號,即其范圍為:0~65536。但0~1023 的端口一般由系統(tǒng)分配給特定的服務(wù)程序,例如 Web 服務(wù)的端口號為 80,F(xiàn)TP 服務(wù)的端口號為 21等。
3、什么是協(xié)議?
協(xié)議(Protocol)是通信雙方進(jìn)行數(shù)據(jù)交互的一種約定。如TCP、UDP協(xié)議:
(1)TCP協(xié)議
TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,數(shù)據(jù)可以準(zhǔn)確發(fā)送,數(shù)據(jù)丟失會重發(fā)。TCP協(xié)議常用于web應(yīng)用中。
TCP連接(三次握手)
TCP傳輸起始時,客戶端、服務(wù)端要完成三次數(shù)據(jù)交互工作才能建立連接,常稱為三次握手??尚蜗蟊扔鳛槿缦聦υ挘?/p>
客戶端:服務(wù)端您好,我有數(shù)據(jù)要發(fā)給你,請求您開通訪問權(quán)限。
服務(wù)端:客戶端您好,已給您開通權(quán)限,您可以發(fā)送數(shù)據(jù)了。
客戶端:收到,謝謝。
具體示意圖為:
這里的SYN和ACK是都是標(biāo)志位,其中SYN代表新建一個連接,ACK代表確認(rèn)。其中m、n都是隨機(jī)數(shù)。具體說明如:
第一次握手:SYN標(biāo)志位被置位,客戶端向服務(wù)端發(fā)送一個隨機(jī)數(shù)m。
第二次握手:ACK、SYN標(biāo)志位被置位。服務(wù)端向客戶端發(fā)送m+1表示確認(rèn)剛才收到的數(shù)據(jù),同時向客戶端發(fā)送一個隨機(jī)數(shù)n。
第三次握手:ACK標(biāo)志被置位。客戶端向服務(wù)端發(fā)送n+1表示確認(rèn)收到數(shù)據(jù)。
TCP斷開(四次揮手)
TCP斷開連接時,客戶端、服務(wù)端要完成四次數(shù)據(jù)交互工作才能建立連接,常稱為四次揮手??尚蜗蟊扔鳛槿缦聦υ挘?/p>
客戶端:服務(wù)端您好,我發(fā)送數(shù)據(jù)完畢了,即將和您斷開連接。
服務(wù)端:客戶端您好,我稍稍準(zhǔn)備一下,再給您斷開
服務(wù)端:客戶端您好,我準(zhǔn)備好了,您可以斷開連接了。
客戶端:好的,合作愉快!
具體示意圖為:
這里的FIN也是一個標(biāo)志位,代表斷開連接。具體說明類似三次握手。
為什么建立連接只需要三次數(shù)據(jù)交互,而斷開連接需要四次呢?
建立連接時,服務(wù)端在監(jiān)聽狀態(tài)下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發(fā)送給客戶端。
而關(guān)閉連接時,當(dāng)收到對方的FIN報文時,僅僅表示對方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),己方也未必全部數(shù)據(jù)都發(fā)送給對方了,所以己方可以立即close,也可以發(fā)送一些數(shù)據(jù)給對方后,再發(fā)送FIN報文給對方來表示同意現(xiàn)在關(guān)閉連接,因此,己方ACK和FIN一般都會分開發(fā)送。
(2)UDP協(xié)議
UDP(User Datagram Protocol, 用戶數(shù)據(jù)報協(xié)議)是一種無連接的傳輸層協(xié)議,提供面向事務(wù)的簡單不可靠信息傳送服務(wù),可以保證通訊效率,傳輸延時小。例如視頻聊天應(yīng)用中用的就是UDP協(xié)議,這樣可以保證及時丟失少量數(shù)據(jù),視頻的顯示也不受很大影響。
4、什么是協(xié)議族?
協(xié)議族是多個協(xié)議的統(tǒng)稱。比如我們的TCP/IP協(xié)議族,其不僅僅是TCP協(xié)議、IP協(xié)議,而是多個協(xié)議的集合,其包含IP、TCP、UDP、FTP、SMTP等協(xié)議。
1、Linux下的socket API接口
(1)創(chuàng)建socket:socket()函數(shù)
函數(shù)原型:
int socket(int af, int type, int protocol);
af參數(shù):af 為地址族(Address Family),也就是 IP 地址類型,常用的有 AF_INET 和 AF_INET6,其前綴也可以是PF(Protocol Family),即PF_INET 和 PF_INET6。
type參數(shù):type 為數(shù)據(jù)傳輸方式,常用的有 面向連接(SOCK_STREAM)方式(即TCP) 和 無連接(SOCK_DGRAM)的方式(即UDP)。
protocol參數(shù):protocol 表示傳輸協(xié)議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協(xié)議和 UDP 傳輸協(xié)議。
使用示例:
創(chuàng)建TCP套接字:
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
創(chuàng)建UDP套接字:
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
(2)綁定套接字:bind()函數(shù)
函數(shù)原型:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
sock參數(shù):sock 為 socket 文件描述符。
addr參數(shù):addr 為 sockaddr 結(jié)構(gòu)體變量的指針。
addrlen參數(shù):addrlen 為 addr 變量的大小,可由 sizeof() 計算得出。
使用示例:
將創(chuàng)建的套接字ServerSock與本地IP127.0.0.1、端口1314進(jìn)行綁定:
/* 創(chuàng)建服務(wù)端socket */int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);/* 設(shè)置服務(wù)端信息 */struct sockaddr_in ServerSockAddr;memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結(jié)構(gòu)體ServerSockAddr清零ServerSockAddr.sin_family = PF_INET; // 使用IPv4地址ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本機(jī)IP地址ServerSockAddr.sin_port = htons(1314); // 端口/* 綁定套接字 */bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));
其中struct sockaddr_in類型的結(jié)構(gòu)體變量用于保存IPv4的IP信息。若是IPv6,則有對應(yīng)的結(jié)構(gòu)體:
struct sockaddr_in6 { sa_family_t sin6_family; // 地址類型,取值為AF_INET6 in_port_t sin6_port; // 16位端口號 uint32_t sin6_flowinfo; // IPv6流信息 struct in6_addr sin6_addr; // 具體的IPv6地址 uint32_t sin6_scope_id; // 接口范圍ID};
(3)建立連接:connect()函數(shù)
函數(shù)原型:
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
參數(shù)與bind()的參數(shù)類似。
使用示例:
int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));
(4)監(jiān)聽:listen()函數(shù)
函數(shù)原型:
int listen(int sock, int backlog);
sock參數(shù):sock 為需要進(jìn)入監(jiān)聽狀態(tài)的套接字。
backlog參數(shù):backlog 為請求隊列的最大長度。
使用示例:
/* 進(jìn)入監(jiān)聽狀態(tài) */listen(ServerSock, 10);
(5)接收請求:accept()函數(shù)
函數(shù)原型:
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
sock參數(shù):sock 為服務(wù)器端套接字。
addr參數(shù):addr 為 sockaddr_in 結(jié)構(gòu)體變量。
addrlen參數(shù):addrlen 為參數(shù) addr 的長度,可由 sizeof() 求得。
返回值:一個新的套接字,用于與客戶端通信。
使用示例:
/* 監(jiān)聽客戶端請求,accept函數(shù)返回一個新的套接字,發(fā)送和接收都是用這個套接字 */int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);
(6)關(guān)閉:close()函數(shù)
函數(shù)原型:
int close(int fd);
fd:要關(guān)閉的文件描述符。
使用示例:
close(ServerSock);
(7)數(shù)據(jù)的接收和發(fā)送
數(shù)據(jù)收發(fā)函數(shù)有幾組:
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
函數(shù)原型:
ssize_t read(int fd, void *buf, size_t count);ssize_t write(int fd, const void *buf, size_t count);ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
這里介紹一下recv()/send()、recvfrom()/sendto()。
recv()函數(shù):
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd參數(shù):sockfd為要接收數(shù)據(jù)的套接字。
buf參數(shù):buf 為要接收的數(shù)據(jù)的緩沖區(qū)地址。
len參數(shù):len 為要接收的數(shù)據(jù)的字節(jié)數(shù)。
flags參數(shù):flags 為接收數(shù)據(jù)時的選項,常設(shè)為0。
send()函數(shù):
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd參數(shù):sockfd為要發(fā)送數(shù)據(jù)的套接字。
buf參數(shù):buf 為要發(fā)送的數(shù)據(jù)的緩沖區(qū)地址。
len參數(shù):len 為要發(fā)送的數(shù)據(jù)的字節(jié)數(shù)。
flags參數(shù):flags 為發(fā)送數(shù)據(jù)時的選項,常設(shè)為0。
recvfrom()函數(shù):
ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
sock:用于接收UDP數(shù)據(jù)的套接字;
buf:保存接收數(shù)據(jù)的緩沖區(qū)地址;
nbytes:可接收的最大字節(jié)數(shù)(不能超過buf緩沖區(qū)的大小);
flags:可選項參數(shù),若沒有可傳遞0;
from:存有發(fā)送端地址信息的sockaddr結(jié)構(gòu)體變量的地址;
addrlen:保存參數(shù) from 的結(jié)構(gòu)體變量長度的變量地址值。
sendto()函數(shù):
ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
sock:用于傳輸U(kuò)DP數(shù)據(jù)的套接字;
buf:保存待傳輸數(shù)據(jù)的緩沖區(qū)地址;
nbytes:帶傳輸數(shù)據(jù)的長度(以字節(jié)計);
flags:可選項參數(shù),若沒有可傳遞0;
to:存有目標(biāo)地址信息的 sockaddr 結(jié)構(gòu)體變量的地址;
addrlen:傳遞給參數(shù) to 的地址值結(jié)構(gòu)體變量的長度。
2、windows下的socket API接口
跟Linux下的差不多:
SOCKET socket(int af, int type, int protocol);int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);int listen(SOCKET sock, int backlog);SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); int closesocket( SOCKET s);int send(SOCKET sock, const char *buf, int len, int flags);int recv(SOCKET sock, char *buf, int len, int flags);int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen);int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);
3、TCP、UDP通信的socket編程過程圖
(1)TCP通信socket編程過程
(2)UDP通信socket編程過程
四、socket的應(yīng)用實例
1、基于TCP的本地客戶端、服務(wù)端信息交互實例
本例的例子實現(xiàn)的功能為:本地TCP客戶端往本地TCP服務(wù)端發(fā)送數(shù)據(jù),TCP服務(wù)端收到數(shù)據(jù)則會打印輸出,同時把原數(shù)據(jù)返回給TCP客戶端。這個例子類似于我們在做單片機(jī)的串口實驗時,串口上位機(jī)往我們的單片機(jī)發(fā)送數(shù)據(jù),單片機(jī)收到數(shù)據(jù)則把該數(shù)據(jù)原樣返回給上位機(jī)。
(1)windows的程序:
服務(wù)端程序tcp_server.c:
#include
客戶端程序tcp_client.c:
#include
我們上邊的IP地址概念那一部分中,有強(qiáng)調(diào)127.0.0.1這個IP是一個特殊的IP地址,這是回送地址,即本地機(jī),一般用來測試使用。這個例子中我們就用到了。此外,端口我們設(shè)置為1314,這是隨意設(shè)置的,只要范圍在1024~65536之間就可以。
本文使用的是gcc編譯器編譯,編譯命令如下:
gcc tcp_client.c -o tcp_client.exe -lwsock32gcc tcp_server.c -o tcp_server.exe -lwsock32
這里必須要加-lwsock32這個參數(shù)用于鏈接windows下socket編程必須的winsock2這個庫。若是使用集成開發(fā)環(huán)境,則需要把wsock32.lib放在工程目錄下,并在我們代碼中#include
實驗現(xiàn)象:
先啟動服務(wù)端程序tcp_server.exe,再啟動客戶端程序tcp_client.exe,并在客戶端中輸入字符串,則當(dāng)服務(wù)端會接收到字符串時會打印輸出,與此同時也會往客戶端返回相同的數(shù)據(jù):
(2)Linux的程序:
在linux下,“一切都是文件”,所以這里我們的套接字也當(dāng)做文件來看待。
服務(wù)端程序linux_tcp_server.c:
#include
客戶端程序linux_tcp_client.c:
#include
Linux下編譯就不需要添加-lwsock32參數(shù):
gcc linux_tcp_server.c -o linux_tcp_servergcc linux_tcp_client.c -o linux_tcp_client
實驗現(xiàn)象:
在調(diào)試這份程序時,出現(xiàn)了綁定錯誤:
經(jīng)上網(wǎng)查詢發(fā)現(xiàn)是端口重復(fù)使用,可以在調(diào)用bind()函數(shù)之前調(diào)用setsockopt()函數(shù)以解決端口重復(fù)使用的問題:
2、基于UDP的本地客戶端、服務(wù)端信息交互實例
(1)windows的程序
服務(wù)端程序udp_server.c:
#include
客戶端程序udp_client.c:
#include
(2)Linux下的程序
服務(wù)端程序linux_udp_server.c:
#include
客戶端程序linux_udp_client.c:
#include
實驗現(xiàn)象:
實驗現(xiàn)象如實例1。
五、總結(jié)
本筆記簡單介紹了一些與socket編程相關(guān)的一些知識點(diǎn):IP地址,什么是端口,協(xié)議等。重點(diǎn)介紹了TCP、UDP通信的一些原理及其API接口的用法,并給出了windows和linux下的TCP、UDP通信實例。以上就是關(guān)于socket編程的一些總結(jié),如有錯誤,歡迎指出!
-
Socket
+關(guān)注
關(guān)注
0文章
212瀏覽量
34902 -
TCP
+關(guān)注
關(guān)注
8文章
1378瀏覽量
79328 -
UDP
+關(guān)注
關(guān)注
0文章
327瀏覽量
34056
發(fā)布評論請先 登錄
相關(guān)推薦
評論