Linux下簡單的socket通信實例
If you spend too much time thinking about a thing, you’ll never get it done.
—Bruce Lee
學習網絡編程也一段時間了,剛開始看《UNIX網絡編程》的時候,覺得這本厚厚的書好難啊!看到后來,發現並沒有想象中的那么難。如果你是新手,建議你看到第二部分結束后,開始着手寫代碼。不寫代碼肯定是不行的。看100遍也沒有敲一遍實現一遍來的清楚。敲完以后,帶着問題去看書,你會更加有針對性。提高的速度是飛快的,這也是學習任何一本書、一門語言的唯一手段。
寫這個博客也是因為剛開始學的時候,查了好多別人寫的東西,百度了以后,發現大家只是把所有的代碼一貼。並沒有講解每個函數的功能。我甚至不知道哪個函數是哪個頭文件下的。造成我對函數很不理解。下面我會對每個函數的功能,和它的頭文件以及函數原型寫出來,讓大家參考,第一次寫博客,有什么錯誤的地方,希望大家指正。可以在下面給我留言,也是我繼續寫下去的動力。
我很希望和大家一起分享學習網絡編程遇到的種種困難與不順,也希望和大家一起討論其中遇到的問題,一起成長,如果你剛開始打算學習網絡編程,那這篇文章一定能給你一些幫助。
我的郵箱:cvmimi_linhai@foxmail.com,轉載請注明出處:http://www.cnblogs.com/yusenwu/p/4579167.html。
關於怎樣介紹這個簡單的實例:(基本上涵蓋了《UNIX網絡編程》1-5章的內容,更深,更細的,需要我們再細讀這本書)
--> 1、代碼展示,功能介紹
--> 2、首先介紹一下客戶端和服務端中函數的功能以及函數的原形。
--> 3、關於連接三次握手和TCP連接關閉時候的分組交換
--> 4、IPv4、IPv6套接字的地址結構
--> 5、一些好的學習網站總結
--> 6、代碼下載
--> 7、總結
--> 8、實現一個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 服務端程序-------------------------------------------------------
> 1、代碼展示,功能介紹
上面這個簡單的socket通信的代碼要實現的功能:從客戶端發送一條消息后,服務端接收這條消息,並在服務端顯示(recv msg from client:****)。
> 2、首先介紹一下客戶端和服務端中函數的功能以及函數的原形。
#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
//1、如果第二三個參數為空,代表了,我們對客戶的身份不感興趣,因此置為NULL;
//2、第一個參數為socket創建的監聽套接字,返回的是已連接套接字,兩個套接字是有區別的,而且非常重要。區別:我們所創建的監聽套接字一般服務器只創建一個,並且一直存在。而內核會為每一個服務器進程的客戶連接建立一個連接套接字,當服務器完成對某個給定客戶的服務時,連接套接字就會被關閉。
總結:我們學校的實驗室是雲計算實驗室,有很多的集群,我在上面開了2台虛擬機,在兩台Linux系統上跑。可以成功接收。只要將IP設置好即可,注意,關掉防火牆:service iptables stop;
> 3、關於連接三次握手和TCP連接關閉時候的分組交換
三次握手:
為了更好的理解connect、bind、close三個函數,了解一下TCP連接的建立和終止是很有必要的。(請務必理解理解上面的所有的函數后,再看這節)。
1、服務器首先必須被打開,等待准備接受外來的連接。我們上面的例子用到了socket、bind、listen這3個函數。之后,我們稱為服務端被被動打開了。
2、客戶端是通過connect發起主動打開。
3、主動打開后,客戶TCP發送了一個SYN(同步)分節,它告訴服務器客戶將在連接中只發送的數據的初始序列號,SYN分節不攜帶數據。它發送的IP數據報,只有一個IP首部、一個TCP首部以及TCP選項。
4、服務器必須確認(ACK)客戶的SYN,同時自己也發送一個SYN分節,它含有服務器將在同一連接中發送的數據的初始序列號。服務器在單個分節中發送SYN和對客戶SYN的ACK確認(+1)。
5、客戶必須確認服務器的SYN分節。
上面的過程稱為TCP的三次握手。
注:SYN(synchronous)是TCP/IP建立連接時使用的握手信號。在客戶機和服務器之間建立正常的TCP網絡連接時,客戶機首先發出一個SYN消息,服務器使用SYN+ACK應答表示接收到了這個消息,最后客戶機再以ACK消息響應。這樣在客戶機和服務器之間才能建立起可靠的TCP連接,數據才可以在客戶機和服務器之間傳遞
TCP連接終止
終止一個連接需要4個分節。
1、通過調用close,我們執行主動關閉,TCP發送一個FIN(finish,表示結束),表示數據發送完畢。
2、對端接收到FIN后,執行被動關閉。
3、一段時候后,接收到文件結束符的應用進程,將調用close關閉它的套接字。於是套接字也發送一個了FIN。
4、確認這個FIN ACK+1 下圖很清楚的表達了。
5、我們也稱它為TCP四次握手。
> 4、IPv4、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; };
> 5、一些好的學習網站總結
1、關於51CTO上的這個視頻http://edu.51cto.com/course/course_id-903.html,我買了,但是講的非常爛,建議大家不要購買。教課的老師也就是照着書念,還不如自己。浪費錢。
2、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
這幾篇博客不錯,能帶你入門。
> 6、代碼下載
Github: https://github.com/micwu/Demo
> 7、總結
學習之路是很蠻長的。想要學好,非常難,需要長期的積累。我也正在學習中。經過了很多的挫折,但是有理想,就一定能成功。希望大家想走Linux下服務器編程的同志們,一起加油吧。
> 8、echo實現
代碼下載:Github