客户端程序

  1. /* UDP 客户端 */   
  2. #include <string.h>   
  3. #include <stdio.h>   
  4. #include <unistd.h>   
  5. #include <stdlib.h>   
  6. #include <sys/socket.h>   
  7. #include <netinet/in.h>   
  8. #include <arpa/inet.h>   
  9.    
  10. #define SERV_PORT 9877 /* 通用端口号 */   
  11.    
  12. extern void err_sys(const char *, ...);   
  13. extern void err_quit(const char *, ...);   
  14. extern void dg_cli(FILE *fd, int sockfd, struct sockaddr *addr, socklen_t addrlen);   
  15.    
  16. int main(int argc, char **argv)   
  17. {   
  18.     int                 sockfd;   
  19.     struct sockaddr_in  servaddr;   
  20.    
  21.     if (argc != 2)   
  22.         err_quit("usage: udpcli <IPaddress>");   
  23.    
  24.     bzero(&servaddr, sizeof(servaddr));   
  25.     servaddr.sin_family = AF_INET;   
  26.     servaddr.sin_port = htons(SERV_PORT);   
  27.     inet_pton(AF_INET, argv[1], &servaddr.sin_addr);   
  28.    
  29.     if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)   
  30.         err_sys("socket err");   
  31. /* 客户端处理函数:从标准输入读入文本行,发送给服务器;接收来自服务器的回射文本,并把它显示到标准输出 */   
  32.     dg_cli(stdin, sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));   
  33.    
  34.     exit(0);   
  35. }   

客户端处理函数

  1. #include    "unp.h"   
  2.    
  3. void   
  4. dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)   
  5. {   
  6.     int n;   
  7.     char    sendline[MAXLINE], recvline[MAXLINE + 1];   
  8.    
  9.     while (Fgets(sendline, MAXLINE, fp) != NULL) {   
  10. /* 把从标准输入读取的文本行发送给服务器套接字 */   
  11.         Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);   
  12. /* 接收来自服务器回射的文本行 */   
  13.         n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);   
  14.    
  15.         recvline[n] = 0;    /* null terminate */   
  16.         Fputs(recvline, stdout);   
  17.     }   
  18. }   
  1.  $./serv &   
  2. [1] 17911   
  3. $ ./client 127.0.0.1   
  4. sending text based on UDP   
  5. sending text based on UDP   
  6. goodbyte..   
  7. goodbyte..   

数据报丢失

由于 UDP 是一种不可靠的传输协议。在上面的客户端 / 服务器 程序中,若数据报在传输的过程中丢失,那么客户端就是阻塞于 dg_cli 处理函数中的 recvfrom 函数调用,等待一个永远都不会达到的服务器应答。也有可能是,客户端数据报成功到达服务器,但是服务器的应答数据报丢失,同样,客户端也将永远阻塞于 recvfrom 函数调用。一般来说,会给客户端 recvfrom 函数调用设置一个超时时钟,但是超时时钟并不能确定是客户端数据报不能到达服务器还是服务器应答不能到达客户端。所以我们可以采用验证接收到的响应。即在 recvfrom 函数调用以返回数据报发送者的 IP 地址和端口号,保留来自数据报所发往服务器的应答。

UDP 中使用 connect 函数

在没有启动 UDP 服务器的情况下,客户端键入文本行之后,并不会回显该文本行。此时客户端永远阻塞于它的 recvfrom 调用,等待一个永远不会出现的服务器应答。由于服务器没有启动,因此会响应一个端口不可到达的 ICMP 错误消息(即异步错误),但是该 ICMP 错误消息并不会到达客户端进程,因此客户端进程根本不知道发生什么,一直阻塞于它的 recvfrom 调用。为了能使这个异步错误到达客户端进程,我们可以在 UDP 中调用 connect 函数,使其成为一个已连接的 UDP 套接字,但是该链接不会像 TCP 那样引起三次握手过程。内核只是检查是否存在立即可知的错误,并记录对端的 IP 地址和端口号,然后立即返回到调用进程。

下面要区分 未连接 UDP 套接字 和 已连接 UDP 套接字:

● 未连接 UDP 套接字:新创建 UDP 套接字默认为该情况;

● 已连接 UDP 套接字:对 UDP 套接字调用 connect 函数的结果;

已连接 UDP 套接字 相对于 未连接 UDP 套接字 会有以下的变化:

1、不能给输出操作指定目的 IP 地址和端口号(因为调用 connect 函数时已经指定),即不能使用 sendto 函数,而是使用 write 或 send 函数。写到已连接 UDP 套接字上的内容都会自动发送到由 connect 指定的协议地址;

2、不必使用 recvfrom 函数以获悉数据报的发送者,而改用 read、recv 或 recvmsg 函数。在一个已连接 UDP 套接字上,由内核为输入操作返回的数据报只有那些来自 connect 函数所指定的协议地址的数据报。目的地为这个已连接 UDP 套接字的本地协议地址,发源地不是该套接字早先 connect 到的协议地址的数据报,不会投递到该套接字。即只有发源地的协议地址与 connect 所指定的地址相匹配才可以把数据报传输到该套接字。这样已连接 UDP 套接字只能与一个对端交换数据报;

3、由已连接 UDP 套接字引发的异步错误会返回给它们所在的进程,而未连接 UDP 套接字不会接收任何异步错误;

UDP 客户端进程或服务器进程只在使用自己的 UDP 套接字与确定的唯一对端通信时,才可以调用 connect 函数。调用 connect 函数的通常是 UDP 客户端。以下是调用 connect 函数的客户端处理函数:

  1. #include    "unp.h"   
  2.    
  3. void   
  4. dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)   
  5. {   
  6.     int     n;   
  7.     char    sendline[MAXLINE], recvline[MAXLINE + 1];   
  8.    
  9.     Connect(sockfd, (SA *) pservaddr, servlen);   
  10.    
  11.     while (Fgets(sendline, MAXLINE, fp) != NULL) {   
  12.    
  13.         Write(sockfd, sendline, strlen(sendline));   
  14.    
  15.         n = Read(sockfd, recvline, MAXLINE);   
  16.    
  17.         recvline[n] = 0;    /* null terminate */   
  18.         Fputs(recvline, stdout);   
  19.     }   
  20. }   

此时若不启动服务器,只启动客户端,并键入文本行时,客户端会接收到 异步错误。

  1. $ ./client 127.0.0.1   
  2. message...   
  3. read error: Connection refused   




相关内容