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

相关文章
相关标签/搜索