linux socket ip层配置,Linux下Socket通信(TCP实现)
近期在做的項(xiàng)目中,涉及到了進(jìn)程間數(shù)據(jù)傳輸,系統(tǒng)的原本實(shí)現(xiàn)是通過管道,但是原有的實(shí)現(xiàn)中兩個(gè)進(jìn)程是在同一臺(tái)機(jī)器,而且兩個(gè)進(jìn)程的關(guān)系為父子關(guān)系,而我們要做的是將其中一個(gè)進(jìn)程移植到服務(wù)器上,因此兩個(gè)進(jìn)程要分開,所以管道必然是不可行的方案,而對(duì)于其它的進(jìn)程通信方式,FIFO,消息隊(duì)列,信號(hào)量和共享內(nèi)存,顯然也是不可行的。因此采取了通過socket的通信方式,即網(wǎng)絡(luò)套接字,用來做數(shù)據(jù)的傳輸。接下來,將對(duì)自己對(duì)socket的學(xué)習(xí)一個(gè)整理,socket是什么?socket的創(chuàng)建,綁定,發(fā)送,接收消息過程進(jìn)行分析,同時(shí)附帶一個(gè)簡(jiǎn)單的代碼實(shí)例。
網(wǎng)絡(luò)套接字Socket
套接字是通信端點(diǎn)的抽象,其英文socket,即為插座,孔的意思。如果兩個(gè)機(jī)子要通信,中間要通過一條線,這條線的兩端要連接通信的雙方,這條線在每一臺(tái)機(jī)子上的接入點(diǎn)則為socket,即為插孔,所以在通信前,我們?cè)谕ㄐ诺膬啥吮仨氁⒑眠@個(gè)插孔,同時(shí)為了保證通信的正確,端和端之間的插孔必須要一一對(duì)應(yīng),這樣兩端便可以正確的進(jìn)行通信了,而這個(gè)插孔對(duì)應(yīng)到我們實(shí)際的操作系統(tǒng)中,就是socket文件,我們?cè)賱?chuàng)建它之后,就會(huì)得到一個(gè)操作系統(tǒng)返回的對(duì)于該文件的描述符,然后應(yīng)用程序可以通過使用套接字描述符訪問套接字,向其寫入輸入,讀出數(shù)據(jù)。
站在更貼近系統(tǒng)的層級(jí)去看,兩個(gè)機(jī)器間的通信方式,無非是要通過運(yùn)輸層的TCP/UDP,網(wǎng)絡(luò)層IP,因此socket本質(zhì)是編程接口(API),對(duì)TCP/UDP/IP的封裝,TCP/UDP/IP也要提供可供程序員做網(wǎng)絡(luò)開發(fā)所用的接口,這就是Socket編程接口。
Socket的創(chuàng)建
#include
int socket (int domain, int type, int protocol);
創(chuàng)建一個(gè)socket
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
這樣,我們便創(chuàng)建了一個(gè)socket,對(duì)于socket接收的參數(shù)都有什么意義呢?從上面,我們可以知道socket是對(duì)于底層網(wǎng)絡(luò)通信的一個(gè)封裝,而對(duì)于底層的網(wǎng)絡(luò)通信也是具備多種類型的。而這些參數(shù)則是通過組合來表示各類通信的特征,從而建立正確的套接字。
domain:通信的特性,每個(gè)域有自己的地址表示格式,AF打頭,表示地址族(Address family)
type:套接字的類型,進(jìn)一步確定通信特征。
protocol:表示為給定域和套接字類型選擇默認(rèn)協(xié)議,當(dāng)對(duì)同一域和套接字類型支持多個(gè)協(xié)議時(shí),可以通過該字段來選擇一個(gè)特定協(xié)議,通常默認(rèn)為0.上面設(shè)置的socket類型,在執(zhí)行的時(shí)候也會(huì)有默認(rèn)的協(xié)議類型提供,比如SOCK_STREAM就TCP協(xié)議。
從上面的socket類型中,我們看到有SOCK_RAW該種類型,SOCK_RAW套接字提供一個(gè)數(shù)據(jù)報(bào)接口。通過這個(gè)我們可以直接訪問下面的網(wǎng)絡(luò)層,繞過TCP/UDP,因此我們可以進(jìn)行制定自己的傳輸層協(xié)議。
至此,我們的socket已經(jīng)創(chuàng)建出來了,當(dāng)我們不再使用的時(shí)候,我們可以調(diào)用close函數(shù)來將其關(guān)閉,釋放該文件描述符,這樣便可以得到重新的使用。
套接字通信是雙向的,但是,我們可以采用shutdown函數(shù)來禁止一個(gè)套接字的I/O.
#include
int shutdown(int sockfd, int how);
how可以用來指定讀端口或者是寫端口,這樣我們便可以關(guān)閉掉讀端或者寫端。
通信
我么已經(jīng)創(chuàng)建好了Socket,接下來要做的就是通過socket進(jìn)行通信了,在兩個(gè)進(jìn)程間進(jìn)行通信,首先,我們要找到這些進(jìn)程,找到進(jìn)程,也就是能夠有這些進(jìn)程的唯一標(biāo)示,有了這些標(biāo)示,我們才可以確定通信的雙方,然后進(jìn)行數(shù)據(jù)的傳輸,對(duì)于一個(gè)通信進(jìn)程的標(biāo)示,所采取的方式是通過一個(gè)網(wǎng)絡(luò)地址,也就是IP地址,戰(zhàn)找到我們要通信的主機(jī),然后通過端口號(hào),找到相應(yīng)的服務(wù)。網(wǎng)絡(luò)地址+端口號(hào)唯一標(biāo)示了一個(gè)我們要通信的目標(biāo)進(jìn)程。
字節(jié)序
字節(jié)序是處理器架構(gòu)的特性,用來指示像整數(shù)這種數(shù)據(jù)類型的內(nèi)部如何排序,大端和小端,因此如果通信雙方的處理器架構(gòu)不同,則會(huì)導(dǎo)致字節(jié)序的不一致的問題出現(xiàn)。最底層的網(wǎng)絡(luò)協(xié)議指定了字節(jié)序,大端字節(jié)序,但是應(yīng)用程序在處理數(shù)據(jù)時(shí),則會(huì)遇到字節(jié)序不一致的問題。對(duì)此,系統(tǒng)提供了進(jìn)行處理器字節(jié)序和網(wǎng)絡(luò)字節(jié)序之間實(shí)施轉(zhuǎn)換的函數(shù)。
#include
uint32_t htonl(uint32_t hostint32)//主機(jī)字節(jié)轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序
uint16_t htons(uint16_t hostint16)
uint32_t ntohl(uint32_t netint32)//網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)化為主機(jī)字節(jié)序
unint16_t ntohs(uint16_t netint16)
地址格式
上面,我們已經(jīng)談到如何表示一個(gè)要通信的進(jìn)程,需要一個(gè)網(wǎng)絡(luò)地址和端口,而在系統(tǒng)中如何具體的標(biāo)示這一特征呢?根據(jù)之前socket的創(chuàng)建,我們知道不同socket對(duì)應(yīng)了不同的通信特征,而對(duì)于不同的通信特征,其地址表示上也有一些差別。
這里我們只看一下IPV4因特網(wǎng)域地址的表示結(jié)構(gòu)。
struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr;}
sin_family: 通信的的域,這里為AF_INET
sin_port:通信的端口
sin_addr:網(wǎng)絡(luò)地址
套接字和地址關(guān)聯(lián)
我們套接字已經(jīng)創(chuàng)建好了,地址結(jié)構(gòu)也已經(jīng)了解了,接下來就是要將套接字和地址進(jìn)行關(guān)聯(lián),關(guān)聯(lián)的方法則是通過bind
函數(shù)。
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
創(chuàng)建地址
struct sockaddr_in server_sockaddr;server_sockaddr.sin_family = AF_INET;server_sockaddr.sin_port = htons(PORT);server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t server_len = sizeof(server_sockaddr);
bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)
通過bind函數(shù),我們實(shí)現(xiàn)了socket和地址的綁定。
建立連接
socket建立好了,地址也綁定好了,這個(gè)時(shí)候,我們就可以進(jìn)行連接了,要有一方進(jìn)行連接的建立,通過調(diào)用connect
函數(shù)。
#include int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
sockfd:這個(gè)就是本地端socket描述符,如果我們沒有賦值,系統(tǒng)會(huì)默認(rèn)提供一個(gè)值。只有當(dāng)服務(wù)器開啟,并正常運(yùn)行,我們的連接才能夠正常建立。
如何讓socket接收連接請(qǐng)求呢?在另一端,我們調(diào)用listen
方法來接收連接請(qǐng)求。
#include int listen(int sockfd, int backlog);
sockfd:綁定了地址的socket文件描述符。
backlog:服務(wù)器負(fù)載,提示系統(tǒng)進(jìn)程所要入隊(duì)的未完成請(qǐng)求數(shù)量。
通過listen我們得到了連接請(qǐng)求,接下來,就是建立連接,通過函數(shù)accept
#include
int accept(int sockfd, struct sockaddr *restric addr, socklen_t *restrict len);
調(diào)用accept函數(shù)的返回值是套接字文件描述符,該描述符連接到調(diào)用connect的客戶端。
一旦服務(wù)器調(diào)用了listen,所用的套接字就能接收連接請(qǐng)求,使用accept函數(shù)獲得連接請(qǐng)求并建立連接。
使用accept函數(shù)獲得連接請(qǐng)求并建立連接。
int accept(int sockfd, struct sockaddr *restrict addr, socklent_t *restrict len);
當(dāng)調(diào)用accept函數(shù)會(huì)產(chǎn)生一個(gè)新的套接字,這個(gè)新的套接字和原始套接字有相同的套接類型。這個(gè)時(shí)候,我們可以傳入一個(gè)指向socket的指針和其大小,設(shè)置之后,調(diào)用了accept就會(huì)將客戶端的地址進(jìn)行緩沖。
數(shù)據(jù)傳輸
連接已經(jīng)建立好了,由于socket本身都是文件描述符,因此接下來就可以調(diào)用所read和write來通過套接字通信。
對(duì)于面向連接的數(shù)據(jù)傳輸,我們需要的兩個(gè)函數(shù)是send和recv。
#include
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags)
sockfd:accept返回的socket文件描述符。
buf:發(fā)送數(shù)據(jù),
bytes:發(fā)送數(shù)據(jù)大小
flags:對(duì)于傳送數(shù)據(jù)的一些配置項(xiàng)
對(duì)于不同的socket類型,系統(tǒng)提供了不同的發(fā)送方法。
#include
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags)
具體參數(shù)和send類似。
socket選項(xiàng)設(shè)置
對(duì)于Socket,系統(tǒng)提供了更具體細(xì)致化的一些配置選項(xiàng),通過這些配置選項(xiàng),我們可以進(jìn)行進(jìn)一步具體的配置。
#include
int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
sockfd:我們要進(jìn)行配置的socket
level:根據(jù)我們選用的協(xié)議,配置相應(yīng)的協(xié)議編號(hào)
option:選項(xiàng)即為上表
最后參數(shù)是用來存放返回值
實(shí)現(xiàn)demo實(shí)例
server
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 22468
#define KEY 123
#define SIZE 1024
int main()
{
char buf[100];
memset(buf,0,100);
int server_sockfd,client_sockfd;
socklen_t server_len,client_len;
struct sockaddr_in server_sockaddr,client_sockaddr;
/*create a socket.type is AF_INET,sock_stream*/
server_sockfd = socket(AF_INET,SOCK_STREAM,0);
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
server_len = sizeof(server_sockaddr);
int on;
setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR,&on,sizeof(on));
/*bind a socket or rename a sockt*/
if(bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)==-1){
printf("bind error");
exit(1);
}
if(listen(server_sockfd, 5)==-1){
printf("listen error");
exit(1);
}
client_len = sizeof(client_sockaddr);
pid_t ppid,pid;
while(1) {
if((client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_sockaddr, &client_len)) == -1){
printf("connect error");
exit(1);
} else {
printf("create connection successfully\n");
int error = send(client_sockfd, "You have conected the server", strlen("You have conected the server"), 0);
printf("%d\n", error);
}
}
return 0;
}
client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 22468
#define MAXDATASIZE 100
#define SERVER_IP "Your IP"
int main() {
int sockfd, numbytes;
char buf[MAXDATASIZE];
struct sockaddr_in server_addr;
printf("\n======================client initialization======================\n");
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
bzero(&(server_addr.sin_zero),sizeof(server_addr.sin_zero));
if (connect(sockfd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) == -1){
perror("connect error");
exit(1);
}
while(1) {
bzero(buf,MAXDATASIZE);
printf("\nBegin receive...\n");
if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1){
perror("recv");
exit(1);
} else if (numbytes > 0) {
int len, bytes_sent;
buf[numbytes] = '\0';
printf("Received: %s\n",buf);
printf("Send:");
char msg[100];
scanf("%s",msg);
len = strlen(msg);
//sent to the server
if(send(sockfd, msg,len,0) == -1){
perror("send error");
}
} else {
printf("soket end!\n");
break;
}
}
close(sockfd);
return 0;
}
總結(jié)
最近也在看的一個(gè)RPC框架,thrift,定義好我們的接口文件,然后可以幫助我們生成兩端的樁文件,而且實(shí)現(xiàn)原理上,也不過是通過底層的socket通信做了包裝,執(zhí)行相應(yīng)的調(diào)用。socket通信在大三的OS課上寫過,本文主要目的記錄本次學(xué)習(xí),對(duì)于socket知識(shí)進(jìn)行了一個(gè)回顧。
總結(jié)
以上是生活随笔為你收集整理的linux socket ip层配置,Linux下Socket通信(TCP实现)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中with open写csv
- 下一篇: 农夫山泉被指虚假宣传 官方回应:在海拔高