The TCP network programming based on Linux

基于LinuxTCP网络编程

.LinuxTCP编程框架

clip_image001

TCP网络编程的流程包含服务器和客户端两种模式。服务器模式创建一个服务程序,等待客户端用户的连接,接收到用户的连接请求后,根据用户的请求进行处理;客户端模式则根据目的服务器的地址和端口进行连接,向服务器发送请求并对服务器的响应进行数据处理。

1.服务器端程序包括

Ø  Ø  建立套接字( socket()

Ø  Ø  套接字与端口的绑定(bind())

Ø  Ø  设置服务器的侦听连接(listen()

Ø  Ø  接收客户端连接(accept()

Ø  Ø  接收和发送数据(send(),recv()

Ø  Ø  关闭套接字(close())

2.说明

1>套接字初始化过程中,根据用户对套接字的需求来确定套接字的选项。按照用户定义的网络类型,协议类型和具体的协议标号等参数来定以socket()函数。系统根据用户的需求生成一个套接字文件描述符供用户使用。

2>套接字与端口的绑定过程中,将套接字与一个地址结构进行绑定。绑定之后,套接字所代表IP地址和端口地址及协议类型等参数按照绑定值进行操作。

3>由于一个服务器需要满足多个客户端的连接请求,而服务器在某个时间仅能处理有限个数的客户端连接请求,所以服务器需要设置服务器端排队队列的长度。

4>在客户端发送连接请求之后,服务器需要接收客户端的连接,然后才能进行其他的处理。

5>在服务器接收客户端请求之后,可以从套接字文件描述符中读取数据或者向文件描述符发送数据。接收数据后服务器按照定义的规则对数据进行处理,并将结果发送给客户端。

6>当服务器处理完数据,要结束与客户端的通信过程的时候,需要关闭套接字连接

2.客户端程序包括

Ø  Ø  建立套接字(socket())

Ø  Ø  连接服务器(connect())

Ø  Ø  读写网络数据(send(),recv())

Ø  Ø  关闭套接字(close())

3.服务器端和客户端程序的区别

客户端程序和服务器端程序不同之处是客户端在建立套接字之后可以不进行地址绑定,而是直接连接服务器端。

服务器端有listen()accept()两个函数,而客户端不需要这两个函数。

.基于LinuxTCP套接字函数

1. socket

1> 函数原型:

int socket(int domain,int type,int protocol)

2> 函数功能:

函数socket()用于创建一个套接字描述符。

3> 形参:

Ø  domain:用于指定创建套接字所使用的协议族,在头文件

<linux/socket.h>中定义。有时候程序中会使用PF_INET,在头文件中AF_INETPF_INET的数值是一致的。

常见的协议族如下:

AF_UNIX:创建只在本机内进行通信的套接字。

AF_INET:使用IPv4TCP/IP协议

AF_INET6:使用IPv6 TCP/IP协议

说明:

AF_UNIX只能用于单一的UNIX系统进程间通信,而AF_INET是针对Interne的,因而可以允许在远程主机之间通信。一般把它赋为AF_INET

Ø  type:指明套接子通信的类型,对应的参数如下

SOCK_STREAM:创建TCP流套接字

SOCK_DGRAM:创建UDP数据报套接字

SOCK_RAW:创建原始套接字

Ø  protocol:指定某个协议的特定类型

参数protocol通常设置为0,表示通过参数domain指定的协议族和参数type指定的套接字类型来确定使用的协议。当为原始套接字时,系统无法唯一的确定协议,此时就需要使用使用该参数指定所使用的协议。

4> 返回值:执行成功后返回一个新创建的套接字;若有错误发生则返回一个-1,错误代码存入errno中。

5> 举例:调用socket函数创建一个UDP套接字

int sock_fd;

sock_fd = socket(AF_INET,SOCK_DGRAM,0);

if(sock_fd< 0){

       perror(“socket”);

       exit(1);

}

2. bind

1> 函数原型:

int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen)

2> 函数功能

函数bind()的作用是将一个套接字文件描述符与地址和端口绑定。

3> 形参:

Ø  sockfd:sockfd是调用socket函数返回的文件描述符;

Ø  addrlensockaddr结构的长度。

Ø  my_addr: 是一个指向sockaddr结构的指针,它保存着本地套接字的地址(即端口和IP地址)信息。不过由于系统兼容性的问题,一般不使用这个结构,而使用另外一个结构(structsockaddr_in)来代替

4> 套接字地址结构:

(1)struct sockaddr:

结构struct  sockaddr定义了一种通用的套接字地址,它在

sys/socket.h 中定义。

struct sockaddr{

       unsigned short  sa_family;/*地址类型,AF_XXX*/

       char          sa_data[14];/*14字节的协议地址*/

}

a. sin_family:表示地址类型,对于使用TCP/IP协议进行的网络编程,该值只能是AF_INET.

b. sa_data:存储具体的协议地址。

(2)sockaddr_in

每种协议族都有自己的协议地址格式,TCP/IP协议组的地址格式为结构体struct sockaddr_in,它在netinet/in.h头文件中定义。

structsockaddr_in{

   unsigned short  sin_family;/*地址类型*/

   unsigned short  sin_port;/*端口号*/

   struct in_addr   sin_addr;/*IP地址*/

   unsigned char  sin_zero[8];/*填充字节,一般赋值为0*/

}

a. sin_family:表示地址类型,对于使用TCP/IP协议进行的网络编程,该值只能是AF_INET.

b. sin_port:是端口号

c. sin_addr:用来存储32位的IP地址。

d. 数组sin_zero为填充字段,一般赋值为0.

e. struct in_addr的定义如下:

structin_addr{

                     unsigned long s_addr;

}

结构体sockaddr的长度为16字节,结构体sockaddr_in的长度为16字节。可以将参数my_addrsin_addr设置为INADDR_ANY而不是某个确定的IP地址就可以绑定到任何网络接口。对于只有一IP地址的计算机,INADDR_ANY对应的就是它的IP地址;对于多宿主主机(拥有多个网卡)INADDR_ANY表示本服务器程序将处理来自所有网络接口上相应端口的连接请求

5> 返回值:

函数成功后返回0,当有错误发生时则返回-1,错误代码存入errno中。

6>举例:调用socket函数创建一个UDP套接字

struct sockaddr_in addr_serv,addr_client;/*本地的地址信息*/

memset(&serv_addr,0,sizeof(structsockaddr_in));

addr_serv.sin_family= AF_INET;/*协议族*/

addr_serv.sin_port= htons(SERV_PORT);/*本地端口号*/

addr_serv.sin_addr.s_addr= htonl(INADDR_ANY); /*任意本地地址*/
/*
套接字绑定
*/

if(bind(sock_fd,(structsockaddr *)&addr_serv),sizeof(struct sockaddr_in)) <0)

{

       perror(“bind”);

       exit(1);

}

3. 监听本地端口listen()

1>函数功能:函数listen()用来初始化服务器可连接队列,服务器处理客户端连接请求的时候是顺序处理的,同一时间仅能处理一个客户端连接。当多个客户端的连接请求同时到来的时候,服务器并不是同时处理,而是将不能处理的客户端连接请求放到等待队列中,这个队列的长度由listen()函数来定义。

2>函数原型:

#includ<sys/socket.h>

int listen(int sockfd,int backlog);

3>形参

Ø  sockfd: sockfd是调用socket函数返回的文件描述符

Ø  backlog:指定该连接队列的最大长度。如果连接队列已经达到最大,之后的连接请求被服务器拒绝。大多数系统的设置为20,可以将其设置修改为5或者10,根据系统可承受负载或者应用程序的需求来确定。

4>返回值:当listen()函数成功运行时,返回值为0;当运行失败时,它的返回值为-1,错误代码存入errno中。

5>.listen()函数的例子:

#define SERV_PORT 3000

int main(int argc,char *argv[])

{

int sock_fd;

struct sockaddr_in addr_serv,addr_client;/*本地的地址信息*/

sock_fd = socket(AF_INET,SOCK_DGRAM,0);

if(sock_fd< 0){

       perror(“socket”);

       exit(1);

}

memset(&serv_addr,0,sizeof(structsockaddr_in));

addr_serv.sin_family= AF_INET;/*协议族*/

addr_serv.sin_port= htons(SERV_PORT);/*本地端口号*/

addr_serv.sin_addr.s_addr= htonl(INADDR_ANY); /*任意本地地址*/
/*
套接字绑定
*/

if(bind(sock_fd,(structsockaddr *)&addr_serv),sizeof(struct sockaddr_in)) <0)

{

       perror(“bind”);

       exit(1);

}

//设置服务器侦听队列的长度

if(listen(sock_fd,5) <0){

       perror(“listen”);

       exit(1);

}

4. accept(接收一个网络请求)

1>函数功能:

当一个客户端的连接请求到达服务器主机侦听的端口时,此时客户端的连接会在队列中等待,知道使用服务器处理接收请求。

函数accept()成功执行后,会返回一个新的套接口文件描述符来表示客户端的连接,客户端连接的信息可以通过这个新描述符来获得。因此当服务器成功处理客户端的请求连接后,会有两个文件描述符,老的文件描述符表示客户端的连接,函数send()recv()通过新的文件描述符进行数据收发。

2>函数原型:

#include<sys/types.h>

#include<sys/socket.h>

int accept(int sock_fd,struct sockaddr*addr,socklen_t *addrlen);

3>形参

Ø  sock_fd:是由函数socket创建,经函数bind绑定到本地某一端口上,然后通过函数listen转化而来的监听套接字。

Ø  addr:用来保存发起连接请求的主机的地址和端口。

Ø  addrlenaddr 所指向的结构体的大小。

4>返回值:accept()函数的返回值是新连接的客户端套接字文件描述符,与客户端之间的通信是通过accept()返回的新套接字文件描述符来进行的,而不是通过建立套接字时的文件描述符。如果accept()函数发生错误,accept()会返回-1,通过errno可以得到错误值。

5>如果参数sock_fd所指定的套接字被设置为阻塞方式(Linux下的默认方式),且连接请求队列为空,则accept()将被阻塞直到有连接请求到此为止;如果参数s所指定的套接字被设置为非阻塞方式,如果队列为空,accept将立即返回-1errno被设置为EAGAIN.

6>实例:

int client_fd;

int client_len;

struct sockaddr_in  client_addr;

client_len = sizeof(struct sockaddr_in);

client_fd = accept(sock_fd,(struct sockaddr *)&client_addr,&client_len);

if(conn_fd< 0){

       perror(“accept”);

exit(1);

}

5. connect(连接目标网络服务器)

1>函数功能:

客户端在建立套接字之后,不需要进行地址绑定,就可以直接连接服务器。连接服务器的函数为connect(),此函数连接指定参数的服务器,例如IP地址,端口号。

如果是TCP编程,则connect()函数用于服务器发出连接请求,服务器的IP地址和端口号由 参数serv_addr指定。

如果是UDP编程,则connect函数并不建立真正的连接,它只是告诉内核与该套接字进行通信的目的地址(由第二个参数指定),只有该目的地址发来的数据才会被该socket接收。调用connect函数的好处是不必在每次发送和接收数据时都指定目的地址。

2>函数原型:

#include<sys/types.h>

#include<sys/socket.h>

int connect(int sock_fd,struct sockaddr  *serv_addr,socklen_taddrlen);

3>形参:

Ø  sock_fd:建立套接字时返回的套接字文件描述符,调用socket()返回的。

Ø  serv_addr:是一个指向数据结构sockaddr的指针,其中包括客户端需要连接的服务器的目的IP地址和端口号。

Ø  addrlen:表示了第二了参数的大小,可以使用sizeof(struct sockaddr)

4>执行成功后返回0,有错误发生则返回-1,错误代码存入errno中。

5>实例:

int sock_fd;

struct sockaddr_in serv_addr;
if(-1 == (sock_fd == socket(AF_INET,SOCK_STREAM,0))){

              printf(“Error: Unable to createsocket(%i)…\n”,errno);

              perror(“sockets”);

              exit(1);

}

memset(&serv_addr,0,sizeof(structsockaddr_in));

serv_addr.sin_family= AF_INET;

serv_addr.sin_port= htons(DEST_PORT);

serv_addr.sin_addr.s_addr= inet(DEST_IP_ADDRESS);

if(-1== connect(sock_fd,(struct sockaddr *)&serv_add,sizeof(struct sockaddr))){

       printf(“Error:unable to the establishconnection to socket(%i)…\n”,errno);

       perror(“socks”);

       close(sock_fd);

       exit(1);

}

6. send(发送数据)

1>函数功能:函数send用来在TCP套接字上发送数据,send只能对处于连接状态的套接字使用。

2>函数原型

#include<sys/types.h>

#include<sys/socket.h>

ssize_t send(int conn_fd,const void *msg,size_t len, int flags);

3>函数形参:

Ø  conn_fd:为已建立好连接的套接字描述符,即调用accept()函数后返回的套接字描述符。

Ø  msg:存放发送数据的缓冲区。

Ø  len:发送缓冲区的长度

Ø  flags:为控制选项,一般设置为0,或取以下值:

²  MSG_OOB:在指定的套接字上发送带外数据(out-of-band data,该类型的套接字必须支持带外数据(如:SOCK_STREAM.

²  MSG_DONTROUTE:通过最直接的路径发送数据,而忽略下层协议的路由设置。

4>返回值:

执行成功返回实际发送数据的字节数,出错则返回-1,错误代码存入errno中。

执行成功只是说明数据写入套接字的缓冲区中,并不表示数据已经成功地通过网络发送到目的地。

5>实例:

#define  BUFFERSIZE  1500

char  send_buf[BUFFERSIZE];

……

if(send(conn_fd,send_buf,len,0)< 0){

       perror(“send”);

exit(1);

}

7. recv(接收数据)

1>函数功能:recv()用来TCP套接字上接收数据。函数recv从指定的套接字描述符上接收数据并保存到指定buf中。

2>函数原型

#include<sys/types.h>

#include<sys/socket.h>

ssize_t recv(int conn_fd,void *buf,size_t len,int flags);

3>函数形参:

Ø  conn_fd: 为已建立好连接的套接字描述符,即调用accept()函数后返回的套接字描述符

Ø  buf:接收缓冲区

Ø  len:接收缓冲区的大小

Ø  flags:为控制选项,一般设置为0或取以下数值

²  MSG_OOB:请求接收带外数据

²  MSG_PEEK:只查看数据而不读出

²  MSG_WAITALL:只在接收缓冲区满时才返回。

4>函数返回值

函数执行成功返回接收到的数据字节数,出错返回-1,错误代码存入errno中。

5>实例:

#define  BUFFERSIZE  1500

char recv_buf[BUFFERSIZE];

……

if(recv(conn_fd,recv_buf,sizeof(recv_buf),0)< 0){

       perror(“recv”);

exit(1);

}

8. close

1>函数原型:

int  close(int fd);

2>函数功能:

函数close用来关闭一个套接字描述符。

3>函数形参:

Ø  参数fd为一个套接字描述符。

4>返回值:

执行成功返回0,出错则返回-1.错误代码存入errno中。

说明:close()函数的头文件是#include<unistd.h>.

.基于LinuxTCP套接字编程实例

1.实例程序分为服务器端和客户端,客户端把Hello tigerjibo发送给服务器端;服务器端接收到字符串后,发送接收到的总字符串个数给客户端;

clip_image002

2.服务器端程序:

clip_image003

clip_image004

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<errno.h>
  5
  6
  7 #include<sys/types.h>
  8 #include<sys/socket.h>
  9 #include<unistd.h>//close()
10 #include<netinet/in.h>//struct sockaddr_in
11 #include<arpa/inet.h>//inet_ntoa
12 #define  QUEUE_LINE  12
13 #define  SOURCE_PORT 8000
14
15 #define  SOURCE_IP_ADDRESS “192.168.1.6”
16
17 void process_info(int s)
18 {
19         int recv_num;
20         int send_num;
21         char recv_buf[50];
22         char send_buf[50];
23         while(1){
24                 printf(“begin recv:\n”);
25                 recv_num = recv(s,recv_buf,sizeof(recv_buf),0);
26                 if(recv_num <0){
27                         perror(“recv”);
28                         exit(1);
29                 } else {
30                         recv_buf[recv_num] = ‘\0’;
31                         printf(“recv sucessful:%s\n”,recv_buf);
32                 }
33                 sprintf(send_buf,”recv %d numbers bytes\n”,recv_num);
34                 printf(“begin send\n”);
35                 send_num = send(s,send_buf,sizeof(send_buf),0);
36                 if (send_num < 0){
37                         perror(“sned”);
38                         exit(1);
39                 } else {

40                         printf(“send sucess\n”);
41                 }
42         }
43 }
44 int main()
45 {
46         int sock_fd,conn_fd;
47         int client_len;
48         pid_t pid;
49         struct sockaddr_in addr_serv,addr_client;
50         sock_fd = socket(AF_INET,SOCK_STREAM,0);
51         if(sock_fd < 0){
52                 perror(“socket”);
53                 exit(1);
54         } else {
55                 printf(“sock sucessful\n”);
56         }
57         //
初始化服务器端地址

58         memset(&addr_serv,0,sizeof(addr_serv));
59         addr_serv.sin_family = AF_INET;
60         addr_serv.sin_port = htons(SOURCE_PORT);
61         addr_serv.sin_addr.s_addr =inet_addr(SOURCE_IP_ADDRESS);
62         client_len = sizeof(struct sockaddr_in);
63         if(bind(sock_fd,(struct sockaddr *)&addr_serv,sizeof(struct sockaddr_in))<0){
64                 perror(“bind”);
65                 exit(1);
66         } else {
67                 printf(“bind sucess\n”);
68         }
69         if (listen(sock_fd,QUEUE_LINE) < 0){
70                 perror(“listen”);
71                 exit(1);
72         } else {
73                 printf(“listen sucessful\n”);
74         }
75         while(1){
76                  printf(“begin accept:\n”);
77                  conn_fd = accept(sock_fd,(struct sockaddr *)&addr_client,&client_len);

78                  if(conn_fd < 0){
79                         perror(“accept”);
80                         exit(1);
81                  }
82                  printf(“accept a new client,ip:%s\n”,inet_ntoa(addr_client.sin_addr));
83                  pid = fork();
84                  if(0 == pid){         //
子进程
85                         close(sock_fd);//
在子进程中关闭服务器的侦听
86                         process_info(conn_fd);//
处理信息
87                  } else {
88                         close(conn_fd);//
在父进程中关闭客户端的连接

89                  }
90         }
91
92 }

3.客户端程序:

clip_image005

clip_image006

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<stdlib.h>
  4 #include<errno.h>
  5
  6 #include<sys/types.h>
  7 #include<sys/socket.h>
  8 #include<unistd.h>//close()
  9 #include<netinet/in.h>//struct sockaddr_in
10 #include<arpa/inet.h>//inet_ntoa
11
12 #define DEST_PORT 8000
13 #define DEST_IP_ADDRESS “192.168.1.6”
14
15 /*
客户端的处理过程
*/
16 void process_info(int s)
17 {
18         int send_num;
19         int recv_num;
20         char send_buf[]=”tigerjibo”;
21         char recv_buf[50];
22         while(1){
23                 printf(“begin send\n”);
24                 send_num = send(s,send_buf,sizeof(send_buf),0);
25                 if (send_num < 0){
26                         perror(“send”);
27                         exit(1);
28                 } else {
29                         printf(“send sucess:%s\n”,send_buf);
30                 }
31                 printf(“begin recv:\n”);
32                 recv_num = recv(s,recv_buf,sizeof(recv_buf),0);
33                 if(recv_num < 0){
34                         perror(“recv”);
35                         exit(1);
36                 } else {
37                         recv_buf[recv_num]=’\0′;
38                         printf(“recv sucess:%s\n”,recv_buf);
39                 }

40         }
41 }
42 int main(int argc,char *argv[])
43 {
44         int sock_fd;
45         struct sockaddr_in addr_serv;//
服务器端地址

46
47         sock_fd = socket(AF_INET,SOCK_STREAM,0);
48         if(sock_fd < 0){
49                 perror(“sock”);
50                 exit(1);
51         } else {
52                 printf(“sock sucessful:\n”);
53         }
54         memset(&addr_serv,0,sizeof(addr_serv));
55         addr_serv.sin_family = AF_INET;
56         addr_serv.sin_port =  htons(DEST_PORT);
57         addr_serv.sin_addr.s_addr = inet_addr(DEST_IP_ADDRESS);
58        if( connect(sock_fd,(struct sockaddr *)&addr_serv,sizeof(struct sockaddr)) < 0){
59                 perror(“connect”);
60                 printf(“connect (%d)\n”,errno);
61                 exit(1);
62        } else {
63                 printf(“connect sucessful\n”);
64        }
65         process_info(sock_fd);
66         close(sock_fd);
67 }

 

使用Code::Blocks制作Makefile(适用于linux mac windows)

首先下载cbp2make ,用Code::Blocks编译,将bin/Realease/下的所有内容复制到任意文件夹,比如我放在Code::Blocks的安装目录下的cbp2make文件夹下(该文件夹自己创建的)。启动Code::Blocks 点击Tools—->Configure tools—->add 输入名称、路径及相关参数即可。一个例子见下图(因为本人想生成unix、linnux中的make文件,我的Code::Blocks安装在windows下的,所以有 -unix选项。也可以是 -mac 或-windows,如果你想生成所有平台的就将该参数改为 all-os 即可):

6598214358262017695

运行时直接点击Tools—->cbp2make即可在你的工程(例如help.cbp)中生成Makefile文件,方便你在不同的系统下编译你的工程,弹出的界面如下(当然,在以上的图中的parameters选项不是唯一的,你也可以自己修改):

6597574442494860177

完结。

 

注:原创作品,引用时请注明出处。引用格式:该文章参考自郭大侠:http://guoshaoguang.com/

rpm转换成deb

rpm转换成deb

Ubuntu的软件包格式是deb,如果要安装rpm的包,则要先用alienrpm转换成deb

首先安装alien

sudo apt-get install alien   #alien默认没有安装,所以首先要安装它,如果找不到这个包,请检查你的/etc/apt/sources.list

安装完成后可以看到,alien的描述:Convert or install an alien binary package

测试

       随便选了一个rpm包,地址为:

http://dl.fedoraproject.org/pub/fedora/linux/releases/18/Everything/i386/os/Packages/t/tar-1.26-9.fc18.i686.rpm ,下载下来为tar-1.26-9.fc18.i686.rpm

使用sudo alien tar-1.26-9.fc18.i686.rpm

处理信息为:

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

Warning: Skipping conversion of scripts in package tar: postinst prerm

Warning: Use the –scripts parameter to include the scripts.

warning: tar-1.26-9.fc18.i686.rpm: Header V3 RSA/SHA256 Signature, key ID de7f38bd: NOKEY

tar_1.26-10_i386.deb generated

#rpm转换位deb,完成后会生成一个同名的tar_1.26-10_i386.deb(当然一些系统信息可能会去掉,比如fc18ubuntu中使用alien转换后,就木有了)

安装转换后的包

sudo dpkg -i tar_1.26-10_i386.deb #安装注意,用alien转换的deb包并不能保证100%顺利安装,所以可以找到deb最好直接用deb有时候,我们想要使用的软件并没有被包含到 Ubuntu 的仓库中,而程序本身也没有提供让 Ubuntu 可以使用的 deb 包,你又不愿从源代码编译。但假如软件提供有 rpm 包的话,我们也是可以在 Ubuntu 中安装的。

关于rpc简介、原理、实例-缘于difx

关于rpc简介、原理、实例缘于difx

       今天稍微研究了一下关于rpc的东东,主要是因为Pro.Z碰到的问题,在解决了以后,对rpc产生了一些兴趣,考虑到我目前的程序也可以从比较底层的socket方式概率为rpc方式,看的过程中,看到了一个网上的实例教程,估计已经转载了N遍了,所以有几个错误,包括rpc unknown protocalconfliction definecannot register service等,在测试的时候也进行了一些改正,并且进行了测试。

       单单是这个小程序,就可以做一个时间服务器性质的程序了。

简介

在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构在网络日益发展的今天已无法适应实际需求。总所周知,传统过程调用模式无法充分利用网络上其他主机的资源(如CPUMemory等),也无法提高代码在实体间的共享程度,使得主机资源大量浪费。

      通过RPC我们可以充分利用非共享内存的多处理器环境(例如通过局域网连接得多台工作站),这样可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。

RPC的结构原理及其调用机制

如前所述RPC其实也是种C/S的编程模式,有点类似C/S Socket 编程模式,但要比它

更高一层。当我们在建立RPC服务以后,客户端的调用参数通过底层的RPC传输通道,可以是UDP,也可以是TCP(也即TI-RPC—无关性传输),并根据传输前所提供的目的地址及RPC上层应用程序号转至相应的RPC应用程序服务端,且此时的客户端处于等待状态,直至收到应答或Time Out超时信号。具体的流程图如F1。当服务器端获得请求消息,则会根据注册RPC时告诉RPC系统的例程入口地址,执行相应的操作,并将结果返回至客户端。

 
  clip_image002


F1

当一次RPC调用结束后,相应线程发送相应的信号,客户端程序才会继续运行。

在这个过程中,一个远程过程是有三个要素来唯一确定的:程序号、版本号和过程号。

程序号是用来区别一组相关的并且具有唯一过程好的远程过程。一个程序可以有一个或几个不同的版本,而每个版本的程序都包含一系列能被远程调用的过程,通过版本的引入,使得不同版本下的RPC能同时提供服务。每个版本都包含有许多可供远程调用的过程,每个过程则有其唯一标示的过程号。

基于RPC的应用系统开发

通过以上对RPC原理的简介后,我们再来继续讨论如何来开发基于RPC的应用系统。

一般而言在开发RPC时,我们通常分为三个步骤:

a、 定义说明客户/服务器的通信协议:这里所说的通信协议是指定义服务过程的名称、调用参数的数据类型和返回参数的数据类型,还包括底层传输类型(可以是UDPTCP),当然也可以由RPC底层函数自动选择连接类型建立TI-RPC。最简单的协议生成的方法是采用协议编译工具,常用的有Rpcgen,我会在后面实例中详细描述其使用方法。

b、  开发客户端程序。

c、  开发服务器端程序。

开发客户端和服务器端的程序时,RPC提供了我们不同层次的开发例程调用接口。不同层次的接口提供了对RPC不同程度控制。一般可分为5个等级的编程接口,接下来我们分别讨论一下各层所提供的功能函数。

简单层例程

简单层是面向普通RPC应用,为了快速开发RPC应用服务而设计的,他提供了如下功能函数。

  函数名

             功能描述

Rpc_reg( )

在一特定类型的传输层上注册某个过程,来作为提供服务的RPC程序

Rpc_call( )

远程调用在指定主机上指定的过程

Rpc_Broadcast( )

向指定类型的所有传输端口上广播一个远程过程调用请求

                                

高层例程

在这一层,程序需要在发出调用请求前先创建一个客户端句柄,或是在侦听请求前先建立一个服务器端句柄。程序在该层可以自由的将自己的应用绑在所有的传输端口上,它提供了如下功能函数。

          

  函数名

             功能描述

Clnt_create( )

程序通过这个功能调用,告诉底层RPC服务器的位置及其传输类型

Clnt_create_timed( )

定义每次尝试连接的超时最大时间

Svc_create( )

在指定类型的传输端口上建立服务器句柄,告诉底层RPC事件过程的相应入口地址

Clnt_call()

向服务器端发出一个RPC调用请求

中间层例程

中间层向程序提供更为详细的RPC控制接口,而这一层的代码变得更为复杂,但运行也更为有效,它提供了如下功能函数。

  函数名

             功能描述

Clnt_tp_create( )

在指定的传输端口上建立客户端句柄

Clnt_tp_create_timed( )

定义最大传输时延

Svc_tp_creaet( )

在指定的传输端口上建立服务句柄

Clnt_call( )

向服务器端发出RPC调用请求

                

专家层例程

这层提供了更多的一系列与传输相关的功能调用,它提供了如下功能函数。

 

  函数名

             功能描述

Clnt_tli_create( )

在指定的传输端口上建立客户端句柄

Svc_tli_create( )

在指定的传输端口上建立服务句柄

Rpcb_set( )

通过调用rpcbindRPC服务和网络地址做映射

Rpcb_unset( )

删除rpcb_set( ) 所建的映射关系

Rpcb_getaddr( )

调用rpcbind来犯会指定RPC服务所对应的传输地址

Svc_reg( )

将指定的程序和版本号与相应的时间例程建起关联

Svc_ureg( )

删除有svc_reg( ) 所建的关联

Clnt_call( )

客户端向指定的服务器端发起RPC请求

底层例程

该层提供了所有对传输选项进行控制的调用接口,它提供了如下功能函数。

  函数名

             功能描述

Clnt_dg_create( )

采用无连接方式向远程过程在客户端建立客户句柄

Svc_dg_create( )

采用无连接方式建立服务句柄

Clnt_vc_create( )

采用面向连接的方式建立客户句柄

Svc_vc_create( )

采用面向连接的方式建立RPC服务句柄

Clnt_call( )

客户端向服务器端发送调用请求

实例介绍

以下我将通过实例向读者介绍通过简单层RPC的实现方法。通常在此过程中我们将使用RPC协议编译工具—RpcgenRpcgen 工具用来生成远程程序接口模块,它将以RPC语言书写的源代码进行编译,Rpc 语言在结构和语法上同C语言相似。由Rpcgen 编译生成的C源程序可以直接用C编译器进行编译,因此整个编译工作将分为两个部分。Rpcgen的源程序以.x结尾,通过其编译将生成如下文件:

a) 一个头文件(.h)包括服务器和客户端程序变量、常量、类型等说明。

b) 一系列的XDR例程,它可以对头文件中定义的数据类型进行处理。

c)  一个Server 端的标准程序框架。

d) 一个Client 端的标准程序框架。

   当然,这些输出可以是选择性的,Rpcgen 的编译选项说明如下:

  选项

功能

‘-‘ a

生成所有的模板文件

‘-‘ Sc

生成客户端的模板文件

‘-‘ Ss

生成服务器端的模板文件

‘-‘ Sm

生成Makefile 文件

      (详见Solaris Rpcgen Manaul)

 

Rpcgen 源程序 time.x:

/* time.x: Remote time printing protocol */

program TIMEPROG {

version PRINTIMEVERS {

string PRINTIME(string) = 1;

} = 1;

} = 0x20000001;

 

time_proc.c源程序:

/* time_proc.c: implementation of the remote procedure "printime" */ 

#include <stdio.h>

#include <rpc/rpc.h> /* always needed */

#include "time.h" /* time.h will be generated by rpcgen */

#include <time.h>

/* Remote version of "printime" */

char ** printime_1_svc(char **msg,struct svc_req *req)



{

static char * result; /* must be static! */

static char tmp_char[100];

time_t rawtime;



FILE *f;



f = fopen("/tmp/rpc_result", "a+");

if (f == (FILE *)NULL) {

strcpy(tmp_char,”Error”);

result = tmp_char;;

return (&result);

}

fprintf(f, "%sn", *msg); //used for debugging

fclose(f);

time(&rawtime);

sprintf(tmp_char,"Current time is :%s",ctime(&rawtime));

result =tmp_char;

return (&result);

}

 

rtime.c源代码

/*

* rtime.c: remote version

* of "printime.c"

*/



#include <stdio.h>

#include "time.h" /* time.h generated by rpcgen */



main(int argc, char **argv)



{

CLIENT *clnt;

char *result;

char *server;

char *message;



if (argc != 3) {



fprintf(stderr, "usage: %s host messagen", argv[0]);

exit(1);

}



server = argv[1];

message = argv[2];



/*

* Create client "handle" used for

* calling TIMEPROG on the server

* designated on the command line.

*/



//clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "visible");

clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "TCP");





if (clnt == (CLIENT *)NULL) {

/*

* Couldn't establish connection

* with server.

* Print error message and die.

*/



clnt_pcreateerror(server);

exit(1);

}



/*



* Call the remote procedure

* "printime" on the server

*/



result =*printime_1(&message,clnt);

if (result== (char *)NULL) {

/*

* An error occurred while calling

* the server.

* Print error message and die.

*/



clnt_perror(clnt, server);

exit(1);

}



/* Okay, we successfully called

* the remote procedure.

*/



if (strcmp(result,“Error”) == 0) {



/*

* Server was unable to print

* the time.

* Print error message and die.

*/



fprintf(stderr, "%s: could not get the timen",argv[0]);

exit(1);

}

printf("From the Time Server ...%sn",result);

clnt_destroy( clnt );

exit(0);

}

 

编译方法

   有了以上的三段代码后,就可用rpcgen 编译工具进行RPC协议编译,命令如下:

   $rpcgen time.x

   rpcgen 会自动生成time.htime_svc.ctime_clnt.c

   再用系统提供的gcc进行C的编译,命令如下:

   $gcc rtime.c time_clnt.c -o rtime –lnsl                            //客户端编译

$gcc time_proc.c time_svc.c -o time_server  –lnsl     error//服务器端编译

编译成功后即可在Server端运行time_server,立即将该服务绑定在rpc服务端口上提供服务。在客户端运行./rdate hostname msg (msg 是一字符串,笔者用来测试时建立的),立即会返回hostname 端的时间。

出错问题

Cannot register service

RPC: Authentication error; why = Client credential too weak unable to register (TIMEPROG, PRINTIMEVERS, udp)

解决方法:使用root账户即可

time_proc.c:7: error: conflicting types for printime_1?

源程序time_proc.c中报错

解决方法,改为char ** printime_1_svc(char **msg,struct svc_req *req)即可。

RPC: Unknown protocol

      这个原因可能是rpc依赖包没有安装,或者使用函数clnt_create的时候,最后一个参数应该不使用visible,而是tcp。