• lv7 嵌入式开发-网络编程开发 07 TCP服务器实现


    目录

    1 函数介绍

    1.1 socket函数 与 通信域

    1.2 bind函数 与 通信结构体

    1.3 listen函数 与 accept函数

    2 TCP服务端代码实现

     3 TCP客户端代码实现

    4 代码优化

    5 练习


    1 函数介绍

    其中read、write、close在IO中已经介绍过,只需了解socket、bind、listen、accept等

    1.1 socket函数 与 通信域

    1. #include
    2. #include
    3. int socket(int domain, int type, int protocol);

    参数:

    • domain:指定套接字的协议域(protocol family),可以是 AF_INET(IPv4)或 AF_INET6(IPv6)等。
    • type:指定套接字的类型,可以是 SOCK_STREAM(流套接字,用于可靠的、面向连接的通信)或 SOCK_DGRAM(数据报套接字,用于无连接的通信)等。
    • protocol:指定使用的协议,可以是 IPPROTO_TCP(TCP)或 IPPROTO_UDP(UDP)等。所以无需要指定协议,设为0即可

    返回值:

    • 成功创建套接字时,返回一个非负整数,代表新创建的套接字描述符。
    • 创建套接字失败时,返回 -1,并设置 errno 来表示具体的错误原因。

     示例:

    1. #include
    2. #include
    3. int main() {
    4. int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //最后一个参数也可以是0
    5. if (sockfd == -1) {
    6. // 处理创建套接字失败的情况
    7. return -1;
    8. }
    9. // 套接字创建成功,可以进行后续操作
    10. return 0;
    11. }

    1.2 bind函数 与 通信结构体

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

    参数解释:

    • sockfd:要进行绑定的套接字描述符。
    • addr:指向sockaddr结构体的指针,其中包含了要绑定的地址信息。
    • addrlenaddr指向的结构体的大小。

    返回值:

    • 成功时,返回0。
    • 失败时,返回-1,并且在错误码中设置相应的错误标志,可以通过errno全局变量获取具体错误信息。

    ipv4结构体 

    1. struct sockaddr_in {
    2. sa_family_t sin_family; /* 地址族: AF_INET */
    3. in_port_t sin_port; /* 网络字节序的端口号 */
    4. struct in_addr sin_addr; /*IP地址结构体 */
    5. };
    6. /* IP地址结构体 */
    7. struct in_addr {
    8. uint32_t s_addr; /* 网络字节序的IP地址 */
    9. };
    10. /*通用地址族结构体*/
    11. struct sockaddr {
    12. sa_family_t sa_family;
    13. char sa_data[14];
    14. }

    注意事项:

    • 调用bind()函数之前,需要先创建一个套接字,并确保该套接字是未绑定的。
    • bind()函数通常在服务器端使用,用于将服务器的套接字与指定的本地地址绑定,从而监听并接收该地址发来的连接请求。
    • 在调用bind()函数时,要根据实际情况提供正确的地址信息,如IP地址和端口号等。
    • 在IPv4中,地址信息存储在sockaddr_in结构体中;而在IPv6中,地址信息存储在sockaddr_in6结构体中。

    示例:强制转换

    1. #include <sys/types.h>
    2. #include <sys/socket.h>
    3. #include <netinet/in.h>
    4. int main() {
    5. int sockfd;
    6. struct sockaddr_in server_addr;
    7. // 创建套接字
    8. sockfd = socket(AF_INET, SOCK_STREAM, 0);
    9. // 设置服务器地址信息
    10. server_addr.sin_family = AF_INET;
    11. server_addr.sin_port = htons(8080);
    12. server_addr.sin_addr.s_addr = INADDR_ANY;
    13. // 绑定套接字和地址
    14. if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
    15. perror("bind");
    16. return 1;
    17. }
    18. // 其他操作...
    19. return 0;
    20. }

    1.3 listen函数 与 accept函数

    1. /*监听套接字*/
    2. int listen(int sockfd, int backlog);

    参数:

    • sockfd:要监听的套接字描述符。
    • backlog:定义允许排队等待的连接请求的最大数量。

    返回值:

    • 成功调用 listen() 函数时,返回 0 表示成功。
    • 调用 listen() 函数失败时,返回 -1 并设置 errno 来表示具体的错误原因。

    函数功能: listen() 函数被用于 TCP 服务器端,用于将指定的套接字标记为被动套接字(passive socket),开始监听传入的连接请求。在调用 listen() 之前,服务器需要使用 socket() 函数创建一个套接字,并使用 bind() 函数将套接字与特定的地址和端口绑定。

    一旦套接字被标记为监听状态,它就可以开始接受传入的连接请求。这些连接请求会被放置在一个连接请求队列中,等待服务器进程使用 accept() 函数来接受这些请求并建立连接。

    注意事项:

    • backlog 参数指定了连接请求队列的最大长度。如果队列已满,则新的连接请求将被拒绝。实际允许的队列长度可能会受到系统限制。
    • 在调用 listen() 之后,通常需要调用 accept() 函数来接受连接请求并建立连接。

    示例:

    1. #include <sys/types.h>
    2. #include <sys/socket.h>
    3. int main() {
    4. int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    5. if (sockfd == -1) {
    6. // 处理创建套接字失败的情况
    7. return -1;
    8. }
    9. // 套接字创建成功,可以进行后续操作
    10. if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    11. // 处理绑定地址和端口失败的情况
    12. return -1;
    13. }
    14. if (listen(sockfd, 10) == -1) {
    15. // 处理监听套接字失败的情况
    16. return -1;
    17. }
    18. // 套接字处于监听状态,可以接受连接请求并建立连接
    19. return 0;
    20. }
    1. /*处理客户端发起的连接,生成新的套接字*/
    2. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    3. -sockfd: 函数socket生成的套接字
    4. -addr:客户端的地址族信息
    5. -addrlen:地址族结构体的长度

    参数:

    • sockfd:监听套接字描述符,即之前调用 listen() 函数返回的套接字描述符。
    • addr:指向用于存储客户端地址信息的结构体 sockaddr 的指针,可以为 NULL
    • addrlen:指向存储客户端地址长度的变量的指针,如果 addr 不为 NULL,则需要将 addrlen 设置为 sizeof(struct sockaddr)

    返回值:

    • 成功调用 accept() 函数时,返回一个新的套接字描述符,用于处理与客户端的连接。
    • 调用 accept() 函数失败时,返回 -1 并设置 errno 来表示具体的错误原因。

    函数功能: accept() 函数用于监听套接字上接受传入的连接请求,并创建一个新的套接字来处理与客户端的连接。该新的套接字用于与客户端进行通信。在调用 accept() 函数之前,需要先使用 socket()bind()listen() 函数来准备监听套接字。

    当有一个连接请求到达监听套接字时,accept() 函数会从连接请求队列中取出一个请求,创建一个新的套接字来处理该连接,并返回新创建的套接字描述符。可以通过新创建的套接字描述符进行与客户端的通信。

    如果传入的 addr 不为 NULL,则 accept() 函数会将客户端的地址信息存储在 addr 指向的结构体中。同时,addrlen 也需要传入一个指向存储客户端地址长度的变量的指针。

    注意事项:

    • accept() 函数是一个阻塞调用,当没有连接请求时,它会一直等待,直到有连接请求到达或出现错误才返回。
    • 通常在多线程或多进程环境中使用 accept() 函数来实现并发处理多个连接请求的功能。

    示例: 

    1. #include <sys/types.h>
    2. #include <sys/socket.h>
    3. int main() {
    4. int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    5. if (sockfd == -1) {
    6. // 处理创建套接字失败的情况
    7. return -1;
    8. }
    9. // 套接字创建成功,可以进行后续操作
    10. if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    11. // 处理绑定地址和端口失败的情况
    12. return -1;
    13. }
    14. if (listen(sockfd, 10) == -1) {
    15. // 处理监听套接字失败的情况
    16. return -1;
    17. }
    18. // 套接字处于监听状态,可以接受连接请求并建立连接
    19. struct sockaddr_in client_addr;
    20. socklen_t client_addrlen = sizeof(client_addr);
    21. int client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addrlen);
    22. if (client_sockfd == -1) {
    23. // 处理接受连接请求失败的情况
    24. return -1;
    25. }
    26. // 成功接受连接请求,可以使用 client_sockfd 进行与客户端的通信
    27. return 0;
    28. }

    2 TCP服务端代码实现

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #define PORT 5001
    8. #define BACKLOG 5
    9. int main(int argc, char *argv[])
    10. {
    11. int fd, newfd;
    12. char buf[BUFSIZ] = {}; //BUFSIZ 8142
    13. struct sockaddr_in addr;
    14. /*创建套接字*/
    15. fd = socket(AF_INET, SOCK_STREAM, 0);
    16. if(fd < 0){
    17. perror("socket");
    18. exit(0);
    19. }
    20. addr.sin_family = AF_INET;
    21. addr.sin_port = htons(PORT);
    22. addr.sin_addr.s_addr = 0;
    23. /*绑定通信结构体*/
    24. if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
    25. perror("bind");
    26. exit(0);
    27. }
    28. /*设置套接字为监听模式*/
    29. if(listen(fd, BACKLOG) == -1){
    30. perror("listen");
    31. exit(0);
    32. }
    33. /*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
    34. newfd = accept(fd, NULL, NULL);
    35. if(newfd < 0){
    36. perror("accept");
    37. exit(0);
    38. }
    39. printf("BUFSIZ = %d\n", BUFSIZ);
    40. read(newfd, buf, BUFSIZ);
    41. printf("buf = %s\n", buf);
    42. close(fd);
    43. return 0;
    44. }

     3 TCP客户端代码实现

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #define PORT 5001
    8. #define BACKLOG 5
    9. #define STR "Hello World!"
    10. int main(int argc, char *argv[])
    11. {
    12. int fd;
    13. struct sockaddr_in addr;
    14. /*创建套接字*/
    15. fd = socket(AF_INET, SOCK_STREAM, 0);
    16. if(fd < 0){
    17. perror("socket");
    18. exit(0);
    19. }
    20. addr.sin_family = AF_INET;
    21. addr.sin_port = htons(PORT);
    22. addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    23. /*向服务端发起连接请求*/
    24. if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
    25. perror("connect");
    26. exit(0);
    27. }
    28. write(fd, STR, sizeof(STR) );
    29. printf("STR = %s\n", STR);
    30. close(fd);
    31. return 0;
    32. }

    4 代码优化

    服务端

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define BACKLOG 5
    9. int main(int argc, char *argv[])
    10. {
    11. int fd, newfd, ret;
    12. char buf[BUFSIZ] = {}; //BUFSIZ 8142
    13. struct sockaddr_in addr;
    14. if(argc < 3){
    15. fprintf(stderr, "%s\n", argv[0]);
    16. exit(0);
    17. }
    18. /*创建套接字*/
    19. fd = socket(AF_INET, SOCK_STREAM, 0);
    20. if(fd < 0){
    21. perror("socket");
    22. exit(0);
    23. }
    24. addr.sin_family = AF_INET;
    25. addr.sin_port = htons( atoi(argv[2]) );
    26. if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
    27. fprintf(stderr, "Invalid address\n");
    28. exit(EXIT_FAILURE);
    29. }
    30. /*绑定通信结构体*/
    31. if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
    32. perror("bind");
    33. exit(0);
    34. }
    35. /*设置套接字为监听模式*/
    36. if(listen(fd, BACKLOG) == -1){
    37. perror("listen");
    38. exit(0);
    39. }
    40. /*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
    41. newfd = accept(fd, NULL, NULL);
    42. if(newfd < 0){
    43. perror("accept");
    44. exit(0);
    45. }
    46. while(1){
    47. memset(buf, 0, BUFSIZ);
    48. ret = read(newfd, buf, BUFSIZ);
    49. if(ret < 0)
    50. {
    51. perror("read");
    52. exit(0);
    53. }
    54. else if(ret == 0)
    55. break;
    56. else
    57. printf("buf = %s\n", buf);
    58. }
    59. close(newfd);
    60. close(fd);
    61. return 0;
    62. }

    客户端

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define BACKLOG 5
    9. int main(int argc, char *argv[])
    10. {
    11. int fd;
    12. struct sockaddr_in addr;
    13. char buf[BUFSIZ] = {};
    14. if(argc < 3){
    15. fprintf(stderr, "%s\n", argv[0]);
    16. exit(0);
    17. }
    18. /*创建套接字*/
    19. fd = socket(AF_INET, SOCK_STREAM, 0);
    20. if(fd < 0){
    21. perror("socket");
    22. exit(0);
    23. }
    24. addr.sin_family = AF_INET;
    25. addr.sin_port = htons( atoi(argv[2]) );
    26. if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
    27. fprintf(stderr, "Invalid address\n");
    28. exit(EXIT_FAILURE);
    29. }
    30. /*向服务端发起连接请求*/
    31. if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
    32. perror("connect");
    33. exit(0);
    34. }
    35. while(1){
    36. printf("Input->");
    37. fgets(buf, BUFSIZ, stdin);
    38. write(fd, buf, strlen(buf) );
    39. }
    40. close(fd);
    41. return 0;
    42. }

    5 练习

    实现TCP通信代码,并使用Makefile进行编译。提交代码和完成通信的截图

    client

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define CLIENT_MAX_NUM 5
    7. int main(int argc, char * argv[])
    8. {
    9. int clientfd;
    10. struct sockaddr_in server_addr;
    11. char buf[BUFSIZ];
    12. int ret;
    13. if( argc < 3)
    14. {
    15. printf("%s \n",argv[0]);
    16. return 0;
    17. }
    18. clientfd = socket(AF_INET, SOCK_STREAM,0);
    19. if(clientfd == -1)
    20. {
    21. perror("socket");
    22. return 0;
    23. }
    24. server_addr.sin_family = AF_INET;
    25. server_addr.sin_port = htons( atoi(argv[2]) ) ;
    26. if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
    27. {
    28. printf("Invalid address:%s\n",argv[1]);
    29. return 0;
    30. }
    31. if(connect(clientfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    32. {
    33. perror("connect");
    34. return 0;
    35. }
    36. while(1)
    37. {
    38. printf(">");
    39. fgets(buf, BUFSIZ, stdin);
    40. write(clientfd, buf, strlen(buf));
    41. }
    42. close(clientfd);
    43. return 0;
    44. }

    server

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define CLIENT_MAX_NUM 5
    7. int main(int argc, char * argv[])
    8. {
    9. int sockfd, clientfd;
    10. struct sockaddr_in server_addr;
    11. char buf[BUFSIZ];
    12. int ret;
    13. if( argc < 3)
    14. {
    15. printf("%s \n",argv[0]);
    16. return 0;
    17. }
    18. sockfd = socket(AF_INET, SOCK_STREAM,0);
    19. if(sockfd == -1)
    20. {
    21. perror("socket");
    22. return 0;
    23. }
    24. server_addr.sin_family = AF_INET;
    25. server_addr.sin_port = htons( atoi(argv[2]) ) ;
    26. if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
    27. {
    28. printf("Invalid address:%s\n",argv[1]);
    29. return 0;
    30. }
    31. if(bind(sockfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    32. {
    33. perror("bind");
    34. return 0;
    35. }
    36. if(listen(sockfd, CLIENT_MAX_NUM) == -1)
    37. {
    38. perror("listen");
    39. return 0;
    40. }
    41. clientfd = accept(sockfd, NULL, NULL);
    42. if( clientfd == -1)
    43. {
    44. perror("accept");
    45. return 0;
    46. }
    47. while(1)
    48. {
    49. memset(buf, 0, BUFSIZ);
    50. ret = read(clientfd, buf, BUFSIZ);
    51. if(ret < 0)
    52. {
    53. perror("read");
    54. return 0;
    55. }
    56. else if( ret == 0 )
    57. {
    58. break;
    59. }
    60. else
    61. {
    62. printf("buf = %s\n", buf);
    63. }
    64. }
    65. close(clientfd);
    66. close(sockfd);
    67. return 0;
    68. }

    makefile

    1. CC=gcc
    2. CFLAGS=-Wall
    3. all:tcp_client tcp_server
    4. clean:
    5. rm tcp_server tcp_client

  • 相关阅读:
    WordPress多语言翻译插件小语种互译
    UBUNTU新版本,一键安装NETCDF,安装netcdf-c netcdf-v
    中国式现代化落地社区的“3510”模式示范点在烟台正式启动
    Kafka 消息保留策略及其影响详解
    以太坊的账户(外部账户和合约账户)
    Druid SQL和Security在美团点评的实践
    sqlite3日期时间格式化和自动输入
    【C语言】三子棋(经典解法+一览图)
    基于SpringBoot+Vue的搬家服务系统
    计算机毕业设计django基于python企业资产管理系统(源码+系统+mysql数据库+Lw文档)
  • 原文地址:https://blog.csdn.net/m0_60718520/article/details/133551836