學習要由淺入深、由易到難,分析Linux內核中網絡部分就要從內核對外提供的socket封裝接口提及,典型以TCP協議C/S方式socket通訊大體過程如圖所示:linux
(圖片來源於網絡)git
從圖中能夠看到TCP服務端server的初始化過程複雜一些,就像開一個小賣鋪,你要登記爲個體工商戶其中最重要的就是營業地址(也就是bind綁定IP地址和端口號),而後就能夠開門營業了(listen),營業須要有營業員在那等着接待客戶(也就是accept),這樣就完成了TCP服務端server的初始化。github
TCP客戶端client的初始化比較簡單一些,就像你要去小賣鋪買東西,你只要知道小賣鋪的營業地址(IP地址和端口號),就能夠去買東西了(connect)。編程
客戶端connect服務端accept對接上了,客戶和營業員就能夠談生意,你一句我一句(send和recv),達成交易客戶端close離場,服務端繼續等着接待客戶(也就是accept)。網絡
接下來以一個簡單代碼hello/hi範例來具體瞭解TCP協議C/S方式socket通訊代碼。
首先看服務端程序代碼,來一個客戶就reply hi。app
#include"syswrapper.h" #define MAX_CONNECT_QUEUE 1024 int main() { char szBuf[MAX_BUF_LEN] = "\0"; char szReplyMsg[MAX_BUF_LEN] = "hi\0"; InitializeService(); while(1) { ServiceStart(); RecvMsg(szBuf); SendMsg(szReplyMsg); ServiceStop(); } ShutdownService(); return 0; }
而後看客戶端程序代碼,發送hello,接收hi。less
#include"syswrapper.h" #define MAX_CONNECT_QUEUE 1024 int main() { char szBuf[MAX_BUF_LEN] = "\0"; char szMsg[MAX_BUF_LEN] = "hello\0"; OpenRemoteService(); SendMsg(szMsg); RecvMsg(szBuf); CloseRemoteService(); return 0; }
以上客戶端和服務端代碼咱們都作了簡單的封裝,實際上看不到具體的socket代碼,具體用到socket接口的代碼以下:socket
/********************************************************************/ /* Copyright (C) SSE-USTC, 2012 */ /* */ /* FILE NAME : syswraper.h */ /* PRINCIPAL AUTHOR : Mengning */ /* SUBSYSTEM NAME : system */ /* MODULE NAME : syswraper */ /* LANGUAGE : C */ /* TARGET ENVIRONMENT : Linux */ /* DATE OF FIRST RELEASE : 2012/11/22 */ /* DESCRIPTION : the interface to Linux system(socket) */ /********************************************************************/ /* * Revision log: * * Created by Mengning,2012/11/22 * */ #ifndef _SYS_WRAPER_H_ #define _SYS_WRAPER_H_ #include<stdio.h> #include<arpa/inet.h> /* internet socket */ #include<string.h> //#define NDEBUG #include<assert.h> #define PORT 5001 #define IP_ADDR "127.0.0.1" #define MAX_BUF_LEN 1024 /* private macro */ #define PrepareSocket(addr,port) \ int sockfd = -1; \ struct sockaddr_in serveraddr; \ struct sockaddr_in clientaddr; \ socklen_t addr_len = sizeof(struct sockaddr); \ serveraddr.sin_family = AF_INET; \ serveraddr.sin_port = htons(port); \ serveraddr.sin_addr.s_addr = inet_addr(addr); \ memset(&serveraddr.sin_zero, 0, 8); \ sockfd = socket(PF_INET,SOCK_STREAM,0); #define InitServer() \ int ret = bind( sockfd, \ (struct sockaddr *)&serveraddr, \ sizeof(struct sockaddr)); \ if(ret == -1) \ { \ fprintf(stderr,"Bind Error,%s:%d\n", \ __FILE__,__LINE__); \ close(sockfd); \ return -1; \ } \ listen(sockfd,MAX_CONNECT_QUEUE); #define InitClient() \ int ret = connect(sockfd, \ (struct sockaddr *)&serveraddr, \ sizeof(struct sockaddr)); \ if(ret == -1) \ { \ fprintf(stderr,"Connect Error,%s:%d\n", \ __FILE__,__LINE__); \ return -1; \ } /* public macro */ #define InitializeService() \ PrepareSocket(IP_ADDR,PORT); \ InitServer(); #define ShutdownService() \ close(sockfd); #define OpenRemoteService() \ PrepareSocket(IP_ADDR,PORT); \ InitClient(); \ int newfd = sockfd; #define CloseRemoteService() \ close(sockfd); #define ServiceStart() \ int newfd = accept( sockfd, \ (struct sockaddr *)&clientaddr, \ &addr_len); \ if(newfd == -1) \ { \ fprintf(stderr,"Accept Error,%s:%d\n", \ __FILE__,__LINE__); \ } #define ServiceStop() \ close(newfd); #define RecvMsg(buf) \ ret = recv(newfd,buf,MAX_BUF_LEN,0); \ if(ret > 0) \ { \ printf("recv \"%s\" from %s:%d\n", \ buf, \ (char*)inet_ntoa(clientaddr.sin_addr), \ ntohs(clientaddr.sin_port)); \ } #define SendMsg(buf) \ ret = send(newfd,buf,strlen(buf),0); \ if(ret > 0) \ { \ printf("rely \"hi\" to %s:%d\n", \ (char*)inet_ntoa(clientaddr.sin_addr), \ ntohs(clientaddr.sin_port)); \ } #endif /* _SYS_WRAPER_H_ */
這裏經過宏定義的方式對socket接口作了簡單的封裝,封裝起來有兩個好處:一是把全部和socket有關的代碼放在一塊兒便於維護和移植,另外一個是使得上層代碼的業務過程更清晰。固然這裏與咱們理解socket接口的關係不太大,能理解socket的通訊過程就好。ide
這段代碼裏涉及了socket接口的相關內容,好比網絡地址的結構體變量、socket函數及其參數等,須要咱們仔細研究瞭解他們的具體做用。函數
通常在linux環境下/usr/include/bits/socket.h或/usr/include/sys/socket.h能夠看到sockaddr的結構體聲明。
/* Structure describing a generic socket address. */ struct sockaddr { __SOCKADDR_COMMON (sa_); /* Common data: address family and length. */ char sa_data[14]; /* Address data. */ };
這是一個通用的socket地址能夠兼容不一樣的協議,固然包括基於TCP/IP的互聯網協議,爲了方便起見互聯網socket地址的結構提供定義的更具體見/usr/include/netinet/in.h文件中的struct sockaddr_in。
/* Structure describing an Internet socket address. */ struct sockaddr_in { __SOCKADDR_COMMON (sin_); in_port_t sin_port; /* Port number. */ struct in_addr sin_addr; /* Internet address. */ /* Pad to size of `struct sockaddr'. */ unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; };
sockaddr和sockaddr_in的關係有點像面向對象編程中的父類和子類,子類從新定義了父類的地址數據格式。同一塊數據咱們根據須要使用兩個不一樣的結構體變量來存取數據內容,這也是最簡單的面向對象編程中的多態特性的實現方法。
在/usr/include/bits/socket.h或/usr/include/sys/socket.h中通常能夠找到AF_INET和PF_INET的宏定義以下。
/* Protocol families. */ ... #define PF_INET 2 /* IP protocol family. */ ... /* Address families. */ ... #define AF_INET PF_INET ...
儘管他們的值相同,但它們的含義是不一樣的,網上不少代碼將AF_INET和PF_INET混用,若是您瞭解他們的含義就不會隨便混用了,根據以下注釋能夠看到A表明Address families,P表明Protocol families,也就是說當表示地址時用AF_INET,表示協議時用PF_INET。參見咱們實驗室代碼中的使用方法,「serveraddr.sin_family = AF_INET;」中使用AF_INET,而「sockfd = socket(PF_INET,SOCK_STREAM,0);」中使用PF_INET。
在/usr/include/bits/socket_type.h能夠找到「__socket_type」,不一樣協議族通常都會定義不一樣的類型的通訊方式,對於基於TCP/IP的互聯網協議族(即PF_INET),面向鏈接的TCP協議的socket類型即爲SOCK_STREAM,無鏈接的UDP協議即爲SOCK_DGRAM,而SOCK_RAW 工做在網絡層。SOCK_RAW 能夠處理ICMP、IGMP等網絡報文、特殊的IPv4報文等。
/* Types of sockets. */ enum __socket_type { SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams. */ #define SOCK_STREAM SOCK_STREAM SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams of fixed maximum length. */ #define SOCK_DGRAM SOCK_DGRAM SOCK_RAW = 3, /* Raw protocol interface. */ #define SOCK_RAW SOCK_RAW SOCK_RDM = 4, /* Reliably-delivered messages. */ #define SOCK_RDM SOCK_RDM SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based, datagrams of fixed maximum length. */ ...
如上幾點對於咱們後續進一步理解和分析Linux網絡代碼比較重要,代碼中涉及的其餘接口及參數能夠在實驗過程當中自行查閱相關資料。
本實驗環境見 https://www.shiyanlou.com/courses/1198#labs
以上代碼能夠clone linuxnet.git並參照以下指令編譯執行代碼:
shiyanlou:~/ $ cd cd LinuxKernel shiyanlou:Code/ $ git clone shiyanlou:Code/ $ cd linuxnet shiyanlou:linuxnet/ (master) $ cd lab1 shiyanlou:lab1/ (master) $ ls client.c server.c syswrapper.h shiyanlou:lab1/ (master) $ make shiyanlou:lab1/ (master*) $ ./server recv "hello" from 127.0.0.1:58911 send "hi" to 127.0.0.1:58911
右擊水平分割Xfce終端(Terminal),執行client
shiyanlou:lab1/ (master*) $ ./client send "hi" to 0.0.0.0:60702 recv "hi" from 0.0.0.0:60702 shiyanlou:lab1/ (master*) $
本博文摘取自專欄《庖丁解牛Linux網絡核心》,如今訂閱,搶200個早鳥名額!
首先聲明本專欄的目標並非幫助你們得到當即可能使用的專業技能,而是但願能經過研究分析Linux內核中網絡部分的代碼實現來深入理解互聯網運做的核心機制,看完本專欄預期能夠達成以下目標:
從總體上理解互聯網運做的方式;能分析上網打開一個網頁的過程當中互聯網底層具體作了哪些工做,從而在遇到網絡相關問題時能獨立分析定位問題;因爲咱們涉及的實驗都是在Linux系統完成的,您還會進一步熟悉Linux系統;分析Linux內核中網絡部分固然也少不了對網絡協議及RFC文檔的討論,相信您也能對網絡標準有更多的瞭解。