嵌入式Linux网络编程

转载来自:http://blog.csdn.net/hongdongyu/article/details/8028194

一、TCP/IP协议

1、TCP/IP参考模型

TCP/IP协议模型遵循简单明确的设计思路,包括以下四层协议:

  • 网络接口层:负责将二进制流转换为数据帧,并进行数据帧的发送和接受。数据帧是独立的网络信息传输单元。
  • 网络层:负责将数据帧封装成IP数据包,并运行必要的路由算法。
  • 传输层:负责端对端之间的通信会话连接与建立。传输协议的选择根据数据传输方式而定。
  • 应用层:负责应用程序的网络访问,通过端口号来识别各个不同的进程。

2、TCP和UDP

1)TCP向相邻的高层提供服务。因为TCP上一层是应用层,因此,TCP数据传输实现从一个应用程序到另一个应用程序的数据传递。应用程序通过编程调用TCP并使用TCP服务,提供需要准备发送的数据,来区分接受数据应用的目的地址和端口号。它允许数据同网络上的其他节点进行可靠的交换,它能提供端口编号的译码,以识别主机的应用程序,而且完成数据的可靠传输。TCP协议具有严格的内装差错检验算法确保数据的完整性,它是面向字节的顺序协议,这意味着包内的每个字节被分配一个顺序编号,并分配给每个包一个顺序编号。

2)UDP协议是一种无连接协议,不需要像TCP一样建立连接,一个UDP应用可同时作为应用的客户或服务器方。当接收数据时它不向发送方提供确认信息,如果出现丢失或重复的情况,也不会向发送方发出差错报文。由于它执行功能时具有较低的开销,因而执行速度比TCP快。

3、协议的选择

1)对数据要求高可靠性的应用选择TCP协议,如验证、密码字段的传送都是不许出错的,而对数据可靠性要求不那么高的可选择UDP传送。

2)TCP的传送会有较大的延迟,不适合对实时性要求较高的应用,如VOIP、视频监控等。相反,UDP协议则在这些应用中发挥很好的作用。

3)TCP协议主要解决网络的可靠性问题,它通过各种机制减少错误发生的概率。因此,在网络状况不是很好的情况下用TCP协议,但是若在网络状况很好的情况下就不需要采用TCP协议,而是选择UDP协议来减少网络负荷。

二、网络基础编程

1、socket概述

Linux中的网络编程是通过socket接口来进行的,它也是一种文件描述符。通过它不仅可以在本地机器上实现进程间的通信,而且通过网络能够在不同的机器上的进程之间进行通信。socket也有一个类似打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立等操作都是通过socket来实现的。

socket类型常见有以下三种:

  • 流式socket(SOCK_STREAM):流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,保证了数据的正确性和顺序性。
  • 数据报socket(SOCK_DGRAM):数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,而且不保证是可靠、无差错的。使用数据报协议UDP。
  • 原始socket:允许对底层协议进行直接访问,功能强大但使用不便,主要用于一些协议的开发。

2、地址及顺序处理

1)地址结构相关处理

一种常用的用于保存网络地址的数据结构sockaddr_in,其结构如下:

[csharp] view plaincopy
 
  1. struct sockaddr_in  
  2. {  
  3.       short int sin_family; /*地址族*/  
  4.       unsigned short int sin_port; /*端口号*/  
  5.       struct in_addr sin_addr; /*IP地址*/  
  6.       unsigned char sin_zero[8]; /*填充0*/  
  7. };  

该结构sin_family字段可选常见值:

AF_INET:IPv4协议

AF_INET6:IPv6协议

AF_LOCAL:UNIX域协议

AF_LINK:链路地址协议

AF_KEY:密钥套接字

2)数据存储优先顺序

计算机数据存储有两种字节优先顺序:高位字节优先(大端模式)和低位字节优先(小段模式)。Internet上以高位字节优先的顺序在网络传输,而PC机通常采用小端模式,因此有时候需要对两个字节存储优先顺序进行转换。用到了4个函数:htons()、ntohs()、htonl()和ntohl()。h代表host,n代表network,s代表short,l代表long。通常16位的IP端口号用s,而IP地址用l。

3)地址格式转换

IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的IP地址是由32位整数表示,为了转换可以使用下面三个函数:

[csharp] view plaincopy
 
  1. int inet_aton(const char *cp,struct in_addr *inp);  
  2. char *inet_ntoa(struct in_addr in);  
  3. in_addr_t inet_addr(const char *cp);  

其中inet_aton将a.b.c.d形式的IP转换为32位的IP,存储在inp指针里面;inet_ntoa是将32位IP转换为a.b.c.d的格式;inet_addr将一个点分十进制的IP转换成一个长整数型数。

4)名字地址转换

通常,人们在使用过程中不愿记忆冗长的IP地址,因此,使用主机名是很好的选择。gethostbyname()将主机名转化为IP地址,gethostbyaddr()则是逆操作,将IP地址转换为主机名。它们都涉及到一个hostent的结构体,如下:

[csharp] view plaincopy
 
  1. struct hostent  
  2. {  
  3.       char *h_name; /*正式主机名*/  
  4.       char **h_aliases; /*主机别名*/  
  5.       int h_addrtype; /*地址类型*/  
  6.       int h_length; /*地址字节长度*/  
  7.       char **h_addr_list; /*指向IPv4或IPv6的地址指针数组*/  
  8. };  

 我们调用gethostbyname()或者gethostbyaddr()后就能返回hostent结构体的相关信息。

三、socket基础编程

1、相关函数介绍

socket编程的基本函数有socket()、bind()、listen()、accept()、sent()、sendto()、recv()、以及recvfrom()等,具体介绍如下:

  • socket():用于建立一个socket连接,可指定socket类型等信息。建立之后,可对sockaddr或sockaddr_in结果进行初始化,以保存所建立的socket地址信息。

  •  bind():用于将本地IP地址绑定到端口号。

  • listen():在服务程序成功建立套接字和地址进行绑定后,调用listen()函数来创建一个等待队列,在其中存放未处理的客户端连接请求。

  • accept():服务器调用listen()创建等待队列之后,调用accept()等待并接收客户端的连接请求。通常从由bind()所创建的等待队列中取出第一个未处理的连接请求。

  • connect():用于bind()之后的client端,用于与服务器端建立连接。

  • send()和recv():这两个函数分别用于发送和接收数据,可以用在TCP或者UDP中。用在UDP时可以在connect()建立连接之后再用。

 

  • sendto()和recvfrom():作用与前两个类似,当用在TCP时,后面的几个与地址有关参数不起作用,等同于send()、recv();用在UDP时,可用在之前没有使用connect的情况下,这两个函数可自动寻找指定地址并进行连接。

2、流程及流程图

基于TCP-服务器:创建socket()—>bind()绑定IP地址、端口信息到socket上—>listen()设置允许最大连接数—>accept()等待来自客户端的连接请求—>send()、recv()或者read()、write()收发数据—>关闭连接。

基于TCP-客户端:创建socket()—>设置要连接的服务器IP地址和端口等属性—>connect()连接服务器—>send()、recv()或read()、write()收发数据—>关闭网络连接。

基于UDP-服务器:创建socket()—>bind()绑定IP地址、端口等信息到socket上—>循环接受数据,用recvfrom()—>关闭网络连接。

基于UDP-客户端:创建socket()—>bind()绑定IP地址、端口等信息到socket上—>设置对方IP地址和端口信息—>sendto()发送数据—>关闭网络连接。

四、服务器类型

循环服务器:服务器在同一时间只能响应一个客户端的请求。

并发服务器:服务器在同一时刻可以响应多个客户端的请求。

UDP循环服务器

[csharp] view plaincopy
 
  1. socket(...);  
  2. bind(...);  
  3. while(1)  
  4. {  
  5.    recvfrom(...);  
  6.    process(...);  
  7.    sendto(...);  
  8. }  

TCP循环服务器

[csharp] view plaincopy
 
  1. socket(...);  
  2. bind(...);  
  3. listen(...);  
  4. while(1)  
  5. {  
  6.    accept(...);  
  7.    process(...);  
  8.    close(...);  
  9. }  

TCP循环服务器一次只能处理一个客户端的请求,只有这个客户的所有请求都满足后,才可以继续后面的请求。这样如果一个客户端占住服务器不放,其他的客户都不能工作,所以TCP服务器一般很少用循环服务器模型。而UDP循环服务器可以同时相应多个客户端的请求。

TCP并发服务器

[csharp] view plaincopy
 
  1. socket(...);  
  2. bind(...);  
  3. listen(...);  
  4. while(1)  
  5. {  
  6.    accept(...);  
  7.    if(fork()==0)  
  8.      {  
  9.         process(...);  
  10.         close(...);  
  11.         exit(...);  
  12.      }  
  13.      close(...);  
  14. }  

并发服务器的思想是每一个客户端的请求并不由服务器直接处理,而是有服务器创建一个子进程或者线程来处理。

五、使用实例

[csharp] view plaincopy
 
  1. /*server_thread.c*/  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/socket.h>  
  5. #include <sys/un.h>  
  6. #include <errno.h>  
  7. #include <unistd.h>  
  8. #include <signal.h>  
  9. #include <sys/wait.h>  
  10. #include <netdb.h>  
  11. #include <pthread.h>  
  12.   
  13. //线程执行函数负责读写  
  14. void *thr_fn(void *arg)  
  15. {  
  16.     int size,j;  
  17.     char recv_buf[1024];  
  18.     int *parg=(int *)arg;  
  19.     int new_fd=*parg;  
  20.     printf("new_fd=%d\n",new_fd);  
  21.     while((size=read(new_fd,recv_buf,1024))>0)  
  22.     {  
  23.         if(recv_buf[0]=='@')  
  24.             break;  
  25.         printf("Message from client(%d): %s\n",size,recv_buf);  
  26.         for(j=0;j<size;j++)  
  27.             recv_buf[j]=toupper(recv_buf[j]);  
  28.         write(new_fd,recv_buf,size);  
  29.     }  
  30.     close(new_fd);  
  31.     return 0;  
  32. }  
  33.   
  34. int main(int argc,char *argv[])  
  35. {  
  36.     socklen_t clt_addr_len;  
  37.     int listen_fd;  
  38.     int com_fd;  
  39.     int ret;  
  40.     int i;  
  41.     static char recv_buf[1024];  
  42.     int len;  
  43.     int port;  
  44.     pthread_t tid;  
  45.   
  46.     struct sockaddr_in clt_addr;  
  47.     struct sockaddr_in srv_addr;  
  48.   
  49.     //服务器端运行时要给出端口信息,该端口为监听端口   
  50.     if(argc!=2)  
  51.     {  
  52.         printf("Usage:%s port\n",argv[0]);  
  53.         return 1;  
  54.     }  
  55.   
  56.     //获得输入的端口   
  57.     port=atoi(argv[1]);  
  58.   
  59.     //创建套接字用于服务器的监听   
  60.     listen_fd=socket(PF_INET,SOCK_STREAM,0);  
  61.     if(listen_fd<0)  
  62.     {  
  63.         perror("cannot create listening socket");  
  64.         return 1;  
  65.     }  
  66.   
  67.     //填充关于服务器的套节字信息  
  68.     memset(&srv_addr,0,sizeof(srv_addr));  
  69.     srv_addr.sin_family=AF_INET;  
  70.     srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);  
  71.     srv_addr.sin_port=htons(port);  
  72.   
  73.   
  74.     //将服务器和套节字绑定  
  75.     ret=bind(listen_fd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));  
  76.     if(ret==-1)  
  77.     {  
  78.         perror("cannot bind server socket");  
  79.         close(listen_fd);  
  80.         return 1;  
  81.     }  
  82.   
  83.     //监听指定端口,连接5个客户端   
  84.     ret=listen(listen_fd,5);  
  85.     if(ret==-1)  
  86.     {  
  87.         perror("cannot listen the client connect request");  
  88.         close(listen_fd);  
  89.         return 1;  
  90.     }  
  91.     //对每个连接来的客户端创建一个线程,单独与其进行通信   
  92.     //首先调用read函数读取客户端发送来的信息   
  93.     //将其转换成大写后发送回客户端   
  94.     //当输入“@”时,程序退出   
  95.     while(1)  
  96.     {  
  97.         len=sizeof(clt_addr);  
  98.         com_fd=accept(listen_fd,(struct sockaddr *)&clt_addr,&len);  
  99.         if(com_fd<0)  
  100.         {  
  101.             if(errno==EINTR)  
  102.             {  
  103.                 continue;  
  104.             }  
  105.             else  
  106.             {  
  107.                 perror("cannot accept client connect request");  
  108.                 close(listen_fd);  
  109.                 return 1;  
  110.             }  
  111.         }  
  112.         printf("com_fd=%d\n",com_fd);//打印建立连接的客户端产生的套节字  
  113.         if((pthread_create(&tid,NULL,thr_fn,&com_fd))==-1)  
  114.         {  
  115.             perror("pthread_create error");  
  116.             close(listen_fd);  
  117.             close(com_fd);  
  118.             return 1;  
  119.         }  
  120.     }  
  121.     return 0;  
  122. }  

 

[csharp] view plaincopy
 
  1. #include <stdio.h>  
  2. #include <sys/types.h>  
  3. #include <sys/socket.h>  
  4. #include <sys/un.h>  
  5. #include <netdb.h>  
  6. #include <unistd.h>  
  7.   
  8. int main(int argc,char *argv[])  
  9. {  
  10.     int connect_fd;  
  11.     int ret;  
  12.     char snd_buf[1024];  
  13.     int i;  
  14.     int port;  
  15.     int len;  
  16.   
  17.     static struct sockaddr_in srv_addr;  
  18.   
  19.     //客户端运行需要给出具体的连接地址和端口   
  20.     if(argc!=3)  
  21.     {  
  22.         printf("Usage: %s server_ip_address port\n",argv[0]);  
  23.         return 1;  
  24.     }  
  25.   
  26.     //获得输入的端口  
  27.     port=atoi(argv[2]);  
  28.   
  29.     //创建套节字用于客户端的连接  
  30.     connect_fd=socket(PF_INET,SOCK_STREAM,0);  
  31.     if(connect_fd<0)  
  32.     {  
  33.         perror("cannot create communication socket");  
  34.         return 1;  
  35.     }  
  36.   
  37.     //填充关于服务器的套节字信息  
  38.     memset(&srv_addr,0,sizeof(srv_addr));  
  39.     srv_addr.sin_family=AF_INET;  
  40.     srv_addr.sin_addr.s_addr=inet_addr(argv[1]);  
  41.     srv_addr.sin_port=htons(port);  
  42.   
  43.     //连接指定的服务器   
  44.     ret=connect(connect_fd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));  
  45.     if(ret==-1)  
  46.     {  
  47.         perror("cannot connect to the server");  
  48.         close(connect_fd);  
  49.         return 1;  
  50.     }  
  51.   
  52.     memset(snd_buf,0,1024);  
  53.     //用户输入信息后,程序将输入的信息通过套接字发送给服务器   
  54.     //然后调用read函数从服务器中读取发送来的信息   
  55.     //当输入“@”时,程序退出   
  56.     while(1)  
  57.     {  
  58.         write(STDOUT_FILENO,"input message:",14);  
  59.         len=read(STDIN_FILENO,snd_buf,1024);  
  60.         if(len>0)  
  61.             write(connect_fd,snd_buf,len);  
  62.         len=read(connect_fd,snd_buf,len);  
  63.         if(len>0)  
  64.             printf("Message form server: %s\n",snd_buf);  
  65.         if(snd_buf[0]=='@')  
  66.             break;  
  67.     }  
  68.     close(connect_fd);  
  69.     return 0;  
  70. }  
posted @ 2015-05-25 10:53  chrispauls  阅读(312)  评论(0编辑  收藏  举报