终于解决了之前的一个Bug
Posted randyniu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了终于解决了之前的一个Bug相关的知识,希望对你有一定的参考价值。
没心情说话,直接贴源码
//commsocket.h #ifndef __COMMSOCKET__H #define __COMMSOCKET__H #ifdef __cplusplus extern ‘C‘ { #endif #define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0) typedef struct _Handle { int sockfd; int conntime; int sendtime; int recvtime; }Handle; typedef struct _packet { int len; char buf[1024]; }packet; ////////////////////////////////////////////////// // base functions ssize_t readn(int fd, void *buf, size_t count); ssize_t writen(int fd, const void *buf, size_t count); ssize_t recv_peek(int sockfd, void *buf, size_t len); ssize_t readline(int sockfd, void *buf, size_t maxline); int activate_nonblock(int fd); int deactivate_nonblock(int fd); ///////////////////////////////////////////////////// //超时处理函数 int connect_timeout(int fd, struct sockaddr_in *addr, int wait_seconds); int accept_timeout(int fd, struct sockaddr_in *addr, int wait_seconds); int read_timeout(int fd, int wait_seconds); int write_timeout(int fd, int wait_seconds); /////////////////////////////////////////////////////// int cliet_init(Handle **handle, int conntime, int sendtime, int recvtime); int cliet_getconn(Handle *handle, char *ip, int port, int *connfd); int client_send(Handle *handle, packet* sendbuf); int client_recv(Handle *handle, packet* recvbuf); //////////////////////////////////////////////////// //server functions int server_init(int port, int *listenfd); int server_accept(int listenfd, int *connfd, struct sockaddr_in *addr, int timeout); int server_recv(int connfd, packet* recvbuf, int timeout); int server_send(int connfd, packet* sendbuf, int timeout); #ifdef __cpluspluse } #endif #endif
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include "commsocket.h" ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char*)buf; while (nleft > 0) { if ((nread = read(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten = 0; char *bufp = (char*)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } ssize_t recv_peek(int sockfd, void *buf, size_t len) { while (1) { int ret = recv(sockfd, buf, len, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } ssize_t readline(int sockfd, void *buf, size_t maxline) { int ret; int nread; char *bufp = buf; int nleft = maxline; while (1) { ret = recv_peek(sockfd, bufp, nleft); if (ret < 0) return ret; else if (ret == 0) return ret; nread = ret; int i; for (i=0; i<nread; i++) { if (bufp[i] == ‘\n‘) { ret = readn(sockfd, bufp, i+1); if (ret != i+1) exit(EXIT_FAILURE); return ret; } } if (nread > nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd, bufp, nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; } return -1; } /** * activate_noblock - 设置I/O为非阻塞模式 * @fd: 文件描符符 */ int activate_nonblock(int fd) { int ret = 0; int flags = fcntl(fd, F_GETFL); if (flags == -1) { ret = flags; printf("activate_nonblock() Err:%d", ret); return ret; } flags |= O_NONBLOCK; ret = fcntl(fd, F_SETFL, flags); if (ret == -1) { printf("activate_nonblock() Err : %d", ret); return ret; } return ret; } /** * deactivate_nonblock - 设置I/O为阻塞模式 * @fd: 文件描符符 */ int deactivate_nonblock(int fd) { int ret = 0; int flags = fcntl(fd, F_GETFL); if (flags == -1) { ret = flags; printf("deactivate_nonblock() Err:%d", ret); return ret; } flags &= ~O_NONBLOCK; ret = fcntl(fd, F_SETFL, flags); if (ret == -1) { printf("deactivate_nonblock() Err:%d", ret); return ret; } return ret; } /** * read_timeout - 读超时检测函数,不含读操作 * @fd: 文件描述符 * @wait_seconds: 等待超时秒数,如果为0表示不检测超时 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */ int read_timeout(int fd, int wait_seconds) { int ret = 0; if (wait_seconds > 0) { fd_set read_fdset; FD_ZERO(&read_fdset); FD_SET(fd, &read_fdset); struct timeval timeout; timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; //select返回值三态 //1 若timeout时间到(超时),没有检测到读事件 ret返回=0 //2 若ret返回<0 && errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理) //2-1 若返回-1,select出错 //3 若ret返回值>0 表示有read事件发生,返回事件发生的个数 do { ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; } else if (ret == 1) ret = 0; } return ret; } /** * write_timeout - 写超时检测函数,不含写操作 * @fd: 文件描述符 * @wait_seconds: 等待超时秒数,如果为0表示不检测超时 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */ int write_timeout(int fd, int wait_seconds) { int ret = 0; if (wait_seconds > 0) { fd_set write_fdset; FD_ZERO(&write_fdset); FD_SET(fd, &write_fdset); struct timeval timeout; timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; } else if (ret == 1) ret = 0; } return ret; } /** * accept_timeout - 带超时的accept * @fd: 套接字 * @addr: 输出参数,返回对方地址 * @wait_seconds: 等待超时秒数,如果为0表示正常模式 * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT,错误返回-1 */ int accept_timeout(int fd, struct sockaddr_in *addr, int wait_seconds) { int ret; if(wait_seconds > 0) { fd_set accept_fdset; FD_ZERO(&accept_fdset); FD_SET(fd, &accept_fdset); struct timeval timeout; timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == -1) return -1; else if (ret == 0) { errno = ETIMEDOUT; return -1; } } //一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立 //此时再调用accept将不会堵塞 socklen_t addrlen = sizeof(struct sockaddr_in); if (addr != NULL) ret = accept(fd, (struct sockaddr*)addr, &addrlen); //返回已连接套接字 else ret = accept(fd, NULL, NULL); if (ret < 0) { printf("accept() Err:%d \n", errno); return ret; } return ret; } /** * connect_timeout - connect连接超时 * @fd: 套接字 * @addr: 要连接的对方地址 * @wait_seconds: 等待超时秒数,如果为0表示正常模式 * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT */ int connect_timeout(int fd, struct sockaddr_in *addr, int wait_seconds) { int ret; socklen_t addrlen = sizeof(struct sockaddr_in); if (wait_seconds > 0) { ret = activate_nonblock(fd); if(ret == -1) return -1; } ret = connect(fd, (struct sockaddr*)addr, addrlen); if (ret < 0 && errno == EINPROGRESS) { fd_set connect_fdset; FD_ZERO(&connect_fdset); FD_SET(fd, &connect_fdset); struct timeval timeout; timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { // 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中 ret = select(fd + 1, NULL, &connect_fdset, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; return ret; } else if (ret < 0) return -1; else if (ret == 1) { /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/ /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */ int err; socklen_t socklen = sizeof(err); int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen); if (sockoptret == -1) { return -1; } if (err == 0) { ret = 0; } else { errno = err; ret = -1; } } } if (wait_seconds > 0) { int tmp; tmp = deactivate_nonblock(fd); if(tmp == -1) return -1; } return ret; } //客户端环境初始化 设置结构体中的信息 /** * cliet_init - 创建客户端环境初始化 * @handle: 句柄 * @conntime: 连接最长时长 * @sendtime: 发送最长时长 * @recvtime: 接受最长时长 * 成功返回 0,失败返回-1, */ int cliet_init(Handle **handle, int conntime, int sendtime, int recvtime) { int ret = 0; if (handle == NULL ||conntime < 0 || sendtime < 0 || recvtime < 0) { printf("cliet_init() Parem Err! \n"); return -1; } Handle *tmp = (Handle *)malloc(sizeof(Handle)); if (tmp == NULL) { printf("cliet_init() malloc Err!\n"); return -1; } tmp->conntime = conntime; tmp->sendtime = sendtime; tmp->recvtime = recvtime; *handle = tmp; return ret; } /** * cliet_getconn - 创建客户端连接 * @handle: 句柄 * @ip: ip地址 * @port: 端口 * @conn: 回传出去的连接 * 成功返回 0,失败返回-1, 超时返回-1并且errno = ETIMEDOUT */ int cliet_getconn(Handle *handle, char *ip, int port, int *connfd) { int ret = 0; if (handle == NULL || ip==NULL || connfd==NULL || port<0 || port>65537) { printf("cliet_getconn() Param Err! \n"); return -1; } int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { ret = errno; printf("socket() Err!\n"); return ret; } struct sockaddr_in servaddr; bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); servaddr.sin_addr.s_addr = inet_addr(ip); Handle *tmp = (Handle* )handle; ret = connect_timeout(sockfd, (struct sockaddr_in*) (&servaddr), (unsigned int)tmp->conntime); if (ret < 0) return ret; handle->sockfd = sockfd; *connfd = sockfd; return ret; } //客户端发送报文 /** * client_send - 发送报文数据 * @handle: 句柄 * @sendbuf: 发送数据packet * 成功返回成功发送数据的长度,失败返回-1 或者 < 发送数据的长度 */ int client_send(Handle *handle, packet* sendbuf) { int ret = write_timeout(handle->sockfd, handle->sendtime); if (ret < 0) { //失败返回-1,超时返回-1并且errno = ETIMEDOUT if (ret == -1 && errno == ETIMEDOUT) { printf("client_send() write_timeout() Timeout Err!\n "); return ret; } else return ret; } int n = strlen(sendbuf->buf); sendbuf->len = htonl(n); writen(handle->sockfd, sendbuf, 4 + n); } int client_recv(Handle *handle, packet* recvbuf) { int ret = read_timeout(handle->sockfd, handle->recvtime); if (ret < 0) { //失败返回-1,超时返回-1并且errno = ETIMEDOUT if (ret == -1 && errno == ETIMEDOUT) { printf("client_rev() read_timeout() Timeout Err!\n "); return ret; } else return ret; } readn(handle->sockfd, &recvbuf->len, 4); //将转换为本地字节存储 int n = ntohl(recvbuf->len); return readn(handle->sockfd, &recvbuf->buf, n); } //服务器端初始化 /** * server_init - 服务器初始化环境 * @port:服务器端口 * @listenfd:传出参数 返回监听套接字 * 成功返回0, 失败返回相应的错误码 */ int server_init(int port, int *listenfd) { int ret = 0; int listen_fd; listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) { ret = errno ; printf("socket() Err: %d \n", ret); return ret; } //设置地址复用 int on = 1; ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ); if (ret < 0) { ret = errno ; printf("setsockopt() Err:%d \n", ret); return ret; } struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); servaddr.sin_addr.s_addr = inet_addr("172.20.53.162"); //servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //inet_aton("127.0.0.1", &servaddr.sin_addr); ret = bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)); if (ret < 0) { ret = errno ; printf("bind() Err:%d \n", ret); return ret; } ret = listen(listen_fd, SOMAXCONN); if (ret < 0) { ret = errno ; printf("listen() Err:%d \n", ret); return ret; } *listenfd = listen_fd; return 0; } //服务器accept函数 /** * server_accept - 服务器接受 * @listenfd: 监听套接字 * @connfd: 输出参数,返回连接套接字 * @timeout: 等待超时秒数,如果为0表示正常模式 * 成功返回0,失败返回对应的错误码 */ int server_accept(int listenfd, int *connfd, struct sockaddr_in *addr, int timeout) { int ret = 0; ret = accept_timeout(listenfd, addr, (unsigned int)timeout); if (ret < 0) { if (ret == -1 && errno == ETIMEDOUT) { printf("accept_timeout() Err!\n"); return ret; } ret = errno; return ret; } *connfd = ret; return 0; } //服务器端端接受报文 /** * server_send - 服务器发送报文 * @connfd: 连接套接字 * @recvbuf: 接受数据packet * @timeout: 等待超时秒数,如果为0表示正常模式 * 成功返回读取数据的长度,失败-1, 超时返回-1错误码为ETIMEOUT */ int server_recv(int connfd, packet* recvbuf, int timeout) { int ret = read_timeout(connfd, timeout); if (ret < 0) { if (ret == -1 && errno == ETIMEDOUT)//失败返回-1,超时返回-1并且errno = ETIMEDOUT { printf("server_recv() read_timeout() Timeout Err!\n "); return ret; } else return ret; } //读取包的头4字节 ret = readn(connfd, recvbuf, 4); //读包头 4个字节 if (ret == -1) { printf("readn error\n"); return -1; }//如果读取的个数小于4,则客服端已经关闭 else if (ret < 4) { printf("client close\n"); return -1; } int n = ntohl(recvbuf->len); return readn(connfd, &(recvbuf->buf), n); //根据长度读数据 } //服务器端发送报文 /** * server_send - 服务器发送报文 * @connfd: 连接套接字 * @sendbuf: 发送数据packet * @timeout: 等待超时秒数,如果为0表示正常模式 * 成功返回0,失败返回对应的错误码 */ int server_send(int connfd, packet* sendbuf, int timeout) { int ret = write_timeout(connfd, timeout); if (ret < 0) { if (ret == -1 && errno == ETIMEDOUT)//失败返回-1,超时返回-1并且errno = ETIMEDOUT { printf("server_send_() write_timeout() Timeout Err!\n "); return ret; } else return ret; } int n = ntohl(sendbuf->len); //将接受到的数据再直接发出去。也就是所谓的echo服务器模型 return writen(connfd, sendbuf, 4 + n); //注意写数据的时候,多加4个字节 }
// echoclient.c #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include "commsocket.h" int echo_cli(int sockfd, Handle *handle) { packet sendbuf; packet recvbuf; bzero(&sendbuf, sizeof(sendbuf)); bzero(&recvbuf, sizeof(recvbuf)); while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL) { int ret = client_send(handle, &sendbuf); client_recv(handle, &recvbuf); fputs(recvbuf.buf, stdout); bzero(&sendbuf, sizeof(sendbuf)); bzero(&recvbuf, sizeof(recvbuf)); } close(sockfd); } int main(void) { // 作为传出参数 int sockfd; //保存信息的句柄 Handle *handle = NULL; //设置相应的超时信息。 int conntime = 15; int sendtime = 5; int recvtime = 5; int ret; ret = cliet_init(&handle, conntime, sendtime, recvtime); ret = cliet_getconn(handle, "172.20.53.162", 8001, &sockfd); if(ret!=0) { printf("cliet_getconn() Err!\n"); return ret; } struct sockaddr_in localaddr; socklen_t addrlen = sizeof(localaddr); if (getsockname(sockfd, (struct sockaddr*)&localaddr, &addrlen) < 0) ERR_EXIT("getsockname"); printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); echo_cli(sockfd, handle); close(sockfd); return 0; }
// echoserver.c #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include "commsocket.h" int echo_srv(int connfd) { //定义了自定封包结构体 packet recvbuf; bzero(&recvbuf, sizeof(recvbuf)); int ret; while (1) { server_recv(connfd, &recvbuf, 20); fputs(recvbuf.buf, stdout); server_send(connfd, &recvbuf, 20); bzero(&recvbuf, sizeof(recvbuf)); } } void handle_sigchld(int sig) { int pid = 0; while ( ( pid = waitpid(-1, NULL, WNOHANG)) >0 ) { printf("子进程退出,父进程要收尸: %d \n", pid); } } int main(void) { //注册信号处理函数 signal(SIGCHLD, handle_sigchld); int port = 8001; //设置监听的事件,过了监听的时间就自动退出了。 int listenfd = 0; //服务器环境初始化 int ret = server_init(port, &listenfd); if (ret != 0) { printf("server_init() Err : %d \n", ret); return ret; } //接下来尝试进行连接 while (1) { struct sockaddr_in peeraddr; int connfd; int timeout = 0; //服务器accept函数,可以接受等待时间 ret = server_accept(listenfd, &connfd, &peeraddr, timeout); if (ret != 0) { printf("server_accept() Err!\n"); return ret; } printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid_t pid = fork(); if (pid == -1) ERR_EXIT("fork"); if (pid == 0) { close(listenfd); echo_srv(connfd); close(connfd); exit(EXIT_SUCCESS); } else { close(connfd); } } return 0; }
哈,就是要给Echo服务器模型,不过使用了select对其进行了优化,酱紫。
以上是关于终于解决了之前的一个Bug的主要内容,如果未能解决你的问题,请参考以下文章
PhpStorm 2016.3 For Mac 重大里程碑更新 -- 终于解决了不能输入中文标点符号的重大bug