前面学习了readn和writen封装函数的相关内容,接下来学习:read、write与recv、send的区别,readline实现,用readline实现回射客户/服务器以及getsockname、getpeername等一系列函数。
1.read、write与recv、send
(1)recv函数只能用于套接口I\O,而read函数可以用于任何I\O
(2)recv函数与read函数相比,多了一个flags选项。该选型可以指定接收的行为(常用的有MSG_OOB,MSG_PEEK)。
(3)MSG_PEEK:可以接收缓冲区中的数据,但是并没有将数据从缓冲区中清除。下面封装一个recv_peek函数:
1 2 3 4 5 6 7 8 9 10 |
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; } } |
2.readline实现
(1)使用上面封装的recv_peek函数来实现readline功能——按行读取。即,读到\n为一条消息。
使用readline也可以解决粘包问题,ftp就是采用类似的机制。
(2)实现按行读取标准的做法是一个字符一个字符的读取,并判断该字符是否为\n。但是,这种做法效率较低。这里采用“偷窥”的方法
(3)使用recv_peek函数来实现readline:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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; } |
1)首先调用recv_peek函数,来偷窥“接收”缓冲区中是否有\n。(recv_peek不清除缓冲区)
2)前一步偷窥到\n后,就可以利用readn函数将\n之前的数据(包括\n)读走。
3)若没有偷窥到\n字符,那么也可以断言recv_peek读到的这些数据也是包含在“发送数据”之内的,调用readn将其发送出去(同时清除了缓冲区)。然后,再进行下一次“偷窥”…
3.readline实现回射客户/服务器
echosrv.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
#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> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while (0) 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; 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; } } size_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; } void do_service(int conn) { char recvbuf[1024]; while(1){ memset(recvbuf, 0, sizeof(recvbuf)); int ret = readline(conn, recvbuf,1024); if(ret == -1) ERR_EXIT("readline"); else if(ret ==0 ){ printf("client close\n"); break; } fputs(recvbuf, stdout); writen(conn, recvbuf, strlen(recvbuf)); } } int main (void) { int listenfd; if((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5581); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); int on = 1; if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt"); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind"); if(listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("liten"); struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn; pid_t pid; while(1) { if((conn = accept(listenfd, (struct sockaddr*) &peeraddr, &peerlen)) < 0) ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); pid = fork(); if(pid == -1) ERR_EXIT("fork"); if(pid == 0) { close(listenfd); do_service(conn); exit(EXIT_SUCCESS); } else close(conn); } return 0; } |
echocli.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
#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> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while (0) 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; 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; } } size_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; } int main (void) { int sock; if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5581); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(connect(sock, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect"); struct sockaddr_in localaddr; socklen_t addrlen = sizeof(localaddr); if(getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0) ERR_EXIT("getsockname"); printf("ip = %s port = %d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); char sendbuf[1024]= {0}; char recvbuf[1024]= {0}; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL){ writen(sock, sendbuf, strlen(sendbuf)); int ret = readline (sock, recvbuf, sizeof(recvbuf)); if(ret == -1) ERR_EXIT("readline"); else if(ret ==0 ){ printf("server close\n"); break; } fputs(recvbuf,stdout); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close (sock); return 0; } |
4.getsockname,getpeername
1 2 3 4 5 6 |
#include <sys/socket.h> int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen); int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen); //返回值:若成功,返回0;失败返回-1 |
需要这两个函数的理由主要有:
(1)在一个没有调用bind的TCP客户上,connect成功返回后,getsockname用于返回由内核赋予该连接的本地IP地址和本地端口号。
(2)在以端口号0调用bind(告知内核去选择本地端口号)后,getsockname用于返回由内核赋予的本地端口号。
(3)getsockname可用于获取某个套接字的地址族。
(4)在一个以通配IP地址调用bind的TCP服务器上,与某个客户的连接一旦建立(accept成功返回),getsockname就可以用于返回由内核赋予该连接的本地IP地址。在这样的调用中,套接字描述符参数必须是已连接套接字的描述符,而不是监听套接字的描述符。
如上客户端示例,getsockname()函数用于获取一个套接字的名字。它用于一个已捆绑或已连接套接字s,本地地址将被返回。