嵌入式linux TCP socket编程

来源:互联网 发布:php权限管理教程 编辑:程序博客网 时间:2024/06/06 04:00

一、利用内核编程的API

sendto  和  recvfrom


sendto()_Linux C函数


sendto(经socket传送数据)

相关函数

  send , sendmsg,recv , recvfrom , socket

表头文件

  #include < sys/types.h >
  #include < sys/socket.h >

定义函数

  int sendto ( socket s , const void * msg, int len, unsigned int flags, const
  struct sockaddr * to , int tolen ) ;

函数说明

  sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作。参数msg指向欲连线的数据内容,参数flags 一般设0,详细描述请参考send()。参数to用来指定欲传送的网络地址,结构sockaddr请参考bind()。参数tolen为sockaddr的结构长度。

返回值

 返回值:成功则返回接收到的字符数,失败返回-1.

错误代码

  EBADF 参数s非法的socket处理代码。
  EFAULT 参数中有一指针指向无法存取的内存空间。
  WNOTSOCK 参数 s为一文件描述词,非socket。
  EINTR 被信号所中断。
  EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断的。
  ENOBUFS 系统的缓冲内存不足。
  EINVAL 传给系统调用的参数不正确。

定义函数

  ssize_t recvfrom(int sockfd,void *buf,int len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);

函数说明

  recvfrom()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度.参数flags一般设0,其他数值定义参考recv().参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.参数fromlen为sockaddr的结构长度.
 

返回值

 返回值:成功则返回接收到的字符数,失败返回-1.

错误代码

EBADF 参数s非合法的socket处理代码
  EFAULT 参数中有一指针指向无法存取的内存空间。
  ENOTSOCK 参数s为一文件描述词,非socket。
  EINTR 被信号所中断。
  EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断。
  ENOBUFS 系统的缓冲内存不足
  ENOMEM 核心内存不足
  EINVAL 传给系统调用的参数不正确。


范例

  #include < sys/types.h >
  #include < sys/socket.h >
  #include <arpa.inet.h>
  #define PORT 2345 /*使用的port*/
  main(){
  int sockfd,len;
  struct sockaddr_in addr;
  char buffer[256];
  /*建立socket*/
  if(sockfd=socket (AF_INET,SOCK_DGRAM,0))<0){
  perror (“socket”);
  exit(1);
  }
  /*填写sockaddr_in 结构*/

  bzero ( &addr, sizeof(addr) );

  addr.sin_family=AF_INET;
  addr.sin_port=htons(PORT);
  addr.sin_addr=hton1(INADDR_ANY) ;
  if (bind(sockfd, &addr, sizeof(addr))<0){
  perror(“connect”);
  exit(1);
  }
  while(1){
  bzero(buffer,sizeof(buffer));
  len = recvfrom(socket,buffer,sizeof(buffer), 0 , &addr &addr_len);
  /*显示client端的网络地址*/
  printf(“receive from %s\n “ , inet_ntoa( addr.sin_addr));
  /*将字串返回给client端*/
  sendto(sockfd,buffer,len,0,&addr,addr_len);”

  }

  }


二、利用socket文件描述符

write/read


TCPServer端

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <strings.h>  
  4. #include <sys/types.h>  
  5. #include <sys/socket.h>  
  6. #include <memory.h>  
  7. #include <unistd.h>  
  8. //#include <linux/in.h>  
  9. #include <netinet/in.h>  
  10. //#include <linux/inet_diag.h>  
  11. #include <arpa/inet.h>  
  12.   
  13. #include <signal.h>  
  14.   
  15. /** 
  16.   关于 sockaddr  sockaddr_in  socketaddr_un说明 
  17.   http://maomaozaoyue.blog.sohu.com/197538359.html 
  18.   */  
  19.   
  20. #define PORT    11910   //定义通信端口  
  21. #define BACKLOG 5       //定义侦听队列长度  
  22. #define buflen  1024  
  23.   
  24. void process_conn_server(int s);  
  25. void sig_pipe(int signo);  
  26.   
  27. int ss,sc;  //ss为服务器socket描述符,sc为某一客户端通信socket描述符  
  28.   
  29. int main(int argc,char *argv[])  
  30. {  
  31.   
  32.     struct sockaddr_in server_addr; //存储服务器端socket地址结构  
  33.     struct sockaddr_in client_addr; //存储客户端 socket地址结构  
  34.   
  35.     int err;    //返回值  
  36.     pid_t pid;  //分叉进行的ID  
  37.   
  38.     /*****************socket()***************/  
  39.     ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流  
  40.     if(ss<0)  
  41.     {  
  42.         printf("server : server socket create error\n");  
  43.         return -1;  
  44.     }  
  45.     //注册信号  
  46.     sighandler_t ret;  
  47.     ret = signal(SIGTSTP,sig_pipe);  
  48.     if(SIG_ERR == ret)  
  49.     {  
  50.         printf("信号挂接失败\n");  
  51.         return -1;  
  52.     }  
  53.     else  
  54.         printf("信号挂接成功\n");  
  55.   
  56.   
  57.     /******************bind()****************/  
  58.     //初始化地址结构  
  59.     memset(&server_addr,0,sizeof(server_addr));  
  60.     server_addr.sin_family = AF_INET;           //协议族  
  61.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   //本地地址  
  62.     server_addr.sin_port = htons(PORT);  
  63.   
  64.     err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr));  
  65.     if(err<0)  
  66.     {  
  67.         printf("server : bind error\n");  
  68.         return -1;  
  69.     }  
  70.   
  71.     /*****************listen()***************/  
  72.     err = listen(ss,BACKLOG);   //设置监听的队列大小  
  73.     if(err < 0)  
  74.     {  
  75.         printf("server : listen error\n");  
  76.         return -1;  
  77.     }  
  78.   
  79.     /****************accept()***************/  
  80.     /** 
  81.     为类方便处理,我们使用两个进程分别管理两个处理: 
  82.     1,服务器监听新的连接请求;2,以建立连接的C/S实现通信 
  83.     这两个任务分别放在两个进程中处理,为了防止失误操作 
  84.     在一个进程中关闭 侦听套接字描述符 另一进程中关闭 
  85.     客户端连接套接字描述符。注只有当所有套接字全都关闭时 
  86.     当前连接才能关闭,fork调用的时候父进程与子进程有相同的 
  87.     套接字,总共两套,两套都关闭掉才能关闭这个套接字 
  88.     */  
  89.   
  90.     for(;;)  
  91.     {  
  92.         socklen_t addrlen = sizeof(client_addr);  
  93.         //accept返回客户端套接字描述符  
  94.         sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen);  //注,此处为了获取返回值使用 指针做参数  
  95.         if(sc < 0)  //出错  
  96.         {  
  97.             continue;   //结束此次循环  
  98.         }  
  99.         else  
  100.         {  
  101.             printf("server : connected\n");  
  102.         }  
  103.   
  104.         //创建一个子线程,用于与客户端通信  
  105.         pid = fork();  
  106.         //fork 调用说明:子进程返回 0 ;父进程返回子进程 ID  
  107.         if(pid == 0)        //子进程,与客户端通信  
  108.         {  
  109.             close(ss);  
  110.             process_conn_server(sc);  
  111.         }  
  112.         else  
  113.         {  
  114.             close(sc);  
  115.         }  
  116.     }  
  117. }  
  118.   
  119. /** 
  120.   服务器对客户端连接处理过程;先读取从客户端发送来的数据, 
  121.   然后将接收到的数据的字节的个数发送到客户端 
  122.   */  
  123.   
  124. //通过套接字 s 与客户端进行通信  
  125. void process_conn_server(int s)  
  126. {  
  127.     ssize_t size = 0;  
  128.     char buffer[buflen];  //定义数据缓冲区  
  129.     for(;;)  
  130.     {  
  131.         //等待读  
  132.         for(size = 0;size == 0 ;size = read(s,buffer,buflen));  
  133.         //输出从客户端接收到的数据  
  134.         printf("%s",buffer);  
  135.   
  136.         //结束处理  
  137.         if(strcmp(buffer,"quit") == 0)  
  138.         {  
  139.             close(s);   //成功返回0,失败返回-1  
  140.             return ;  
  141.         }  
  142.         sprintf(buffer,"%d bytes altogether\n",size);  
  143.         write(s,buffer,strlen(buffer)+1);  
  144.     }  
  145. }  
  146. void sig_pipe(int signo)  
  147. {  
  148.     printf("catch a signal\n");  
  149.     if(signo == SIGTSTP)  
  150.     {  
  151.         printf("接收到 SIGTSTP 信号\n");  
  152.         int ret1 = close(ss);  
  153.         int ret2 = close(sc);  
  154.         int ret = ret1>ret2?ret1:ret2;  
  155.         if(ret == 0)  
  156.             printf("成功 : 关闭套接字\n");  
  157.         else if(ret ==-1 )  
  158.             printf("失败 : 未关闭套接字\n");  
  159.   
  160.         exit(1);  
  161.     }  
  162. }  



TCPClient端


[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <strings.h>  
  3. #include <unistd.h>  
  4. #include <sys/types.h>  
  5. #include <sys/socket.h>  
  6. //#include <linux/in.h>  
  7. #include <stdlib.h>  
  8. #include <memory.h>  
  9. #include <arpa/inet.h>  
  10. #include <netinet/in.h>  
  11.   
  12. #include <signal.h> //添加信号处理  防止向已断开的连接通信  
  13.   
  14. /** 
  15.   信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号, 
  16.   信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认 
  17.   响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行 
  18.   */  
  19.   
  20.   
  21. #define PORT    11910  
  22. #define Buflen  1024  
  23.   
  24. void process_conn_client(int s);  
  25. void sig_pipe(int signo);    //用户注册的信号函数,接收的是信号值  
  26.   
  27. int s;  //全局变量 , 存储套接字描述符  
  28.   
  29. int main(int argc,char *argv[])  
  30. {  
  31.   
  32.     struct sockaddr_in server_addr;  
  33.     int err;  
  34.     sighandler_t ret;  
  35.     char server_ip[50] = "";  
  36.     /********************socket()*********************/  
  37.     s= socket(AF_INET,SOCK_STREAM,0);  
  38.     if(s<0)  
  39.     {  
  40.         printf("client : create socket error\n");  
  41.         return 1;  
  42.     }  
  43.   
  44.     //信号处理函数  SIGINT 是当用户按一个 Ctrl-C 建时发送的信号  
  45.     ret = signal(SIGTSTP,sig_pipe);  
  46.     if(SIG_ERR == ret)  
  47.     {  
  48.         printf("信号挂接失败\n");  
  49.         return -1;  
  50.     }  
  51.     else  
  52.         printf("信号挂接成功\n") ;  
  53.   
  54.   
  55.     /*******************connect()*********************/  
  56.     //设置服务器地址结构,准备连接到服务器  
  57.     memset(&server_addr,0,sizeof(server_addr));  
  58.     server_addr.sin_family = AF_INET;  
  59.     server_addr.sin_port = htons(PORT);  
  60.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  61.   
  62.     /*将用户数入对额字符串类型的IP格式转化为整型数据*/  
  63.     //inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr);  
  64.     printf("please input server ip address : \n");  
  65.     read(0,server_ip,50);  
  66.     //err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr);  
  67.     server_addr.sin_addr.s_addr = inet_addr(server_ip);  
  68.   
  69.     err = connect(s,(struct sockaddr *)&server_addr,sizeof(struct sockaddr));  
  70.     if(err == 0)  
  71.     {  
  72.         printf("client : connect to server\n");  
  73.     }  
  74.     else  
  75.     {  
  76.         printf("client : connect error\n");  
  77.         return -1;  
  78.     }  
  79.     //与服务器端进行通信  
  80.     process_conn_client(s);  
  81.     close(s);  
  82.   
  83. }  
  84. void process_conn_client(int s)  
  85. {  
  86.   
  87.     ssize_t size = 0;  
  88.     char buffer[Buflen];  
  89.   
  90.     for(;;)  
  91.     {  
  92.         memset(buffer,'\0',Buflen);  
  93.         /*从标准输入中读取数据放到缓冲区buffer中*/  
  94.         size = read(0,buffer,Buflen);   // 0,被默认的分配到标准输入  1,标准输出  2,error  
  95.         if(size >  0)  
  96.         {  
  97.             //当向服务器发送 “quit” 命令时,服务器首先断开连接  
  98.             write(s,buffer,strlen(buffer)+1);   //向服务器端写  
  99.   
  100.             //等待读取到数据  
  101.             for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) );  
  102.   
  103.             write(1,buffer,strlen(buffer)+1);   //向标准输出写  
  104.         }  
  105.     }  
  106. }  
  107.   
  108. void sig_pipe(int signo)    //传入套接字描述符  
  109. {  
  110.     printf("Catch a signal\n");  
  111.     if(signo == SIGTSTP)  
  112.     {  
  113.   
  114.         printf("接收到 SIGTSTP 信号\n");  
  115.         int ret = close(s);  
  116.         if(ret == 0)  
  117.             printf("成功 : 关闭套接字\n");  
  118.         else if(ret ==-1 )  
  119.             printf("失败 : 未关闭套接字\n");  
  120.         exit(1);  
  121.     }  
  122. }  

注意这里 的
  1.     sighandler_t ret;  

在不同的编译环境中可能会有错误的

所以编译选项要加上

 -D_GNU_SOURCE

三、基于I/O多路复用技术的并发TCP

       在实际的应用中, 要求一个服务器能同时处理大量的客户请求, 所有这些客户将访问绑
定在某一个特定套接字地址上的服务器。 因此, 服务器必须满足并发的需求。 如果不采用并
发技术, 当服务器处理一个客户请求时, 会拒绝其他客户端请求, 造成其他客户要不断的请
求并长期等待。
LinuxUnix) 系统中并发服务器有三种设计方式:
1) 多进程
进程是执行中的计算机程序, 可以认为是一个程序的一次运行。 它是一个动态的实体,
是独立的任务。 每个单独的进程运行在自己的虚拟地址空间中, 并且它只能通过安全的内核
管理机制和其它进程交互。 若是一个进程崩溃不会引起其它进程崩溃。
Linux(Unix)系统中, 多个进程可以同时执行相同的代码, 从而支持并发。
对于单个
CPU 系统而言, CPU 一次只能执行一个进程, 但操作系统可通过分时处理,
每个进程在每个时间段中执行, 因此对于用户而言, 这些进程在同时执行。
2) 多线程
线程与进程类似, 也支持并发执行。 与进程不同的一点, 在同一进程中所有线程共享
相同的全程变量以及系统分配给进程的资源。 因此, 线程占用较少的系统资源, 并且线程之
间切换更快。
3I/O多路复用( select poll函数)
另一种支持并发的方法是
I/O多路复用。select()函数是系统提供的, 它可以在多个描
述符中选择被激活的描述符进行操作。
例如: 一个进程中有多个客户连接, 即存在多个
TCP套接字描述符。select()函数阻塞
直到任何一个描述符被激活, 即有数据传输。 从而避免了进程为等待一个已连接上的数据而
无法处理其他连接。 因而, 这是一个时分复用的方法, 从用户角度而言, 它实现了一个进程
或线程中的并发处理。
I/O 多路复用技术的最大优势是系统开销小, 系统不必创建进程、 线程, 也不必维护这
些进程
/线程, 从而大大减少了系统的开销。
select()函数用于实现I/O多路复用, 它允许进程指示系统内核等待多个事件中的任何一
个发生, 并仅在一个或多个事情发送或经过某指定的时间后才唤醒进程。
它的原型如下,
#include<sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set * errorfds, struct timeval *timeout);

ndfs: select()
函数监视描述符数的最大值。 根据进程中打开的描述符数而定, 一般设为要
监视的描述符的最大数加
1
readfds: select() 函数监视的可读描述符集合。
writefds: select()函数监视的可写描述符集合。
errorfds: select()函数监视的异常描述符集合。
timeout: select()函数超时结束时间
返回值。 如果成功返回总的位数, 这些位对应已准备好的描述符。 否则返回
-1, 并在errno
中设置相应的错误码。
FD_ZERO(fd_set *fdset): 清空fdset与所有描述符的联系
FD_SET(int fd, fd_set *fdset): 建立描述符fdfdset的联系
FD_CLR(int fd, fd_set *fdset): 撤销描述符fdfdset的联系
FD_ISSET(int fd,fd_set *fdset) :: 检查与fdset联系的描述符 fd 是否可读写, 返回非 0
表示可读写。


采用 select()函数实现I/O多路复用的基本步骤如下:
( 1) 清空描述符集合
( 2) 建立需要监视的描述符与描述符集合的联系
( 3) 调用
select()函数
( 4) 检查所有需要监视的描述符, 利用
FD_ISSET 判断是否准备好
( 5) 对已准备好的描述符进行
I/O
操作

下面是在
eHome 中使用的一个select函数实例。
// name : Ehome_server.c
// author : pyy
// date : 2008-3-5
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORT 1234 //
使用的 port号码
#define MAXSOCKFD 10 //可同时服务的最大连接数目
int main()
{
int sockfd,newsockfd,is_connected[MAXSOCKFD],fd;
struct sockaddr_in addr;
int addr_len = sizeof(struct sockaddr_in);
fd_set readfds;
char buffer[256];
int length;
char buf2[256]; //add
if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)
{ perror("socket"); exit(1);}
//
填写 sockaddr_in 结构
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd,(struct sockaddr *)&addr,sizeof(addr))<0)
{ perror("bind"); exit(1);}
if(listen(sockfd,3)<0)
 
{ perror("listen"); exit(1);}
//
清楚连线状态的旗标
for(fd=0;fd<MAXSOCKFD;fd++)
is_connected[fd] = 0;
while(1)
{
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
for(fd=0; fd<MAXSOCKFD; fd++)
if(is_connected[fd]) FD_SET(fd,&readfds);
if(!select(MAXSOCKFD,&readfds,NULL,NULL,NULL)) continue;
//
判断是否有新的连线或新信息进来
for(fd=0; fd<MAXSOCKFD; fd++)
if(FD_ISSET(fd,&readfds))
{
if(sockfd == fd)
{
//
接收新连线
if((newsockfd= accept(sockfd,(struct sockaddr *)&addr,&addr_len))<0)
perror("accept");
//
将欢迎字符串送给 client
is_connected[newsockfd] = 1;
printf("Connect from %s\n",inet_ntoa(addr.sin_addr));
}
else
{
//
接收新信息
bzero(buffer,sizeof(buffer));
if( ( length=read(fd,buffer,sizeof(buffer)) ) <=0)
{
//
连线已中断, 清除连线状态旗标
printf("Connection closed.\n");
is_connected[fd] = 0;
close(fd);
}
else
{
printf("Receive message: %s\n",buffer);
write(fd,buffer,length);
bzero(buf2,sizeof(buf2));
sprintf(buf2,"%5.3f%5.3f%5.3f%5.2f%5.3f%5.2f",1.305,2.226,3.333,20.56,9.0,28.5);
length= write(fd,buf2,strlen(buf2));

printf("Send message: %s length= %d\n",buf2,length);
}
}
}
}
}





0 0
原创粉丝点击