工做以來,寫了不少socket相關的代碼。磕磕碰碰,走了不少彎路,也積累了一些東西,今天正好整理一下。爲了證實不是從書上抄來的,邏輯會有點亂(藉口,呵呵)!知識點的介紹也不會像書上說的那麼詳細和精準,畢竟我的水平也就這樣了。固然,主要仍是以上手爲主,不過度剖析原理性內容。一些陌生的函數要用到的頭文件,使用man查看一下就能解決了。既然該文的名稱爲「快速上手」,那我的認爲下述內容都不存在水分,都是必需要掌握的,一點都不能急躁!linux
對於程序員來講,開始的時候只會把socket編程當成一個工具,儘快上手,儘快解決戰鬥。因而乎最關心的就是socket那些函數的調用順序,那就先給出UDP/TCP的流程圖(從《UNIX網絡編程》)吧:程序員
有了流程圖,再找一些資料,就很容易寫出下面這樣的代碼(以TCP爲例):編程
服務器程序:服務器
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 10 #define PORT 1234 11 #define BACKLOG 5 12 #define MAXDATASIZE 1000 13 14 int main() 15 { 16 int listenfd, connectfd; 17 struct sockaddr_in server; 18 struct sockaddr_in client; 19 socklen_t addrlen; 20 char szbuf[MAXDATASIZE] = {0}; 21 22 if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 23 { 24 perror("Creating socket failed."); 25 exit(1); 26 } 27 28 bzero(&server, sizeof(server)); 29 server.sin_family = AF_INET; 30 server.sin_port = htons(PORT); 31 server.sin_addr.s_addr = htonl(INADDR_ANY); 32 if (bind(listenfd, (struct sockaddr *)&server, \ 33 sizeof (server)) == -1) 34 { 35 perror("Bind()error."); 36 exit(1); 37 } 38 if (listen(listenfd, BACKLOG) == -1) 39 { 40 perror("listen()error\n"); 41 exit(1); 42 } 43 44 addrlen = sizeof(client); 45 if ((connectfd = accept(listenfd, \ 46 (struct sockaddr*)&client, &addrlen)) == -1) 47 { 48 perror("accept()error\n"); 49 exit(1); 50 } 51 printf("You got a connection from cient's ip is %s, \ 52 prot is %d\n", inet_ntoa(client.sin_addr), \ 53 htons(client.sin_port)); 54 55 memset(szbuf, 'a', sizeof(szbuf)); 56 while (1) 57 { 58 send(connectfd, szbuf, sizeof(szbuf), 0); 59 } 60 61 close(connectfd); 62 close(listenfd); 63 64 return 0; 65 }
客戶端程序:網絡
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <string.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <netdb.h> 9 10 #define PORT 1234 11 #define MAXDATASIZE 1000 12 13 int main(int argc, char *argv[]) 14 { 15 int sockfd, num; 16 char buf[MAXDATASIZE + 1] = {0}; 17 struct sockaddr_in server; 18 19 if (argc != 2) 20 { 21 printf("Usage:%s <IP Address>\n", argv[0]); 22 exit(1); 23 } 24 25 if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1) 26 { 27 printf("socket()error\n"); 28 exit(1); 29 } 30 bzero(&server, sizeof(server)); 31 server.sin_family = AF_INET; 32 server.sin_port = htons(PORT); 33 server.sin_addr.s_addr = inet_addr(argv[1]); 34 if (connect(sockfd, (struct sockaddr *)&server, \ 35 sizeof(server)) == -1) 36 { 37 printf("connect()error\n"); 38 exit(1); 39 } 40 41 while (1) 42 { 43 memset(buf, 0, sizeof(buf)); 44 if ((num = recv(sockfd, buf, \ MAXDATASIZE,0)) == -1) 45 { 46 printf("recv() error\n"); 47 exit(1); 48 } 49 buf[num - 1]='\0'; 50 printf("Server Message: %s\n",buf); 51 } 52 53 close(sockfd); 54 55 return 0; 56 }
有了這些代碼,順利地話一次性就能編過,也都正常跑起來。有些人就認爲已經學會了socket,哈哈,當時我就是這樣!更多的狀況是,咱們參考資料所用的運行環境每每和咱們本身電腦不一致,就須要本身來修改代碼。而後就會發現代碼裏出現AF_INET、htons、inet_addr、struct sockaddr……這些徹底不知道幹什麼的東西。更有甚者,那人還不知道使用man,不喜歡看書,那就只能剩下猜謎了。運氣好一點,最後能瞎貓遇見死老鼠,要是運氣差一點,估計頭就大了。框架
Socket編程的關鍵函數天然不能不懂,但基礎知識一樣重要,不理解基礎知識到最後只會一團亂。書上寫了好幾頁,其實內容也並非太多。耐住性子弄明白,後面就會沒那麼添堵了。下面整理了三點:socket
通常編程而言,socket(特指socket這個系統調用)函數的第一個參數都使用AF_INET,表示是在IPv4因特網域。也能夠改變爲其它的值,用於IPv6網絡等。ide
第二個參數爲套接字類型,TCP連接使用SOCK_STREAM,UDP連接使用SOCK_DGRAM,基本就這兩個。若是想寫一個相似於ping這樣直接訪問網絡層的一個程序,那就要使用其餘類型了。函數
和1不一樣,1設置不對你就別想正確創建連接,可是字節序問題跳過去,可能開始並不會形成太大的影響。因此字節序轉換每每就被人忽略了。工具
字節序問題是由「大端系統」和「小端系統」這二者的差別形成的。網絡通訊時,咱們並不知道對端系統採用什麼樣的存儲方式,這個時候,就引入了一個網絡字節序的概念。網絡協議統一使用大端字節序,無論什麼類型的主機,在經過協議棧發送和接收時就知道數據需不須要轉換了。
另外,並非全部數據都須要轉換的。若是採用字節流(沒有相似int,short這種多字節格式化數據類型)方式進行通訊,無需字節序轉換,由於數據都是有序的,無論在什麼系統中順序都是一致的。
對於字節序的轉換關係,建議仍是本身畫圖理解一下,光動腦子想不是太容易理解。
字節序轉換函數很少,只有四個,而且很容易記憶,它們分別是:
htonl、htons、ntohl、ntohs
其中h表示「主機(host)字節序」,n表示「網絡(network)字節序」,l表示「長(long)整數(4個字節)」,s表示「短(short)整數(2個字節)」,to表示「從誰向誰轉換」……比Stevens的原著都詳細,這樣再沒法記住,還真是沒什麼辦法了。
有時也會涉及到更多字節的字節序轉換,好比long long類型(8個字節),其實,用上述函數也是有辦法可以完成的。
實際使用時,IPv4和IPv6使用的地址結構是不一樣的。IPv4使用struct sockaddr_in,然後者使用struct sockaddr_in。但不一樣網絡(好比IPv四、IPv6)編程使用的socket接口函數都是同樣的,這是怎麼作到的呢?答案是接口函數都使用了統一的地址結構struct sockaddr。這些地址結構的具體定義可使用man查看,能夠發現,這些結構的框架是相同的,在正確地使用方法下地址結構間能夠相互強制轉換。它們都符合
struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
這種形式。所以,不論是在哪一個網域下調用socket函數,只要將有差別的地址結構使用struct sockaddr進行強制類型轉換就能實現接口的統一了。
接着,針對具體的IPv4編程中地址的賦值詳細說明一下。上面提到,IPv4使用的地址結構及其定義爲
struct sockaddr_in {
short sin_family; /* Address family */
unsigned short sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
在linux下:
in_addr結構
typedef struct in_addr{
unsigned long s_addr;
};
上面說到協議棧都須要使用網絡字節序,sin_family天然是等於AF_INET(協議棧提供的值,不須要考慮字節序了)。sin_port(端口)是雙字節變量,須要使用htons轉字節序。sin_addr.s_addr(網絡地址),它不是使用192.168.1.2這種點分十進制形式,而是須要將點分十進制地址轉換成網絡字節序的一個整數。這時就須要對192.168.1.2這種地址進行轉換,系統提供了inet_addr函數(只能用於IPv4地址轉換)。sin_addr.s_addr賦值爲inet_addr(192.168.1.2)。能轉過去就必定能變回來,系統提供了inet_ntoa函數進行逆變換。inet_addr和inet_ntoa只能適用於IPv4地址轉換,系統還提供了inet_ntop和inet_pton函數,後兩個函數兼容IPv4和IPv6,使用起來更加方便。
掌握這些基礎知識以後寫起代碼來就不必定再須要拿着參考書來看了,這也是爲一個健壯、穩定的網絡程序作了熱身運動,這但是必不可少的。
原本想一篇就寫完的,寫到這發現內容還真沒想得那麼少,在下一章會講述網絡程序的一些細節問題,也都是必不可少的。