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操做有下面幾組:
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不太熟悉,經過今天一天的回顧和整理,加深了個人理解。學無止境,加油!