代码拉取完成,页面将自动刷新
/*tcp服务器,基于epoll(et)模型实现,稍显复杂难懂*/
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>
#ifndef STR_CONV
#include "./libs/functions.h"
#endif
#ifndef ERRORS
#include "./errors.c"
#endif
#define CLIENT_CONN_MAX_NUM 8192
const int PORT = 9505;
const char SERVER_IP[15] = {"0.0.0.0"};
int set_non_block(int socket);
int main() {
//TCP 参数: SOCK_STREAM、IPPROTO_TCP ;UDP参数: SOCK_DGRAM 、IPPTOTO_UDP
int server_socket = -1, client_socket = -1;
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
printf("%s\n", TCP_SOCK_INIT_FAIL);
return -1;
}
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_size;
int received_data_len;
char buffer[BUFSIZ];//缓冲区大小
char tmp_out_res[BUFSIZ] = {0};
int sockopt = 0;
sockopt = SO_REUSEADDR;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &server_socket, sizeof(server_socket));
//对内存清零
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET; //选择IPV4地址类型
server_addr.sin_port = htons(PORT); //选择端口号
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //选择IP地址
int server_addr_len = sizeof(server_addr);
// 绑定
if (bind(server_socket, (struct sockaddr *) &server_addr, server_addr_len) < 0)//绑定套接字
{
printf("%s\n", TCP_SOCK_BIND_FAIL);
return -1;
}
//监听
if (listen(server_socket, SOMAXCONN) < 0)//调用listen对指定端口进行监听
{
printf("%s\n", TCP_SOCK_LISTEN_FAIL);
return -1;
}
// epoll(et)模型,必须将sockserver设置为非阻塞
set_non_block(server_socket);
//epoll 事件监听,所有连接的tcp客户端都会触发同一个事件函数(epoll结构体的属性值会改变)
struct epoll_event event;
//存储所有的客户端,包括服务端
struct epoll_event wait_event_list[CLIENT_CONN_MAX_NUM];
int j = 0;
int epoll_fd = epoll_create(64);
if (epoll_fd == -1) {
printf("%s\n", TCP_CREATE_EPOLL_FAIL);
return -1;
}
event.events = EPOLLIN|EPOLLET;//事件类型设置为 EPOLLIN+epoll(et) 模型
event.data.fd = server_socket;//server_socket
// 将服端 socketserver注册在epoll模型中,epoll就开始监听 socket 文件的变化
int result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event);
if (result == -1) {
printf("%s\n", TCP_SERVERSOCK_REGISTER_EPOLL_FAIL);
return -1;
}
while (1) {
errno = 0;
// 阻塞监听当前连接的socket套接字状态是否发生变化,有变化就会有返回值,wait_event_list 数组返回的是最近一次变化(活跃)的socket
result = epoll_wait(epoll_fd, wait_event_list, CLIENT_CONN_MAX_NUM, -1);
if (result == 0) {
continue;
} else if (result < 0) {
printf("%s,错误编号:%d,错误详情:%s\n", TCP_EPOLL_WAIT_ERR, errno, strerror(errno));
continue;
}
for (j = 0; j < result; j++) {
if (server_socket == wait_event_list[j].data.fd && wait_event_list[j].events) {
client_addr_size = sizeof(client_addr);
while(1){
client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &client_addr_size);
if (client_socket==-1){ // 没有需要处理的连接客户端
break;
}
//获取客户端的基本信息,ip、port
printf("Client_IP: %s\n", inet_ntoa(client_addr.sin_addr));
printf("Client_Port: %d\n", htons(client_addr.sin_port));
char msg[] = "成功连接到tcpserver_epoll_et!";
send(client_socket, msg, strlen(msg), 0);
set_non_block(client_socket); //设置非阻塞
event.events = EPOLLIN|EPOLLET; //事件类型设置为 可读+边缘触发 模式 |EPOLLET
event.data.fd = client_socket;
result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event);
if (result == -1) {
printf("%s,错误信息:%s\n", TCP_CLIENT_REGISTER_FAIL, strerror(errno));
}
}
continue;
}
//客户端套接字本身有变化(属于客户端消息事件)
if (wait_event_list[j].data.fd != server_socket) {
memset(&buffer, 0, sizeof(buffer));
received_data_len = read(wait_event_list[j].data.fd, buffer, BUFSIZ);
buffer[BUFSIZ] = '\0'; // 最后一位强制设置为结束标记,避免中英文混合发的时候数据对不齐,造成最后一位出现不确定字符
// 不是 utf8 编码,那么就当做是 gbk 去处理, 目前就先支持 gbk系列 和 utf8系列
if (!is_utf8_format(buffer)) {
// 以下函数的最后一个参数,输出缓冲区的长度计算依据:在linux系统,gbk编码占2字节,utf8 编码占3字节,也就是说,你输入了一个中文字,那么输出空间的长度就是输入空间的1.5倍,保险起见,我们就直接设置为 2倍
gbk_to_utf8(buffer, received_data_len, tmp_out_res, received_data_len * 2);
if (errno != 0) {
printf("%s,错误编码:%d, 错误信息:%s\n", STRCONV_FAIL, errno, strerror(errno));
continue;
}
}else{
strcpy(tmp_out_res,buffer);
}
switch (wait_event_list[j].events) {
case EPOLLIN:
case EPOLLET|EPOLLIN: // 这里需要注意,et 模型注册的事件类型是 位或运算的结果
case EPOLLOUT:
case EPOLLPRI:
if (received_data_len > 0) {
// 向客户端发送消息,原路返回, 注意发送消息的长度应该是实际长度,不能 sizeof(buffer), 这样计算出来是 8192,就会有空数据发送出去
send(wait_event_list[j].data.fd, tmp_out_res, strlen(tmp_out_res), 0);
break;
}
case EPOLLERR:
case EPOLLHUP:
close((wait_event_list[j]).data.fd);
event.data.fd = wait_event_list[j].data.fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, wait_event_list[j].data.fd, &event);
printf("%s,%d,可能的错误:%s\n", UNKNOW_CLIENT_STATUS, wait_event_list[j].events, strerror(errno));
break;
default:
printf("events 事件值未匹配到以上列表,是否发生错误?错误coe:%d, 错误消息:%s\n",errno,strerror(errno));
}
}
}
}
close(server_socket);//关闭套接字
return 0;
}
int set_non_block(int socket) {
int flags = fcntl(socket, F_GETFL, 0);
flags |= O_NONBLOCK;
return fcntl(socket, F_SETFL, flags);
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。