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不太熟悉,经过今天一天的回顾和整理,加深了个人理解。学无止境,加油!