2007年8月20日星期一

编写Linux下的UDP Client/Server程序




   1:一、引言
2:UDP是TCP/IP协议中的传输层协议的一种,本文介绍了在Linux下编写基于UDP协议的Client/Server模型的程序的方法,并给出了一个echo Client/Server例子程序。
3:
4:二、UDP协议简介
5:UDP是一种简单的传输层协议,在RFC768中有详细描述。UDP协议是一种非连接的、不可靠的数据报文协议,完全不同于提供面向连接的、可靠的字节流的TCP协议。虽然UDP有很多不足,但是还是有很多网络程序使用它,例如DNS(域名解析服务)、NFS(网络文件系统)、SNMP(简单网络管理协议)等。
6:通常,UDP Client程序不和Server程序建立连接,而是直接使用sendto()来发送数据。同样,UDP Server程序不需要允许Client程序的连接,而是直接使用recvfrom()来等待直到接收到Client程序发送来的数据。
7:这里,我们使用一个简单的echo Client/Server程序来介绍在Linux下编写UDP程序的方法。Client程序从stdin读取数据并通过网络发送到Server程序,Server程序在收到数据后直接再发送回Client程序,Client程序收到Server发回的数据后再从stdout输出。
8:
9:三、UDP Server程序
10:1、编写UDP Server程序的步骤
11:(1)使用socket()来建立一个UDP socket,第二个参数为SOCK_DGRAM。
12:(2)初始化sockaddr_in结构的变量,并赋值。sockaddr_in结构定义:
13:struct sockaddr_in {
14:uint8_t sin_len;
15:sa_family_t sin_family;
16:in_port_t sin_port;
17:struct in_addr sin_addr;
18:char sin_zero[8];
19:};
20:这里使用“08”作为服务程序的端口,使用“INADDR_ANY”作为绑定的IP地址即任何主机上的地址。
21:(3)使用bind()把上面的socket和定义的IP地址和端口绑定。这里检查bind()是否执行成功,如果有错误就退出。这样可以防止服务程序重复运行的问题。
22:(4)进入无限循环程序,使用recvfrom()进入等待状态,直到接收到客户程序发送的数据,就处理收到的数据,并向客户程序发送反馈。这里是直接把收到的数据发回给客户程序。
23:
24:2、udpserv.c程序内容:
25:#include <sys/types.h>
26:#include <sys/socket.h>
27:#include <string.h>
28:#include <netinet/in.h>
29:#include <stdio.h>
30:#include <stdlib.h>
31:
32:#define MAXLINE 80
33:#define SERV_PORT 8888
34:
35:void do_echo(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen)
36:{
37:int n;
38:socklen_t len;
39:char mesg[MAXLINE];
40:
41:for(;;)
42:{
43:len = clilen;
44:/* waiting for receive data */
45:n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
46:/* sent data back to client */
47:sendto(sockfd, mesg, n, 0, pcliaddr, len);
48:}
49:}
50:
51:int main(void)
52:{
53:int sockfd;
54:struct sockaddr_in servaddr, cliaddr;
55:
56:sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* create a socket */
57:
58:/* init servaddr */
59:bzero(&servaddr, sizeof(servaddr));
60:servaddr.sin_family = AF_INET;
61:servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
62:servaddr.sin_port = htons(SERV_PORT);
63:
64:/* bind address and port to socket */
65:if(bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
66:{
67:perror("bind error");
68:exit(1);
69:}
70:
71:do_echo(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
72:
73:return 0;
74:}
75:
76:
77:四、UDP Client程序
78:1、编写UDP Client程序的步骤
79:(1)初始化sockaddr_in结构的变量,并赋值。这里使用“8888”作为连接的服务程序的端口,从命令行参数读取IP地址,并且判断IP地址是否符合要求。
80:(2)使用socket()来建立一个UDP socket,第二个参数为SOCK_DGRAM。
81:(3)使用connect()来建立与服务程序的连接。与TCP协议不同,UDP的connect()并没有与服务程序三次握手。上面我们说了UDP是非连接的,实际上也可以是连接的。使用连接的UDP,kernel可以直接返回错误信息给用户程序,从而避免由于没有接收到数据而导致调用recvfrom()一直等待下去,看上去好像客户程序没有反应一样。
82:(4)向服务程序发送数据,因为使用连接的UDP,所以使用write()来替代sendto()。这里的数据直接从标准输入读取用户输入。
83:(5)接收服务程序发回的数据,同样使用read()来替代recvfrom()
84:(6)处理接收到的数据,这里是直接输出到标准输出上。
85:
86:2、udpclient.c程序内容:
87:#include <sys/types.h>
88:#include <sys/socket.h>
89:#include <string.h>
90:#include <netinet/in.h>
91:#include <stdio.h>
92:#include <stdlib.h>
93:#include <arpa/inet.h>
94:#include <unistd.h>
95:
96:#define MAXLINE 80
97:#define SERV_PORT 8888
98:
99:void do_cli(FILE *fp, int sockfd, struct sockaddr *pservaddr, socklen_t servlen)
100:{
101:int n;
102:char sendline[MAXLINE], recvline[MAXLINE + 1];
103:
104:/* connect to server */
105:if(connect(sockfd, (struct sockaddr *)pservaddr, servlen) == -1)
106:{
107:perror("connect error");
108:exit(1);
109:}
110:
111:while(fgets(sendline, MAXLINE, fp) != NULL)
112:{
113:/* read a line and send to server */
114:write(sockfd, sendline, strlen(sendline));
115:/* receive data from server */
116:n = read(sockfd, recvline, MAXLINE);
117:if(n == -1)
118:{
119:perror("read error");
120:exit(1);
121:}
122:recvline[n] = 0; /* terminate string */
123:fputs(recvline, stdout);
124:}
125:}
126:
127:int main(int argc, char **argv)
128:{
129:int sockfd;
130:struct sockaddr_in srvaddr;
131:
132:/* check args */
133:if(argc != 2)
134:{
135:printf("usage: udpclient <IPaddress>\n");
136:exit(1);
137:}
138:
139:/* init servaddr */
140:bzero(&servaddr, sizeof(servaddr));
141:servaddr.sin_family = AF_INET;
142:servaddr.sin_port = htons(SERV_PORT);
143:if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
144:{
145:printf("[%s] is not a valid IPaddress\n", argv[1]);
146:exit(1);
147:}
148:
149:sockfd = socket(AF_INET, SOCK_DGRAM, 0);
150:
151:do_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
152:
153:return 0;
154:}
155:
156:
157:五、运行例子程序
158:1、编译例子程序
159:使用如下命令来编译例子程序:
160:gcc -Wall -o udpserv udpserv.c
161:gcc -Wall -o udpclient udpclient.c
162:编译完成生成了udpserv和udpclient两个可执行程序。
163:
164:2、运行UDP Server程序
165:执行./udpserv &命令来启动服务程序。我们可以使用netstat -ln命令来观察服务程序绑定的IP地址和端口,部分输出信息如下:
166:Active Internet connections (only servers)
167:Proto Recv-Q Send-Q Local Address Foreign Address State
168:tcp 0 0 0.0.0.0:32768 0.0.0.0:* LISTEN
169:tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
170:tcp 0 0 0.0.0.0:6000 0.0.0.0:* LISTEN
171:tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
172:udp 0 0 0.0.0.0:32768 0.0.0.0:*
173:udp 0 0 0.0.0.0:8888 0.0.0.0:*
174:udp 0 0 0.0.0.0:111 0.0.0.0:*
175:udp 0 0 0.0.0.0:882 0.0.0.0:*
176:可以看到udp处有“0.0.0.0:8888”的内容,说明服务程序已经正常运行,可以接收主机上任何IP地址且端口为8888的数据。
177:如果这时再执行./udpserv &命令,就会看到如下信息:
178:bind error: Address already in use
179:说明已经有一个服务程序在运行了。
180:
181:3、运行UDP Client程序
182:执行./udpclient 127.0.0.1命令来启动客户程序,使用127.0.0.1来连接服务程序,执行效果如下:
183:Hello, World!
184:Hello, World!
185:this is a test
186:this is a test
187:^d
188:输入的数据都正确从服务程序返回了,按ctrl+d可以结束输入,退出程序。
189:如果服务程序没有启动,而执行客户程序,就会看到如下信息:
190:$ ./udpclient 127.0.0.1
191:test
192:read error: Connection refused
193:说明指定的IP地址和端口没有服务程序绑定,客户程序就退出了。这就是使用connect()的好处,注意,这里错误信息是在向服务程序发送数据后收到的,而不是在调用connect()时。如果你使用tcpdump程序来抓包,会发现收到的是ICMP的错误信息。

没有评论: