当前位置:   article > 正文

Linux网络编程:socket、客户端服务器端使用socket通信(TCP)_linux下的socket编程实验报告

linux下的socket编程实验报告

1. socket概念

socket(套接字),用于网络中不同主机间进程的通信。

socket是一个伪文件,包含读缓冲区、写缓冲区。

socket必须成对出现。

socket可以建立主机进程间的通信,但需要协议(IPV4、IPV6等)、port端口、IP地址。


2. 客户端服务端socket网络通信步骤(TCP)

2.1 服务器端步骤:

        (1)创建流式socket套接字。

                a)此socket套接字一直用于后续的监听连接。

                b)socket函数。

        (2)绑定本机IP地址和port。

                b)bind函数。

        (3)监听。

                a)将socket套接字由主动变为被动。

                b)创建未完成连接队列、已完成连接队列;未完成连接接经历3次握手才变成已完成连接。

                c)listen函数。

        (4)提取。

                a)从已完成连接队列提取连接,创建一个新的已连接socket套接字用于和客户端通信。

                b)accept函数。

        (5)读写数据。

        (6)关闭socket。

2.2 客户端步骤:

        (1)创建流式socket套接字。

                a)socket函数。

        (2)连接服务器。

                a)指定服务器的IP协议(IPV4或IPV6)、port、IP地址。

                b)connect函数(该函数包含TCP的三次握手)。

        (3)读写数据。

        (4)关闭socket。


3. socket相关结构体和函数

3.1 socket相关结构体

(1)IPV4套接字结构体

  1. #include<netinet/in.h>
  2. struct sockaddr_in {
  3. sa_family_t sin_family; /* 协议:AF_INET */
  4. in_port_t sin_port; /* 端口 */
  5. struct in_addr sin_addr; /* IP地址 */
  6. };
  7. /* Internet address. */
  8. struct in_addr {
  9. uint32_t s_addr; /* IP地址的网络字节序 */
  10. };

(2)IPV6套接字结构体

  1. #include<netinet/in6.h>
  2. struct sockaddr_in6 {
  3. sa_family_t sin6_family; /* AF_INET6 */
  4. in_port_t sin6_port; /* port number */
  5. uint32_t sin6_flowinfo; /* IPv6 flow information */
  6. struct in6_addr sin6_addr; /* IPv6 address */
  7. uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
  8. };
  9. struct in6_addr {
  10. unsigned char s6_addr[16]; /* IPv6 address */
  11. };

(3)通用套接字结构体

为了接口通用,出现通用套接字结构体。

  1. #include<sys/socket.h>
  2. struct sockaddr {
  3. sa_family_t sa_family; /* AF_INET 或 AF_INET6 */
  4. char sa_data[14]; /* address data */
  5. };

3.2 socket相关函数

(1) socke函数:创建套接字

  1. #include<sys/socket.h>
  2. int socket(int domain, int type, int protocol);
  3. /*
  4. 功能:
  5. 创建套接字
  6. 参数:
  7. domain:
  8. AF_INET
  9. AF_INET6
  10. 等等
  11. type:
  12. SOCK_STREAM:TCP流式套接字
  13. SOCK_DGRAM:UDP报式套接字
  14. SOCK_RAW:组包更多
  15. 等等
  16. protocol:0,自动填充
  17. 返回值:
  18. 成功:文件描述符
  19. 失败:-1
  20. */

(2)connect函数:客户端连接服务器

  1. #include<sys/socket.h>
  2. int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
  3. /*
  4. 功能:
  5. 连接服务器
  6. 参数:
  7. sockfd:套接字文件描述符
  8. addr:IPV4套接字结构体地址 强转为通用套接字结构体
  9. 包含目的主机的IP和port
  10. addrlen:IPV4套接字结构体大小
  11. 返回值:
  12. 成功:0
  13. 失败:-1,并设置errno
  14. EACCES:权限不足或被防火墙拒绝
  15. EADDRINUSE:本地地址已被其他套接字使用
  16. ECONNREFUSED:远程主机拒绝连接
  17. ETIMEDOUT:连接超时
  18. */

(3)bind函数:服务器端绑定自己固定的IP和port

  1. #include<sys/socket.h>
  2. int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
  3. /*
  4. 功能:
  5. 给套接字sockfd绑定固定的的IP地址和port
  6. 参数:
  7. sockfd:套接字文件描述符
  8. addr:IPV4套接字结构体地址
  9. addrlen:IPV4套接字结构体大小
  10. 返回值:
  11. 成功:0
  12. 失败:-1
  13. */

(4)listen函数:服务器端监听是否有连接请求

  1. #include<sys/socket.h>
  2. int listen(int sockfd, int backlog);
  3. /*
  4. 功能:
  5. 监听是否有客户端请求连接
  6. 参数:
  7. sockfd:套接字文件描述符
  8. backlog:已完成连接数量与未完成连接数量之和的最大值,一般写128
  9. 返回值:
  10. 成功:0
  11. 失败:-1
  12. */

(5)accept函数: 从已完成连接队列提取连接

  1. #include<sys/socket.h>
  2. int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
  3. /*
  4. 功能:
  5. 从已完成连接队列提取连接
  6. 参数:
  7. sockfd:套接字文件描述符
  8. addr:IPV4套接字结构体地址,以获取的客户端IP和port信息
  9. addrlen:存储IPV4套接字结构体大小的变量的地址。
  10. 返回值:
  11. 成功:新连接socket的文件描述符
  12. 失败:-1
  13. */

3.3 socket通信示例

(1)TCP客户端连接服务器示例:

  1. #include<arpa/inet.h>
  2. #include<stdio.h>
  3. #include<sys/socket.h>
  4. #include<unistd.h>
  5. int main() {
  6. /* 1.创建socket */
  7. int sock_fd;
  8. sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  9. /* 2.连接服务器 */
  10. // IPV4套接字结构体
  11. struct sockaddr_in addr;
  12. // IPV4
  13. addr.sin_family = AF_INET;
  14. // 服务器的port,转为网络字节序
  15. addr.sin_port = htons(8888);
  16. // 服务器IP地址,转为网络字节序存入addr.sin_addr.s_addr
  17. inet_pton(AF_INET, "192.168.0.11", &addr.sin_addr.s_addr);
  18. // 连接
  19. connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
  20. /* 3.读写数据 */
  21. char buf[1024] = "";
  22. while (1) {
  23. int n = read(STDIN_FILENO, buf, sizeof(buf)); // 从终端读入buf
  24. write(sock_fd, buf, n);
  25. n = read(sock_fd, buf, sizeof(buf));
  26. write(STDOUT_FILENO, buf, n);
  27. }
  28. /* 4.关闭 */
  29. close(sock_fd);
  30. return 0;
  31. }

运行结果:


(2)TCP服务器端示例:

  1. #include<stdio.h>
  2. #include<arpa/inet.h>
  3. #include<sys/socket.h>
  4. #include<unistd.h>
  5. #include<stdlib.h>
  6. #include<string.h>
  7. int main(int argc, const char* argv[]) {
  8. /* 1.创建socket */
  9. int lfd = socket(AF_INET, SOCK_STREAM, 0);
  10. /* 2.绑定本机IP地址和port */
  11. struct sockaddr_in addr;
  12. addr.sin_family = AF_INET;
  13. addr.sin_port = htons(8000);
  14. // INADDR_ANY为0,表示绑定通配地址,即本地所有IP地址
  15. // addr.sin_addr.s_addr = INADDR_ANY;
  16. inet_pton(AF_INET, "192.168.124.128", &addr.sin_addr.s_addr);
  17. int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
  18. if(ret < 0) {
  19. perror("bind");
  20. exit(0);
  21. }
  22. /* 3.监听 */
  23. listen(lfd, 128);
  24. /* 4.提取 */
  25. struct sockaddr_in cliaddr;
  26. socklen_t len = sizeof(cliaddr);
  27. int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
  28. char ip[16] = "";
  29. printf("新连接到来!IP:%s, port:%d\n",
  30. inet_ntop(AF_INET, &(cliaddr.sin_addr.s_addr), ip, 16),
  31. ntohs(cliaddr.sin_port));
  32. /* 5.读写 */
  33. char buf[1024] = "";
  34. while (1) {
  35. bzero(buf, sizeof(buf));
  36. int n = read(STDIN_FILENO, buf, sizeof(buf)); // 从终端读入buf
  37. write(cfd, buf, n);
  38. n = read(cfd, buf, sizeof(buf));
  39. printf("%s", buf);
  40. }
  41. /* 6.关闭 */
  42. close(lfd);
  43. close(cfd);
  44. return 0;
  45. }

运行结果:

注意:服务器进程被杀死,其占用的端口不会立即释放,再次连接会出现如下错误:

因为系统防止短时间内频繁开关相同的端口而将端口设置为TIME_WAIT状态(一般为2MSL,MSL一般为30s),因此TIME_WAIT大概持续60s;过了TIME_WAIT状态才可使用该端口。

若要立即使用该端口,可使用端口复用机制


4. 补充:

4.1 recv和send

读写时,除了使用read、write函数,还可使用recv和send函数,用法类似于read和write,flags默认写0即可:

  1. #include<sys/types.h>
  2. #include<sys/socket.h>
  3. ssize_t recv(int sockfd, void* buf, size_t len, int flags);
  4. ssize_t send(int sockfd, const void* buf, size_t len, int flags);

4.2 socket包裹函数

将socket编程常用函数中的错误判断等封装为函数,使用更方便: mayueming1/socket-wrap-func: socket常用的包裹函数 (github.com)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/622904
推荐阅读
相关标签
  

闽ICP备14008679号