Linux Socket基础介绍
Linux Socket函數(shù)庫是從Berkeley大學(xué)開發(fā)的BSD UNIX系統(tǒng)中移植過來的。BSD Socket接口是眾多Unix系統(tǒng)中被廣泛支持的TCP/IP通信接口,Linux下的Socket程序設(shè)計,除了微小的差別之外,也適用于大多數(shù)其它Unix系統(tǒng)。
Socket接口是TCP/IP網(wǎng)絡(luò)的API,Socket接口定義了許多函數(shù)或例程,程序員可以用它們來開發(fā)TCP/IP網(wǎng)絡(luò)上的應(yīng)用程序。網(wǎng)絡(luò)的Socket數(shù)據(jù)傳輸是一種特殊的I/O,Socket也是一種文件描述符。
Socket的使用和文件操作比較類似。如同文件的讀、寫、打開、關(guān)閉等操作一樣,TCP/IP網(wǎng)絡(luò)通信同樣也有這些操作,不過它使用的接口不是文件描述符或者FILE*,而是一個稱做Socket的描述符。類似于文件操作,對于Socket,也通過讀、寫、打開、關(guān)閉操作來進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)傳送。同時,還有一些輔助的函數(shù),如域名/IP地址查詢、Socket功能設(shè)置等。
套接字有三種類型:流式套接字、數(shù)據(jù)報套接字及原始套接字。
流式套接字定義了一種可靠的面向連接的服務(wù),實(shí)現(xiàn)了無差錯無重復(fù)的順序數(shù)據(jù)傳輸。數(shù)據(jù)報套接字定義了一種無連接的服務(wù),數(shù)據(jù)通過相互獨(dú)立的報文進(jìn)行傳輸,是無序的,并且不保證可靠、無差錯。原始套接字允許對底層協(xié)議如IP或ICMP直接訪問,主要用于新的網(wǎng)絡(luò)協(xié)議實(shí)現(xiàn)的測試等。
套接字工作過程如下:服務(wù)器首先啟動,通過調(diào)用socket()建立一個套接字,然后調(diào)用bind()將該套接字和本地網(wǎng)絡(luò)地址聯(lián)系在一起,再調(diào)用listen()使套接字做好偵聽的準(zhǔn)備,并規(guī)定它的請求隊列的長度,之后就調(diào)用accept()來接收連接。客戶在建立套接字后就可以調(diào)用connect()和服務(wù)器建立連接。連接一旦建立,客戶機(jī)和服務(wù)器之間就可以通過調(diào)用read()和write()來發(fā)送和接收數(shù)據(jù)。最后,待數(shù)據(jù)傳送結(jié)束后,雙方調(diào)用close()關(guān)閉套接字。
Socket函數(shù)庫:
1.?socket(int domain, int type,int protocol):此函數(shù)分配一個Socket句柄,用于指定特定網(wǎng)絡(luò)下,使用特定的協(xié)議和數(shù)據(jù)傳送方式進(jìn)行通信。Socket句柄分配以后,如果要開始TCP通信,還需要建立連接。根據(jù)需要,可以主動地建立連接(通過connect())和被動地等待對方建立連接(通過listen()),在連接建立后才能使用讀寫操作通過網(wǎng)絡(luò)連接進(jìn)行數(shù)據(jù)交換。
domain參數(shù)選擇通信中使用的協(xié)議族,也就是網(wǎng)絡(luò)的類型,可以是以下之一:(1)、AF_UNIX:UNIX內(nèi)部協(xié)議;(2)、AF_INET:ARPA Internet協(xié)議,也就是TCP/IP協(xié)議族;(3)、AF_ISO:ISO協(xié)議;(4)、AF_NS:Xerox Network Systems協(xié)議;(5)、AF_IMPLINK:IMP “host at IMP” link layer。
type參數(shù)為數(shù)據(jù)傳送的方式,可以是以下之一:(1)、SOCK_STREAM:保證順序的、可靠傳送的雙向字節(jié)數(shù)據(jù)流,最為常用,也是TCP連接所使用的方式;(2)、SOCK_DGRAM:無連接的、不保證可靠的、固定長度(通常很小)的消息傳送;(3)、SOCK_SEQPACKET:順序的、可靠的雙向固定長度的數(shù)據(jù)報傳送,只用于AF_NS類型的網(wǎng)絡(luò)中;(4)、SOCK_RAW:原始的數(shù)據(jù)傳送,適用于系統(tǒng)內(nèi)部專用的網(wǎng)絡(luò)協(xié)議和接口,和SOCK_RDM一樣,只能由超級用戶使用;(5)、SOCK_RDM:可靠的數(shù)據(jù)報傳送,未實(shí)現(xiàn)。
protocol參數(shù)指定通信中使用的協(xié)議。在給定Socket的協(xié)議族和傳送類型之后,一般情況下所使用的協(xié)議也就固定下來,此時protocol參數(shù)可使用缺省值”0”。但如果還有多個協(xié)議供選擇,則必須使用protocol參數(shù)來標(biāo)識。
socket()函數(shù),正常執(zhí)行時,返回Socket描述符;否則,返回-1,錯誤狀態(tài)在全局變量errno中。
2. close(int fd):Socket和文件描述符的關(guān)閉操作都是使用這個函數(shù)。fd參數(shù)為Socket描述符。返回值,正常是返回0,-1表示出錯。
3. bind(int sockfd, structsockaddr *my_addr, int addrlen):此函數(shù)給已經(jīng)打開的Socket指定本地地址。這個函數(shù)的使用有以下兩種情況:(1)、如果此Socket是面向連接的,而且此Socket在連接建立過程中處于被動的地位,即,乙方程序使用listen函數(shù)等待對方建立連接,對方用connect函數(shù)來向此Socket建立連接,這種情況下,必須用bind給此Socket設(shè)定本地地址。在乙方使用listen函數(shù)時,除指定Socket描述符之外,該Socket必須已經(jīng)用bind函數(shù)設(shè)定好了本地地址(包括IP地址和端口號),這樣,系統(tǒng)在收到建立連接的網(wǎng)絡(luò)請求時,才能根據(jù)請求的目的地址,識別是通向哪個Socket的連接,從而乙方才能用此Socket接收到發(fā)給此Socket地址的數(shù)據(jù)包。不指定Socket的本地地址,就無法將此Socket用于連接建立和數(shù)據(jù)接收。(2)、如果此Socket用于無連接的情形,同樣也要求給該Socket設(shè)定本地地址,這樣,以后系統(tǒng)從網(wǎng)絡(luò)中接收到數(shù)據(jù)后,才知道該送給哪個Socket及其相對應(yīng)的進(jìn)程。
sockfd參數(shù):用于指定Socket描述符。
addrlen參數(shù):my_addr結(jié)構(gòu)的長度。
my_addr參數(shù):用于偵聽連接請求的本地地址。struct sockaddr是一個通用性的結(jié)構(gòu),不僅包含TCP/IP協(xié)議的情況,同時也是為了適合于其它網(wǎng)絡(luò),如AF_NS。由于它的這種通用性,它只是定義了一個一般意義上的存儲空間。
bind函數(shù)返回值:正常時返回0,否則返回-1,同時errno是系統(tǒng)錯誤碼。
4. listen(int s, int backlog):準(zhǔn)備接收連接請求。在用bind()給一個Socket設(shè)定本地地址后,就可以將這個Socket用于接收連接請求,即listen()。調(diào)用listen()之后,系統(tǒng)將給此Socket配備一個連接請求的隊列,暫存系統(tǒng)接收到的、申請向此Socket建立連接的請求,等待用戶程序用accept()正式接收該請求。隊列長度,就由backlog參數(shù)指定。如果短時間內(nèi)向乙方建立連接的請求過多,乙方來不及處理,那么排在backlog之后的請求將被系統(tǒng)拒絕。因此,backlog參數(shù)實(shí)際上規(guī)定了乙方程序能夠容許的連接建立處理速度。至于乙方程序使用此Socket(及其指定的本地地址)實(shí)際建立連接的個數(shù),由乙方程序調(diào)用accept()的次數(shù)來決定。
listen函數(shù)返回值:正常時返回0,否則返回-1.
5. accept(int s, struct sockaddr*addr, int *addrlen):接受指定Socket上的連接請求。在調(diào)用listen()之后,系統(tǒng)就在Socket的連接請求暫存隊列里存放每一個向該Socket(及其本地地址)建立的連接請求。accept()函數(shù)的作用就是,從該暫存隊列中取出一個連接請求,用該Socket的數(shù)據(jù),創(chuàng)建一個新的Socket:Socket_New,并為它分配一個文件描述符。Socket_New即標(biāo)識了此次建立的連接,可被乙方用來向連接的另一方發(fā)送和接收數(shù)據(jù)(write/read, send/recv)。同時,原Socket仍然保持打開狀態(tài)不變,繼續(xù)用于等待網(wǎng)絡(luò)連接請求。
如果該Socket的暫存隊列中沒有待處理的連接請求,根據(jù)Socket的特征選項(是否non_blocking),blocking即阻塞,accept()函數(shù)將選擇兩種方式:如果該Socket不是non_blocking型的,accept()將一直等待,直到收到一個連接請求后才返回;如果該Socket是non_blocking型的,那么accept()將立即返回,但如果沒有連接請求,只返回錯誤信息,不創(chuàng)建新的Socket_New。accept()返回后,如果創(chuàng)建了新的Socket_New來標(biāo)識新建立的連接,那么參數(shù)addr指定的結(jié)構(gòu)里面將會有對方的地址信息,addrlen是地址信息的長度。
s參數(shù):Socket描述符。
addr參數(shù):accept()接受連接后,在addr指向的結(jié)構(gòu)中存放對方的地址信息。如果是AF_INET Socket,該地址信息就是地方的IP地址和端口號。
addrlen參數(shù):在調(diào)用accept()之前,*addrlen必須被設(shè)置為addr數(shù)據(jù)結(jié)構(gòu)的合法長度。在accept()返回之后,*addrlen中是對方地址信息的長度。
accept函數(shù)返回值:如果正常創(chuàng)建了一個新的連接,那么返回非負(fù)的整數(shù),即新連接的Socket描述符(注意,用于等待連接請求的原Socket保持打開狀態(tài)不變,可用于接收新的連接請求),否則,返回-1.
bind、listen、accept等函數(shù),都是用于被動地等待對方建立連接時需要使用的,而connect函數(shù),則是主動地向?qū)Ψ浇⑦B接時使用的。connect()使用一個事先打開的Socket,和目的方(即通信對方,或稱服務(wù)器一方)地址信息,向?qū)Ψ桨l(fā)出連接建立請求。
6. connect(int sockfd, structsockaddr *serv_addr, int addrlen):客戶端發(fā)送服務(wù)請求。
sockfd參數(shù):socket函數(shù)返回的socket描述符。
serv_addr:存儲遠(yuǎn)程計算機(jī)的IP地址和端口信息的結(jié)構(gòu)。
addrlen:是結(jié)構(gòu)體sockaddr_in的長度。
返回值:成功返回0,否則返回-1.
7. send(int s, const void *msg,int len, unsigned int flags)/sendto(int s, const void *msg, int len, unsignedint flags, const struct sockaddr *to, int tolen),recv(int s, void*buf, int len, unsigned iint flags)/recvfrom(int s, void *buf, int len,unsigned int flags, struct sockaddr *from, int *fromlen):用Socket發(fā)送和接收數(shù)據(jù)。在連接建立完成后,通信雙方就可以使用以上這些函數(shù)來進(jìn)行數(shù)據(jù)的發(fā)送和接收操作。其中,send和recv用于連接建立以后的發(fā)送和接收。sendto和recvfrom用于非連接的協(xié)議。對于非non_blocking型的Socket,send將等待數(shù)據(jù)發(fā)送完后才返回;對于non_blocking型的Socket,send將立即返回,用戶程序需要用select()函數(shù)決定網(wǎng)絡(luò)發(fā)送是否結(jié)束。類似地,對于非non_blocking型的Socket,若系統(tǒng)沒有收到任何數(shù)據(jù),recv將等待接收數(shù)據(jù)到達(dá)后才返回;對于non_blocking型的Socket,recv將立即返回,并返回錯誤信息或接收到的數(shù)據(jù)字節(jié)數(shù)。sendto和recvfrom因?yàn)槭欠沁B接型的發(fā)送和接收,必須在參數(shù)中給出目的地址或者存放源地址的空間。
s參數(shù):Socket描述符。
msg,buf參數(shù):存放接收或者發(fā)送數(shù)據(jù)的存儲空間。
len參數(shù):接收或者發(fā)送數(shù)據(jù)的字節(jié)數(shù)。
to,from參數(shù):sendto和recvfrom所使用的,目的方地址和存放源地址的空間。
tolen,fromlen參數(shù):目的地址和源地址空間大小。
flag參數(shù):通常設(shè)為0.
返回值:send/sendto返回實(shí)際發(fā)送的數(shù)據(jù)字節(jié)數(shù),或者-1,表示出錯。recv/recvfrom返回實(shí)際接收到的數(shù)據(jù)字節(jié)數(shù),或者-1,表示出錯。
8. read(int fd, void *buf, size_tcount)/write(int fd, const void *buf, size_t count):用系統(tǒng)文件操作進(jìn)行Socket通信。在連接建立完成后,對于連接建立過程中被動的一方,在accept()正常返回后,它返回一個新的Socket,并且為該Socket分配了一個文件描述符;對于連接請求發(fā)起方,connect()正常返回后,相應(yīng)的Socket中也包含有已分配的文件描述符。因此,可以使用標(biāo)準(zhǔn)的Unix文件讀寫函數(shù)read()/write()來進(jìn)行Socket通信。要注意的是,由于網(wǎng)絡(luò)數(shù)據(jù)和磁盤文件不一樣,不是已經(jīng)準(zhǔn)備好的,因此,每次讀寫操作不一定能傳送完指定長度的數(shù)據(jù),需要由程序反復(fù)進(jìn)行剩余部分的傳送。另外,文件描述符是較底層的文件操作函數(shù),不同于C語言中常用的FILE*。FILE*是使用fread/fwrite函數(shù)來進(jìn)行讀寫操作的。
fd參數(shù):文件或者Socket描述符。
buf參數(shù):數(shù)據(jù)緩沖區(qū)。
count參數(shù):數(shù)據(jù)字節(jié)數(shù)。
函數(shù)返回值:正常時,返回所讀寫的字節(jié)數(shù)(注意,可能小于count參數(shù)指定的數(shù)目);否則,返回-1.
9. getsockopt(int s, int level,int optname, void *optval, int *optlen)/setsockopt(int s, int level, intoptname, const void *optval, int optlen):獲取、設(shè)置Socket特征選項。
常用的幾個轉(zhuǎn)換函數(shù):(1)、inet_addr:將IP地址從點(diǎn)數(shù)格式轉(zhuǎn)換成無符號長整型,它返回的地址是網(wǎng)絡(luò)字節(jié)格式;(2)、inet_ntoa:將一個in_addr結(jié)構(gòu)體輸出成點(diǎn)數(shù)格式;(3)、htonl:將32位的主機(jī)字節(jié)順序轉(zhuǎn)化為32位的網(wǎng)絡(luò)字節(jié)順序;(4)、htons:將16位的主機(jī)字節(jié)順序轉(zhuǎn)化為16位的網(wǎng)絡(luò)字節(jié)順序;(5)、ntohs:將一個無符號短整形數(shù)從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)化為主機(jī)字節(jié)順序;(6)、ntohl:將一個無符號長整形數(shù)從網(wǎng)絡(luò)主機(jī)順序轉(zhuǎn)化為主機(jī)字節(jié)順序。
以下是測試用例:
1. test_client1.cpp:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <iostream>#define PORT 7000int main()
{struct sockaddr_in server;int s, ns;int pktlen, buflen;char buf1[256], buf2[256];s = socket(AF_INET, SOCK_STREAM, 0);server.sin_family = AF_INET;server.sin_port = htons(PORT);server.sin_addr.s_addr = htons(INADDR_ANY);if (connect(s, (struct sockaddr*)&server, sizeof(server)) < 0) {perror("connect()");return -1;}for (;;) {printf("Enter a line: ");std::cin>>buf1;buflen = strlen(buf1);if (buflen == 1)break;send(s, buf1, buflen+1, 0);recv(s, buf2, sizeof(buf2), 0);printf("Received line: %s\n", buf2);}close(s);return 0;
}
2. test_server1.cpp:
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>#define PORT 7000int main()
{struct sockaddr_in client, server;int s, ns, pktlen;char buf[256];s = socket(AF_INET, SOCK_STREAM, 0);memset((char*)&server, sizeof(server), 0);server.sin_family = AF_INET;server.sin_port = htons(PORT);server.sin_addr.s_addr = htons(INADDR_ANY);bind(s, (struct sockaddr*)&server, sizeof(server));listen(s, 1);socklen_t namelen = sizeof(client);ns = accept(s, (struct sockaddr*)&client, &namelen);for (;;) {pktlen = recv(ns, buf, sizeof(buf), 0);if (pktlen == 0)break;printf("Received line:%s\n", buf);for (int i = 0; i < strlen(buf); i++)buf[i] = toupper(buf[i]);send(ns, buf, pktlen, 0);}close(ns);close(s);return 0;
}
執(zhí)行說明:(1)、打開終端,分別執(zhí)行:$ g++ -o server server.cpp ?, $ g++ -o client client.cpp ; (2)、打開終端,先執(zhí)行服務(wù)器端程序:$ ./server ;再打開另一終端,執(zhí)行客戶端程序:$ ./client ?;(3)、程序功能:服務(wù)器端接收從客戶端來的數(shù)據(jù),并將其接收的數(shù)據(jù)小寫字母改為大寫再發(fā)送給客戶端,在客戶端顯示接收后的結(jié)果數(shù)據(jù)。當(dāng)輸入一個字符長度時退出。
注:以上部分內(nèi)容整理自網(wǎng)絡(luò)。
GitHub:https://github.com//fengbingchun/Linux_Code_Test
?
總結(jié)
以上是生活随笔為你收集整理的Linux Socket基础介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu 14.04 64位机上不带
- 下一篇: Linux进程编程基础介绍