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函数 与 通信域

#include <sys/types.h>
#include <sys/socket.h>
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 来表示具体的错误原因。

 示例:

#include <sys/types.h>
#include <sys/socket.h>int main() {int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  //最后一个参数也可以是0if (sockfd == -1) {// 处理创建套接字失败的情况return -1;}// 套接字创建成功,可以进行后续操作return 0;
}

1.2 bind函数 与 通信结构体

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

参数解释:

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

返回值:

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

ipv4结构体 

struct sockaddr_in {sa_family_t    sin_family; /* 地址族: AF_INET */in_port_t      sin_port;   /* 网络字节序的端口号 */struct in_addr sin_addr;   /*IP地址结构体 */
};/* IP地址结构体 */
struct in_addr {uint32_t       s_addr;     /* 网络字节序的IP地址 */
};/*通用地址族结构体*/
struct sockaddr {sa_family_t sa_family;char sa_data[14];
}

注意事项:

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

示例:强制转换

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>int main() {int sockfd;struct sockaddr_in server_addr;// 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);// 设置服务器地址信息server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = INADDR_ANY;// 绑定套接字和地址if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("bind");return 1;}// 其他操作...return 0;
}

1.3 listen函数 与 accept函数

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

参数:

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

返回值:

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

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

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

注意事项:

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

示例:

#include <sys/types.h>
#include <sys/socket.h>int main() {int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (sockfd == -1) {// 处理创建套接字失败的情况return -1;}// 套接字创建成功,可以进行后续操作if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {// 处理绑定地址和端口失败的情况return -1;}if (listen(sockfd, 10) == -1) {// 处理监听套接字失败的情况return -1;}// 套接字处于监听状态,可以接受连接请求并建立连接return 0;
}

/*处理客户端发起的连接,生成新的套接字*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-sockfd: 函数socket生成的套接字
-addr:客户端的地址族信息
-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() 函数来实现并发处理多个连接请求的功能。

示例: 

#include <sys/types.h>
#include <sys/socket.h>int main() {int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (sockfd == -1) {// 处理创建套接字失败的情况return -1;}// 套接字创建成功,可以进行后续操作if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {// 处理绑定地址和端口失败的情况return -1;}if (listen(sockfd, 10) == -1) {// 处理监听套接字失败的情况return -1;}// 套接字处于监听状态,可以接受连接请求并建立连接struct sockaddr_in client_addr;socklen_t client_addrlen = sizeof(client_addr);int client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addrlen);if (client_sockfd == -1) {// 处理接受连接请求失败的情况return -1;}// 成功接受连接请求,可以使用 client_sockfd 进行与客户端的通信return 0;
}

2 TCP服务端代码实现

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>#define PORT 5001
#define BACKLOG 5int main(int argc, char *argv[])
{int fd, newfd;char buf[BUFSIZ] = {}; //BUFSIZ 8142struct sockaddr_in addr;/*创建套接字*/fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0){perror("socket");exit(0);}addr.sin_family = AF_INET;addr.sin_port = htons(PORT);addr.sin_addr.s_addr = 0;/*绑定通信结构体*/if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){perror("bind");exit(0);}/*设置套接字为监听模式*/if(listen(fd, BACKLOG) == -1){perror("listen");exit(0);}/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/newfd = accept(fd, NULL, NULL);if(newfd < 0){perror("accept");exit(0);}printf("BUFSIZ = %d\n", BUFSIZ);read(newfd, buf, BUFSIZ);printf("buf = %s\n", buf);close(fd);return 0;
}

 3 TCP客户端代码实现

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>#define PORT 5001
#define BACKLOG 5
#define STR "Hello World!"int main(int argc, char *argv[])
{int fd;struct sockaddr_in addr;/*创建套接字*/fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0){perror("socket");exit(0);}addr.sin_family = AF_INET;addr.sin_port = htons(PORT);addr.sin_addr.s_addr = inet_addr("127.0.0.1");/*向服务端发起连接请求*/if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){perror("connect");exit(0);}write(fd, STR, sizeof(STR) );printf("STR = %s\n", STR);close(fd);return 0;
}

4 代码优化

服务端

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>#define BACKLOG 5int main(int argc, char *argv[])
{int fd, newfd, ret;char buf[BUFSIZ] = {}; //BUFSIZ 8142struct sockaddr_in addr;if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}/*创建套接字*/fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0){perror("socket");exit(0);}addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if ( inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*绑定通信结构体*/if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){perror("bind");exit(0);}/*设置套接字为监听模式*/if(listen(fd, BACKLOG) == -1){perror("listen");exit(0);}/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/newfd = accept(fd, NULL, NULL);if(newfd < 0){perror("accept");exit(0);}while(1){memset(buf, 0, BUFSIZ);ret = read(newfd, buf, BUFSIZ);if(ret < 0){perror("read");exit(0);}else if(ret == 0)break;elseprintf("buf = %s\n", buf);}close(newfd);close(fd);return 0;
}

客户端

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>#define BACKLOG 5int main(int argc, char *argv[])
{int fd;struct sockaddr_in addr;char buf[BUFSIZ] = {};if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}/*创建套接字*/fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0){perror("socket");exit(0);}addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if ( inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*向服务端发起连接请求*/if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){perror("connect");exit(0);}while(1){printf("Input->");fgets(buf, BUFSIZ, stdin);write(fd, buf, strlen(buf) );}close(fd);return 0;
}

5 练习

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

client

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>#define CLIENT_MAX_NUM 5int main(int argc, char * argv[])
{int clientfd;struct sockaddr_in server_addr;char buf[BUFSIZ];int ret;if( argc < 3){printf("%s <ip> <port>\n",argv[0]);return 0;}clientfd = socket(AF_INET, SOCK_STREAM,0);if(clientfd == -1){perror("socket");return 0;}server_addr.sin_family = AF_INET;server_addr.sin_port = htons( atoi(argv[2]) ) ;if( inet_aton(argv[1], &server_addr.sin_addr) == 0){printf("Invalid address:%s\n",argv[1]);return 0;}if(connect(clientfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1){perror("connect");return 0;}while(1){printf(">");fgets(buf, BUFSIZ, stdin);write(clientfd, buf, strlen(buf));}close(clientfd);return 0;
}

server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>#define CLIENT_MAX_NUM 5int main(int argc, char * argv[])
{int sockfd, clientfd;struct sockaddr_in server_addr;char buf[BUFSIZ];int ret;if( argc < 3){printf("%s <ip> <port>\n",argv[0]);return 0;}sockfd = socket(AF_INET, SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 0;}server_addr.sin_family = AF_INET;server_addr.sin_port = htons( atoi(argv[2]) ) ;if( inet_aton(argv[1], &server_addr.sin_addr) == 0){printf("Invalid address:%s\n",argv[1]);return 0;}if(bind(sockfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1){perror("bind");return 0;}if(listen(sockfd, CLIENT_MAX_NUM) == -1){perror("listen");return 0;}clientfd = accept(sockfd, NULL, NULL); if( clientfd == -1){perror("accept");return 0;}while(1){memset(buf, 0, BUFSIZ);ret = read(clientfd, buf, BUFSIZ);if(ret < 0){perror("read");return 0;}else if( ret == 0 ){break;}else{printf("buf = %s\n", buf);}	}close(clientfd);close(sockfd);return 0;
}

makefile

CC=gcc
CFLAGS=-Wall
all:tcp_client tcp_serverclean:rm tcp_server tcp_client

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/124681.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实战型开发--3/3,clean code

编程的纯粹 hmmm&#xff0c;一开始在这个环节想聊一些具体的点&#xff0c;其实也就是《clean code》这本书中的点&#xff0c;但这个就还是更流于表面&#xff1b; 因为编码的过程&#xff0c;就更接近于运动员打球&#xff0c;艺术家绘画&#xff0c;棋手下棋的过程&#x…

详解Linux的系统调用fork()函数

在Linux系统中&#xff0c;fork()是一个非常重要的系统调用&#xff0c;它的作用是创建一个新的进程。具体来说&#xff0c;fork()函数会在当前进程的地址空间中复制一份子进程&#xff0c;并且这个子进程几乎完全与父进程相同&#xff0c;包括进程代码、数据、堆栈以及打开的文…

[Spring] Spring5——AOP 简介

目录 一、AOP 简介 1、什么是 AOP 二、AOP 底层原理 1、动态代理原理 2、基于接口的 JDK 动态代理 3、基于继承的 CGLib 动态代理 三、底层原理实现—— JDK 动态代理 1、使用 Proxy 类的方法创建代理对象 2、JDK 动态代理示例 四、AOP 操作术语 1、连接点 2、切入…

【算法学习】-【双指针】-【快乐数】

LeetCode原题链接&#xff1a;202. 快乐数 下面是题目描述&#xff1a; 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。 如果…

kafka客户端应用参数详解

一、基本客户端收发消息 Kafka提供了非常简单的客户端API。只需要引入一个Maven依赖即可&#xff1a; <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka_2.13</artifactId><version>3.4.0</version></depend…

react.js在visual code 下的hello World

想学习reacr.js &#xff0c;就开始做一个hello world。 我的环境是visual code &#xff0c;所以我找这个环境下的例子。参照&#xff1a; https://code.visualstudio.com/docs/nodejs/reactjs-tutorial 要学习react.js &#xff0c;还得先安装node.js&#xff0c;我在visual …

mysql面试题12:讲一讲MySQL中什么是半同步复制?底层实现?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:讲一讲mysql中什么是半同步复制?底层实现? MySQL中的半同步复制(Semi-Synchronous Replication)是一种复制模式,它在主服务器将数据写入到二…

【Golang】网络编程

网络编程 网络模型介绍 OSI七层网络模型 在软件开发中我们使用最多的是上图中将互联网划分为五个分层的模型&#xff1a; 物理层数据链路层网络层传输层应用层 物理层 我们的电脑要与外界互联网通信&#xff0c;需要先把电脑连接网络&#xff0c;我们可以用双绞线、光纤、…

计算数组中全部元素的乘积 忽略数组中所有的NaN值 numpy.nanprod()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 计算数组中全部元素的乘积 忽略数组中所有的NaN值 numpy.nanprod() [太阳]选择题 请问代码中最后输出正确的是&#xff1f; import numpy as np a np.array([2, 3, np.nan, 5]) print(&quo…

力扣 -- 377. 组合总和 Ⅳ

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int combinationSum4(vector<int>& nums, int target) {int nnums.size();vector<double> dp(target1);//初始化dp[0]1;//填表for(int i1;i<target;i){for(int j0;j<n;j){//填表if(…

黑马程序员 MySQL数据库入门到精通——进阶篇(2)

黑马程序员 MySQL数据库入门到精通——进阶篇&#xff08;2&#xff09; 1. SQL优化1.1 插入数据1.2 主键优化1.3 order by优化1.4 group by优化1.5 limit优化1.6 count优化1.7 update优化 2. 视图2.1 视图-介绍及基本语法2.2 视图-检查选项(cascaded)2.3 视图-检查选项(local)…

数组(数据结构)

优质博文&#xff1a;IT-BLOG-CN 一、简介 数组Array是一种线性表数据结构&#xff0c;它用一组连续的内存空间&#xff0c;存储一组具有相同类型的数据。 数组因具有连续的内存空间的特点&#xff0c;数据拥有非常高效率的“随机访问”&#xff0c;时间复杂度为O(1)。但因要保…