IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> TCP服务器的实现 -> 正文阅读

[系统运维]TCP服务器的实现

一、一请求一线程

void *client_routine(void *arg) {

	int clientfd = *(int *)arg;

	while (1) {

		char buffer[BUFFER_LENGTH] = {0};
		int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
		if (len < 0) {
			close(clientfd);
			break;
		} else if (len == 0) { // disconnect
			close(clientfd);
			break;
		} else {
			printf("Recv: %s, %d byte(s)\n", buffer, len);
		}

	}

}
int main(int argc, char *argv[]) {

	if (argc < 2) {
		printf("Param Error\n");
		return -1;
	}
	int port = atoi(argv[1]);

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

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY; 

	if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
		perror("bind");
		return 2;
	}

	if (listen(sockfd, 5) < 0) {
		perror("listen");
		return 3;
	}
	
	while (1) {

		struct sockaddr_in client_addr;
		memset(&client_addr, 0, sizeof(struct sockaddr_in));
		socklen_t client_len = sizeof(client_addr);
        // 请求一次 创建一个线程
		int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
		// 创建线程
		pthread_t thread_id;
		pthread_create(&thread_id, NULL, client_routine, &clientfd);

	}
	return 0;
}

二、socket——IO多路复用模型 epoll

随着客户端越来越多,则不适合使用一请求一线程的方式
首先介绍epoll实现的三个函数:

  • epoll_create
  • epoll_ctl
  • epoll_wait

(1) epoll_create()

#include <sys/epoll.h>

int epoll_create(int size);
  • size:从Linux 2.6.8以后就不再使用,但是必须设置一个大于0的值。
  • 返回值:调用成功返回一个非负值的文件描述符epollfd,调用失败返回-1。

(2) epoll_ctl()

有了epollfd之后,我们需要将我们需要检测事件的其他fd绑定到这个epollfd上,
或修改一个已经绑定上去的fd的事件类型,或者在不需要时将fd从epollfd上解绑,
这都可以使用epoll_ctl函数。
#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • epfd:调用epoll_create函数创建的epollfd
  • op:操作类型,取值有EPOLL_CTL_ADD、EPOLL_CTL_MOD和EPOLL_CTL_DEL,分别表示向epollfd上添加、修改和移除一个其他fd,当取值是EPOLL_CTL_DEL,第四个参数event忽略不计,可以设置为NULL。
  • fd: 被操作的fd
  • event: epoll_event结构体的地址
  • 返回值:调用成功返回0,调用失败返回-1,可以通过errno错误码获取具体的错误原因.

(3) epoll_event 结构体

struct epoll_event {
    uint32_t     events; // 需要检测的fd事件,取值与poll函数一样
    epoll_data_t data; // 用户自定义数据
};

typedef union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;
事件宏描述
EPOLLIN数据可读
EPOLLOUT数据可写
EPOLLRDHUPTCP连接被对端关闭,或者关闭了写操作
EPOLLPRITCP连接被对端关闭,或者关闭了写操作
EPOLLRDHUP高优先级数据可读,例如 TCP 带外数据
EPOLLERR错误
EPOLLHUP挂起
EPOLLET边缘触发模式
EPOLLONESHOT最多触发其上注册的事件一次

(4) epoll_wait()

创建epollfd,设置好某个fd上需要检测事件并将该fd绑定到epollfd上去后,就可以调用epoll_wait检测事件了。

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • epfd: epollfd
  • events: 是一个epoll_event结构数组的首地址,这是一个输出参数,函数调用成功后,events中存放的是与就绪事件相关epoll_event结构体数组
  • maxevents : 数组元素的个数
  • timeout : 超时时间,单位是毫秒,如果设置为0,epoll_wait会立即返回。
  • 返回值:调用成功会返回有事件的fd数目;如果返回0表示超时;调用失败返回-1

三、epoll 代码实现

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>

#include <errno.h>
#include <fcntl.h>

#include <sys/epoll.h>

#define BUFFER_LENGTH		1024
#define EPOLL_SIZE			1024


void *client_routine(void *arg) {

	int clientfd = *(int *)arg;

	while (1) {

		char buffer[BUFFER_LENGTH] = {0};
		int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
		if (len < 0) {
			close(clientfd);
			break;
		} else if (len == 0) { // disconnect
			close(clientfd);
			break;
		} else {
			printf("Recv: %s, %d byte(s)\n", buffer, len);
		}

	}

}

int main(int argc, char *argv[]) {

	if (argc < 2) {
		printf("Param Error\n");
		return -1;
	}
	int port = atoi(argv[1]);

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

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY; 

	if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
		perror("bind");
		return 2;
	}

	if (listen(sockfd, 5) < 0) {
		perror("listen");
		return 3;
	}

	int epfd = epoll_create(1);  
	struct epoll_event events[EPOLL_SIZE] = {0};

	struct epoll_event ev;
	ev.events = EPOLLIN; // 数据可读
	ev.data.fd = sockfd; 
	epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);// sockfd交给epoll管理

	
	while (1) {

		int nready = epoll_wait(epfd, events, EPOLL_SIZE, 5);
		if (nready == -1) continue;

		int i = 0;
		for (i = 0;i < nready;i ++) {

			if (events[i].data.fd == sockfd) { // listen 

				struct sockaddr_in client_addr;
				memset(&client_addr, 0, sizeof(struct sockaddr_in));
				socklen_t client_len = sizeof(client_addr);

				int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);

				ev.events = EPOLLIN | EPOLLET; 
				ev.data.fd = clientfd;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);

			} else {

				int clientfd = events[i].data.fd;
				
				char buffer[BUFFER_LENGTH] = {0};
				int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
				if (len < 0) {
					close(clientfd);

					ev.events = EPOLLIN; 
					ev.data.fd = clientfd;
					epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
					
				} else if (len == 0) { // disconnect
					close(clientfd);

					ev.events = EPOLLIN; 
					ev.data.fd = clientfd;
					epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
					
				} else {
					printf("Recv: %s, %d byte(s)\n", buffer, len);
				}
				
				
			}

		}

	}
	
	return 0;
}

四、检测IO有没有数据?

  1. 识别有数据 —— 水平触发
  2. 监测从无到有的过程 —— 边沿触发
  • LT:(Level_triggered,水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你,如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。

  • ET:(Edge_triggered,边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你,这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

五、运行

在这里插入图片描述
成功接收到客户端数据

  • 下一节我们在本服务器实现百万并发量
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-07-04 23:22:23  更:2022-07-04 23:25:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 1:30:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码