(五十)socket编程——出错函数的封装和基于UDP的C/S模型
2017-01-22 16:32
381 查看
一、出错函数的封装
在上一节中我们介绍了一系列的网络套接字操作函数,但是系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息。为使错误处理的代码不影响主程序的可读性,我们把与socket相关的一些系统函数加上错误处理代码包装成新的函数,做成一个模块wrap.c:
/* wrap.c */ #include <stdlib.h> #include <errno.h> #include <sys/socket.h> /* 打印错误原因并退出 */ void perr_exit(const char *s) { perror(s); exit(1); } /* accept出错封装 */ int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) { int n;//保存用于通信的socket文件描述符 again: if ( (n = accept(fd, sa, salenptr)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR)) goto again;//如果连接终止 或 被信号中断 则再试一次 else perr_exit("accept error"); } return n; } /* 绑定IP、端口号和socket,出错则返回错误信息 */ void Bind(int fd, const struct sockaddr *sa, socklen_t salen) { if (bind(fd, sa, salen) < 0) perr_exit("bind error"); } /* 客户机连接服务器函数封装,错误则打印出错信息 */ void Connect(int fd, const struct sockaddr *sa, socklen_t salen) { if (connect(fd, sa, salen) < 0) perr_exit("connect error"); } /* listen函数出错处理封装 */ void Listen(int fd, int backlog) { if (listen(fd, backlog) < 0) perr_exit("listen error"); } /* 建立套接字socket出错处理封装 */ int Socket(int family, int type, int protocol) { int n;//保存建立socket时返回的文件描述符 if ( (n = socket(family, type, protocol)) < 0) perr_exit("socket error"); return n; } /* read函数出错处理封装 */ ssize_t Read(int fd, void *ptr, size_t nbytes) { ssize_t n;//读到的字节数 again: if ( (n = read(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again;//如果是由于信号被中断 则再试一次 else return -1; } return n; } /* write函数出错处理封装 */ ssize_t Write(int fd, const void *ptr, size_t nbytes) { ssize_t n;//被写入的字节数 again: if ( (n = write(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again;//如果是由于信号被中断 则再试一次 else return -1; } return n; } /* close关闭文件出错处理封装 */ void Close(int fd) { if (close(fd) == -1) perr_exit("close error"); } /* 至少读满n个字符再返回 */ ssize_t Readn(int fd, void *vptr, size_t n) { size_t nleft; //剩下的字符数 ssize_t nread; //已读的字符数 char *ptr; //读写指针 /* 初始化 */ ptr = vptr; nleft = n; /* 至少读满n个字节返回,或出错返回 */ while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0)//如果读出错 { if (errno == EINTR)//是由于信号中断的话 则退出读 nread = 0; else return -1; } else if (nread == 0)//信号中断 或 读到空 则退出 break; nleft -= nread;//记录每次读完,还剩的未读的字节数 ptr += nread; //为将下一次读到的值补充到这次的末尾做准备 } return n - nleft; //返回已读到的字节数 } /* 写满n个字符才返回 */ ssize_t Writen(int fd, const void *vptr, size_t n) { size_t nleft; //剩下还要写的字节个数 ssize_t nwritten; //已经写的字节个数 const char *ptr; //写指针 /* 初始化 */ ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; //写完这次之后,剩下还要写的字节个数 ptr += nwritten; //写完这次之后,从这次的末尾继续写 } return n; } /*返回第一次调用时字符串的一个字符,调用一次返回下一个字符,供下面的函数调用*/ /* fd为要读的文件 ptr为要返回的字符(为传出参数) */ static ssize_t my_read(int fd, char *ptr) { /* 定义静态变量,上一次调用的值会被保存 */ static int read_cnt; //剩下待返回的字符串的长度,系统将其初始化为0 static char *read_ptr; //当前读到的位置 static char read_buf[128]; //读到的字符串 if (read_cnt <= 0) { again: if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { if (errno == EINTR)//如果是被信号中断 则再试一次 goto again; return -1; } else if (read_cnt == 0)//如果读到的字节数为0(即读到的文件为空) return 0; /* 否则有读到内容 则将读指针指向该字符串的首部 */ read_ptr = read_buf; } //返回当前指向的字符,之后指向下一个字符,供下一调用时使用 *ptr = *read_ptr++; read_cnt--;//剩下待返回的字符串的长度减一 return 1;//调用成功返回1 } /* 一次读一行 */ ssize_t Readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1)//获取一个字符 { *ptr++ = c; if (c == '\n')//如果是换行就退出 break; } else if (rc == 0)//如果读完了就返回读到的字节数 { *ptr = 0; return n - 1; } else return -1; } *ptr = 0; return n; }
/* wrap.h */ #ifndef __WRAP_H_ #define __WRAP_H_ void perr_exit(const char *s); int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); void Bind(int fd, const struct sockaddr *sa, socklen_t salen); void Connect(int fd, const struct sockaddr *sa, socklen_t salen); void Listen(int fd, int backlog); int Socket(int family, int type, int protocol); ssize_t Read(int fd, void *ptr, size_t nbytes); ssize_t Write(int fd, const void *ptr, size_t nbytes); void Close(int fd); ssize_t Readn(int fd, void *vptr, size_t n); ssize_t Writen(int fd, const void *vptr, size_t n); static ssize_t my_read(int fd, char *ptr); ssize_t Readline(int fd, void *vptr, size_t maxlen); #endif
二、基于UDP的C/S模型
由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现。
编译运行server,在两个终端里各开一个client与server交互,看看server是否具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,看此时client还能否和server联系上。和前面TCP程序的运行果相比较。
/* server.c */ /* 接收数据: * ssize_t recvfrom(int sockfd, //已被构造的套接字用于传输数据 * void *buf, //将接收到的数据保存到buf中 * size_t len, //buf的大小(单位是字节) * int flags, //默认为0,需要时差manpage * struct sockaddr *src_addr,//发送方地址(传出参数) * socklen_t *addrlen); //地址长度(传入传出参数) * * * * 发送数据: * ssize_t sendto(int sockfd, //已被构造的套接字用于传输数据 * const void *buf,//将buf中的数据发送出去 * size_t len, //buf的大小(单位是字节) * int flags, //默认为0,需要时差manpage * const struct sockaddr *dest_addr,//发送给谁 * socklen_t addrlen); //地址长度 * */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <errno.h> #include <sys/socket.h> #define MAXLINE 80 //一次最多发送的字节个数 #define SERV_PORT 8000 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len;//保存客户端地址的长度 int sockfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN];//转换后的IP保存在str中,用于打印用户端地址 int i, n; /* 构造用于UDP通信的套接字(socket) */ sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* 设置IP地址和端口号 */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); /* 绑定IP地址、端口号和套接字(socket) */ bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); printf("Accepting connections ...\n"); while (1) { /* 接收数据 */ cliaddr_len = sizeof(cliaddr); n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &cliaddr_len); if (n == -1) { perror("recvfrom error"); exit(1); } /* 打印发送方的IP地址以及端口号 */ printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); /* 将其转换为大写字母后发送回去 */ for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); if (n == -1) { perror("sendto error"); exit(1); } } }
/* client.c */ #include <stdio.h> #include <unistd.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <errno.h> #include <sys/socket.h> #define MAXLINE 80 #define SERV_PORT 8000 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int sockfd, n; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; socklen_t servaddr_len; /* 构造用于UDP通信的套接字(socket) */ sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* 设置IP地址和端口号 发送给谁的IP和端口(即服务器IP和端口号) */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, argv[1], &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); while (fgets(buf, MAXLINE, stdin) != NULL) { /* 发送数据 */ n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); /* 接收数据 */ n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0); /* 打印接收到的数据 */ write(STDOUT_FILENO, buf, n); } close(sockfd); return 0; }
相关文章推荐
- socket编程 -- 基于UDP协议的C/S通信模型及实现
- 基于UDP协议的socket编程实例
- 【socket编程】一个简单的基于UDP的客户/服务端例子(vs2008)
- 基于TCP/IP协议及UDP协议的socket编程
- 基于Udp的Socket网络编程聊天程序
- 孙鑫VC视频教程笔记之第十四课“基于TCP和UDP的Socket编程”
- 基于Socket的UDP和TCP编程介绍
- 基于linux 的socket UDP编程例程
- 基于Socket的UDP和TCP编程介绍
- 网络编程之基于UDP的Socket编程
- 基于UDP(面向无连接)的socket编程
- 基于Socket的UDP和TCP编程介绍
- 基于udp的网络编程socket的错误10054与SOCKET的发送与接收缓冲区的设置(udp丢包)
- 基于Socket的UDP和TCP编程介绍
- 基于UDP的socket编程是采用的数据报套接字
- 转:基于Socket的UDP和TCP编程介绍
- 基于Socket的UDP和TCP编程介绍
- 基于socket的TCP和UDP编程
- 基于Udp的Socket网络编程
- 基于UDP的socket编程