Linux的Socket編程

1.網絡中進程間的通訊java

      網間進程通訊要解決的是不一樣主機進程間的相互通訊問題,爲此,首先要解決的是網間進程標識問題。同一主機上,不一樣的進程可用進程號(process ID)惟一標識。可是在網絡環境下,各主機獨立分配的進程號不能惟一標識該進程。例如,主機A賦予某進程號5,在B主機中也能夠存在5號進程,這樣「5號進程」就沒意義了。其次,操做系統支持的網絡協議衆多,不一樣的協議的工做方式也不一樣,地質格式也不一樣。所以,網間進程通訊還要結局多重協議的問題。linux

      TCP/IP協議族就解決了這個問題,網絡層的「IP地址」能夠惟一標識網絡中的主機,而傳輸層的「協議+端口」能夠惟一標識主機中的應用程序(進程)。這樣利用三元組(ip地址、協議。端口)就能夠標識網絡的進程了,網絡中的進程通訊就能夠利用這個標識與其餘進程進行通訊了。android

2.什麼是TCP/IP、UDP編程

     TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,存在於OS中,網絡服務經過OS提供,在OS中增長支持TCP/IP的系統調用-——套接字,如Socket,它是應用層與TCP/IP協議族的中間抽象層。數組

   UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。屬於TCP/IP協議族中的一種。服務器

3.什麼是Socket網絡

   3.一、Socket套接字:app

          Socket是應用層和TCP/IP協議族通訊的中間軟件抽象層,它是一組接口,去組織數據,以符合指定的協議。當應用程序要爲因特網通訊而建立一個套接字(socket)時,操做系統就返回一個小整數做爲描述符來標識這個套接字。而後,應用程序以該描述符做爲傳遞參數,經過調用函數來完成某種操做(如,經過網絡傳送數據或接受輸入的數據)。socket

       調用socket將建立一個新的描述符條目:ide

                                  family:PF_INET    //協議族,常見的協議族有:AF_INET(IPV4)、AF_INET6(IPV6)

                                  service:SOCK_STREAM   //Socket類型

                                   Local IP:                           //本地IP

                                   Remote IP:                       //遠程IP

                                   Local port:                        //本地端口

                                   Remote port:                    //遠程端口

   3.二、Socket接口函數

        生活中打電話的例子來講,A要和B講電話,首先,A撥號給B;而後,B拿起電話按接據說:喂,你好!最後A聽到B的聲音並回復:喂,你好!這個時候A與B便創建起了鏈接,這就是TCP的三次握手協議創建鏈接的一個模型。(PS:李老師舉的例子,特別簡單又容易理解這個三次握手的過程)。

 

       如上圖所示,服務器端先初始化Socket,而後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端鏈接。在這時若是有個客戶端初始化一個Socket,而後鏈接服務器(connect),若是鏈接成功,這時客戶端就與服務端的鏈接創建了。客戶端發送數據請求,服務器端接收並處理請求,而後把迴應的數據發送給客戶端,客戶端讀取數據,最後關閉鏈接,一次交互結束。

       3.2.一、socket()函數

          int socket(int protofamily,int type,int protocol);  //返回sockfd描述符

            socket函數對應於普通文件的打開操做。普通文件的打開操做返回一個文件描述符,而socket()用於建立一個socket描述符,它惟一標識一個socket,把它做爲操做參數,經過它進行一些讀寫操做。

           socket函數的三個參數分別爲:

           protofamily:  協議族,常見的協議族有:AF_INET(IPV4)、AF_INET6(IPV6)等,協議族決定了socket的地址類型,在通訊中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位)與端口號(16位)的組合。

           type:  指定socket類型。經常使用的socket類型有:SOCK_STREAM、SOCK_DGRAM、SOCK_PACKET等。

           protocol:  指定協議。經常使用的協議有:IPPROTO_TCP、IPPROTO_UDP等,分別對應TCP傳輸協議、UDP傳輸協議。當protocol爲0時,會自動選擇type類型對應的傳輸協議。

           當調用socket建立一個socket時,返回的socket描述字它存在於協議族(address family,AE_INET)空間中,但沒有一個具體的地址。若是想給它賦值一個地址,就必須調用bind()函數,不然當調用connect()、listen()函數時系統會自動隨機分配一個端口。

       3.2.二、bind()函數

         正如上面所說bind()函數把一個地址族中的特定地址賦給socket。例如AF_INET就是把ipv4地址和端口號組合賦給socket

          int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);

          三個參數分別爲:

          sockfd:  即socket描述字,它是經過socket()函數建立的,惟一表示一個socket。bind()函數就是給這個描述字綁定一個名字(地址和端口號組合)

          addr:  一個const struct sockaddr*指針,指向要綁定給sockfd的協議地址。這個地址結構根據建立socket時的地址協議族的不一樣而不一樣,如:ipv4對應的是:

               struct  sockaddr_in {

                        sa_family_t     sin_family ;   //協議族,AF_INET

                        in_port_t         sin_port ;      //指定端口

                        struct in_sddr      sin_addr ;   //地址

                 };

        addrlen:  對應地址的長度。

         一般服務器在啓動的時候回綁定一個衆所周知的四肢(如ip地址+端口號),用於提供服務,客戶端就能夠經過它來鏈接服務器;而客戶端就不用指定,由系統自動分配一個端口號和自身的ip地址組合。這就是爲何一般服務端在listen前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。

       3.2.三、listen()、connect()函數

        一個服務器在調用socket()、bind()函數以後就會調用listen()來監聽這個socket,若是客戶端這時調用connect()發出鏈接請求,服務器端就會接收到這個請求。

         int listen (int sockfd, int backlog);

         int connect (int sockfd, const struct sockaddr* addr, socklen_t addrlen);

         listen函數的第一個參數即爲要監聽的socket描述字,第二個參數爲相應socket能夠排隊的最大鏈接個數。socket()函數建立的socket默認是一個主動型的,listen函數將socket變爲被動型的,等待客戶的鏈接請求。

         connect函數的第一個參數即爲客戶端的socket描述字,第二個參數爲服務器的socket地址,第三個參數爲socket地址的長度。客戶端經過connect函數來創建與TCP服務器的鏈接。

         3.2.四、accept()函數

          TCP服務器端一次調用socket()、bind()、listen()以後,就會監聽指定的socket地址了。TCP客戶端一次調用socket()、connect()以後就先TCP服務器發送一個鏈接請求。TCP服務器監聽到這個請求以後,就會調用accept()函數去接受請求,這樣鏈接就創建好了。以後就能夠開始網絡I/O操做了。

          int accept(int sockfd, const struct addr* addr , socklen_t *addrlen);   //返回鏈接connect_fd

          sockfd:監聽套接字

          addr: 結果參數,接受客戶端地址的返回值

          addrlen:結果參數,接受上述addr結構體的大小

          2.五、read()、write()函數

         網絡I/O操做有下面幾組:

  • read( ) /write( )  
  • recv( ) /send( )  
  • readv( )/writev( )
  • recvmsg( )/sendmsg( )
  • recvfrom( )/sendto( ) 

           recvmsg( )/sendmsg( )是最通用的I/O函數,實際上能夠把上面的其餘函數都替換成這兩個函數。他們的聲明以下:

            #include<unistd.h>

            ssize_t read(int fd, void *buf, size_ count);

            ssize_t write(int fd, const void *buf, size_ count);

 

             #include<sys/types.h>

             #include<sys/socket.h>

              ssize_t send(int sockfd, const void *buf, size_t_len, int flags );

              ssize_t recv(int sockfd,  void *buf, size_t_len, int flags );

 

              ssize_t sendmsg(int sockfd, const struct msghdr *msg,int flags);

              ssize_t recvmsg(int sockfd,  struct msghdr *msg,int flags);

              read()函數負責從fd中讀取內容,當讀成功時,read返回實際所讀的字節數,若是返回值爲0表示已經讀到文件的結束了,小於0表明出現了錯誤。若是錯誤爲EINTR說明讀是由中斷引發的,若是是ECONNERST表示網絡鏈接出了問題。

              write()函數將buf中的nbytes字節內容寫入文件描述符FD,成功時返回寫的字節數。失敗時返回-1,並設置errno變量。在網絡程序中,當向套接字文件描述符寫時有兩種可能,1)write的返回值大於0,表明寫了部分或者所有的數據,2)小於0,表明出錯。若是錯誤爲EINTR表示在寫的時候出現了終端錯誤,若是是EPIPE表示網絡鏈接出現問題。

            4.Socket編程實例

       服務器端:一直監聽本機8000號端口,若是收到鏈接請求,將請求接收並接收客戶端發來的消息

     

 1 /*File Name: server.c */
 2 #include<stdio.h>  
 3 #include<stdlib.h>  
 4 #include<string.h>  
 5 #include<errno.h>  
 6 #include<sys/types.h>  
 7 #include<sys/socket.h>  
 8 #include<netinet/in.h>  
 9 
10 #define DEFAULT_PORT 8000  
11 #define MAXLINE 4096  
12 int main(int argc, char* argv[])  
13 {  
14     int    socket_fd, connect_fd;  //服務端套接字描述符,創建鏈接後返回的套接字描述符 
15     struct sockaddr_in   servaddr;  //服務端地址結構體 
16     char    buff[4096];  //數據緩衝區
17     int     n;  
18     //初始化Socket  
19     if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){  
20     printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);  
21     exit(0);  
22     }  
23     //初始化  
24     memset(&servaddr, 0, sizeof(servaddr));  
25     servaddr.sin_family = AF_INET;  
26     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址設置成INADDR_ANY,讓系統自動獲取本機的IP地址。  
27     servaddr.sin_port = htons(DEFAULT_PORT);//設置的端口爲DEFAULT_PORT  
28   
29     //將本地地址綁定到所建立的套接字上  
30     if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){  
31     printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);  
32     exit(0);  
33     }  
34     //開始監聽是否有客戶端鏈接 ,10爲能夠排隊的最大鏈接數
35     if( listen(socket_fd, 10) == -1){  
36     printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);  
37     exit(0);  
38     }  
39     printf("======waiting for client's request======\n");  
40     while(1){  
41 //阻塞直到有客戶端鏈接,否則浪費CPU資源,connect_fd爲返回的新的套接字描述符  
42         if( (connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1){  
43         printf("accept socket error: %s(errno: %d)",strerror(errno),errno);  
44         continue;  
45     }  
46 //接受客戶端傳過來的數據  
47     n = recv(connect_fd, buff, MAXLINE, 0);  
48 //向客戶端發送迴應數據  
49     if(!fork()){   
50         if(send(connect_fd, "Hello,you are connected!\n", 26,0) == -1)  
51         perror("send error");  
52         close(connect_fd);  
53         exit(0);  
54     }  
55     send(connect_fd, "Hello,you are connected!\n", 26,0);
56     //close(connect_fd);    
57     closesocket(connect_fd);
58     buff[n] = '\0';  
59     printf("recv msg from client: %s\n", buff); 
60    //close(connect_fd);    
61     closesocket(connect_fd);  
62     }  
63     //close(socket_fd);    
64     closesocket(socket_fd);  
65 }  

客戶端:

 

 1 /*File Name:client.c */
 2 
 3 #include<stdio.h>  
 4 #include<stdlib.h>  
 5 #include<string.h>  
 6 #include<errno.h>  
 7 #include<sys/types.h>  
 8 #include<sys/socket.h>  
 9 #include<netinet/in.h>  
10   
11 #define MAXLINE 4096  
12   
13   //argc表示命令行參數的個數,char** argv爲命令行的字符串數組
14 int main(int argc, char** argv)  
15 {  
16     int    sockfd, n,rec_len;  
17     char    recvline[4096], sendline[4096];  
18     char    buf[MAXLINE];  //緩衝數組
19     struct sockaddr_in    servaddr;  
20   
21   //提示在命令行輸入2個參數,如:client.exe 128.0.0.1,第二個參數爲服務器端的ip地址
22     if( argc != 2){  
23     printf("usage: ./client <ipaddress>\n");  
24     exit(0);  
25     }  
26    
27     if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){  
28     printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);  
29     exit(0);  
30     }  
31   
32     memset(&servaddr, 0, sizeof(servaddr));  
33     servaddr.sin_family = AF_INET;  
34     servaddr.sin_port = htons(8000); 
35     //inet_pton爲linux下IP地址轉換函數,將IP地址在「點分十進制」和整數之間轉換,argv[1]字符數組即爲第二個參數的字符,將字符串轉換到網絡地址。
36     if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){  
37     printf("inet_pton error for %s\n",argv[1]);  
38     exit(0);  
39     }  
40   
41   
42     if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){  
43     printf("connect error: %s(errno: %d)\n",strerror(errno),errno);  
44     exit(0);  
45     }  
46   
47     printf("send msg to server: \n");  
48     fgets(sendline, 4096, stdin);  
49     //發送從鍵盤輸入的數據,若返回值小於0表明出現錯誤,等於0表明已經讀到文件的介紹了,返回的是字節數表明讀取成功
50     if( send(sockfd, sendline, strlen(sendline), 0) < 0)  
51     {  
52     printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);  
53     exit(0);  
54     }  
55     //接收緩衝區裏的數據,recv()函數返回的是實際所讀的字節數
56     if((rec_len = recv(sockfd, buf, MAXLINE,0)) == -1) {  
57        perror("recv error");  
58        exit(1);  
59     }  
60     //'\0'爲字符串的結尾標識
61     buf[rec_len]  = '\0';  
62     printf("Received : %s ",buf);  
63     close(sockfd);  
64     exit(0);  
65 }  

1.在虛擬機上的ubantu測試:

編譯server.c

# gcc server.c -o server1

運行server啓動進程

#./server1

顯示結果:等待客戶端鏈接


編譯client.c

#gcc client.c -o client1

運行即客戶端去鏈接服務器端

#./client 127.0.0.1

注意:此時設置的命令行參數若不爲2,則提示:usage: ./client <ipaddress>,即要在./client後加上服務端的ip地址

運行結果:等待輸入消息

輸入hello

服務端:

客戶端:

2.在開發板測試:服務端仍是server.c文件,客戶端編程安卓文件,在手機上運行,工程名爲TestSocket

   1)首先創建同一局域網,使用路由器。手機連上路由器的wifi,手機ip爲192.168.1.100

   2) 用網線鏈接開發板和路由器,自動獲取開發板ip,自動獲取ip命令爲 udhcpc -i eth0,獲取到開發板ip爲192.128.1.101

   3)看手機和開發板可不能夠ping通,在開發板終端,ping 192.168.1.100,結果能夠ping通。

   到這裏,socket通訊的準備工做已經完成,下面開始測試!

 

服務端:

在虛擬機上交叉編譯server.c,注意:必定要交叉編譯arm-linux-gcc!!!
#arm-linux-gcc server.c -o server

將server可執行文件拷貝到開發板運行

#./server

結果:等待客戶端鏈接

客戶端:

先附上安卓程序的主要代碼

  1 package com.example.testsocket;
  2 
  3 import android.support.v7.app.ActionBarActivity;
  4 import android.text.TextUtils;
  5 import android.util.Log;
  6 
  7 import java.io.BufferedReader;
  8 import java.io.DataOutputStream;
  9 import java.io.IOException;
 10 import java.io.InputStreamReader;
 11 import java.io.OutputStream;
 12 import java.net.DatagramSocket;
 13 import java.net.InetAddress;
 14 import java.net.Socket;
 15 import java.net.UnknownHostException;
 16 
 17 import android.R.integer;
 18 import android.content.Context;
 19 import android.graphics.PorterDuff;
 20 import android.nfc.cardemulation.HostApduService;
 21 import android.os.Bundle;
 22 import android.view.Menu;
 23 import android.view.MenuItem;
 24 import android.view.View;
 25 import android.view.View.OnClickListener;
 26 import android.widget.Button;
 27 import android.widget.EditText;
 28 import android.widget.Toast;
 29 
 30 public class MainActivity extends ActionBarActivity {
 31     
 32     protected static final String TAG = "TXY";
 33     private Button btn_load; // 聲明Button類型的對象
 34     private EditText et_IP;  //聲明EditText類型的對象
 35     private Context mContext ;
 36     //private Socket socket ;
 37     private BufferedReader in = null;
 38     public static String host=null;
 39     //public final int port=8000;
 40     //public final int port_p=8000;
 41     //public Socket socket2;
 42     int i=0;//判斷服務器是否鏈接成功
 43     /*DatagramSocket能夠用來建立收、發數據報的socket對象,
 44      * 這個數據報包必須知道本身來自何處,以及打算去哪裏。因此自己必須包含IP地址、端口號和數據內容。
 45      * DatagramSocket能夠用來建立收、發數據報的socket對象
 46      * 若是用它來接收數據,應該用下面這個建立方法:public DatagramSocket(int port),port指定接收時的端口
 47      * 接收數據時,可使用它的receive(DatagramPacket data)方法。獲取的數據報將存放在data中。發送數據時,可使用它的send(DatagramPacket data)方法。
 48      * 發送的端口、目的地址和數據都在data中。
 49     */
 50     //public DatagramSocket socket1; 
 51 
 52     @Override
 53     protected void onCreate(Bundle savedInstanceState) {
 54         super.onCreate(savedInstanceState);
 55         setContentView(R.layout.activity_main);
 56         
 57         btn_load = (Button) findViewById(R.id.btn_load);
 58         et_IP = (EditText) findViewById(R.id.et_IP);
 59         mContext = this ;//this爲MainActivity
 60         /*try {
 61             Socket socket = new Socket("59.71.228.107", 10001);
 62             //設置10秒以後即認爲是超時
 63             socket.setSoTimeout(10000);
 64             BufferedReader br = new BufferedReader(new InputStreamReader(
 65                     socket.getInputStream()));
 66             String line = br.readLine();
 67 
 68             et_IP.setText("來自服務器的數據:"+line);
 69 
 70             br.close();
 71             socket.close();
 72 
 73         } catch (UnknownHostException e) {
 74             // TODO Auto-generated catch block
 75             Log.e("UnknownHost", "來自服務器的數據");
 76             e.printStackTrace();
 77         } catch (IOException e) {
 78             Log.e("IOException", "來自服務器的數據");
 79             // TODO Auto-generated catch block
 80             e.printStackTrace();
 81         }
 82         */
 83         btn_load.setOnClickListener(new OnClickListener() {
 84             
 85             @Override
 86             public void onClick(View v) {
 87                 Toast.makeText(mContext, "登陸前", Toast.LENGTH_SHORT).show();
 88                 Log.e(TAG,"登陸前" );
 89                 login();
 90                 
 91                 
 92             }
 93         });
 94     }
 95 
 96     private void login () {
 97         //拿到服務器ip
 98         // host = et_IP.getText().toString();
 99         //host = "127.0.0.1";
100         //Log.e(TAG,host);
101         //鏈接服務器
102         Log.e(TAG,"鏈接前");
103             connectServer ();
104             Log.e(TAG,"打印i的值");
105             System.out.println(i);//打印i的值,若i爲1則鏈接服務器成功
106             Log.e(TAG,"打印i的值....");
107             if(i==1){
108                 System.out.println("鏈接服務器成功!");
109                 Log.e(TAG,"鏈接服務器成功!");
110                 Toast.makeText(mContext, "鏈接服務器成功!", Toast.LENGTH_SHORT).show();
111                 Log.e(TAG,"aaaaaaaaaaa");
112         }
113     }
114     
115     //鏈接服務器
116     public void connectServer () {
117         try {
118             //Log.e(TAG,"鏈接...");
119             //InetAddress serverAddr = InetAddress.getByName(host);
120             Log.e(TAG,"申請鏈接前");
121             //System.out.print("申請鏈接前");
122             //申請鏈接
123             Socket s = new Socket("192.168.1.101",8000);
124             Log.e(TAG,"申請鏈接後");        
125             DataOutputStream dos = new DataOutputStream(s.getOutputStream());
126             Log.e(TAG,"管道輸出流");    
127             dos.writeUTF("hello server!");
128             Log.e(TAG,"輸出hello server");        
129             dos.flush();
130             Log.e(TAG,"清空管道緩衝");        
131             dos.close();
132             Log.e(TAG,"關閉管道");
133             s.close();
134             Log.e(TAG,"關閉通訊");
135             Log.e(TAG,"i=1以前");
136             i=1;
137             Log.e(TAG,"i=1以後");
138           } catch (Exception e) {
139             
140             e.printStackTrace();
141         }
142     }
143     
144     
145     
146 }

在手機上運行,理論上點擊"登陸"按鈕,應該會與開發板鏈接成功,可是實際上卻不是這樣,加log打印,結果以下圖所示

即程序運行到建立Socket s = new Socket("192.168.1.101",8000);這裏就不動了,緣由還不清楚,檢查了端口號和ip地址都沒問題,那麼接下來仍是要再認真學一遍java下的socket編程,感受以前學的不夠紮實!

3.測試李老師的參賽安卓程序,他用的是UDP通訊

    1)將個人筆記本電腦連上路由器的wifi,並查看此時的ip地址,爲192.168.1.102

    2 ) 查看筆記本電腦與手機是否能夠ping通,ping 192.168.1.100,結果是能夠ping通的

    3)打開網絡調試助手,設置UDP鏈接,本地ip爲192.168.1.102,本地端口位8080,點擊鏈接

    4) 在此APP端輸入遠程IP地址192.169.1.102,遠程端口8080,本地端口可隨便設,這裏設爲1234,點擊鏈接

    5)以下圖所示,鏈接成功,在電腦的網絡調試助手發送欄輸入111,點擊發送,在手機APP端的下面接收到111,並顯示

        UDP通訊測試完畢!接下來是要學習Linux下的UDP通訊編程,以及JAVA下的UDP通訊編程,暫時考慮項目用UDP通訊。固然 以前學了TCP如今嘗試失敗了也不能放棄,等過兩天再回過頭來看能不能解決這個問題。

         第一次寫博客,收穫不少,以前對Socket不太熟悉,經過今天一天的回顧和整理,加深了個人理解。學無止境,加油!

相關文章
相關標籤/搜索