• Linux网络编程---Socket编程


    一、网络套接字

    一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。)

    在通信过程中,套接字一定是成对出现的

    套接字通讯原理示意图:

    二、预备知识 

    1.  网络字节序

    内存中的多字节数据相对于内存地址有大端和小端之分

    小端法:(PC本地存储)高位存高地址,低位存低地址

    大端法:(网络存储)高位存低地址,低位存高地址

    网络的数据流采用大端字节序,而本地的数据流采用小端字节序,因此要通过函数来完成络字节序和主机字节序的转换

    htonl:本地 -->网络 (IP协议)        本地字节序转网络字节序,转32位

    htons:本地 --> 网络 (port端口)

    ntohl:网络 --> 本地 (IP)

    ntohs:网络 --> 本地 (port)

    其中:h表示host,n表示network,l表示32位(4字节)长整数,s表示16位短整数

    2. IP地址转换函数 

    int inet_pton(int af, const char *src, void *dst);

    本地字节序(string IP)转换成网络字节序

    参数

            aft:AF_INET、AF_INET6
            src:传入,IP地址(点分十进制)
            dst:传出,转换后的网络字节序的IP地址。
    返回值
            成功:1
            异常:0,说明src指向的不是一个有效的ip地址。

            失败:-1

    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

    网络字节序转换成本地字节序(string IP)

    参数

            aft:AF_INET、AF_INET6
            src:网络字节序IP地址
            dst:本地字节序(string IP)

            size:dst的大小
    返回值
            成功:dst

            失败:NULL

    3. sockaddr地址结构

    struct sockaddr_in addr ;
    addr.sin_family = AF_INET/AF_INET6                man 7 ip
    addr.sin_port = htons(9527);
            int dst ;
            inet _pton(AF_INET,"192.157.22.45",(void *)&dst) ;

    addr.sin_addr.s_addr = dst;
    addr.sin_addr. s_addr = htonl(INADDR_ANY); 取出系统中有效的任意IP地址。二进制类型。
    bind(fd,(struct sockaddr*)&addr,size);

    1. sin_family:表示你要使用的地址结构类型,AF_INET是IPV4,AF_INET6是IPV6;
    2. sin_port:网络字节序的端口号,因此要使用htons转换一下;
    3. struct in_addr sin_addr:一个结构体,里面有一个s_addr,要传入网络字节序的ip地址,因此要使用inet_pton函数;也可以使用第二种方法使用宏:addr.sin_addr. s_addr = htonl(INADDR_ANY);

    三、网络套接字函数 

    1. socket模型创建流程分析

    当服务器端调用socket函数时会产生一个套接字,而accept函数会阻塞监听客户端连接,当有客户端过来连接的时候,该函数会返回一个新的套接字去和客户端连接,因此一个socket建立会有两个套接字,另外一个套接字用于监听。即:整个流程一共用到3个套接字,一对用于通信,一个用于监听

    2. socket函数

    头文件

    #include

    #include


    int socket(int domain, int type,int protocol) ;        创建一个套接字

    参数:
            domain:AF_INET、AF_INET6、AF_UNIX
            type:数据传输协议,SOCK_STREAM或SOCK_DGRAM
            protocol:默认传0,根据type来选择,SOCK_STREAM的代表协议是TCP,SOCK_DGRAM的是UDP

    返回值:

            成功:新套接字所对应文件描述符
            失败::-1 errno

    3. bind函数 

    头文件:

    #include

    int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen) ;
    给socket绑定一个地址结构(IP+port)

    参数:
            sockfd:socket函数的返回值
                    struct sockaddr_in addr;

                    addr.sin_family= AF_INET;

                    addr.sin_port = htons(8888);
                    addr.sin_addr. s_addr = htonl (INADDR_ANY);

            网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址
            addr:传入参数(struct sockaddr *)&addr
            addrlen:sizeof(addr)地址结构的大小。
    返回值:
            成功:0
            失败:-1 errno

    4. listen函数

    int listen(int sockfd, int backlog); 

    设置同时与服务器建立连接的上限数(同时进行3次握手的客户端数量) 

    参数:   

            sockfd:socket的返回值

            backlog:上限数值

    返回值

            成功:0

            失败:-1 error

    5. accept函数

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

    阻塞等待客户端建立连接,成功的话 返回一个与客户端成功连接的socket文件描述符。

    参数:

            sockfd:socket的返回值

            addr:传出参数,成功与服务器建立连接的那个客户端的地址结构(IP+port)

            addrlen: 传入传出参数,&clit_addr_len

                            入:addr的大小

                            出:客户端addr的实际大小

                    socklen_t clit_addr_len = sizeof(addr);

    返回值

            成功:能与服务器进行数据通信的 socket 对应的文件描述符

            失败:-1 error

    6. connect函数

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 

    使用客户端现有的socket与服务器进行连接。

    参数:

            sockfd:socket的返回值

                          struct sockaddr_in serv_addr;        //服务器地址结构
                          serv_addr.sin_family = AF_INET;
                          serv_addr.sin_port = htons(SERV_PORT);//和服务器bind时设定的port一致
                          inet_pton(AF_INET,"服务器的IP地址",&serv_addr.sin_addr.s_addr);

            addr:传入参数,服务器的地址结构

                    inet_pton()

            addrlen:服务器地址结构的大小

    返回值

            成功:0

            失败:-1 error

    如果不使用bind绑定客户端地址结构,采用"隐式绑定" 

    四、CS模型的TCP通信流程分析

    server:

    1. socket()                创建socket
    2. bind()                    绑定服务器地址结构
    3. listen()                  设置监听上限
    4. accep()                 阻塞监听客户端连接
    5. read(fd)                读socket获取客户端数据
    6.小写转换大写        toupper()
    7. write(fd)
    8. close();

    client:

    1. socket()             创建socket
    2. connect();         与服务器建立连接

    3. write()               写数据到socket
    4. read()                读转换后的数据
    5. 显示读取结果
    6. close()

     server的实现,server.c的作用是从客户端读字符,然后将每个字符转换为大写并回送给客户端

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define SERV_PORT 9527
    11. void sys_err(const char *str)
    12. {
    13. perror(str);
    14. exit(1);
    15. }
    16. int main(int argc,char *argv[])
    17. {
    18. int lfd = 0,cfd = 0;
    19. int ret;
    20. char buf[BUFSIZ];//4096
    21. struct sockaddr_in serv_addr,clit_addr;
    22. socklen_t clit_addr_len;
    23. serv_addr.sin_family = AF_INET;
    24. serv_addr.sin_port = htons(SERV_PORT);
    25. serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    26. lfd = socket(AF_INET,SOCK_STREAM,0);
    27. if (lfd == -1)
    28. {
    29. sys_err("socket errno");
    30. }
    31. ret = bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    32. if (ret == -1)
    33. {
    34. sys_err("bind error");
    35. }
    36. ret = listen(lfd,128);
    37. if (ret == -1)
    38. {
    39. sys_err("listen error");
    40. }
    41. clit_addr_len = sizeof(clit_addr);
    42. cfd = accept(lfd,(struct sockaddr *)&clit_addr,&clit_addr_len);
    43. if (cfd == -1)
    44. {
    45. sys_err("accept error");
    46. }
    47. while(1)
    48. {
    49. ret = read(cfd,buf,sizeof(buf));
    50. write(STDOUT_FILENO,buf,ret);
    51. for (int i = 0;i
    52. {
    53. buf[i] = toupper(buf[i]);
    54. }
    55. write(cfd,buf,ret);
    56. }
    57. close(lfd);
    58. close(cfd);
    59. return 0;
    60. }

    可以通过命令nc+ip地址+端口号进行连接,测试服务器,可以看到能够正常的小写转大写,输出如下所示:

    client的实现,client.c的作用是从命令行参数中获得一个字符串发给服务器,然后接收服务器返回的字符串并打印

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define SERV_PORT 9527
    11. void sys_err(const char *str)
    12. {
    13. perror(str);
    14. exit(1);
    15. }
    16. int main(int argc,char *argv[])
    17. {
    18. int cfd;
    19. int counter = 10;
    20. char buf[BUFSIZ];
    21. struct sockaddr_in serv_addr;//服务器地址结构
    22. serv_addr.sin_family = AF_INET;
    23. serv_addr.sin_port = htons(SERV_PORT);
    24. inet_pton(AF_INET,"192.168.88.129",&serv_addr.sin_addr.s_addr);
    25. cfd = socket(AF_INET,SOCK_STREAM,0);
    26. if (cfd == -1)
    27. {
    28. sys_err("socket error");
    29. }
    30. int ret = connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    31. if (ret == -1)
    32. {
    33. sys_err("connect error");
    34. }
    35. while(--counter)
    36. {
    37. write(cfd,"hello\n",6);
    38. ret = read(cfd,buf,sizeof(buf));
    39. write(STDOUT_FILENO,buf,ret);
    40. sleep(1);
    41. }
    42. close(cfd);
    43. return 0;
    44. }

    在server.c中加入以下代码打印ip及端口号:

    printf("client ip is:%s port:%d\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port));

    两个终端分别执行服务端和客户端,输出如下所示: 

    五、错误函数封装 

            上面的例子不仅功能简单,而且简单到几乎没有什么错误处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息。

            为使错误处理的代码不影响主程序的可读性,我们把与socket相关的一些系统函数加上错误处理代码包装成新的函数,做成一个模块wrap.c:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void perr_exit(const char *s)
    7. {
    8. perror(s);
    9. exit(-1);
    10. }
    11. int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
    12. {
    13. int n;
    14. again:
    15. if ((n = accept(fd, sa, salenptr)) < 0) {
    16. if ((errno == ECONNABORTED) || (errno == EINTR))
    17. goto again;
    18. else
    19. perr_exit("accept error");
    20. }
    21. return n;
    22. }
    23. int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
    24. {
    25. int n;
    26. if ((n = bind(fd, sa, salen)) < 0)
    27. perr_exit("bind error");
    28. return n;
    29. }
    30. int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
    31. {
    32. int n;
    33. if ((n = connect(fd, sa, salen)) < 0)
    34. perr_exit("connect error");
    35. return n;
    36. }
    37. int Listen(int fd, int backlog)
    38. {
    39. int n;
    40. if ((n = listen(fd, backlog)) < 0)
    41. perr_exit("listen error");
    42. return n;
    43. }
    44. int Socket(int family, int type, int protocol)
    45. {
    46. int n;
    47. if ((n = socket(family, type, protocol)) < 0)
    48. perr_exit("socket error");
    49. return n;
    50. }
    51. ssize_t Read(int fd, void *ptr, size_t nbytes)
    52. {
    53. ssize_t n;
    54. again:
    55. if ( (n = read(fd, ptr, nbytes)) == -1) {
    56. if (errno == EINTR)
    57. goto again;
    58. else
    59. return -1;
    60. }
    61. return n;
    62. }
    63. ssize_t Write(int fd, const void *ptr, size_t nbytes)
    64. {
    65. ssize_t n;
    66. again:
    67. if ( (n = write(fd, ptr, nbytes)) == -1) {
    68. if (errno == EINTR)
    69. goto again;
    70. else
    71. return -1;
    72. }
    73. return n;
    74. }
    75. int Close(int fd)
    76. {
    77. int n;
    78. if ((n = close(fd)) == -1)
    79. perr_exit("close error");
    80. return n;
    81. }
    82. /*参三: 应该读取的字节数*/
    83. ssize_t Readn(int fd, void *vptr, size_t n)
    84. {
    85. size_t nleft; //usigned int 剩余未读取的字节数
    86. ssize_t nread; //int 实际读到的字节数
    87. char *ptr;
    88. ptr = vptr;
    89. nleft = n;
    90. while (nleft > 0) {
    91. if ((nread = read(fd, ptr, nleft)) < 0) {
    92. if (errno == EINTR)
    93. nread = 0;
    94. else
    95. return -1;
    96. } else if (nread == 0)
    97. break;
    98. nleft -= nread;
    99. ptr += nread;
    100. }
    101. return n - nleft;
    102. }
    103. ssize_t Writen(int fd, const void *vptr, size_t n)
    104. {
    105. size_t nleft;
    106. ssize_t nwritten;
    107. const char *ptr;
    108. ptr = vptr;
    109. nleft = n;
    110. while (nleft > 0) {
    111. if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
    112. if (nwritten < 0 && errno == EINTR)
    113. nwritten = 0;
    114. else
    115. return -1;
    116. }
    117. nleft -= nwritten;
    118. ptr += nwritten;
    119. }
    120. return n;
    121. }
    122. static ssize_t my_read(int fd, char *ptr)
    123. {
    124. static int read_cnt;
    125. static char *read_ptr;
    126. static char read_buf[100];
    127. if (read_cnt <= 0) {
    128. again:
    129. if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
    130. if (errno == EINTR)
    131. goto again;
    132. return -1;
    133. } else if (read_cnt == 0)
    134. return 0;
    135. read_ptr = read_buf;
    136. }
    137. read_cnt--;
    138. *ptr = *read_ptr++;
    139. return 1;
    140. }
    141. ssize_t Readline(int fd, void *vptr, size_t maxlen)
    142. {
    143. ssize_t n, rc;
    144. char c, *ptr;
    145. ptr = vptr;
    146. for (n = 1; n < maxlen; n++) {
    147. if ( (rc = my_read(fd, &c)) == 1) {
    148. *ptr++ = c;
    149. if (c == '\n')
    150. break;
    151. } else if (rc == 0) {
    152. *ptr = 0;
    153. return n - 1;
    154. } else
    155. return -1;
    156. }
    157. *ptr = 0;
    158. return n;
    159. }

    wrap.h

    1. #ifndef __WRAP_H_
    2. #define __WRAP_H_
    3. void perr_exit(const char *s);
    4. int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
    5. int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
    6. int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
    7. int Listen(int fd, int backlog);
    8. int Socket(int family, int type, int protocol);
    9. ssize_t Read(int fd, void *ptr, size_t nbytes);
    10. ssize_t Write(int fd, const void *ptr, size_t nbytes);
    11. int Close(int fd);
    12. ssize_t Readn(int fd, void *vptr, size_t n);
    13. ssize_t Writen(int fd, const void *vptr, size_t n);
    14. ssize_t my_read(int fd, char *ptr);
    15. ssize_t Readline(int fd, void *vptr, size_t maxlen);
    16. #endif

    错误函数封装在多线程并发服务器实现中使用

  • 相关阅读:
    K线形态识别_倒锤头线和射击之星(流星、扫帚星)
    如何使用PowerShell批量删除注册表项
    网络拥塞控制的经济学原理
    万字详文:TCP 拥塞控制详解
    数据库:什么是数据库,有哪些数据库,数据库用来干什么,图导,拓展
    华纳云:远程桌面服务器出现乱码是什么原因?
    mysql存储引擎
    GraphQL+Koa2实现服务端API结合Apollo+Vue
    《计算机视觉中的多视图几何》笔记(12)
    企业如何选择合适的精益生产方案?
  • 原文地址:https://blog.csdn.net/qq_51647149/article/details/138022847