带错误处理的回声服务器的实现
结合之前的博客我们已经知道了什么是网络编程以及如何通过调用socket函数来编写一个回声服务器,这个过程中我们可能遇到了很多的问题,但是我们都一点一点的克服了。这本身就是一种进步了。这个部分就是我们的简单的回声服务器的最后一篇博客了。在完成这篇博客之后可以进行下一个部分的学习(实现高并发http 服务器)。
server.cpp
#include<stdio.h> #include<unistd.h> #include<string.h> #include<sys/socket.h> #include<arpa/inet.h> #include<stdlib.h> #include<netinet/in.h> #include<ctype.h> #include "error_handling.h" #define SERVER_PORT 6666 #define MAXLINE 100 int main(void) { /*定义 server端套接字文件描述符:sfd client套接字文件描述符:cfd read函数读取到的字符数:n */ int sfd, cfd, n; /* server端地址定义(包含IP、PORT、协议族)暂未指定:server_addr client端地址定义(包含IP、PORT、协议族)不需要再server.c定义,accept函数会自动填充*/ struct sockaddr_in server_addr, client_addr; socklen_t client_len;//为 accept函数第三个参数做准备 char buf[MAXLINE];//接收client端发来的字符的缓冲区 /*bzero:将server端置空,为了给后续的IP、PORT、协议族赋值所用 后续操作是为了 bind函数绑定IP、PORT和协议族的固定操作。*/ bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET;//IPV4 server_addr.sin_port = htons(SERVER_PORT);//转换为网络字节序 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); sfd = Socket(AF_INET, SOCK_STREAM, 0); //调用 socket函数值后会返回一个文件描述符 Bind(sfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); //绑定IP、PORT、协议族 Listen(sfd, 21); /* accept函数放在 while 里面和外面的结果是不一样的, accept放在while里面代表客户端只能和服务器端通信一次 accept放在while外面那么客户端就可以一直和服务器进行通信 */ client_len = sizeof(client_addr); cfd = Accept(sfd, (struct sockaddr*)&client_addr, &client_len);//accept调用和会给server端返回一个和client端连接好的socket。 while (1) { n = Read(cfd, buf, MAXLINE); for (int i = 0; i < n; i++) { buf[i] = toupper(buf[i]); } Write(cfd, buf, n); } Close(cfd); return 0; }
client.cpp
#include<stdio.h> #include<unistd.h> #include<string.h> #include<sys/socket.h> #include<arpa/inet.h> #include<stdlib.h> #include<netinet/in.h> #include<ctype.h> #include "error_handling.h" #define SERVER_PORT 6666 #define MAXLINE 100 int main(void) { int sfd, n; struct sockaddr_in server_addr; char buf[MAXLINE]; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); sfd = Socket(AF_INET, SOCK_STREAM, 0); connect(sfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); while (1) { fgets(buf, sizeof(buf), stdin); Write(sfd, buf, strlen(buf)); n = read(sfd, buf, sizeof(buf)); Write(STDOUT_FILENO, buf, n); } Close(sfd); return 0; }
error_handling.cpp
#include<stdio.h> #include<unistd.h> #include<string.h> #include<sys/socket.h> #include<arpa/inet.h> #include<stdlib.h> #include<netinet/in.h> #include<errno.h> #include<ctype.h> #include "error_handling.h" void perr_exit(const char* s) { perror(s); //输出打印信息。 exit(-1); } int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr) { int n; again: if ((n = accept(fd, sa, salenptr)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR)) goto again; else perr_exit("accept error"); } return n; } int Bind(int fd, const struct sockaddr* sa, socklen_t salen) { int n; if ((n = bind(fd, sa, salen)) < 0) perr_exit("bind error"); return n; } int Connect(int fd, const struct sockaddr* sa, socklen_t salen) { int n; n = connect(fd, sa, salen); if (n < 0) { perr_exit("connect error"); } return n; } int Listen(int fd, int backlog) { int n; if ((n = listen(fd, backlog)) < 0) perr_exit("listen error"); return n; } int Socket(int family, int type, int protocol) { int n; if ((n = socket(family, type, protocol)) < 0) perr_exit("socket error"); return n; } 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; } 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; } int Close(int fd) { int n; if ((n = close(fd)) == -1) perr_exit("close error"); return n; } /*参三: 应该读取的字节数*/ //socket 4096 readn(cfd, buf, 4096) nleft = 4096-1500 ssize_t Readn(int fd, void* vptr, size_t n) { size_t nleft; //usigned int 剩余未读取的字节数 ssize_t nread; //int 实际读到的字节数 char* ptr; ptr = (char*)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; //nleft = nleft - nread ptr += nread; } return n - nleft; } ssize_t Writen(int fd, const void* vptr, size_t n) { size_t nleft; ssize_t nwritten; const char* ptr; ptr = (char*)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; } ssize_t My_read(int fd, char* ptr) { static int read_cnt; static char* read_ptr; static char read_buf[100]; if (read_cnt <= 0) { again: if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { //"hello\n" if (errno == EINTR) goto again; return -1; } else if (read_cnt == 0) return 0; read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } /*readline --- fgets*/ //传出参数 vptr ssize_t Readline(int fd, void* vptr, size_t maxlen) { ssize_t n, rc; char c, * ptr; ptr = (char*)vptr; for (n = 1; n < maxlen; n++) { if ((rc = My_read(fd, &c)) == 1) { //ptr[] = hello\n *ptr++ = c; if (c == '\n') break; } else if (rc == 0) { *ptr = 0; return n - 1; } else return -1; } *ptr = 0; return n; }
error_handling.h
#ifndef __ERROR_HANDING__ #define __ERROR_HANDING__ //下面的所有替换socket网络编程函数,在函数内部都增加了输出错误信息的判断语句。 //打印输出错误信息 void perr_exit(const char* s); //替换Socket函数,并在内部调用perr_exit 函数,可以输出错误信息 int Socket(int family, int type, int protocol); //替换Bind函数,并在内部调用perr_exit 函数,可以输出错误信息 int Bind(int fd, const struct sockaddr* sa, socklen_t salen); //替换Listen函数,并在内部调用perr_exit 函数,可以输出错误信息 int Listen(int fd, int backlog); //替换 accept 函数,并在内部调用perr_exit 函数,可以输出错误信息 int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr); //替换Connect函数,并在内部调用perr_exit 函数,可以输出错误信息 int Connect(int fd, const struct sockaddr* sa, socklen_t salen); //替换Close函数,并在内部调用perr_exit 函数,可以输出错误信息 int Close(int fd); ssize_t Read(int fd, void* ptr, size_t nbytes); ssize_t Write(int fd, const void* ptr, size_t nbytes); ssize_t Readn(int fd, void* vptr, size_t n); ssize_t Writen(int fd, const void* vptr, size_t n); ssize_t My_read(int fd, char* ptr); ssize_t Readline(int fd, void* vptr, size_t maxlen); #endif
遇到的问题,自定义函数显示未定义的引用。
server.c
问题分析:
Q1:先在 vs里面点击 server.cpp看能否跳转到我们自定义的错误函数处理部分。
A1::经测试发现能够直接跳转到我们自定义的错误处理函数,说明代码是没有错误的
Q2:是否是因为没有编译我们的 error_handling.cpp 导致的
测试:g++ error_handling.cpp -o error_handling
这里面连 main函数都没有定义,你又怎么可能编译成功呢?
同类问题:
c++中如何在主函数中调用其他文件内的函数?
其实大家也看到了,我们需要编写Makefile了,哈哈。以前大家被vs惯坏了,既然现在大家要投入linux中那么就需要去编写自己的Makefile了。博主对此不详细描述。
解决方法:
不再和大家开玩笑了,其实这就是vs的好处了,但现在我们要在linux下运行,我们就需要用到我们大名鼎鼎makefil了,在这里博主不详细介绍Makefile的编写规则,需要的话可以留言博主会开一个博客专门介绍makefil。
编写我们的Makefile
src = $(wildcard *.c) obj = $(patsubst %.c, %.o, $(src)) all: server client server: server.o error_handling.o gcc server.o error_handling.o -o server -Wall client: client.o error_handling.o gcc client.o error_handling.o -o client -Wall %.o:%.c gcc -c $< -Wall .PHONY: clean all clean: -rm -rf server client $(obj)
make
测试:
正常测试:
先关掉服务器,在关掉客户端马上在连接:
看到了吧,因为我们封装了错误处理函数,我们直接就能看到错误的原因。
其余测试:
还有很多种测试方法大家可以,具体的方案大家可以修改server.cpp的Accept函数和Close函数,以及client.cpp 的Close函数的位置来进行多种方案的测试,博主在这里就不在演示了。
回声服务器的结语及下一部分的展望
至此我们的 回声服务器已经是完成了,不说超级牛逼,但也能吊打很多回声服务器了(毕竟我们完成了各个函数的出错处理的方案,不需要大家在一步一步调试信息的慢慢寻找了。)
下一部分内容: