使用完整读写函数的网络应用程序
Linux的设计原则是将硬件设备抽象成文件,用户可以像操作文件一样操作设备,前边已经说过,我们可以使用文件操作函数操作套接字。
使用read函数读取套接字另一端发送过来的内容,使用write函数也可以向套接字另一端发送内容。但是,在网络环境中有一个很大的问题就是延时的问题,对于本地文件夹来说,字节流在本地传输的延时可以忽略不计,但是网络环境中传输延时可能会很长造成I/O阻塞。其次,网络应用程序要能够处理因为中断或者网络连接等问题造成的读写操作异常返回。
对于第一个问题,解决的办法只有使用非阻塞I/O或者多路I/O转接。对于第二个问题,网络环境的读写操作会常因为信号中断而异常返回,这时候read和write函数返回-1,errno错误号设置为EINTR,遇到这种情况应该从上一次成功读写的地方重新进行读写,对于其他出错一场,应该打出错误号和错误原因。因此可以设计一个新的read和write函数,屏蔽这种异常处理的细节。下面是my_read函数的执行流程图。
my_read函数和my_write函数的代码如下
my_read函数代码:
ssize_t my_read(int fd, void *buffer, size_t length) { ssize_t done = length; ///读入的字节数 while(done > 0) ///如果因为信号中断而导致异常则多次读取 { done = read(fd, buffer, length); ///调用read函数 if(done == -1) if(errno == EINTR) ///如果时信号中断导致的错误,则舍弃已读入的内容重新读 done = length; else { perror("fail to read"); return -1; } else break; } return done; ///返回实际读入的字节数 }
my_write函数代码:
ssize_t my_write(int fd, void *buffer, size_t length) { ssize_t done = length; ///实际写入的字节数 while(done > 0) ///如果因为信号中断而导致异常,则多次写缓冲区 { done = write(fd, buffer, length); ///调用write函数 if(done != length) ///异常出错 if(errno == EINTR) ///如果时信号中断导致的错误,则舍弃已写入的内容,重新写缓存区 done = length; else { perror("fail to write"); return -1; } else break; } return done; ///返回实际写的字节数 }
可以将自己设计的my_read和my_write函数写到自己的文件I/O函数库,方便以后使用。添加相应的iolib.h文件,文件中包含对自己的文件库函数的声明,程序如下:
extern ssize_t my_read(int fd, void *buffer, size_t length); extern ssize_t my_write(int fd, void *buffer, size_t length);
在之前的客户端程序和服务器端程序中添加my_read函数和my_write函数就能进行出错和异常处理,代码如下:
Server.c:
#include <stdio.h> #include <unistd.h> #include <strings.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <sys/socket.h> #include <arpa/inet.h> #include "iolib.h" #define MAX_LINE 100 /*处理函数,用于将大写字符转换为小写字符。参数为需要转换的字符串*/ void my_fun(char * p) { if(p == NULL) return; for(;*p != '\0'; p++) if(*p >= 'A' && *p <= 'Z') *p = *p - 'A' + 'a'; } int main(void) { struct sockaddr_in sin; struct sockaddr_in cin; int l_fd; int c_fd; socklen_t len; char buf[MAX_LINE]; char addr_p[INET_ADDRSTRLEN]; int port = 8000; int n; bzero(&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(port); ///端口号转换为网络字节序 if((l_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("fail to creat socket"); exit(1); } if(bind(l_fd, (struct sockaddr *) &sin, sizeof(sin)) == -1) { perror("fail to bind"); exit(1); } if(listen(l_fd, 10) == -1) ///开始连续监听 { perror("fail to listen"); exit(1); } printf("waiting...\n"); while(1) { if((c_fd = accept(l_fd, (struct sockaddr*) &cin, &len)) == -1) { perror("fail to accept"); exit(1); } n = my_read(c_fd, buf, MAX_LINE); if(n == -1) exit(1); else if(n == 0) { printf("the connect has been closed\n"); close(c_fd); continue; } inet_ntop(AF_INET, &cin.sin_addr, addr_p, sizeof(addr_p)); printf("client IP is %s, port is %d\n", addr_p, ntohs(cin.sin_port)); printf("content is : %s\n", buf); my_fun(buf); n = my_write(c_fd, buf, n); if(n == -1) { exit(1); } if(close(c_fd) == -1) { perror("fail to close"); exit(1); } } return 0; }
client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include "iolib.h" #define MAX_LINE 100 int main(int argc, char *argv[]) { struct sockaddr_in sin; char buf[MAX_LINE]; int s_fd; int port = 8000; char *str = "test string"; int n; if(argc > 1) { str = argv[1]; } bzero(&sin, sizeof(sin)); sin.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr); sin.sin_port = htons(port); if((s_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("fail to creat socket"); exit(1); } if(connect(s_fd, (struct sockaddr *) &sin, sizeof(sin)) == -1) { perror("fail to connect"); exit(1); } n = my_write(s_fd, str, strlen(str) + 1); if(n == -1) exit(1); n = my_read(s_fd, buf, MAX_LINE); if(n == -1) exit(1); printf("receive from server: %s\n", buf); if(close(s_fd) == -1) { perror("fail to close"); exit(1); } return 0; }