Linux下簡單的socket通訊實例html
If you spend too much time thinking about a thing, you’ll never get it done.git
—Bruce Leegithub
學習網絡編程也一段時間了,剛開始看《UNIX網絡編程》的時候,以爲這本厚厚的書好難啊!看到後來,發現並無想象中的那麼難。若是你是新手,建議你看到第二部分結束後,開始着手寫代碼。不寫代碼確定是不行的。看100遍也沒有敲一遍實現一遍來的清楚。敲完之後,帶着問題去看書,你會更加有針對性。提升的速度是飛快的,這也是學習任何一本書、一門語言的惟一手段。編程
寫這個博客也是由於剛開始學的時候,查了好多別人寫的東西,百度了之後,發現你們只是把全部的代碼一貼。並無講解每一個函數的功能。我甚至不知道哪一個函數是哪一個頭文件下的。形成我對函數很不理解。下面我會對每一個函數的功能,和它的頭文件以及函數原型寫出來,讓你們參考,第一次寫博客,有什麼錯誤的地方,但願你們指正。能夠在下面給我留言,也是我繼續寫下去的動力。服務器
我很但願和你們一塊兒分享學習網絡編程遇到的種種困難與不順,也但願和你們一塊兒討論其中遇到的問題,一塊兒成長,若是你剛開始打算學習網絡編程,那這篇文章必定能給你一些幫助。網絡
個人郵箱:cvmimi_linhai@foxmail.com,轉載請註明出處:http://www.cnblogs.com/yusenwu/p/4579167.html。socket
關於怎樣介紹這個簡單的實例:(基本上涵蓋了《UNIX網絡編程》1-5章的內容,更深,更細的,須要咱們再細讀這本書)函數
--> 一、代碼展現,功能介紹學習
--> 二、首先介紹一下客戶端和服務端中函數的功能以及函數的原形。網站
--> 三、關於鏈接三次握手和TCP鏈接關閉時候的分組交換
--> 四、IPv四、IPv6套接字的地址結構
--> 五、一些好的學習網站總結
--> 六、代碼下載
--> 七、總結
--> 八、實現一個echo的實例,代碼能夠到Github上下載
client.c
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <sys/types.h> 4 #include <stdlib.h> 5 #include <netinet/in.h> 6 #include <errno.h> 7 #include <string.h> 8 #include <arpa/inet.h> 9 #include <unistd.h> 10 #define MAXLINE 1024 11 int main(int argc,char **argv) 12 { 13 char *servInetAddr = "127.0.0.1"; 14 int socketfd; 15 struct sockaddr_in sockaddr; 16 char recvline[MAXLINE], sendline[MAXLINE]; 17 int n; 18 19 if(argc != 2) 20 { 21 printf("client <ipaddress> \n"); 22 exit(0); 23 } 24 25 socketfd = socket(AF_INET,SOCK_STREAM,0); 26 memset(&sockaddr,0,sizeof(sockaddr)); 27 sockaddr.sin_family = AF_INET; 28 sockaddr.sin_port = htons(10004); 29 inet_pton(AF_INET,servInetAddr,&sockaddr.sin_addr)
30 if((connect(socketfd,(struct sockaddr*)&sockaddr,sizeof(sockaddr))) < 0 )
31 { 31 printf("connect error %s errno: %d\n",strerror(errno),errno); 32 exit(0); 33 } 34 35 printf("send message to server\n"); 36 37 fgets(sendline,1024,stdin); 38 39 if((send(socketfd,sendline,strlen(sendline),0)) < 0) 40 { 41 printf("send mes error: %s errno : %d",strerror(errno),errno); 42 exit(0); 43 } 44 45 close(socketfd); 46 printf("exit\n"); 47 exit(0); 48 }
-執行:gcc client.c -o client 後啓動 ./client 客戶端程序 啓動前先啓動./server-----------------------------------------
server.c
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <sys/types.h> 4 #include <string.h> 5 #include <netinet/in.h> 6 #include <stdlib.h> 7 #include <errno.h> 8 #include <unistd.h> 9 #include <arpa/inet.h> 10 11 #define MAXLINE 1024 12 int main(int argc,char **argv) 13 { 14 int listenfd,connfd; 15 struct sockaddr_in sockaddr; 16 char buff[MAXLINE]; 17 int n; 18 19 memset(&sockaddr,0,sizeof(sockaddr)); 20 21 sockaddr.sin_family = AF_INET; 22 sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); 23 sockaddr.sin_port = htons(10004); 24 25 listenfd = socket(AF_INET,SOCK_STREAM,0); 26 27 bind(listenfd,(struct sockaddr *) &sockaddr,sizeof(sockaddr)); 28 29 listen(listenfd,1024); 30 31 32 printf("Please wait for the client information\n"); 33 34 for(;;) 35 { 36 if((connfd = accept(listenfd,(struct sockaddr*)NULL,NULL))==-1) 37 { 38 printf("accpet socket error: %s errno :%d\n",strerror(errno),errno); 39 continue; 40 } 41 42 n = recv(connfd,buff,MAXLINE,0); 43 buff[n] = '\0'; 44 printf("recv msg from client:%s",buff); 45 close(connfd); 46 } 47 close(listenfd); 48 }
-執行:gcc server.c -o server 後啓動 ./server 服務端程序-------------------------------------------------------
> 一、代碼展現,功能介紹
上面這個簡單的socket通訊的代碼要實現的功能:從客戶端發送一條消息後,服務端接收這條消息,並在服務端顯示(recv msg from client:****)。
> 二、首先介紹一下客戶端和服務端中函數的功能以及函數的原形。
#include <sys/socket.h> int socket(int family, int type, int protocol); //指按期望的通訊協議類型,返回的文件描述符和套接字描述符相似,咱們成爲套接字描述符,簡稱sockfd
family:協議族
family | 說明 |
AF_INET | IPv4協議 |
AF_INET6 | IPv6 |
AF_LOCAL | Unix域協議(15章) |
AF_ROUTE | 路由套接字(18章) |
AF_KEY | 密鑰套接字(19章) |
type:套接字的類型
type | 說明 |
SOCK_STREAM(經常使用) | 字節流套接字 |
SOCK_DGRAM | 數據報套接字 |
SOCK_SEQPACKET | 有序分組套接字 |
SOCK_RAW | 原始套接字 |
protocol:協議類型的常量或設置爲0,以選擇給定的family和type組合的系統默認值
protocol | 說明 |
IPPROTO_TCP | TCP傳輸協議 |
IPPROTO_UDP | UDP傳輸協議 |
IPPROTO_SCTP | SCTP傳輸協議 |
#include<arpa/inet.h> int inet_pton(int family,const char *strptr,void *addrptr);//成功返回1,格式不對返回0,出錯返回-1
//做用:p表明表達式 n表明數值 之後所寫的全部代碼中都有可能會須要這個函數,因此這個函數很重要
//將char所指向的字符串,經過addrptr指針存放
//他的反函數: inet_ntop()做用相反。能夠百度查閱這個函數的功能。由於例子裏咱們沒有涉及到,就不介紹了。之後用到的時候再說
//須要注意的是:當他發生錯誤的時候,errno的值會被置爲EAFNOSUPPORT 關於errno值咱們一下子介紹。
#include <sys/socket.h> int connect(int sockfd,const struct sockaddr* servaddr,socklen_t addrlen);//用connect函數來創建與TCP服務器的鏈接
#include<unistd.h> int close(int sockfd);//關閉socket,並終止TCP鏈接
#include <sys/socket.h> int bind(int sockfd,const struct* myaddr,socklen_t addrlen);//把本地協議地址賦予一個套接字。也就是將32位的IPv4或128位ipv6與16位的TCP或者UDP組合。
#include<sys/socket.h> int listen(int sockfd,int backlog)//成功返回0,失敗返回-1 listen函數僅由TCP服務器調用
//listen函數將會作兩件事:
//1:咱們在建立套接字的時候使用了socket函數,它建立的套接字是主動套接字,bind函數的功能就是經過這個將主動套接字,變成被動套接字,告訴內核應該接受指向這個套接字的請//求,CLOSED狀態變成LISTEN狀態
//2:本函數的第二個參數規定了內核要爲該套接字排隊的最大鏈接個數。
#include <sys/socket.h> int accept(int sockfd,struct sockaddr* cliaddr,socklen_t *addrlen);//成功返回描述符,失敗返回-1
//一、若是第二三個參數爲空,表明了,咱們對客戶的身份不感興趣,所以置爲NULL;
//二、第一個參數爲socket建立的監聽套接字,返回的是已鏈接套接字,兩個套接字是有區別的,並且很是重要。區別:咱們所建立的監聽套接字通常服務器只建立一個,而且一直存在。而內核會爲每個服務器進程的客戶鏈接創建一個鏈接套接字,當服務器完成對某個給定客戶的服務時,鏈接套接字就會被關閉。
總結:咱們學校的實驗室是雲計算實驗室,有不少的集羣,我在上面開了2臺虛擬機,在兩臺Linux系統上跑。能夠成功接收。只要將IP設置好便可,注意,關掉防火牆:service iptables stop;
> 三、關於鏈接三次握手和TCP鏈接關閉時候的分組交換
三次握手:
爲了更好的理解connect、bind、close三個函數,瞭解一下TCP鏈接的創建和終止是頗有必要的。(請務必理解理解上面的全部的函數後,再看這節)。
一、服務器首先必須被打開,等待準備接受外來的鏈接。咱們上面的例子用到了socket、bind、listen這3個函數。以後,咱們稱爲服務端被被動打開了。
二、客戶端是經過connect發起主動打開。
三、主動打開後,客戶TCP發送了一個SYN(同步)分節,它告訴服務器客戶將在鏈接中只發送的數據的初始序列號,SYN分節不攜帶數據。它發送的IP數據報,只有一個IP首部、一個TCP首部以及TCP選項。
四、服務器必須確認(ACK)客戶的SYN,同時本身也發送一個SYN分節,它含有服務器將在同一鏈接中發送的數據的初始序列號。服務器在單個分節中發送SYN和對客戶SYN的ACK確認(+1)。
五、客戶必須確認服務器的SYN分節。
上面的過程稱爲TCP的三次握手。
注:SYN(synchronous)是TCP/IP創建鏈接時使用的握手信號。在客戶機和服務器之間創建正常的TCP網絡鏈接時,客戶機首先發出一個SYN消息,服務器使用SYN+ACK應答表示接收到了這個消息,最後客戶機再以ACK消息響應。這樣在客戶機和服務器之間才能創建起可靠的TCP鏈接,數據才能夠在客戶機和服務器之間傳遞
TCP鏈接終止
終止一個鏈接須要4個分節。
一、經過調用close,咱們執行主動關閉,TCP發送一個FIN(finish,表示結束),表示數據發送完畢。
二、對端接收到FIN後,執行被動關閉。
三、一段時候後,接收到文件結束符的應用進程,將調用close關閉它的套接字。因而套接字也發送一個了FIN。
四、確認這個FIN ACK+1 下圖很清楚的表達了。
五、咱們也稱它爲TCP四次握手。
> 四、IPv四、IPv6套接字的地址結構
IPv4地址結構:
1 struct in_addr { 2 in_addr_t s_addr; 3 }; 4 5 struct sockaddr_in { 6 uint8_t sin_len; //無符號8位整型 7 sa_family_t sin_famliy; /*AF_INET*/ 8 in_port_t sin_port;
9 struct in_addr sin_addr; /*32位 IPv4 地址*/ 10 char sin_zero[8]; /*unuse*/ 11 };
//頭文件 #include <sys/types.h>
//sa_family_t和socklen_t 頭文件 #include <sys/socket.h>
//in_addr_t in_port_t 頭文件 #include <netinet/in.h>
IPv6地址結構:
struct in6_addr { uint8_t s6_addr[16]; }; #define SIN6_LEN struct sockaddr_in6 { uint8_t sin6_len; sa_family_t sin6_famliy; in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr; uint32_t sin6_scope_id; };
> 五、一些好的學習網站總結
一、關於51CTO上的這個視頻http://edu.51cto.com/course/course_id-903.html,我買了,可是講的很是爛,建議你們不要購買。教課的老師也就是照着書念,還不如本身。浪費錢。
二、http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html
http://blog.csdn.net/hguisu/article/details/7445768/
http://www.oschina.net/code/snippet_97047_675
這幾篇博客不錯,能帶你入門。
> 六、代碼下載
Github: https://github.com/micwu/Demo
> 七、總結
學習之路是很蠻長的。想要學好,很是難,須要長期的積累。我也正在學習中。通過了不少的挫折,可是有理想,就必定能成功。但願你們想走Linux下服務器編程的同志們,一塊兒加油吧。
> 八、echo實現
代碼下載:Github