C/C++实现tcp客户端和服务端的实现(从零开始写自己的高性能服务器)

news/2024/11/20 13:28:01/

目录

tcp客户端通信流程

tcp客户端设计

1、创建通信对象

2、链接服务器

3、发送数据 / 读取数据

4、关闭通信

tcp服务端设计

1、创建通信对象

2、绑定服务器地址信息

3、设置服务器为监听模式

4、接收客户的链接请求

编写tcp客户端和服务端,实现双向通信

server.c

client.c


tcp客户端通信流程

1、创建通信对象

2、链接服务器

3、发送数据 / 读取数据

4、关闭通信

tcp客户端设计

1、创建通信对象

#include <sys/socket.h>

int socket(int domain, int type, int protocol);  //创建一个终端链接

domain:网络层协议  
type:     传输层协议   SOCK_STREAM    TCP协议     
                                  SOCK_DGRAM     UDP协议  

protocol:属性,默认为 0   
返回值: 成功  通信对象 
             失败  -1 

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

tcp_socket = socket(AF_INET, SOCK_STREAM, 0);    //创建TCP对象 
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);     //创建UDP对象 
raw_socket = socket(AF_INET, SOCK_RAW, protocol); //创建自定义协议对象

demo:

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);if (tcp_socket < 0)
{perror("创建TCP客户端通信对象失败");return 1;
}
else
{printf("创建TCP客户端通信对象成功\n");
}

2、链接服务器

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

sockfd:客户端通信对象 
addr:服务器地址 
addrlen:服务器地址的大小 
返回值:  成功   0  
              失败   -1

sockaddr_in结构体

struct sockaddr_in 
{
sa_family_t    sin_family;    /* address family: AF_INET 网络层协议*/
in_port_t      sin_port;        /* port in network byte order 端口*/  
struct in_addr sin_addr;   /* internet address 网络地址*/  
};struct in_addr 
{uint32_t    s_addr;     /* address in network byte order 网络地址*/
};   

sockaddr和sockaddr_in区别

struct sockaddr
{  
     sa_family_t sin_family;//地址族
     char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
}; 

sockaddr的缺陷是sa_data把目标地址和端口信息混在一起了


二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了

demo:

//网络服务器地址设置
struct sockaddr_in addr;                         // 创建一个服务器地址结构体
addr.sin_family = AF_INET;                       // 设置地址族为IPv4
addr.sin_port = htons(8888);                     // 设置端口号为8888
addr.sin_addr.s_addr = inet_addr("172.17.112.1"); // 设置IP地址为(我本机的网卡地址)//链接服务器
if (connect(tcp_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{perror("连接服务器失败:\n");return 1;
}
else
{printf("连接服务器成功\n");
}

补充:

#include <arpa/inet.h>

uint16_t htons(uint16_t hostshort);  //把本地端口转换为网络端口
uint16_t ntohs(uint16_t netshort);   //把网络端口转换为本地端口

---------------------------------------------------------------------------------------------------------------------------------

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);  //把字符串地址转换为网络地址  
char *inet_ntoa(struct in_addr in);   //把网络地址转换为字符串地址

 

3、发送数据 / 读取数据

a1fa416818f6444ea3338130f7c9d19c.png

4、关闭通信

#include <unistd.h>                                                                                                                          int close(int fd);

---------------------------------------------------------------------------------------------------------------------------------

tcp服务端设计

1.创建通信对象

2.绑定服务器地址信息

3.设置服务器为监听模式

4.接收客户的链接请求

5.读取数据

6.发送数据

7.关闭通信

1、创建通信对象

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

2、绑定服务器地址信息

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

sockfd:服务器对象 
addr:服务器的地址信息 
addrlen:服务器地址信息的大小 
返回值: 成功  0  
             失败  -1 

 

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡的IP地址if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{perror("bind 失败");return -1;
}

一般绑定的都是本机的网卡地址,可以通过ip addr查看本机的ip地址

3、设置服务器为监听模式

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

sockfd:服务器socket  
backlog:最大监听数  (同时可以有多少个客户端链接进来)
返回值:成功  0    
            失败  -1

if (listen(sockfd, 10) < 0)
{perror("listen 失败");return -1;
}

4、接收客户的链接请求

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

sockfd:服务器对象 
addr:对方的IP地址信息  (保存链接者的地址)
addrlen:对方的IP地址大小
返回值:      成功  返回一个新的文件描述符(用于与当客户端通信)
                  失败  -1  

1.accept 接口会一直阻塞等待,直到有客户端链接为止!       
2.accept 会返回一个新的文件描述符,该描述符用于读写通信!
 

int new_socket = accept(sockfd, NULL, NULL);
if (new_socket < 0)
{perror("accept 失败");return -1;
}char buf[1024] = {0};
scanf("%s", buf);
write(new_socket, buf, strlen(buf));

编写tcp客户端和服务端,实现双向通信

server.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>void *send_task(void *arg)
{int *new_socket = arg;while (1){char buf[1024] = {0};scanf("%s", buf);ssize_t n = write(*new_socket, buf, strlen(buf));
#if 0if (n < 0){if (errno == EPIPE){// 对端关闭了连接printf("客户端已关闭,线程退出\n");close(*new_socket);break;}perror("write 错误");}
#endif}return NULL;
}int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0){perror("bind 失败");return -1;}if (listen(sockfd, 10) < 0){perror("listen 失败");return -1;}while (1){printf("等待客户端链接\n");int new_socket = accept(sockfd, NULL, NULL);if (new_socket < 0){perror("accept 失败");return -1;}printf("新的客户端链接\n");pthread_t tid;pthread_create(&tid, NULL, send_task, &new_socket);// 读取客户端发送过来的数据char buf[1024] = {0};ssize_t n;while ((n = read(new_socket, buf, sizeof(buf))) > 0){buf[n] = '\0';printf("客户端发送过来的数据:%s\n", buf);}
#if 0if (n == 0){// 客户端关闭连接printf("客户端关闭连接,关闭套接字\n");close(new_socket);}else if (n < 0){perror("read 错误");}}
#endifreturn 0;
}

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int tcp_socket;// 发送任务
void *send_task(void *arg)
{while (1){// 3.发送数据char buf[1024] = {0};scanf("%s", buf);write(tcp_socket, buf, strlen(buf));}
}int main(int argc, char *argv[])
{if (argc!= 3){printf("请输入服务器的IP和端口\n");return 0;}// 1.创建客户端对象tcp_socket = socket(AF_INET, SOCK_STREAM, 0);// 2.设置服务器地址信息struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(atoi(argv[2]));      // 端口server_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP// 3.连接服务器int ret = connect(tcp_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));if (ret < 0){printf("连接服务器失败\n");return 0;}else{printf("连接服务器成功\n");}// 创建一个线程pthread_t tid;pthread_create(&tid, NULL, send_task, NULL);char buf[1024] = {0};ssize_t n;while ((n = read(tcp_socket, buf, sizeof(buf))) > 0){printf("服务端发来的数据:%s\n", buf);}
#if 0if (n == 0){// 服务器关闭连接printf("服务器已关闭连接,客户端退出\n");close(tcp_socket);}else if (n < 0){perror("读取数据出错");// 可以根据错误情况决定是否尝试重新连接或者直接退出close(tcp_socket);}
#endifreturn 0;
}

        在demo中注释掉的代码是因为当服务器(客户端)进程退出时,客户端(服务器)的read函数会持续返回 0(表示对端关闭),导致无限循环接收空内容。需要在read函数返回 0 或者发生错误时进行适当处理,以避免这种情况。


http://www.ppmy.cn/news/1548495.html

相关文章

CKA认证 | Day2 K8s内部监控与日志

第三章 Kubernetes监控与日志 1、查看集群资源状态 在 Kubernetes 集群中&#xff0c;查看集群资源状态和组件状态是非常重要的操作。以下是一些常用的命令和解释&#xff0c;帮助你更好地管理和监控 Kubernetes 集群。 1.1 查看master组件状态 Kubernetes 的 Master 组件包…

[模板总结] - 单向链表LinkedList操作

题目汇总 Leetcode 21, 82, 160, 206, 237, 268 Leetcode 21. 合并两个有序链表 归并排序的思路&#xff0c;创建一个哨兵节点从两个链表中按大小插入即可。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(…

【Redis_Day4】内部编码和单线程模型

【Redis_Day4】内部编码和单线程模型 五大数据类型内部编码object encoding key1&#xff1a;查询key1对应值的内部编码 redis中的单线程模型 redis中的数据都是以键值对的方式存的&#xff0c;redis内部用哈希表组织这些键值对。 五大数据类型 站在用户角度&#xff0c; 在一…

汽车科技前沿:Spring Boot资讯快车道

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

如何删除pdf里的任意一页?删除PDF里任意一页的几种方法

如何删除pdf里的任意一页&#xff1f;尽管PDF文件具有许多优点&#xff0c;如跨平台兼容性和格式保真性&#xff0c;但在编辑和修改方面&#xff0c;它与像Word或Excel这类文档格式不同&#xff0c;通常不能像其他文档那样轻松进行直接的内容删除或修改。这让很多人以为&#x…

VMware高危漏洞VMSA-2024-0019修复堆溢出和权限提升漏洞

一、概述 VMware vCenter Server 高危漏洞&#xff08;CVE-2024-38812、CVE-2024-38813&#xff09;再次受到攻击&#xff0c;需要升级补丁&#xff0c;详情查看之前文章紧急通告VMware vCenter高危漏洞CVE-2024-38812和CVE-2024-38813修复方案 再次更新了漏洞 二、漏洞影像描…

纯血鸿蒙NEXT-组件导航 (Navigation)

Navigation组件是路由导航的根视图容器&#xff0c;一般作为Page页面的根容器使用&#xff0c;其内部默认包含了标题栏、内容区和工具栏&#xff0c;其中内容区默认首页显示导航内容&#xff08;Navigation的子组件&#xff09;或非首页显示&#xff08;NavDestination的子组件…

基于卷积神经网络的航空发动机剩余寿命预测Matlab实现

本文利用NASA提供的涡扇发动机退化数据集&#xff0c;进行数据预处理&#xff0c;构建训练样本和测试样本&#xff0c;然后搭建卷积神经网络&#xff08;Convolutional Neural Network,CNN&#xff09;&#xff0c;学习训练数据&#xff0c;最后利用测试数据&#xff0c;分析神…