recv

image.png

  1. int recv(SOCKET s, char FAR, int len, int flags)

recv只能用于套接口io,不能用于文件io,而read函数则都可以。
recv多了个flags选项,通过这个选项能够执行接收行为,但是一般都填0
主要两个参数为

  • MSG_OOB 接收通过紧急指针发送的数据
  • MSG_PEEK 接收缓冲区的数据,并不清楚缓冲区的数据,而read函数读取之后会清楚缓冲区数据。

封装一个recv_peek

  1. ssize_t recv_peek(int socked, void *buf, size_t len){
  2. while(1){
  3. int ret = recv(sockfd, buf, len, MSG_PEEK);
  4. if(ret == -1&&errno == EINTR){
  5. continue;//此时是中断的情况
  6. } else if(ret < 4){
  7. printf("client close\n");
  8. break;
  9. }
  10. }
  11. }

使用上面封装的recv_peek函数来实现readline功能

readline实现按行读取,遇到换行符\n即停止读取。
readline也能解决粘包问题。

  1. ssize_t readline(int sockfd, void *buf, size_t maxline){
  2. int ret;
  3. int nread;
  4. char *bufp = buf;
  5. int nleft = maxline;
  6. while(1){
  7. ret = recv_peek(sockfd, bufp, nleft);//这里虽然读取的数据,但是不会影响到缓冲区
  8. if(ret < 0){
  9. return ret;
  10. } else if(ret == 0){
  11. return ret;
  12. }
  13. nread = ret;//读了多少个数据
  14. int i;
  15. for(i = 0; i < nread; i++){
  16. if(bufp[i] == '\n'){//找到一行的末尾
  17. ret = readn(sockfd, bufp, i+1);
  18. if(ret != i+1){
  19. exit(EXIT_FAILURE);
  20. }
  21. return ret;
  22. }
  23. }
  24. if(nread > nleft){
  25. exit(EXIT_FAILURE);
  26. }
  27. nleft -= nread
  28. ret = readn(sockfd, bufp, nread);//这里不是很懂,为什么要在使用一边readn呢?为什么不直接使用上面的bufp呢?
  29. if(ret != nread){
  30. exit(EXIT_FAILURE);
  31. }
  32. bufp += nread;
  33. }
  34. return -1;
  35. }

使用readline解决粘包

修改服务器端

  1. void do_service(int conn){
  2. char recvbuf[1024];
  3. while(1){
  4. memset(recvbuf, 0, sizeof(recvbuf));
  5. int ret = readline(conn, recvbuf, 1024);
  6. if(ret == -1){
  7. ERR_EXIT("readline");
  8. }
  9. if(ret == 0){
  10. printf("client close\n");
  11. break;
  12. }
  13. fputs(recvbuf, stdout);
  14. writen(conn, recvbuf, strlen(recvbuf));
  15. }
  16. }

修改客户端

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <sys/socket.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <errno.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <string.h>
  10. struct packet{
  11. int len;
  12. char buf[1024];
  13. }
  14. ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
  15. size_t nleft = count;//剩余字节数
  16. ssize_t nread;//已接受的字节数
  17. char *bufp = (char *)buf;
  18. while(nleft > 0){
  19. if(nread = read(fd, bufp, nleft) < 0){
  20. if(errno == EINTR)
  21. //这里有两种情况,可能是信号中断,并不被视为错误
  22. continue;
  23. return -1;//视为错误
  24. } else if (nread == 0){
  25. return count - nleft;//这里传输的是整个函数已经接收了多少个。
  26. } else {
  27. bufp += nread;//更新
  28. nleft -= nread;
  29. }
  30. }
  31. return count;
  32. }
  33. ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
  34. size_t nleft = count;//剩余字节数
  35. ssize_t nwritten;//已发送的字节数
  36. char *bufp = (char *)buf;
  37. while(nleft > 0){
  38. if(nwritten = write(fd, bufp, nleft) < 0){
  39. if(errno == EINTR)
  40. continue;//这里有两种情况,可能是信号中断,并不被视为错误
  41. return -1;//视为错误
  42. } else if (nread == 0){
  43. continue;//如果等于零,对于write操作等于无事发生
  44. } else {
  45. bufp += nwritten;//更新
  46. nleft -= nwritten;
  47. }
  48. }
  49. return count;
  50. }
  51. int main(){
  52. int sock;
  53. if(sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) < 0)//其实前两个已经表示为是tcp协议了,第三个参数可以填0
  54. {
  55. ERR_EXIT("socket");//报错误的宏
  56. }
  57. struct sockaddr_in servaddr;
  58. memset(&servaddr, 0, sizeof(servaddr));//全部填充0
  59. servaddr.sin_family = AF_INET;
  60. servaddr.sin_prot = htons(5188);
  61. servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  62. if(connect(sock, (struct sockaddr*)servaddr, sizeof(servaddr)) < 0){
  63. ERR_EXIT("connect");
  64. }
  65. char recvbuf[1024] = {0};
  66. char sendbuf[1024] = {0};
  67. memset(sendbuf, 0, sizeof(sendbuf));
  68. memset(recvbuf, 0, sizeof(recvbuf));
  69. while(fgets(sendbuf, sizeof(sendbuf), stdin)!= NULL){//此处使用结构体里面的成员
  70. n = strlen(sendbuf.buf);
  71. sendbuf.len = htonl(n);//此处多此一举是为了统一字节序转换为网络字节序,此处是包体的长度
  72. writen(sock, sendbuf, strlen(sendbuf));//包头有四个字节
  73. int ret = readline(sock, recvbuf, sizeof(recvbuf));
  74. if{ret == -1 }{
  75. ERR_EXIT("readline");
  76. } else if(ret == 0){
  77. printf("client_close\n");
  78. break;
  79. }
  80. fputs(recvbuf, stdout);
  81. memset(sendbuf, 0, sizeof(sendbuf));
  82. memset(recvbuf, 0, sizeof(recvbuf));
  83. }
  84. close(sock);
  85. return 0;
  86. }

getsockname

可以在客户端上面使用,获取自己的地址和端口号

  1. sturct sockaddr_in localaddr;
  2. if(getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0){
  3. ERROR_EXIT("connect");
  4. }
  5. printf("ip = %s port = %d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));//inet_ntoa网络地址转换为点分十进制地址。

getpeername

通过一个已连接的套接口,返回对方的地址和端口号

  1. getpeername(sock, (struct sockaddr*)&peeraddr, &addrlen)

gethostname

获取主机名

  1. char host[100] = {0};
  2. if(gethostname(host, sizeof(host))<0)
  3. {
  4. ERR_EXIT("gethostname");
  5. }

gethostbyname

通过主机名,获取主机上所有的ip地址
image.png
上面的 h_addr_list为主机ip列表
gethostbyname的输入参数可以是ip地址和域名

  1. #include <netdb.h>
  2. char host[100] = {0};
  3. if(gethostname(host, sizeof(host))<0)//先获取主机名
  4. {
  5. ERR_EXIT("gethostname");
  6. }
  7. struct hostent *hp;
  8. if((hp = gethostbyname(host)) == NULL){
  9. ERR_EXIT("gethostbyname");
  10. }
  11. int i = 0;//这里别忘记初始化为零
  12. while(hp->h_addr_list[i] != NULL){
  13. printf("%s\n", inet_ntoa(*(struct in_addr*)hp->h_addr_list[i])); //in_addr是网络地址,转换为点分十进制地址
  14. i++;
  15. }

gethostaddr

根据本机的地址结构,获取所有的地址保存在结构体当中。
image.png